@askexenow/exe-os 0.8.85 → 0.8.87
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/cleanup-stale-review-tasks.js +57 -19
- package/dist/bin/cli.js +510 -340
- package/dist/bin/exe-agent-config.js +242 -0
- package/dist/bin/exe-agent.js +3 -3
- package/dist/bin/exe-boot.js +344 -346
- package/dist/bin/exe-dispatch.js +375 -250
- package/dist/bin/exe-forget.js +5 -1
- package/dist/bin/exe-gateway.js +260 -135
- package/dist/bin/exe-healthcheck.js +133 -1
- package/dist/bin/exe-heartbeat.js +72 -31
- package/dist/bin/exe-link.js +25 -2
- package/dist/bin/exe-new-employee.js +22 -0
- package/dist/bin/exe-pending-messages.js +55 -17
- package/dist/bin/exe-pending-reviews.js +57 -19
- package/dist/bin/exe-search.js +6 -2
- package/dist/bin/exe-session-cleanup.js +260 -135
- package/dist/bin/exe-start-codex.js +2598 -0
- package/dist/bin/exe-start.sh +15 -3
- package/dist/bin/exe-status.js +57 -19
- package/dist/bin/git-sweep.js +391 -266
- package/dist/bin/install.js +22 -0
- package/dist/bin/scan-tasks.js +394 -269
- package/dist/bin/setup.js +50 -5
- package/dist/gateway/index.js +257 -132
- package/dist/hooks/bug-report-worker.js +242 -117
- package/dist/hooks/commit-complete.js +389 -264
- package/dist/hooks/error-recall.js +6 -2
- package/dist/hooks/ingest-worker.js +314 -193
- package/dist/hooks/post-compact.js +84 -46
- package/dist/hooks/pre-compact.js +272 -147
- package/dist/hooks/pre-tool-use.js +104 -66
- package/dist/hooks/prompt-submit.js +126 -66
- package/dist/hooks/session-end.js +277 -152
- package/dist/hooks/session-start.js +70 -28
- package/dist/hooks/stop.js +90 -52
- package/dist/hooks/subagent-stop.js +84 -46
- package/dist/hooks/summary-worker.js +175 -114
- package/dist/index.js +296 -171
- package/dist/lib/agent-config.js +167 -0
- package/dist/lib/cloud-sync.js +25 -2
- package/dist/lib/exe-daemon.js +338 -213
- package/dist/lib/hybrid-search.js +7 -2
- package/dist/lib/messaging.js +95 -39
- package/dist/lib/runtime-table.js +16 -0
- package/dist/lib/session-wrappers.js +22 -0
- package/dist/lib/tasks.js +242 -117
- package/dist/lib/tmux-routing.js +314 -189
- package/dist/mcp/server.js +573 -274
- package/dist/mcp/tools/create-task.js +260 -135
- package/dist/mcp/tools/list-tasks.js +68 -30
- package/dist/mcp/tools/send-message.js +100 -44
- package/dist/mcp/tools/update-task.js +123 -67
- package/dist/runtime/index.js +276 -151
- package/dist/tui/App.js +479 -354
- package/package.json +1 -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"]);
|
|
@@ -584,18 +635,18 @@ var init_database = __esm({
|
|
|
584
635
|
});
|
|
585
636
|
|
|
586
637
|
// src/lib/license.ts
|
|
587
|
-
import { readFileSync as
|
|
638
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
588
639
|
import { randomUUID } from "crypto";
|
|
589
|
-
import
|
|
640
|
+
import path6 from "path";
|
|
590
641
|
import { jwtVerify, importSPKI } from "jose";
|
|
591
642
|
var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
|
|
592
643
|
var init_license = __esm({
|
|
593
644
|
"src/lib/license.ts"() {
|
|
594
645
|
"use strict";
|
|
595
646
|
init_config();
|
|
596
|
-
LICENSE_PATH =
|
|
597
|
-
CACHE_PATH =
|
|
598
|
-
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");
|
|
599
650
|
PLAN_LIMITS = {
|
|
600
651
|
free: { devices: 1, employees: 1, memories: 5e3 },
|
|
601
652
|
pro: { devices: 3, employees: 5, memories: 1e5 },
|
|
@@ -607,12 +658,12 @@ var init_license = __esm({
|
|
|
607
658
|
});
|
|
608
659
|
|
|
609
660
|
// src/lib/plan-limits.ts
|
|
610
|
-
import { readFileSync as
|
|
611
|
-
import
|
|
661
|
+
import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
|
|
662
|
+
import path7 from "path";
|
|
612
663
|
function getLicenseSync() {
|
|
613
664
|
try {
|
|
614
|
-
if (!
|
|
615
|
-
const raw = JSON.parse(
|
|
665
|
+
if (!existsSync7(CACHE_PATH2)) return freeLicense();
|
|
666
|
+
const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
|
|
616
667
|
if (!raw.token || typeof raw.token !== "string") return freeLicense();
|
|
617
668
|
const parts = raw.token.split(".");
|
|
618
669
|
if (parts.length !== 3) return freeLicense();
|
|
@@ -650,8 +701,8 @@ function assertEmployeeLimitSync(rosterPath) {
|
|
|
650
701
|
const filePath = rosterPath ?? EMPLOYEES_PATH;
|
|
651
702
|
let count = 0;
|
|
652
703
|
try {
|
|
653
|
-
if (
|
|
654
|
-
const raw =
|
|
704
|
+
if (existsSync7(filePath)) {
|
|
705
|
+
const raw = readFileSync7(filePath, "utf8");
|
|
655
706
|
const employees = JSON.parse(raw);
|
|
656
707
|
count = Array.isArray(employees) ? employees.length : 0;
|
|
657
708
|
}
|
|
@@ -680,19 +731,19 @@ var init_plan_limits = __esm({
|
|
|
680
731
|
this.name = "PlanLimitError";
|
|
681
732
|
}
|
|
682
733
|
};
|
|
683
|
-
CACHE_PATH2 =
|
|
734
|
+
CACHE_PATH2 = path7.join(EXE_AI_DIR, "license-cache.json");
|
|
684
735
|
}
|
|
685
736
|
});
|
|
686
737
|
|
|
687
738
|
// src/lib/notifications.ts
|
|
688
739
|
import crypto from "crypto";
|
|
689
|
-
import
|
|
740
|
+
import path8 from "path";
|
|
690
741
|
import os5 from "os";
|
|
691
742
|
import {
|
|
692
|
-
readFileSync as
|
|
743
|
+
readFileSync as readFileSync8,
|
|
693
744
|
readdirSync,
|
|
694
745
|
unlinkSync as unlinkSync2,
|
|
695
|
-
existsSync as
|
|
746
|
+
existsSync as existsSync8,
|
|
696
747
|
rmdirSync
|
|
697
748
|
} from "fs";
|
|
698
749
|
async function writeNotification(notification) {
|
|
@@ -851,11 +902,11 @@ var init_state_bus = __esm({
|
|
|
851
902
|
|
|
852
903
|
// src/lib/tasks-crud.ts
|
|
853
904
|
import crypto3 from "crypto";
|
|
854
|
-
import
|
|
905
|
+
import path9 from "path";
|
|
855
906
|
import os6 from "os";
|
|
856
907
|
import { execSync as execSync4 } from "child_process";
|
|
857
908
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
858
|
-
import { existsSync as
|
|
909
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
|
|
859
910
|
async function writeCheckpoint(input) {
|
|
860
911
|
const client = getClient();
|
|
861
912
|
const row = await resolveTask(client, input.taskId);
|
|
@@ -1030,8 +1081,8 @@ ${laneWarning}` : laneWarning;
|
|
|
1030
1081
|
}
|
|
1031
1082
|
if (input.baseDir) {
|
|
1032
1083
|
try {
|
|
1033
|
-
await mkdir3(
|
|
1034
|
-
await mkdir3(
|
|
1084
|
+
await mkdir3(path9.join(input.baseDir, "exe", "output"), { recursive: true });
|
|
1085
|
+
await mkdir3(path9.join(input.baseDir, "exe", "research"), { recursive: true });
|
|
1035
1086
|
await ensureArchitectureDoc(input.baseDir, input.projectName);
|
|
1036
1087
|
await ensureGitignoreExe(input.baseDir);
|
|
1037
1088
|
} catch {
|
|
@@ -1067,10 +1118,10 @@ ${laneWarning}` : laneWarning;
|
|
|
1067
1118
|
});
|
|
1068
1119
|
if (input.baseDir) {
|
|
1069
1120
|
try {
|
|
1070
|
-
const EXE_OS_DIR =
|
|
1071
|
-
const mdPath =
|
|
1072
|
-
const mdDir =
|
|
1073
|
-
if (!
|
|
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 });
|
|
1074
1125
|
const reviewer = input.reviewer ?? input.assignedBy;
|
|
1075
1126
|
const mdContent = `# ${input.title}
|
|
1076
1127
|
|
|
@@ -1095,7 +1146,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
|
|
|
1095
1146
|
Do NOT let a failed commit or any error prevent you from calling update_task(done).
|
|
1096
1147
|
`;
|
|
1097
1148
|
await writeFile3(mdPath, mdContent, "utf-8");
|
|
1098
|
-
} catch {
|
|
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
|
+
);
|
|
1099
1154
|
}
|
|
1100
1155
|
}
|
|
1101
1156
|
return {
|
|
@@ -1355,9 +1410,9 @@ async function deleteTaskCore(taskId, _baseDir) {
|
|
|
1355
1410
|
return { taskFile, assignedTo, assignedBy, taskSlug };
|
|
1356
1411
|
}
|
|
1357
1412
|
async function ensureArchitectureDoc(baseDir, projectName) {
|
|
1358
|
-
const archPath =
|
|
1413
|
+
const archPath = path9.join(baseDir, "exe", "ARCHITECTURE.md");
|
|
1359
1414
|
try {
|
|
1360
|
-
if (
|
|
1415
|
+
if (existsSync9(archPath)) return;
|
|
1361
1416
|
const template = [
|
|
1362
1417
|
`# ${projectName} \u2014 System Architecture`,
|
|
1363
1418
|
"",
|
|
@@ -1390,10 +1445,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
|
|
|
1390
1445
|
}
|
|
1391
1446
|
}
|
|
1392
1447
|
async function ensureGitignoreExe(baseDir) {
|
|
1393
|
-
const gitignorePath =
|
|
1448
|
+
const gitignorePath = path9.join(baseDir, ".gitignore");
|
|
1394
1449
|
try {
|
|
1395
|
-
if (
|
|
1396
|
-
const content =
|
|
1450
|
+
if (existsSync9(gitignorePath)) {
|
|
1451
|
+
const content = readFileSync9(gitignorePath, "utf-8");
|
|
1397
1452
|
if (/^\/?exe\/?$/m.test(content)) return;
|
|
1398
1453
|
await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
|
|
1399
1454
|
} else {
|
|
@@ -1424,8 +1479,8 @@ var init_tasks_crud = __esm({
|
|
|
1424
1479
|
});
|
|
1425
1480
|
|
|
1426
1481
|
// src/lib/tasks-review.ts
|
|
1427
|
-
import
|
|
1428
|
-
import { existsSync as
|
|
1482
|
+
import path10 from "path";
|
|
1483
|
+
import { existsSync as existsSync10, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
|
|
1429
1484
|
async function countPendingReviews(sessionScope) {
|
|
1430
1485
|
const client = getClient();
|
|
1431
1486
|
if (sessionScope) {
|
|
@@ -1606,11 +1661,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
1606
1661
|
);
|
|
1607
1662
|
}
|
|
1608
1663
|
try {
|
|
1609
|
-
const cacheDir =
|
|
1610
|
-
if (
|
|
1664
|
+
const cacheDir = path10.join(EXE_AI_DIR, "session-cache");
|
|
1665
|
+
if (existsSync10(cacheDir)) {
|
|
1611
1666
|
for (const f of readdirSync2(cacheDir)) {
|
|
1612
1667
|
if (f.startsWith("review-notified-")) {
|
|
1613
|
-
unlinkSync3(
|
|
1668
|
+
unlinkSync3(path10.join(cacheDir, f));
|
|
1614
1669
|
}
|
|
1615
1670
|
}
|
|
1616
1671
|
}
|
|
@@ -1631,7 +1686,7 @@ var init_tasks_review = __esm({
|
|
|
1631
1686
|
});
|
|
1632
1687
|
|
|
1633
1688
|
// src/lib/tasks-chain.ts
|
|
1634
|
-
import
|
|
1689
|
+
import path11 from "path";
|
|
1635
1690
|
import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
|
|
1636
1691
|
async function cascadeUnblock(taskId, baseDir, now) {
|
|
1637
1692
|
const client = getClient();
|
|
@@ -1648,7 +1703,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
|
|
|
1648
1703
|
});
|
|
1649
1704
|
for (const ur of unblockedRows.rows) {
|
|
1650
1705
|
try {
|
|
1651
|
-
const ubFile =
|
|
1706
|
+
const ubFile = path11.join(baseDir, String(ur.task_file));
|
|
1652
1707
|
let ubContent = await readFile3(ubFile, "utf-8");
|
|
1653
1708
|
ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
|
|
1654
1709
|
ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
|
|
@@ -1717,7 +1772,7 @@ var init_tasks_chain = __esm({
|
|
|
1717
1772
|
|
|
1718
1773
|
// src/lib/project-name.ts
|
|
1719
1774
|
import { execSync as execSync5 } from "child_process";
|
|
1720
|
-
import
|
|
1775
|
+
import path12 from "path";
|
|
1721
1776
|
function getProjectName(cwd) {
|
|
1722
1777
|
const dir = cwd ?? process.cwd();
|
|
1723
1778
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
@@ -1730,7 +1785,7 @@ function getProjectName(cwd) {
|
|
|
1730
1785
|
timeout: 2e3,
|
|
1731
1786
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1732
1787
|
}).trim();
|
|
1733
|
-
repoRoot =
|
|
1788
|
+
repoRoot = path12.dirname(gitCommonDir);
|
|
1734
1789
|
} catch {
|
|
1735
1790
|
repoRoot = execSync5("git rev-parse --show-toplevel", {
|
|
1736
1791
|
cwd: dir,
|
|
@@ -1739,11 +1794,11 @@ function getProjectName(cwd) {
|
|
|
1739
1794
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1740
1795
|
}).trim();
|
|
1741
1796
|
}
|
|
1742
|
-
_cached2 =
|
|
1797
|
+
_cached2 = path12.basename(repoRoot);
|
|
1743
1798
|
_cachedCwd = dir;
|
|
1744
1799
|
return _cached2;
|
|
1745
1800
|
} catch {
|
|
1746
|
-
_cached2 =
|
|
1801
|
+
_cached2 = path12.basename(dir);
|
|
1747
1802
|
_cachedCwd = dir;
|
|
1748
1803
|
return _cached2;
|
|
1749
1804
|
}
|
|
@@ -2216,8 +2271,8 @@ __export(tasks_exports, {
|
|
|
2216
2271
|
updateTaskStatus: () => updateTaskStatus,
|
|
2217
2272
|
writeCheckpoint: () => writeCheckpoint
|
|
2218
2273
|
});
|
|
2219
|
-
import
|
|
2220
|
-
import { writeFileSync as
|
|
2274
|
+
import path13 from "path";
|
|
2275
|
+
import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, unlinkSync as unlinkSync4 } from "fs";
|
|
2221
2276
|
async function createTask(input) {
|
|
2222
2277
|
const result = await createTaskCore(input);
|
|
2223
2278
|
if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
|
|
@@ -2236,11 +2291,11 @@ async function updateTask(input) {
|
|
|
2236
2291
|
const { row, taskFile, now, taskId } = await updateTaskStatus(input);
|
|
2237
2292
|
try {
|
|
2238
2293
|
const agent = String(row.assigned_to);
|
|
2239
|
-
const cacheDir =
|
|
2240
|
-
const cachePath =
|
|
2294
|
+
const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
|
|
2295
|
+
const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
|
|
2241
2296
|
if (input.status === "in_progress") {
|
|
2242
|
-
|
|
2243
|
-
|
|
2297
|
+
mkdirSync5(cacheDir, { recursive: true });
|
|
2298
|
+
writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
|
|
2244
2299
|
} else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
|
|
2245
2300
|
try {
|
|
2246
2301
|
unlinkSync4(cachePath);
|
|
@@ -2707,13 +2762,13 @@ __export(tmux_routing_exports, {
|
|
|
2707
2762
|
verifyPaneAtCapacity: () => verifyPaneAtCapacity
|
|
2708
2763
|
});
|
|
2709
2764
|
import { execFileSync as execFileSync2, execSync as execSync6 } from "child_process";
|
|
2710
|
-
import { readFileSync as
|
|
2711
|
-
import
|
|
2765
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
|
|
2766
|
+
import path14 from "path";
|
|
2712
2767
|
import os7 from "os";
|
|
2713
2768
|
import { fileURLToPath } from "url";
|
|
2714
2769
|
import { unlinkSync as unlinkSync5 } from "fs";
|
|
2715
2770
|
function spawnLockPath(sessionName) {
|
|
2716
|
-
return
|
|
2771
|
+
return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
|
|
2717
2772
|
}
|
|
2718
2773
|
function isProcessAlive(pid) {
|
|
2719
2774
|
try {
|
|
@@ -2724,13 +2779,13 @@ function isProcessAlive(pid) {
|
|
|
2724
2779
|
}
|
|
2725
2780
|
}
|
|
2726
2781
|
function acquireSpawnLock(sessionName) {
|
|
2727
|
-
if (!
|
|
2728
|
-
|
|
2782
|
+
if (!existsSync11(SPAWN_LOCK_DIR)) {
|
|
2783
|
+
mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
|
|
2729
2784
|
}
|
|
2730
2785
|
const lockFile = spawnLockPath(sessionName);
|
|
2731
|
-
if (
|
|
2786
|
+
if (existsSync11(lockFile)) {
|
|
2732
2787
|
try {
|
|
2733
|
-
const lock = JSON.parse(
|
|
2788
|
+
const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
|
|
2734
2789
|
const age = Date.now() - lock.timestamp;
|
|
2735
2790
|
if (isProcessAlive(lock.pid) && age < 6e4) {
|
|
2736
2791
|
return false;
|
|
@@ -2738,7 +2793,7 @@ function acquireSpawnLock(sessionName) {
|
|
|
2738
2793
|
} catch {
|
|
2739
2794
|
}
|
|
2740
2795
|
}
|
|
2741
|
-
|
|
2796
|
+
writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
|
|
2742
2797
|
return true;
|
|
2743
2798
|
}
|
|
2744
2799
|
function releaseSpawnLock(sessionName) {
|
|
@@ -2750,13 +2805,13 @@ function releaseSpawnLock(sessionName) {
|
|
|
2750
2805
|
function resolveBehaviorsExporterScript() {
|
|
2751
2806
|
try {
|
|
2752
2807
|
const thisFile = fileURLToPath(import.meta.url);
|
|
2753
|
-
const scriptPath =
|
|
2754
|
-
|
|
2808
|
+
const scriptPath = path14.join(
|
|
2809
|
+
path14.dirname(thisFile),
|
|
2755
2810
|
"..",
|
|
2756
2811
|
"bin",
|
|
2757
2812
|
"exe-export-behaviors.js"
|
|
2758
2813
|
);
|
|
2759
|
-
return
|
|
2814
|
+
return existsSync11(scriptPath) ? scriptPath : null;
|
|
2760
2815
|
} catch {
|
|
2761
2816
|
return null;
|
|
2762
2817
|
}
|
|
@@ -2822,12 +2877,12 @@ function extractRootExe(name) {
|
|
|
2822
2877
|
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
2823
2878
|
}
|
|
2824
2879
|
function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
2825
|
-
if (!
|
|
2826
|
-
|
|
2880
|
+
if (!existsSync11(SESSION_CACHE)) {
|
|
2881
|
+
mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
2827
2882
|
}
|
|
2828
2883
|
const rootExe = extractRootExe(parentExe) ?? parentExe;
|
|
2829
|
-
const filePath =
|
|
2830
|
-
|
|
2884
|
+
const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
|
|
2885
|
+
writeFileSync7(filePath, JSON.stringify({
|
|
2831
2886
|
parentExe: rootExe,
|
|
2832
2887
|
dispatchedBy: dispatchedBy || rootExe,
|
|
2833
2888
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2835,7 +2890,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
|
|
|
2835
2890
|
}
|
|
2836
2891
|
function getParentExe(sessionKey) {
|
|
2837
2892
|
try {
|
|
2838
|
-
const data = JSON.parse(
|
|
2893
|
+
const data = JSON.parse(readFileSync10(path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
|
|
2839
2894
|
return data.parentExe || null;
|
|
2840
2895
|
} catch {
|
|
2841
2896
|
return null;
|
|
@@ -2843,8 +2898,8 @@ function getParentExe(sessionKey) {
|
|
|
2843
2898
|
}
|
|
2844
2899
|
function getDispatchedBy(sessionKey) {
|
|
2845
2900
|
try {
|
|
2846
|
-
const data = JSON.parse(
|
|
2847
|
-
|
|
2901
|
+
const data = JSON.parse(readFileSync10(
|
|
2902
|
+
path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
|
|
2848
2903
|
"utf8"
|
|
2849
2904
|
));
|
|
2850
2905
|
return data.dispatchedBy ?? data.parentExe ?? null;
|
|
@@ -2905,32 +2960,50 @@ async function verifyPaneAtCapacity(sessionName) {
|
|
|
2905
2960
|
}
|
|
2906
2961
|
function readDebounceState() {
|
|
2907
2962
|
try {
|
|
2908
|
-
if (!
|
|
2909
|
-
|
|
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;
|
|
2910
2974
|
} catch {
|
|
2911
2975
|
return {};
|
|
2912
2976
|
}
|
|
2913
2977
|
}
|
|
2914
2978
|
function writeDebounceState(state) {
|
|
2915
2979
|
try {
|
|
2916
|
-
if (!
|
|
2917
|
-
|
|
2980
|
+
if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
|
|
2981
|
+
writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
|
|
2918
2982
|
} catch {
|
|
2919
2983
|
}
|
|
2920
2984
|
}
|
|
2921
2985
|
function isDebounced(targetSession) {
|
|
2922
2986
|
const state = readDebounceState();
|
|
2923
|
-
const
|
|
2924
|
-
|
|
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;
|
|
2925
2996
|
}
|
|
2926
2997
|
function recordDebounce(targetSession) {
|
|
2927
2998
|
const state = readDebounceState();
|
|
2928
|
-
state[targetSession]
|
|
2999
|
+
const batched = state[targetSession]?.pending ?? 0;
|
|
3000
|
+
state[targetSession] = { lastSent: Date.now(), pending: 0 };
|
|
2929
3001
|
const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
|
|
2930
3002
|
for (const key of Object.keys(state)) {
|
|
2931
|
-
if ((state[key] ?? 0) < cutoff) delete state[key];
|
|
3003
|
+
if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
|
|
2932
3004
|
}
|
|
2933
3005
|
writeDebounceState(state);
|
|
3006
|
+
return batched;
|
|
2934
3007
|
}
|
|
2935
3008
|
function logIntercom(msg) {
|
|
2936
3009
|
const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
|
|
@@ -2975,7 +3048,7 @@ function sendIntercom(targetSession) {
|
|
|
2975
3048
|
return "skipped_exe";
|
|
2976
3049
|
}
|
|
2977
3050
|
if (isDebounced(targetSession)) {
|
|
2978
|
-
logIntercom(`DEBOUNCE \u2192 ${targetSession} (
|
|
3051
|
+
logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
|
|
2979
3052
|
return "debounced";
|
|
2980
3053
|
}
|
|
2981
3054
|
try {
|
|
@@ -2987,14 +3060,14 @@ function sendIntercom(targetSession) {
|
|
|
2987
3060
|
const sessionState = getSessionState(targetSession);
|
|
2988
3061
|
if (sessionState === "no_claude") {
|
|
2989
3062
|
queueIntercom(targetSession, "claude not running in session");
|
|
2990
|
-
recordDebounce(targetSession);
|
|
2991
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process
|
|
3063
|
+
const batched2 = recordDebounce(targetSession);
|
|
3064
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
2992
3065
|
return "queued";
|
|
2993
3066
|
}
|
|
2994
3067
|
if (sessionState === "thinking" || sessionState === "tool") {
|
|
2995
3068
|
queueIntercom(targetSession, "session busy at send time");
|
|
2996
|
-
recordDebounce(targetSession);
|
|
2997
|
-
logIntercom(`QUEUED \u2192 ${targetSession} (session busy
|
|
3069
|
+
const batched2 = recordDebounce(targetSession);
|
|
3070
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
|
|
2998
3071
|
return "queued";
|
|
2999
3072
|
}
|
|
3000
3073
|
if (transport.isPaneInCopyMode(targetSession)) {
|
|
@@ -3002,8 +3075,8 @@ function sendIntercom(targetSession) {
|
|
|
3002
3075
|
transport.sendKeys(targetSession, "q");
|
|
3003
3076
|
}
|
|
3004
3077
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
3005
|
-
recordDebounce(targetSession);
|
|
3006
|
-
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)`);
|
|
3007
3080
|
return "delivered";
|
|
3008
3081
|
} catch {
|
|
3009
3082
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -3105,26 +3178,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3105
3178
|
const transport = getTransport();
|
|
3106
3179
|
const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
|
|
3107
3180
|
const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
|
|
3108
|
-
const logDir =
|
|
3109
|
-
const logFile =
|
|
3110
|
-
if (!
|
|
3111
|
-
|
|
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 });
|
|
3112
3185
|
}
|
|
3113
3186
|
transport.kill(sessionName);
|
|
3114
3187
|
let cleanupSuffix = "";
|
|
3115
3188
|
try {
|
|
3116
3189
|
const thisFile = fileURLToPath(import.meta.url);
|
|
3117
|
-
const cleanupScript =
|
|
3118
|
-
if (
|
|
3190
|
+
const cleanupScript = path14.join(path14.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
|
|
3191
|
+
if (existsSync11(cleanupScript)) {
|
|
3119
3192
|
cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
|
|
3120
3193
|
}
|
|
3121
3194
|
} catch {
|
|
3122
3195
|
}
|
|
3123
3196
|
try {
|
|
3124
|
-
const claudeJsonPath =
|
|
3197
|
+
const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
|
|
3125
3198
|
let claudeJson = {};
|
|
3126
3199
|
try {
|
|
3127
|
-
claudeJson = JSON.parse(
|
|
3200
|
+
claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
|
|
3128
3201
|
} catch {
|
|
3129
3202
|
}
|
|
3130
3203
|
if (!claudeJson.projects) claudeJson.projects = {};
|
|
@@ -3132,17 +3205,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3132
3205
|
const trustDir = opts?.cwd ?? projectDir;
|
|
3133
3206
|
if (!projects[trustDir]) projects[trustDir] = {};
|
|
3134
3207
|
projects[trustDir].hasTrustDialogAccepted = true;
|
|
3135
|
-
|
|
3208
|
+
writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
|
|
3136
3209
|
} catch {
|
|
3137
3210
|
}
|
|
3138
3211
|
try {
|
|
3139
|
-
const settingsDir =
|
|
3212
|
+
const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
|
|
3140
3213
|
const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
|
|
3141
|
-
const projSettingsDir =
|
|
3142
|
-
const settingsPath =
|
|
3214
|
+
const projSettingsDir = path14.join(settingsDir, normalizedKey);
|
|
3215
|
+
const settingsPath = path14.join(projSettingsDir, "settings.json");
|
|
3143
3216
|
let settings = {};
|
|
3144
3217
|
try {
|
|
3145
|
-
settings = JSON.parse(
|
|
3218
|
+
settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
|
|
3146
3219
|
} catch {
|
|
3147
3220
|
}
|
|
3148
3221
|
const perms = settings.permissions ?? {};
|
|
@@ -3170,20 +3243,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3170
3243
|
if (changed) {
|
|
3171
3244
|
perms.allow = allow;
|
|
3172
3245
|
settings.permissions = perms;
|
|
3173
|
-
|
|
3174
|
-
|
|
3246
|
+
mkdirSync6(projSettingsDir, { recursive: true });
|
|
3247
|
+
writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
3175
3248
|
}
|
|
3176
3249
|
} catch {
|
|
3177
3250
|
}
|
|
3178
3251
|
const spawnCwd = opts?.cwd ?? projectDir;
|
|
3179
3252
|
const useExeAgent = !!(opts?.model && opts?.provider);
|
|
3180
|
-
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();
|
|
3181
3257
|
const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
|
|
3182
3258
|
let identityFlag = "";
|
|
3183
3259
|
let behaviorsFlag = "";
|
|
3184
3260
|
let legacyFallbackWarned = false;
|
|
3185
3261
|
if (!useExeAgent && !useBinSymlink) {
|
|
3186
|
-
const identityPath =
|
|
3262
|
+
const identityPath = path14.join(
|
|
3187
3263
|
os7.homedir(),
|
|
3188
3264
|
".exe-os",
|
|
3189
3265
|
"identity",
|
|
@@ -3193,13 +3269,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3193
3269
|
const hasAgentFlag = claudeSupportsAgentFlag();
|
|
3194
3270
|
if (hasAgentFlag) {
|
|
3195
3271
|
identityFlag = ` --agent ${employeeName}`;
|
|
3196
|
-
} else if (
|
|
3272
|
+
} else if (existsSync11(identityPath)) {
|
|
3197
3273
|
identityFlag = ` --append-system-prompt-file ${identityPath}`;
|
|
3198
3274
|
legacyFallbackWarned = true;
|
|
3199
3275
|
}
|
|
3200
3276
|
const behaviorsFile = exportBehaviorsSync(
|
|
3201
3277
|
employeeName,
|
|
3202
|
-
|
|
3278
|
+
path14.basename(spawnCwd),
|
|
3203
3279
|
sessionName
|
|
3204
3280
|
);
|
|
3205
3281
|
if (behaviorsFile) {
|
|
@@ -3214,16 +3290,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3214
3290
|
}
|
|
3215
3291
|
let sessionContextFlag = "";
|
|
3216
3292
|
try {
|
|
3217
|
-
const ctxDir =
|
|
3218
|
-
|
|
3219
|
-
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`);
|
|
3220
3296
|
const ctxContent = [
|
|
3221
3297
|
`## Session Context`,
|
|
3222
3298
|
`You are running in tmux session: ${sessionName}.`,
|
|
3223
3299
|
`Your parent coordinator session is ${exeSession}.`,
|
|
3224
3300
|
`Your employees (if any) use the -${exeSession} suffix.`
|
|
3225
3301
|
].join("\n");
|
|
3226
|
-
|
|
3302
|
+
writeFileSync7(ctxFile, ctxContent);
|
|
3227
3303
|
sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
|
|
3228
3304
|
} catch {
|
|
3229
3305
|
}
|
|
@@ -3237,9 +3313,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3237
3313
|
}
|
|
3238
3314
|
}
|
|
3239
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
|
+
}
|
|
3240
3342
|
let spawnCommand;
|
|
3241
3343
|
if (useExeAgent) {
|
|
3242
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}`;
|
|
3243
3358
|
} else if (useBinSymlink) {
|
|
3244
3359
|
const binName = `${employeeName}-${ccProvider}`;
|
|
3245
3360
|
process.stderr.write(
|
|
@@ -3261,11 +3376,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3261
3376
|
transport.pipeLog(sessionName, logFile);
|
|
3262
3377
|
try {
|
|
3263
3378
|
const mySession = getMySession();
|
|
3264
|
-
const dispatchInfo =
|
|
3265
|
-
|
|
3379
|
+
const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
|
|
3380
|
+
writeFileSync7(dispatchInfo, JSON.stringify({
|
|
3266
3381
|
dispatchedBy: mySession,
|
|
3267
3382
|
rootExe: exeSession,
|
|
3268
|
-
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,
|
|
3269
3386
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3270
3387
|
}));
|
|
3271
3388
|
} catch {
|
|
@@ -3283,6 +3400,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3283
3400
|
booted = true;
|
|
3284
3401
|
break;
|
|
3285
3402
|
}
|
|
3403
|
+
} else if (useCodex) {
|
|
3404
|
+
if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
|
|
3405
|
+
booted = true;
|
|
3406
|
+
break;
|
|
3407
|
+
}
|
|
3286
3408
|
} else {
|
|
3287
3409
|
if (pane.includes("Claude Code") || pane.includes("\u276F")) {
|
|
3288
3410
|
booted = true;
|
|
@@ -3294,9 +3416,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
3294
3416
|
}
|
|
3295
3417
|
if (!booted) {
|
|
3296
3418
|
releaseSpawnLock(sessionName);
|
|
3297
|
-
|
|
3419
|
+
const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
|
|
3420
|
+
return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
|
|
3298
3421
|
}
|
|
3299
|
-
if (!useExeAgent) {
|
|
3422
|
+
if (!useExeAgent && !useCodex) {
|
|
3300
3423
|
try {
|
|
3301
3424
|
transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
|
|
3302
3425
|
} catch {
|
|
@@ -3322,17 +3445,19 @@ var init_tmux_routing = __esm({
|
|
|
3322
3445
|
init_cc_agent_support();
|
|
3323
3446
|
init_mcp_prefix();
|
|
3324
3447
|
init_provider_table();
|
|
3448
|
+
init_agent_config();
|
|
3449
|
+
init_runtime_table();
|
|
3325
3450
|
init_intercom_queue();
|
|
3326
3451
|
init_plan_limits();
|
|
3327
3452
|
init_employees();
|
|
3328
|
-
SPAWN_LOCK_DIR =
|
|
3329
|
-
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");
|
|
3330
3455
|
BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
|
|
3331
3456
|
VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
|
|
3332
3457
|
VERIFY_PANE_LINES = 200;
|
|
3333
3458
|
INTERCOM_DEBOUNCE_MS = 3e4;
|
|
3334
|
-
INTERCOM_LOG2 =
|
|
3335
|
-
DEBOUNCE_FILE =
|
|
3459
|
+
INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
|
|
3460
|
+
DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
|
|
3336
3461
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
3337
3462
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
3338
3463
|
}
|