@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
package/dist/lib/tmux-routing.js
CHANGED
|
@@ -268,75 +268,19 @@ var init_provider_table = __esm({
|
|
|
268
268
|
}
|
|
269
269
|
});
|
|
270
270
|
|
|
271
|
-
// src/lib/intercom-queue.ts
|
|
272
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, renameSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
273
|
-
import path2 from "path";
|
|
274
|
-
import os2 from "os";
|
|
275
|
-
function ensureDir() {
|
|
276
|
-
const dir = path2.dirname(QUEUE_PATH);
|
|
277
|
-
if (!existsSync2(dir)) mkdirSync2(dir, { recursive: true });
|
|
278
|
-
}
|
|
279
|
-
function readQueue() {
|
|
280
|
-
try {
|
|
281
|
-
if (!existsSync2(QUEUE_PATH)) return [];
|
|
282
|
-
return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
|
|
283
|
-
} catch {
|
|
284
|
-
return [];
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function writeQueue(queue) {
|
|
288
|
-
ensureDir();
|
|
289
|
-
const tmp = `${QUEUE_PATH}.tmp`;
|
|
290
|
-
writeFileSync2(tmp, JSON.stringify(queue, null, 2));
|
|
291
|
-
renameSync(tmp, QUEUE_PATH);
|
|
292
|
-
}
|
|
293
|
-
function queueIntercom(targetSession, reason) {
|
|
294
|
-
const queue = readQueue();
|
|
295
|
-
const existing = queue.find((q) => q.targetSession === targetSession);
|
|
296
|
-
if (existing) {
|
|
297
|
-
existing.attempts++;
|
|
298
|
-
existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
299
|
-
existing.reason = reason;
|
|
300
|
-
} else {
|
|
301
|
-
queue.push({
|
|
302
|
-
targetSession,
|
|
303
|
-
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
304
|
-
attempts: 0,
|
|
305
|
-
reason
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
writeQueue(queue);
|
|
309
|
-
}
|
|
310
|
-
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
311
|
-
var init_intercom_queue = __esm({
|
|
312
|
-
"src/lib/intercom-queue.ts"() {
|
|
313
|
-
"use strict";
|
|
314
|
-
QUEUE_PATH = path2.join(os2.homedir(), ".exe-os", "intercom-queue.json");
|
|
315
|
-
TTL_MS = 60 * 60 * 1e3;
|
|
316
|
-
INTERCOM_LOG = path2.join(os2.homedir(), ".exe-os", "intercom.log");
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
// src/lib/db-retry.ts
|
|
321
|
-
var init_db_retry = __esm({
|
|
322
|
-
"src/lib/db-retry.ts"() {
|
|
323
|
-
"use strict";
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
271
|
// src/lib/config.ts
|
|
328
272
|
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
329
|
-
import { readFileSync as
|
|
330
|
-
import
|
|
331
|
-
import
|
|
273
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, renameSync } from "fs";
|
|
274
|
+
import path2 from "path";
|
|
275
|
+
import os2 from "os";
|
|
332
276
|
function resolveDataDir() {
|
|
333
277
|
if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
|
|
334
278
|
if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
|
|
335
|
-
const newDir =
|
|
336
|
-
const legacyDir =
|
|
337
|
-
if (!
|
|
279
|
+
const newDir = path2.join(os2.homedir(), ".exe-os");
|
|
280
|
+
const legacyDir = path2.join(os2.homedir(), ".exe-mem");
|
|
281
|
+
if (!existsSync2(newDir) && existsSync2(legacyDir)) {
|
|
338
282
|
try {
|
|
339
|
-
|
|
283
|
+
renameSync(legacyDir, newDir);
|
|
340
284
|
process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
|
|
341
285
|
`);
|
|
342
286
|
} catch {
|
|
@@ -398,9 +342,9 @@ function normalizeAutoUpdate(raw) {
|
|
|
398
342
|
async function loadConfig() {
|
|
399
343
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
400
344
|
await mkdir(dir, { recursive: true });
|
|
401
|
-
const configPath =
|
|
402
|
-
if (!
|
|
403
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
345
|
+
const configPath = path2.join(dir, "config.json");
|
|
346
|
+
if (!existsSync2(configPath)) {
|
|
347
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
404
348
|
}
|
|
405
349
|
const raw = await readFile(configPath, "utf-8");
|
|
406
350
|
try {
|
|
@@ -418,13 +362,13 @@ async function loadConfig() {
|
|
|
418
362
|
normalizeScalingRoadmap(migratedCfg);
|
|
419
363
|
normalizeSessionLifecycle(migratedCfg);
|
|
420
364
|
normalizeAutoUpdate(migratedCfg);
|
|
421
|
-
const config = { ...DEFAULT_CONFIG, dbPath:
|
|
365
|
+
const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
|
|
422
366
|
if (config.dbPath.startsWith("~")) {
|
|
423
|
-
config.dbPath = config.dbPath.replace(/^~/,
|
|
367
|
+
config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
|
|
424
368
|
}
|
|
425
369
|
return config;
|
|
426
370
|
} catch {
|
|
427
|
-
return { ...DEFAULT_CONFIG, dbPath:
|
|
371
|
+
return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
|
|
428
372
|
}
|
|
429
373
|
}
|
|
430
374
|
var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
|
|
@@ -432,10 +376,10 @@ var init_config = __esm({
|
|
|
432
376
|
"src/lib/config.ts"() {
|
|
433
377
|
"use strict";
|
|
434
378
|
EXE_AI_DIR = resolveDataDir();
|
|
435
|
-
DB_PATH =
|
|
436
|
-
MODELS_DIR =
|
|
437
|
-
CONFIG_PATH =
|
|
438
|
-
LEGACY_LANCE_PATH =
|
|
379
|
+
DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
|
|
380
|
+
MODELS_DIR = path2.join(EXE_AI_DIR, "models");
|
|
381
|
+
CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
|
|
382
|
+
LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
|
|
439
383
|
CURRENT_CONFIG_VERSION = 1;
|
|
440
384
|
DEFAULT_CONFIG = {
|
|
441
385
|
config_version: CURRENT_CONFIG_VERSION,
|
|
@@ -507,11 +451,118 @@ var init_config = __esm({
|
|
|
507
451
|
}
|
|
508
452
|
});
|
|
509
453
|
|
|
454
|
+
// src/lib/runtime-table.ts
|
|
455
|
+
var RUNTIME_TABLE, DEFAULT_RUNTIME;
|
|
456
|
+
var init_runtime_table = __esm({
|
|
457
|
+
"src/lib/runtime-table.ts"() {
|
|
458
|
+
"use strict";
|
|
459
|
+
RUNTIME_TABLE = {
|
|
460
|
+
codex: {
|
|
461
|
+
binary: "codex",
|
|
462
|
+
launchMode: "exec",
|
|
463
|
+
autoApproveFlag: "--full-auto",
|
|
464
|
+
inlineFlag: "--no-alt-screen",
|
|
465
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
466
|
+
defaultModel: "gpt-5.4"
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
DEFAULT_RUNTIME = "claude";
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// src/lib/agent-config.ts
|
|
474
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
475
|
+
import path3 from "path";
|
|
476
|
+
function loadAgentConfig() {
|
|
477
|
+
if (!existsSync3(AGENT_CONFIG_PATH)) return {};
|
|
478
|
+
try {
|
|
479
|
+
return JSON.parse(readFileSync3(AGENT_CONFIG_PATH, "utf-8"));
|
|
480
|
+
} catch {
|
|
481
|
+
return {};
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function getAgentRuntime(agentId) {
|
|
485
|
+
const config = loadAgentConfig();
|
|
486
|
+
const entry = config[agentId];
|
|
487
|
+
if (entry) return entry;
|
|
488
|
+
return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
|
|
489
|
+
}
|
|
490
|
+
var AGENT_CONFIG_PATH, DEFAULT_MODELS;
|
|
491
|
+
var init_agent_config = __esm({
|
|
492
|
+
"src/lib/agent-config.ts"() {
|
|
493
|
+
"use strict";
|
|
494
|
+
init_config();
|
|
495
|
+
init_runtime_table();
|
|
496
|
+
AGENT_CONFIG_PATH = path3.join(EXE_AI_DIR, "agent-config.json");
|
|
497
|
+
DEFAULT_MODELS = {
|
|
498
|
+
claude: "claude-opus-4",
|
|
499
|
+
codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
|
|
500
|
+
opencode: "minimax-m2.7"
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// src/lib/intercom-queue.ts
|
|
506
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
|
|
507
|
+
import path4 from "path";
|
|
508
|
+
import os3 from "os";
|
|
509
|
+
function ensureDir() {
|
|
510
|
+
const dir = path4.dirname(QUEUE_PATH);
|
|
511
|
+
if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
|
|
512
|
+
}
|
|
513
|
+
function readQueue() {
|
|
514
|
+
try {
|
|
515
|
+
if (!existsSync4(QUEUE_PATH)) return [];
|
|
516
|
+
return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
|
|
517
|
+
} catch {
|
|
518
|
+
return [];
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function writeQueue(queue) {
|
|
522
|
+
ensureDir();
|
|
523
|
+
const tmp = `${QUEUE_PATH}.tmp`;
|
|
524
|
+
writeFileSync3(tmp, JSON.stringify(queue, null, 2));
|
|
525
|
+
renameSync2(tmp, QUEUE_PATH);
|
|
526
|
+
}
|
|
527
|
+
function queueIntercom(targetSession, reason) {
|
|
528
|
+
const queue = readQueue();
|
|
529
|
+
const existing = queue.find((q) => q.targetSession === targetSession);
|
|
530
|
+
if (existing) {
|
|
531
|
+
existing.attempts++;
|
|
532
|
+
existing.queuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
533
|
+
existing.reason = reason;
|
|
534
|
+
} else {
|
|
535
|
+
queue.push({
|
|
536
|
+
targetSession,
|
|
537
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
538
|
+
attempts: 0,
|
|
539
|
+
reason
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
writeQueue(queue);
|
|
543
|
+
}
|
|
544
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
545
|
+
var init_intercom_queue = __esm({
|
|
546
|
+
"src/lib/intercom-queue.ts"() {
|
|
547
|
+
"use strict";
|
|
548
|
+
QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
|
|
549
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
550
|
+
INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// src/lib/db-retry.ts
|
|
555
|
+
var init_db_retry = __esm({
|
|
556
|
+
"src/lib/db-retry.ts"() {
|
|
557
|
+
"use strict";
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
|
|
510
561
|
// src/lib/employees.ts
|
|
511
562
|
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
512
|
-
import { existsSync as
|
|
563
|
+
import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
513
564
|
import { execSync as execSync3 } from "child_process";
|
|
514
|
-
import
|
|
565
|
+
import path5 from "path";
|
|
515
566
|
import os4 from "os";
|
|
516
567
|
function normalizeRole(role) {
|
|
517
568
|
return (role ?? "").trim().toLowerCase();
|
|
@@ -530,9 +581,9 @@ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
|
|
|
530
581
|
return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
|
|
531
582
|
}
|
|
532
583
|
function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
|
|
533
|
-
if (!
|
|
584
|
+
if (!existsSync5(employeesPath)) return [];
|
|
534
585
|
try {
|
|
535
|
-
return JSON.parse(
|
|
586
|
+
return JSON.parse(readFileSync5(employeesPath, "utf-8"));
|
|
536
587
|
} catch {
|
|
537
588
|
return [];
|
|
538
589
|
}
|
|
@@ -551,7 +602,7 @@ var init_employees = __esm({
|
|
|
551
602
|
"src/lib/employees.ts"() {
|
|
552
603
|
"use strict";
|
|
553
604
|
init_config();
|
|
554
|
-
EMPLOYEES_PATH =
|
|
605
|
+
EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
|
|
555
606
|
DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
|
|
556
607
|
COORDINATOR_ROLE = "COO";
|
|
557
608
|
MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
|
|
@@ -564,31 +615,38 @@ function getClient() {
|
|
|
564
615
|
if (!_resilientClient) {
|
|
565
616
|
throw new Error("Database client not initialized. Call initDatabase() first.");
|
|
566
617
|
}
|
|
618
|
+
if (process.env.EXE_IS_DAEMON === "1") {
|
|
619
|
+
return _resilientClient;
|
|
620
|
+
}
|
|
621
|
+
if (_daemonClient && _daemonClient._isDaemonActive()) {
|
|
622
|
+
return _daemonClient;
|
|
623
|
+
}
|
|
567
624
|
return _resilientClient;
|
|
568
625
|
}
|
|
569
|
-
var _resilientClient;
|
|
626
|
+
var _resilientClient, _daemonClient;
|
|
570
627
|
var init_database = __esm({
|
|
571
628
|
"src/lib/database.ts"() {
|
|
572
629
|
"use strict";
|
|
573
630
|
init_db_retry();
|
|
574
631
|
init_employees();
|
|
575
632
|
_resilientClient = null;
|
|
633
|
+
_daemonClient = null;
|
|
576
634
|
}
|
|
577
635
|
});
|
|
578
636
|
|
|
579
637
|
// src/lib/license.ts
|
|
580
|
-
import { readFileSync as
|
|
638
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
581
639
|
import { randomUUID } from "crypto";
|
|
582
|
-
import
|
|
640
|
+
import path6 from "path";
|
|
583
641
|
import { jwtVerify, importSPKI } from "jose";
|
|
584
642
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
585
643
|
var init_license = __esm({
|
|
586
644
|
"src/lib/license.ts"() {
|
|
587
645
|
"use strict";
|
|
588
646
|
init_config();
|
|
589
|
-
LICENSE_PATH =
|
|
590
|
-
CACHE_PATH =
|
|
591
|
-
DEVICE_ID_PATH =
|
|
647
|
+
LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
|
|
648
|
+
CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
|
|
649
|
+
DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
|
|
592
650
|
PLAN_LIMITS = {
|
|
593
651
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
594
652
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -600,12 +658,12 @@ var init_license = __esm({
|
|
|
600
658
|
});
|
|
601
659
|
|
|
602
660
|
// src/lib/plan-limits.ts
|
|
603
|
-
import { readFileSync as
|
|
604
|
-
import
|
|
661
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
662
|
+
import path7 from "path";
|
|
605
663
|
function getLicenseSync() {
|
|
606
664
|
try {
|
|
607
|
-
if (!
|
|
608
|
-
const raw = JSON.parse(
|
|
665
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
666
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
609
667
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
610
668
|
const parts = raw.token.split(".");
|
|
611
669
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -643,8 +701,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
643
701
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
644
702
|
let count = 0;
|
|
645
703
|
try {
|
|
646
|
-
if (
|
|
647
|
-
const raw =
|
|
704
|
+
if (existsSync7(filePath)) {
|
|
705
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
648
706
|
const employees = JSON.parse(raw);
|
|
649
707
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
650
708
|
}
|
|
@@ -673,19 +731,19 @@ var init_plan_limits = __esm({
|
|
|
673
731
|
this.name = "PlanLimitError";
|
|
674
732
|
}
|
|
675
733
|
};
|
|
676
|
-
CACHE_PATH2 =
|
|
734
|
+
CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
677
735
|
}
|
|
678
736
|
});
|
|
679
737
|
|
|
680
738
|
// src/lib/notifications.ts
|
|
681
739
|
import crypto from "crypto";
|
|
682
|
-
import
|
|
740
|
+
import path8 from "path";
|
|
683
741
|
import os5 from "os";
|
|
684
742
|
import {
|
|
685
|
-
readFileSync as
|
|
743
|
+
readFileSync as readFileSync8,
|
|
686
744
|
readdirSync,
|
|
687
745
|
unlinkSync as unlinkSync2,
|
|
688
|
-
existsSync as
|
|
746
|
+
existsSync as existsSync8,
|
|
689
747
|
rmdirSync
|
|
690
748
|
} from "fs";
|
|
691
749
|
async function writeNotification(notification) {
|
|
@@ -844,10 +902,11 @@ var init_state_bus = __esm({
|
|
|
844
902
|
|
|
845
903
|
// src/lib/tasks-crud.ts
|
|
846
904
|
import crypto3 from "crypto";
|
|
847
|
-
import
|
|
905
|
+
import path9 from "path";
|
|
906
|
+
import os6 from "os";
|
|
848
907
|
import { execSync as execSync4 } from "child_process";
|
|
849
908
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
850
|
-
import { existsSync as
|
|
909
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
851
910
|
async function writeCheckpoint(input) {
|
|
852
911
|
const client = getClient();
|
|
853
912
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -888,6 +947,35 @@ function extractParentFromContext(contextBody) {
|
|
|
888
947
|
function slugify(title) {
|
|
889
948
|
return title.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
890
949
|
}
|
|
950
|
+
function buildKeywordIndex() {
|
|
951
|
+
const idx = /* @__PURE__ */ new Map();
|
|
952
|
+
for (const [role, keywords] of Object.entries(LANE_KEYWORDS)) {
|
|
953
|
+
for (const kw of keywords) {
|
|
954
|
+
const existing = idx.get(kw) ?? [];
|
|
955
|
+
existing.push(role);
|
|
956
|
+
idx.set(kw, existing);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return idx;
|
|
960
|
+
}
|
|
961
|
+
function checkLaneAffinity(title, context, assigneeName) {
|
|
962
|
+
const employees = loadEmployeesSync();
|
|
963
|
+
const employee = employees.find((e) => e.name === assigneeName);
|
|
964
|
+
if (!employee) return void 0;
|
|
965
|
+
const assigneeRole = employee.role;
|
|
966
|
+
const text = `${title} ${context}`.toLowerCase();
|
|
967
|
+
const matchedRoles = /* @__PURE__ */ new Set();
|
|
968
|
+
for (const [keyword, roles] of KEYWORD_INDEX) {
|
|
969
|
+
if (text.includes(keyword)) {
|
|
970
|
+
for (const role of roles) matchedRoles.add(role);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
if (matchedRoles.size === 0) return void 0;
|
|
974
|
+
if (matchedRoles.has(assigneeRole)) return void 0;
|
|
975
|
+
if (assigneeRole === "COO") return void 0;
|
|
976
|
+
const expectedRoles = Array.from(matchedRoles).join(" or ");
|
|
977
|
+
return `\u26A0\uFE0F Lane mismatch: task content suggests ${expectedRoles}, but assigned to ${assigneeName} (${assigneeRole}).`;
|
|
978
|
+
}
|
|
891
979
|
async function resolveTask(client, identifier, scopeSession) {
|
|
892
980
|
const scope = sessionScopeFilter(scopeSession);
|
|
893
981
|
let result = await client.execute({
|
|
@@ -937,7 +1025,14 @@ async function createTaskCore(input) {
|
|
|
937
1025
|
const id = crypto3.randomUUID();
|
|
938
1026
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
939
1027
|
const slug = slugify(input.title);
|
|
940
|
-
|
|
1028
|
+
let earlySessionScope = null;
|
|
1029
|
+
try {
|
|
1030
|
+
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
1031
|
+
earlySessionScope = resolveExeSession2();
|
|
1032
|
+
} catch {
|
|
1033
|
+
}
|
|
1034
|
+
const scope = earlySessionScope ?? "default";
|
|
1035
|
+
const taskFile = input.taskFile ?? `tasks/${scope}/${input.assignedTo}/${slug}.md`;
|
|
941
1036
|
let blockedById = null;
|
|
942
1037
|
const initialStatus = input.blockedBy ? "blocked" : "open";
|
|
943
1038
|
if (input.blockedBy) {
|
|
@@ -977,22 +1072,24 @@ async function createTaskCore(input) {
|
|
|
977
1072
|
if (dupCheck.rows.length > 0) {
|
|
978
1073
|
warning = `similar active task already exists (${String(dupCheck.rows[0].id)}). Created new task anyway.`;
|
|
979
1074
|
}
|
|
1075
|
+
if (!process.env.DISABLE_LANE_AFFINITY) {
|
|
1076
|
+
const laneWarning = checkLaneAffinity(input.title, input.context, input.assignedTo);
|
|
1077
|
+
if (laneWarning) {
|
|
1078
|
+
warning = warning ? `${warning}
|
|
1079
|
+
${laneWarning}` : laneWarning;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
980
1082
|
if (input.baseDir) {
|
|
981
1083
|
try {
|
|
982
|
-
await mkdir3(
|
|
983
|
-
await mkdir3(
|
|
1084
|
+
await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
1085
|
+
await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
984
1086
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
985
1087
|
await ensureGitignoreExe(input.baseDir);
|
|
986
1088
|
} catch {
|
|
987
1089
|
}
|
|
988
1090
|
}
|
|
989
1091
|
const complexity = input.complexity ?? "standard";
|
|
990
|
-
|
|
991
|
-
try {
|
|
992
|
-
const { resolveExeSession: resolveExeSession2 } = await Promise.resolve().then(() => (init_tmux_routing(), tmux_routing_exports));
|
|
993
|
-
sessionScope = resolveExeSession2();
|
|
994
|
-
} catch {
|
|
995
|
-
}
|
|
1092
|
+
const sessionScope = earlySessionScope;
|
|
996
1093
|
await client.execute({
|
|
997
1094
|
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)
|
|
998
1095
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
@@ -1019,6 +1116,43 @@ async function createTaskCore(input) {
|
|
|
1019
1116
|
now
|
|
1020
1117
|
]
|
|
1021
1118
|
});
|
|
1119
|
+
if (input.baseDir) {
|
|
1120
|
+
try {
|
|
1121
|
+
const EXE_OS_DIR = path9.join(os6.homedir(), ".exe-os");
|
|
1122
|
+
const mdPath = path9.join(EXE_OS_DIR, taskFile);
|
|
1123
|
+
const mdDir = path9.dirname(mdPath);
|
|
1124
|
+
if (!existsSync9(mdDir)) await mkdir3(mdDir, { recursive: true });
|
|
1125
|
+
const reviewer = input.reviewer ?? input.assignedBy;
|
|
1126
|
+
const mdContent = `# ${input.title}
|
|
1127
|
+
|
|
1128
|
+
**ID:** ${id}
|
|
1129
|
+
**Status:** ${initialStatus}
|
|
1130
|
+
**Priority:** ${input.priority}
|
|
1131
|
+
**Assigned by:** ${input.assignedBy}
|
|
1132
|
+
**Assigned to:** ${input.assignedTo}
|
|
1133
|
+
**Project:** ${input.projectName}
|
|
1134
|
+
**Created:** ${now.split("T")[0]}${parentTaskId ? `
|
|
1135
|
+
**Parent task:** ${parentTaskId}` : ""}
|
|
1136
|
+
**Reviewer:** ${reviewer}
|
|
1137
|
+
|
|
1138
|
+
## Context
|
|
1139
|
+
|
|
1140
|
+
${input.context}
|
|
1141
|
+
|
|
1142
|
+
## MANDATORY: When done
|
|
1143
|
+
|
|
1144
|
+
You MUST call update_task with status "done" and a result summary when finished.
|
|
1145
|
+
If you skip this, your reviewer will not know you're done and your work won't be reviewed.
|
|
1146
|
+
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
1147
|
+
`;
|
|
1148
|
+
await writeFile3(mdPath, mdContent, "utf-8");
|
|
1149
|
+
} catch (err) {
|
|
1150
|
+
process.stderr.write(
|
|
1151
|
+
`[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
|
|
1152
|
+
`
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1022
1156
|
return {
|
|
1023
1157
|
id,
|
|
1024
1158
|
title: input.title,
|
|
@@ -1211,7 +1345,7 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
1211
1345
|
return { row, taskFile, now, taskId };
|
|
1212
1346
|
}
|
|
1213
1347
|
}
|
|
1214
|
-
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId
|
|
1348
|
+
if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || isCoordinatorName(input.callerAgentId))) {
|
|
1215
1349
|
process.stderr.write(
|
|
1216
1350
|
`[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
|
|
1217
1351
|
`
|
|
@@ -1276,9 +1410,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
1276
1410
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
1277
1411
|
}
|
|
1278
1412
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
1279
|
-
const archPath =
|
|
1413
|
+
const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
1280
1414
|
try {
|
|
1281
|
-
if (
|
|
1415
|
+
if (existsSync9(archPath)) return;
|
|
1282
1416
|
const template = [
|
|
1283
1417
|
`# ${projectName} \u2014 System Architecture`,
|
|
1284
1418
|
"",
|
|
@@ -1311,10 +1445,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
1311
1445
|
}
|
|
1312
1446
|
}
|
|
1313
1447
|
async function ensureGitignoreExe(baseDir) {
|
|
1314
|
-
const gitignorePath =
|
|
1448
|
+
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
1315
1449
|
try {
|
|
1316
|
-
if (
|
|
1317
|
-
const content =
|
|
1450
|
+
if (existsSync9(gitignorePath)) {
|
|
1451
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
1318
1452
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
1319
1453
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
1320
1454
|
} else {
|
|
@@ -1323,20 +1457,30 @@ async function ensureGitignoreExe(baseDir) {
|
|
|
1323
1457
|
} catch {
|
|
1324
1458
|
}
|
|
1325
1459
|
}
|
|
1326
|
-
var DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
1460
|
+
var LANE_KEYWORDS, KEYWORD_INDEX, DELEGATION_KEYWORDS, TASK_ALREADY_CLAIMED_PREFIX;
|
|
1327
1461
|
var init_tasks_crud = __esm({
|
|
1328
1462
|
"src/lib/tasks-crud.ts"() {
|
|
1329
1463
|
"use strict";
|
|
1330
1464
|
init_database();
|
|
1331
1465
|
init_task_scope();
|
|
1466
|
+
init_employees();
|
|
1467
|
+
LANE_KEYWORDS = {
|
|
1468
|
+
CMO: ["sales", "script", "pitch", "offer", "copy", "objection", "brand", "content", "seo", "marketing", "newsletter", "carousel", "social", "campaign"],
|
|
1469
|
+
CTO: ["spec", "architecture", "migration", "schema", "database", "design doc", "adr", "security audit", "tech stack"],
|
|
1470
|
+
"Principal Engineer": ["implement", "build", "fix", "commit", "refactor", "bug", "feature", "wire", "integration"],
|
|
1471
|
+
"Staff Code Reviewer": ["critique", "verdict", "review", "audit", "code quality"],
|
|
1472
|
+
"Content Production Specialist": ["render", "video", "image", "b-roll", "remotion", "animation", "thumbnail"],
|
|
1473
|
+
"AI Product Lead": ["competitive", "analysis", "benchmark", "compare", "scout", "evaluate", "poc"]
|
|
1474
|
+
};
|
|
1475
|
+
KEYWORD_INDEX = buildKeywordIndex();
|
|
1332
1476
|
DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
|
|
1333
1477
|
TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
|
|
1334
1478
|
}
|
|
1335
1479
|
});
|
|
1336
1480
|
|
|
1337
1481
|
// src/lib/tasks-review.ts
|
|
1338
|
-
import
|
|
1339
|
-
import { existsSync as
|
|
1482
|
+
import path10 from "path";
|
|
1483
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1340
1484
|
async function countPendingReviews(sessionScope) {
|
|
1341
1485
|
const client = getClient();
|
|
1342
1486
|
if (sessionScope) {
|
|
@@ -1358,7 +1502,7 @@ async function countNewPendingReviewsSince(sinceIso, sessionScope) {
|
|
|
1358
1502
|
const result2 = await client.execute({
|
|
1359
1503
|
sql: `SELECT COUNT(*) as cnt FROM tasks
|
|
1360
1504
|
WHERE status = 'needs_review' AND updated_at > ?
|
|
1361
|
-
AND
|
|
1505
|
+
AND session_scope = ?`,
|
|
1362
1506
|
args: [sinceIso, sessionScope]
|
|
1363
1507
|
});
|
|
1364
1508
|
return Number(result2.rows[0]?.cnt) || 0;
|
|
@@ -1376,7 +1520,7 @@ async function listPendingReviews(limit, sessionScope) {
|
|
|
1376
1520
|
const result2 = await client.execute({
|
|
1377
1521
|
sql: `SELECT title, assigned_to, project_name FROM tasks
|
|
1378
1522
|
WHERE status = 'needs_review'
|
|
1379
|
-
AND
|
|
1523
|
+
AND session_scope = ?
|
|
1380
1524
|
ORDER BY priority ASC, created_at DESC LIMIT ?`,
|
|
1381
1525
|
args: [sessionScope, limit]
|
|
1382
1526
|
});
|
|
@@ -1497,14 +1641,14 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
1497
1641
|
if (parts.length >= 3 && parts[0] === "review") {
|
|
1498
1642
|
const agent = parts[1];
|
|
1499
1643
|
const slug = parts.slice(2).join("-");
|
|
1500
|
-
const
|
|
1644
|
+
const legacyTaskFile = `exe/${agent}/${slug}.md`;
|
|
1501
1645
|
const result = await client.execute({
|
|
1502
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
1503
|
-
args: [now,
|
|
1646
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE (task_file = ? OR task_file LIKE ?) AND status = 'needs_review'",
|
|
1647
|
+
args: [now, legacyTaskFile, `tasks/%/${agent}/${slug}.md`]
|
|
1504
1648
|
});
|
|
1505
1649
|
if (result.rowsAffected > 0) {
|
|
1506
1650
|
process.stderr.write(
|
|
1507
|
-
`[review-cleanup] Cascaded original task to done
|
|
1651
|
+
`[review-cleanup] Cascaded original task to done: ${agent}/${slug}.md
|
|
1508
1652
|
`
|
|
1509
1653
|
);
|
|
1510
1654
|
}
|
|
@@ -1517,11 +1661,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
1517
1661
|
);
|
|
1518
1662
|
}
|
|
1519
1663
|
try {
|
|
1520
|
-
const cacheDir =
|
|
1521
|
-
if (
|
|
1664
|
+
const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
|
|
1665
|
+
if (existsSync10(cacheDir)) {
|
|
1522
1666
|
for (const f of readdirSync2(cacheDir)) {
|
|
1523
1667
|
if (f.startsWith("review-notified-")) {
|
|
1524
|
-
unlinkSync3(
|
|
1668
|
+
unlinkSync3(path10.join(cacheDir, f));
|
|
1525
1669
|
}
|
|
1526
1670
|
}
|
|
1527
1671
|
}
|
|
@@ -1542,7 +1686,7 @@ var init_tasks_review = __esm({
|
|
|
1542
1686
|
});
|
|
1543
1687
|
|
|
1544
1688
|
// src/lib/tasks-chain.ts
|
|
1545
|
-
import
|
|
1689
|
+
import path11 from "path";
|
|
1546
1690
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
1547
1691
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
1548
1692
|
const client = getClient();
|
|
@@ -1559,7 +1703,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
1559
1703
|
});
|
|
1560
1704
|
for (const ur of unblockedRows.rows) {
|
|
1561
1705
|
try {
|
|
1562
|
-
const ubFile =
|
|
1706
|
+
const ubFile = path11.join(baseDir, String(ur.task_file));
|
|
1563
1707
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
1564
1708
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
1565
1709
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -1628,7 +1772,7 @@ var init_tasks_chain = __esm({
|
|
|
1628
1772
|
|
|
1629
1773
|
// src/lib/project-name.ts
|
|
1630
1774
|
import { execSync as execSync5 } from "child_process";
|
|
1631
|
-
import
|
|
1775
|
+
import path12 from "path";
|
|
1632
1776
|
function getProjectName(cwd) {
|
|
1633
1777
|
const dir = cwd ?? process.cwd();
|
|
1634
1778
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -1641,7 +1785,7 @@ function getProjectName(cwd) {
|
|
|
1641
1785
|
timeout: 2e3,
|
|
1642
1786
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1643
1787
|
}).trim();
|
|
1644
|
-
repoRoot =
|
|
1788
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
1645
1789
|
} catch {
|
|
1646
1790
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
1647
1791
|
cwd: dir,
|
|
@@ -1650,11 +1794,11 @@ function getProjectName(cwd) {
|
|
|
1650
1794
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1651
1795
|
}).trim();
|
|
1652
1796
|
}
|
|
1653
|
-
_cached2 =
|
|
1797
|
+
_cached2 = path12.basename(repoRoot);
|
|
1654
1798
|
_cachedCwd = dir;
|
|
1655
1799
|
return _cached2;
|
|
1656
1800
|
} catch {
|
|
1657
|
-
_cached2 =
|
|
1801
|
+
_cached2 = path12.basename(dir);
|
|
1658
1802
|
_cachedCwd = dir;
|
|
1659
1803
|
return _cached2;
|
|
1660
1804
|
}
|
|
@@ -1686,7 +1830,7 @@ function findSessionForProject(projectName) {
|
|
|
1686
1830
|
const sessions = listSessions();
|
|
1687
1831
|
for (const s of sessions) {
|
|
1688
1832
|
const proj = s.projectDir.split("/").filter(Boolean).pop();
|
|
1689
|
-
if (proj === projectName &&
|
|
1833
|
+
if (proj === projectName && isCoordinatorName(s.agentId)) return s;
|
|
1690
1834
|
}
|
|
1691
1835
|
return null;
|
|
1692
1836
|
}
|
|
@@ -1732,7 +1876,7 @@ var init_session_scope = __esm({
|
|
|
1732
1876
|
|
|
1733
1877
|
// src/lib/tasks-notify.ts
|
|
1734
1878
|
async function dispatchTaskToEmployee(input) {
|
|
1735
|
-
if (
|
|
1879
|
+
if (isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
|
|
1736
1880
|
let crossProject = false;
|
|
1737
1881
|
if (input.projectName) {
|
|
1738
1882
|
try {
|
|
@@ -2127,8 +2271,8 @@ __export(tasks_exports, {
|
|
|
2127
2271
|
updateTaskStatus: () => updateTaskStatus,
|
|
2128
2272
|
writeCheckpoint: () => writeCheckpoint
|
|
2129
2273
|
});
|
|
2130
|
-
import
|
|
2131
|
-
import { writeFileSync as
|
|
2274
|
+
import path13 from "path";
|
|
2275
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
2132
2276
|
async function createTask(input) {
|
|
2133
2277
|
const result = await createTaskCore(input);
|
|
2134
2278
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -2147,11 +2291,11 @@ async function updateTask(input) {
|
|
|
2147
2291
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
2148
2292
|
try {
|
|
2149
2293
|
const agent = String(row.assigned_to);
|
|
2150
|
-
const cacheDir =
|
|
2151
|
-
const cachePath =
|
|
2294
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
2295
|
+
const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
|
|
2152
2296
|
if (input.status === "in_progress") {
|
|
2153
|
-
|
|
2154
|
-
|
|
2297
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
2298
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
2155
2299
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
2156
2300
|
try {
|
|
2157
2301
|
unlinkSync4(cachePath);
|
|
@@ -2211,7 +2355,7 @@ async function updateTask(input) {
|
|
|
2211
2355
|
}
|
|
2212
2356
|
const isTerminal = input.status === "done" || input.status === "needs_review";
|
|
2213
2357
|
if (isTerminal) {
|
|
2214
|
-
const isCoordinator =
|
|
2358
|
+
const isCoordinator = isCoordinatorName(String(row.assigned_to));
|
|
2215
2359
|
if (!isCoordinator) {
|
|
2216
2360
|
notifyTaskDone();
|
|
2217
2361
|
}
|
|
@@ -2236,7 +2380,7 @@ async function updateTask(input) {
|
|
|
2236
2380
|
}
|
|
2237
2381
|
}
|
|
2238
2382
|
}
|
|
2239
|
-
if (input.status === "done" &&
|
|
2383
|
+
if (input.status === "done" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
|
|
2240
2384
|
Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
|
|
2241
2385
|
({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
|
|
2242
2386
|
taskId,
|
|
@@ -2252,7 +2396,7 @@ async function updateTask(input) {
|
|
|
2252
2396
|
});
|
|
2253
2397
|
}
|
|
2254
2398
|
let nextTask;
|
|
2255
|
-
if (isTerminal &&
|
|
2399
|
+
if (isTerminal && !isCoordinatorName(String(row.assigned_to))) {
|
|
2256
2400
|
try {
|
|
2257
2401
|
nextTask = await findNextTask(String(row.assigned_to));
|
|
2258
2402
|
} catch {
|
|
@@ -2618,13 +2762,13 @@ __export(tmux_routing_exports, {
|
|
|
2618
2762
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
2619
2763
|
});
|
|
2620
2764
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
2621
|
-
import { readFileSync as
|
|
2622
|
-
import
|
|
2623
|
-
import
|
|
2765
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
|
|
2766
|
+
import path14 from "path";
|
|
2767
|
+
import os7 from "os";
|
|
2624
2768
|
import { fileURLToPath } from "url";
|
|
2625
2769
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
2626
2770
|
function spawnLockPath(sessionName) {
|
|
2627
|
-
return
|
|
2771
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2628
2772
|
}
|
|
2629
2773
|
function isProcessAlive(pid) {
|
|
2630
2774
|
try {
|
|
@@ -2635,13 +2779,13 @@ function isProcessAlive(pid) {
|
|
|
2635
2779
|
}
|
|
2636
2780
|
}
|
|
2637
2781
|
function acquireSpawnLock(sessionName) {
|
|
2638
|
-
if (!
|
|
2639
|
-
|
|
2782
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
2783
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
2640
2784
|
}
|
|
2641
2785
|
const lockFile = spawnLockPath(sessionName);
|
|
2642
|
-
if (
|
|
2786
|
+
if (existsSync11(lockFile)) {
|
|
2643
2787
|
try {
|
|
2644
|
-
const lock = JSON.parse(
|
|
2788
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
2645
2789
|
const age = Date.now() - lock.timestamp;
|
|
2646
2790
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2647
2791
|
return false;
|
|
@@ -2649,7 +2793,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
2649
2793
|
} catch {
|
|
2650
2794
|
}
|
|
2651
2795
|
}
|
|
2652
|
-
|
|
2796
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
2653
2797
|
return true;
|
|
2654
2798
|
}
|
|
2655
2799
|
function releaseSpawnLock(sessionName) {
|
|
@@ -2661,13 +2805,13 @@ function releaseSpawnLock(sessionName) {
|
|
|
2661
2805
|
function resolveBehaviorsExporterScript() {
|
|
2662
2806
|
try {
|
|
2663
2807
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2664
|
-
const scriptPath =
|
|
2665
|
-
|
|
2808
|
+
const scriptPath = path14.join(
|
|
2809
|
+
path14.dirname(thisFile),
|
|
2666
2810
|
"..",
|
|
2667
2811
|
"bin",
|
|
2668
2812
|
"exe-export-behaviors.js"
|
|
2669
2813
|
);
|
|
2670
|
-
return
|
|
2814
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
2671
2815
|
} catch {
|
|
2672
2816
|
return null;
|
|
2673
2817
|
}
|
|
@@ -2733,12 +2877,12 @@ function extractRootExe(name) {
|
|
|
2733
2877
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2734
2878
|
}
|
|
2735
2879
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
2736
|
-
if (!
|
|
2737
|
-
|
|
2880
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
2881
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
2738
2882
|
}
|
|
2739
2883
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
2740
|
-
const filePath =
|
|
2741
|
-
|
|
2884
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
2885
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
2742
2886
|
parentExe: rootExe,
|
|
2743
2887
|
dispatchedBy: dispatchedBy || rootExe,
|
|
2744
2888
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2746,7 +2890,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
2746
2890
|
}
|
|
2747
2891
|
function getParentExe(sessionKey) {
|
|
2748
2892
|
try {
|
|
2749
|
-
const data = JSON.parse(
|
|
2893
|
+
const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2750
2894
|
return data.parentExe || null;
|
|
2751
2895
|
} catch {
|
|
2752
2896
|
return null;
|
|
@@ -2754,8 +2898,8 @@ function getParentExe(sessionKey) {
|
|
|
2754
2898
|
}
|
|
2755
2899
|
function getDispatchedBy(sessionKey) {
|
|
2756
2900
|
try {
|
|
2757
|
-
const data = JSON.parse(
|
|
2758
|
-
|
|
2901
|
+
const data = JSON.parse(readFileSync10(
|
|
2902
|
+
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
2759
2903
|
"utf8"
|
|
2760
2904
|
));
|
|
2761
2905
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -2816,32 +2960,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
2816
2960
|
}
|
|
2817
2961
|
function readDebounceState() {
|
|
2818
2962
|
try {
|
|
2819
|
-
if (!
|
|
2820
|
-
|
|
2963
|
+
if (!existsSync11(DEBOUNCE_FILE)) return {};
|
|
2964
|
+
const raw = JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
|
|
2965
|
+
const state = {};
|
|
2966
|
+
for (const [key, val] of Object.entries(raw)) {
|
|
2967
|
+
if (typeof val === "number") {
|
|
2968
|
+
state[key] = { lastSent: val, pending: 0 };
|
|
2969
|
+
} else if (val && typeof val === "object" && "lastSent" in val) {
|
|
2970
|
+
state[key] = val;
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
return state;
|
|
2821
2974
|
} catch {
|
|
2822
2975
|
return {};
|
|
2823
2976
|
}
|
|
2824
2977
|
}
|
|
2825
2978
|
function writeDebounceState(state) {
|
|
2826
2979
|
try {
|
|
2827
|
-
if (!
|
|
2828
|
-
|
|
2980
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
2981
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
2829
2982
|
} catch {
|
|
2830
2983
|
}
|
|
2831
2984
|
}
|
|
2832
2985
|
function isDebounced(targetSession) {
|
|
2833
2986
|
const state = readDebounceState();
|
|
2834
|
-
const
|
|
2835
|
-
|
|
2987
|
+
const entry = state[targetSession];
|
|
2988
|
+
const lastSent = entry?.lastSent ?? 0;
|
|
2989
|
+
if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
|
|
2990
|
+
if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
|
|
2991
|
+
state[targetSession].pending++;
|
|
2992
|
+
writeDebounceState(state);
|
|
2993
|
+
return true;
|
|
2994
|
+
}
|
|
2995
|
+
return false;
|
|
2836
2996
|
}
|
|
2837
2997
|
function recordDebounce(targetSession) {
|
|
2838
2998
|
const state = readDebounceState();
|
|
2839
|
-
state[targetSession]
|
|
2999
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
3000
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
2840
3001
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
2841
3002
|
for (const key of Object.keys(state)) {
|
|
2842
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
3003
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
2843
3004
|
}
|
|
2844
3005
|
writeDebounceState(state);
|
|
3006
|
+
return batched;
|
|
2845
3007
|
}
|
|
2846
3008
|
function logIntercom(msg) {
|
|
2847
3009
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -2886,7 +3048,7 @@ function sendIntercom(targetSession) {
|
|
|
2886
3048
|
return "skipped_exe";
|
|
2887
3049
|
}
|
|
2888
3050
|
if (isDebounced(targetSession)) {
|
|
2889
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
3051
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
2890
3052
|
return "debounced";
|
|
2891
3053
|
}
|
|
2892
3054
|
try {
|
|
@@ -2898,14 +3060,14 @@ function sendIntercom(targetSession) {
|
|
|
2898
3060
|
const sessionState = getSessionState(targetSession);
|
|
2899
3061
|
if (sessionState === "no_claude") {
|
|
2900
3062
|
queueIntercom(targetSession, "claude not running in session");
|
|
2901
|
-
recordDebounce(targetSession);
|
|
2902
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
3063
|
+
const batched2 = recordDebounce(targetSession);
|
|
3064
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
2903
3065
|
return "queued";
|
|
2904
3066
|
}
|
|
2905
3067
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
2906
3068
|
queueIntercom(targetSession, "session busy at send time");
|
|
2907
|
-
recordDebounce(targetSession);
|
|
2908
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
3069
|
+
const batched2 = recordDebounce(targetSession);
|
|
3070
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
2909
3071
|
return "queued";
|
|
2910
3072
|
}
|
|
2911
3073
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -2913,8 +3075,8 @@ function sendIntercom(targetSession) {
|
|
|
2913
3075
|
transport.sendKeys(targetSession, "q");
|
|
2914
3076
|
}
|
|
2915
3077
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
2916
|
-
recordDebounce(targetSession);
|
|
2917
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
3078
|
+
const batched = recordDebounce(targetSession);
|
|
3079
|
+
logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
|
|
2918
3080
|
return "delivered";
|
|
2919
3081
|
} catch {
|
|
2920
3082
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -2944,7 +3106,7 @@ function notifyParentExe(sessionKey) {
|
|
|
2944
3106
|
return true;
|
|
2945
3107
|
}
|
|
2946
3108
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
2947
|
-
if (
|
|
3109
|
+
if (isCoordinatorName(employeeName)) {
|
|
2948
3110
|
return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
|
|
2949
3111
|
}
|
|
2950
3112
|
try {
|
|
@@ -3016,26 +3178,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3016
3178
|
const transport = getTransport();
|
|
3017
3179
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3018
3180
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3019
|
-
const logDir =
|
|
3020
|
-
const logFile =
|
|
3021
|
-
if (!
|
|
3022
|
-
|
|
3181
|
+
const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
|
|
3182
|
+
const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
|
|
3183
|
+
if (!existsSync11(logDir)) {
|
|
3184
|
+
mkdirSync6(logDir, { recursive: true });
|
|
3023
3185
|
}
|
|
3024
3186
|
transport.kill(sessionName);
|
|
3025
3187
|
let cleanupSuffix = "";
|
|
3026
3188
|
try {
|
|
3027
3189
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3028
|
-
const cleanupScript =
|
|
3029
|
-
if (
|
|
3190
|
+
const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
3191
|
+
if (existsSync11(cleanupScript)) {
|
|
3030
3192
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3031
3193
|
}
|
|
3032
3194
|
} catch {
|
|
3033
3195
|
}
|
|
3034
3196
|
try {
|
|
3035
|
-
const claudeJsonPath =
|
|
3197
|
+
const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
|
|
3036
3198
|
let claudeJson = {};
|
|
3037
3199
|
try {
|
|
3038
|
-
claudeJson = JSON.parse(
|
|
3200
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
3039
3201
|
} catch {
|
|
3040
3202
|
}
|
|
3041
3203
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3043,17 +3205,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3043
3205
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3044
3206
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3045
3207
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3046
|
-
|
|
3208
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3047
3209
|
} catch {
|
|
3048
3210
|
}
|
|
3049
3211
|
try {
|
|
3050
|
-
const settingsDir =
|
|
3212
|
+
const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
|
|
3051
3213
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3052
|
-
const projSettingsDir =
|
|
3053
|
-
const settingsPath =
|
|
3214
|
+
const projSettingsDir = path14.join(settingsDir, normalizedKey);
|
|
3215
|
+
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3054
3216
|
let settings = {};
|
|
3055
3217
|
try {
|
|
3056
|
-
settings = JSON.parse(
|
|
3218
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
3057
3219
|
} catch {
|
|
3058
3220
|
}
|
|
3059
3221
|
const perms = settings.permissions ?? {};
|
|
@@ -3081,21 +3243,24 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3081
3243
|
if (changed) {
|
|
3082
3244
|
perms.allow = allow;
|
|
3083
3245
|
settings.permissions = perms;
|
|
3084
|
-
|
|
3085
|
-
|
|
3246
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
3247
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3086
3248
|
}
|
|
3087
3249
|
} catch {
|
|
3088
3250
|
}
|
|
3089
3251
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
3090
3252
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
3091
|
-
const
|
|
3253
|
+
const agentRtConfig = getAgentRuntime(employeeName);
|
|
3254
|
+
const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
|
|
3255
|
+
const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
|
|
3256
|
+
const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
|
|
3092
3257
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
3093
3258
|
let identityFlag = "";
|
|
3094
3259
|
let behaviorsFlag = "";
|
|
3095
3260
|
let legacyFallbackWarned = false;
|
|
3096
3261
|
if (!useExeAgent && !useBinSymlink) {
|
|
3097
|
-
const identityPath =
|
|
3098
|
-
|
|
3262
|
+
const identityPath = path14.join(
|
|
3263
|
+
os7.homedir(),
|
|
3099
3264
|
".exe-os",
|
|
3100
3265
|
"identity",
|
|
3101
3266
|
`${employeeName}.md`
|
|
@@ -3104,13 +3269,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3104
3269
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
3105
3270
|
if (hasAgentFlag) {
|
|
3106
3271
|
identityFlag = ` --agent ${employeeName}`;
|
|
3107
|
-
} else if (
|
|
3272
|
+
} else if (existsSync11(identityPath)) {
|
|
3108
3273
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
3109
3274
|
legacyFallbackWarned = true;
|
|
3110
3275
|
}
|
|
3111
3276
|
const behaviorsFile = exportBehaviorsSync(
|
|
3112
3277
|
employeeName,
|
|
3113
|
-
|
|
3278
|
+
path14.basename(spawnCwd),
|
|
3114
3279
|
sessionName
|
|
3115
3280
|
);
|
|
3116
3281
|
if (behaviorsFile) {
|
|
@@ -3125,16 +3290,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3125
3290
|
}
|
|
3126
3291
|
let sessionContextFlag = "";
|
|
3127
3292
|
try {
|
|
3128
|
-
const ctxDir =
|
|
3129
|
-
|
|
3130
|
-
const ctxFile =
|
|
3293
|
+
const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3294
|
+
mkdirSync6(ctxDir, { recursive: true });
|
|
3295
|
+
const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
|
|
3131
3296
|
const ctxContent = [
|
|
3132
3297
|
`## Session Context`,
|
|
3133
3298
|
`You are running in tmux session: ${sessionName}.`,
|
|
3134
3299
|
`Your parent coordinator session is ${exeSession}.`,
|
|
3135
3300
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3136
3301
|
].join("\n");
|
|
3137
|
-
|
|
3302
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
3138
3303
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
3139
3304
|
} catch {
|
|
3140
3305
|
}
|
|
@@ -3148,9 +3313,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3148
3313
|
}
|
|
3149
3314
|
}
|
|
3150
3315
|
}
|
|
3316
|
+
if (useCodex) {
|
|
3317
|
+
const codexCfg = RUNTIME_TABLE.codex;
|
|
3318
|
+
if (codexCfg?.apiKeyEnv) {
|
|
3319
|
+
const keyVal = process.env[codexCfg.apiKeyEnv];
|
|
3320
|
+
if (keyVal) {
|
|
3321
|
+
envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
|
|
3325
|
+
}
|
|
3326
|
+
if (useOpencode) {
|
|
3327
|
+
const ocCfg = PROVIDER_TABLE.opencode;
|
|
3328
|
+
if (ocCfg?.apiKeyEnv) {
|
|
3329
|
+
const keyVal = process.env[ocCfg.apiKeyEnv];
|
|
3330
|
+
if (keyVal) {
|
|
3331
|
+
envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
3335
|
+
}
|
|
3336
|
+
if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
|
|
3337
|
+
const defaultClaudeModel = DEFAULT_MODELS.claude;
|
|
3338
|
+
if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
|
|
3339
|
+
envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3151
3342
|
let spawnCommand;
|
|
3152
3343
|
if (useExeAgent) {
|
|
3153
3344
|
spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
|
|
3345
|
+
} else if (useCodex) {
|
|
3346
|
+
process.stderr.write(
|
|
3347
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
|
|
3348
|
+
`
|
|
3349
|
+
);
|
|
3350
|
+
spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
|
|
3351
|
+
} else if (useOpencode) {
|
|
3352
|
+
const binName = `${employeeName}-opencode`;
|
|
3353
|
+
process.stderr.write(
|
|
3354
|
+
`[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
|
|
3355
|
+
`
|
|
3356
|
+
);
|
|
3357
|
+
spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
|
|
3154
3358
|
} else if (useBinSymlink) {
|
|
3155
3359
|
const binName = `${employeeName}-${ccProvider}`;
|
|
3156
3360
|
process.stderr.write(
|
|
@@ -3172,11 +3376,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3172
3376
|
transport.pipeLog(sessionName, logFile);
|
|
3173
3377
|
try {
|
|
3174
3378
|
const mySession = getMySession();
|
|
3175
|
-
const dispatchInfo =
|
|
3176
|
-
|
|
3379
|
+
const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
3380
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
3177
3381
|
dispatchedBy: mySession,
|
|
3178
3382
|
rootExe: exeSession,
|
|
3179
|
-
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
|
|
3383
|
+
provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
|
|
3384
|
+
runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
|
|
3385
|
+
model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
|
|
3180
3386
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3181
3387
|
}));
|
|
3182
3388
|
} catch {
|
|
@@ -3194,6 +3400,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3194
3400
|
booted = true;
|
|
3195
3401
|
break;
|
|
3196
3402
|
}
|
|
3403
|
+
} else if (useCodex) {
|
|
3404
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
3405
|
+
booted = true;
|
|
3406
|
+
break;
|
|
3407
|
+
}
|
|
3197
3408
|
} else {
|
|
3198
3409
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
3199
3410
|
booted = true;
|
|
@@ -3205,9 +3416,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3205
3416
|
}
|
|
3206
3417
|
if (!booted) {
|
|
3207
3418
|
releaseSpawnLock(sessionName);
|
|
3208
|
-
|
|
3419
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
3420
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
3209
3421
|
}
|
|
3210
|
-
if (!useExeAgent) {
|
|
3422
|
+
if (!useExeAgent && !useCodex) {
|
|
3211
3423
|
try {
|
|
3212
3424
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
3213
3425
|
} catch {
|
|
@@ -3233,17 +3445,19 @@ var init_tmux_routing = __esm({
|
|
|
3233
3445
|
init_cc_agent_support();
|
|
3234
3446
|
init_mcp_prefix();
|
|
3235
3447
|
init_provider_table();
|
|
3448
|
+
init_agent_config();
|
|
3449
|
+
init_runtime_table();
|
|
3236
3450
|
init_intercom_queue();
|
|
3237
3451
|
init_plan_limits();
|
|
3238
3452
|
init_employees();
|
|
3239
|
-
SPAWN_LOCK_DIR =
|
|
3240
|
-
SESSION_CACHE =
|
|
3453
|
+
SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
|
|
3454
|
+
SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
|
|
3241
3455
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3242
3456
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3243
3457
|
VERIFY_PANE_LINES = 200;
|
|
3244
3458
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3245
|
-
INTERCOM_LOG2 =
|
|
3246
|
-
DEBOUNCE_FILE =
|
|
3459
|
+
INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3460
|
+
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3247
3461
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3248
3462
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
3249
3463
|
}
|