@askexenow/exe-os 0.8.85 → 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.
Files changed (57) hide show
  1. package/dist/bin/cleanup-stale-review-tasks.js +57 -19
  2. package/dist/bin/cli.js +507 -337
  3. package/dist/bin/exe-agent-config.js +242 -0
  4. package/dist/bin/exe-agent.js +3 -3
  5. package/dist/bin/exe-boot.js +344 -346
  6. package/dist/bin/exe-dispatch.js +375 -250
  7. package/dist/bin/exe-forget.js +5 -1
  8. package/dist/bin/exe-gateway.js +260 -135
  9. package/dist/bin/exe-healthcheck.js +133 -1
  10. package/dist/bin/exe-heartbeat.js +72 -31
  11. package/dist/bin/exe-link.js +25 -2
  12. package/dist/bin/exe-new-employee.js +22 -0
  13. package/dist/bin/exe-pending-messages.js +55 -17
  14. package/dist/bin/exe-pending-reviews.js +57 -19
  15. package/dist/bin/exe-search.js +6 -2
  16. package/dist/bin/exe-session-cleanup.js +260 -135
  17. package/dist/bin/exe-start-codex.js +2598 -0
  18. package/dist/bin/exe-start.sh +15 -3
  19. package/dist/bin/exe-status.js +57 -19
  20. package/dist/bin/git-sweep.js +391 -266
  21. package/dist/bin/install.js +22 -0
  22. package/dist/bin/scan-tasks.js +394 -269
  23. package/dist/bin/setup.js +47 -2
  24. package/dist/gateway/index.js +257 -132
  25. package/dist/hooks/bug-report-worker.js +242 -117
  26. package/dist/hooks/commit-complete.js +389 -264
  27. package/dist/hooks/error-recall.js +6 -2
  28. package/dist/hooks/ingest-worker.js +314 -193
  29. package/dist/hooks/post-compact.js +84 -46
  30. package/dist/hooks/pre-compact.js +272 -147
  31. package/dist/hooks/pre-tool-use.js +104 -66
  32. package/dist/hooks/prompt-submit.js +126 -66
  33. package/dist/hooks/session-end.js +277 -152
  34. package/dist/hooks/session-start.js +70 -28
  35. package/dist/hooks/stop.js +90 -52
  36. package/dist/hooks/subagent-stop.js +84 -46
  37. package/dist/hooks/summary-worker.js +175 -114
  38. package/dist/index.js +296 -171
  39. package/dist/lib/agent-config.js +167 -0
  40. package/dist/lib/cloud-sync.js +25 -2
  41. package/dist/lib/exe-daemon.js +338 -213
  42. package/dist/lib/hybrid-search.js +7 -2
  43. package/dist/lib/messaging.js +95 -39
  44. package/dist/lib/runtime-table.js +16 -0
  45. package/dist/lib/session-wrappers.js +22 -0
  46. package/dist/lib/tasks.js +242 -117
  47. package/dist/lib/tmux-routing.js +314 -189
  48. package/dist/mcp/server.js +573 -274
  49. package/dist/mcp/tools/create-task.js +260 -135
  50. package/dist/mcp/tools/list-tasks.js +68 -30
  51. package/dist/mcp/tools/send-message.js +100 -44
  52. package/dist/mcp/tools/update-task.js +123 -67
  53. package/dist/runtime/index.js +276 -151
  54. package/dist/tui/App.js +479 -354
  55. package/package.json +1 -1
  56. package/src/commands/exe/agent-config.md +27 -0
  57. package/src/commands/exe/cc-doctor.md +10 -0
@@ -2853,18 +2853,69 @@ var init_provider_table = __esm({
2853
2853
  }
2854
2854
  });
2855
2855
 
2856
- // src/lib/intercom-queue.ts
2857
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2856
+ // src/lib/runtime-table.ts
2857
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
2858
+ var init_runtime_table = __esm({
2859
+ "src/lib/runtime-table.ts"() {
2860
+ "use strict";
2861
+ RUNTIME_TABLE = {
2862
+ codex: {
2863
+ binary: "codex",
2864
+ launchMode: "exec",
2865
+ autoApproveFlag: "--full-auto",
2866
+ inlineFlag: "--no-alt-screen",
2867
+ apiKeyEnv: "OPENAI_API_KEY",
2868
+ defaultModel: "gpt-5.4"
2869
+ }
2870
+ };
2871
+ DEFAULT_RUNTIME = "claude";
2872
+ }
2873
+ });
2874
+
2875
+ // src/lib/agent-config.ts
2876
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2858
2877
  import path7 from "path";
2878
+ function loadAgentConfig() {
2879
+ if (!existsSync7(AGENT_CONFIG_PATH)) return {};
2880
+ try {
2881
+ return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
2882
+ } catch {
2883
+ return {};
2884
+ }
2885
+ }
2886
+ function getAgentRuntime(agentId) {
2887
+ const config = loadAgentConfig();
2888
+ const entry = config[agentId];
2889
+ if (entry) return entry;
2890
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
2891
+ }
2892
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
2893
+ var init_agent_config = __esm({
2894
+ "src/lib/agent-config.ts"() {
2895
+ "use strict";
2896
+ init_config();
2897
+ init_runtime_table();
2898
+ AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
2899
+ DEFAULT_MODELS = {
2900
+ claude: "claude-opus-4",
2901
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
2902
+ opencode: "minimax-m2.7"
2903
+ };
2904
+ }
2905
+ });
2906
+
2907
+ // src/lib/intercom-queue.ts
2908
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2909
+ import path8 from "path";
2859
2910
  import os5 from "os";
2860
2911
  function ensureDir() {
2861
- const dir = path7.dirname(QUEUE_PATH);
2862
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
2912
+ const dir = path8.dirname(QUEUE_PATH);
2913
+ if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
2863
2914
  }
2864
2915
  function readQueue() {
2865
2916
  try {
2866
- if (!existsSync7(QUEUE_PATH)) return [];
2867
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
2917
+ if (!existsSync8(QUEUE_PATH)) return [];
2918
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
2868
2919
  } catch {
2869
2920
  return [];
2870
2921
  }
@@ -2872,7 +2923,7 @@ function readQueue() {
2872
2923
  function writeQueue(queue) {
2873
2924
  ensureDir();
2874
2925
  const tmp = `${QUEUE_PATH}.tmp`;
2875
- writeFileSync3(tmp, JSON.stringify(queue, null, 2));
2926
+ writeFileSync4(tmp, JSON.stringify(queue, null, 2));
2876
2927
  renameSync3(tmp, QUEUE_PATH);
2877
2928
  }
2878
2929
  function queueIntercom(targetSession, reason) {
@@ -2896,9 +2947,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2896
2947
  var init_intercom_queue = __esm({
2897
2948
  "src/lib/intercom-queue.ts"() {
2898
2949
  "use strict";
2899
- QUEUE_PATH = path7.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2950
+ QUEUE_PATH = path8.join(os5.homedir(), ".exe-os", "intercom-queue.json");
2900
2951
  TTL_MS = 60 * 60 * 1e3;
2901
- INTERCOM_LOG = path7.join(os5.homedir(), ".exe-os", "intercom.log");
2952
+ INTERCOM_LOG = path8.join(os5.homedir(), ".exe-os", "intercom.log");
2902
2953
  }
2903
2954
  });
2904
2955
 
@@ -2919,9 +2970,9 @@ __export(license_exports, {
2919
2970
  stopLicenseRevalidation: () => stopLicenseRevalidation,
2920
2971
  validateLicense: () => validateLicense
2921
2972
  });
2922
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2973
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
2923
2974
  import { randomUUID as randomUUID3 } from "crypto";
2924
- import path8 from "path";
2975
+ import path9 from "path";
2925
2976
  import { jwtVerify, importSPKI } from "jose";
2926
2977
  async function fetchRetry(url, init) {
2927
2978
  try {
@@ -2932,37 +2983,37 @@ async function fetchRetry(url, init) {
2932
2983
  }
2933
2984
  }
2934
2985
  function loadDeviceId() {
2935
- const deviceJsonPath = path8.join(EXE_AI_DIR, "device.json");
2986
+ const deviceJsonPath = path9.join(EXE_AI_DIR, "device.json");
2936
2987
  try {
2937
- if (existsSync8(deviceJsonPath)) {
2938
- const data = JSON.parse(readFileSync6(deviceJsonPath, "utf8"));
2988
+ if (existsSync9(deviceJsonPath)) {
2989
+ const data = JSON.parse(readFileSync7(deviceJsonPath, "utf8"));
2939
2990
  if (data.deviceId) return data.deviceId;
2940
2991
  }
2941
2992
  } catch {
2942
2993
  }
2943
2994
  try {
2944
- if (existsSync8(DEVICE_ID_PATH)) {
2945
- const id2 = readFileSync6(DEVICE_ID_PATH, "utf8").trim();
2995
+ if (existsSync9(DEVICE_ID_PATH)) {
2996
+ const id2 = readFileSync7(DEVICE_ID_PATH, "utf8").trim();
2946
2997
  if (id2) return id2;
2947
2998
  }
2948
2999
  } catch {
2949
3000
  }
2950
3001
  const id = randomUUID3();
2951
- mkdirSync4(EXE_AI_DIR, { recursive: true });
2952
- writeFileSync4(DEVICE_ID_PATH, id, "utf8");
3002
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
3003
+ writeFileSync5(DEVICE_ID_PATH, id, "utf8");
2953
3004
  return id;
2954
3005
  }
2955
3006
  function loadLicense() {
2956
3007
  try {
2957
- if (!existsSync8(LICENSE_PATH)) return null;
2958
- return readFileSync6(LICENSE_PATH, "utf8").trim();
3008
+ if (!existsSync9(LICENSE_PATH)) return null;
3009
+ return readFileSync7(LICENSE_PATH, "utf8").trim();
2959
3010
  } catch {
2960
3011
  return null;
2961
3012
  }
2962
3013
  }
2963
3014
  function saveLicense(apiKey) {
2964
- mkdirSync4(EXE_AI_DIR, { recursive: true });
2965
- writeFileSync4(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
3015
+ mkdirSync5(EXE_AI_DIR, { recursive: true });
3016
+ writeFileSync5(LICENSE_PATH, apiKey.trim(), { encoding: "utf8", mode: 384 });
2966
3017
  }
2967
3018
  async function verifyLicenseJwt(token) {
2968
3019
  try {
@@ -2988,8 +3039,8 @@ async function verifyLicenseJwt(token) {
2988
3039
  }
2989
3040
  async function getCachedLicense() {
2990
3041
  try {
2991
- if (!existsSync8(CACHE_PATH)) return null;
2992
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3042
+ if (!existsSync9(CACHE_PATH)) return null;
3043
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
2993
3044
  if (!raw.token || typeof raw.token !== "string") return null;
2994
3045
  return await verifyLicenseJwt(raw.token);
2995
3046
  } catch {
@@ -2998,8 +3049,8 @@ async function getCachedLicense() {
2998
3049
  }
2999
3050
  function readCachedToken() {
3000
3051
  try {
3001
- if (!existsSync8(CACHE_PATH)) return null;
3002
- const raw = JSON.parse(readFileSync6(CACHE_PATH, "utf8"));
3052
+ if (!existsSync9(CACHE_PATH)) return null;
3053
+ const raw = JSON.parse(readFileSync7(CACHE_PATH, "utf8"));
3003
3054
  return typeof raw.token === "string" ? raw.token : null;
3004
3055
  } catch {
3005
3056
  return null;
@@ -3033,7 +3084,7 @@ function getRawCachedPlan() {
3033
3084
  }
3034
3085
  function cacheResponse(token) {
3035
3086
  try {
3036
- writeFileSync4(CACHE_PATH, JSON.stringify({ token }), "utf8");
3087
+ writeFileSync5(CACHE_PATH, JSON.stringify({ token }), "utf8");
3037
3088
  } catch {
3038
3089
  }
3039
3090
  }
@@ -3086,8 +3137,8 @@ async function validateLicense(apiKey, deviceId) {
3086
3137
  }
3087
3138
  function getCacheAgeMs() {
3088
3139
  try {
3089
- const { statSync: statSync3 } = __require("fs");
3090
- const s = statSync3(CACHE_PATH);
3140
+ const { statSync: statSync2 } = __require("fs");
3141
+ const s = statSync2(CACHE_PATH);
3091
3142
  return Date.now() - s.mtimeMs;
3092
3143
  } catch {
3093
3144
  return Infinity;
@@ -3097,9 +3148,9 @@ async function checkLicense() {
3097
3148
  let key = loadLicense();
3098
3149
  if (!key) {
3099
3150
  try {
3100
- const configPath = path8.join(EXE_AI_DIR, "config.json");
3101
- if (existsSync8(configPath)) {
3102
- const raw = JSON.parse(readFileSync6(configPath, "utf8"));
3151
+ const configPath = path9.join(EXE_AI_DIR, "config.json");
3152
+ if (existsSync9(configPath)) {
3153
+ const raw = JSON.parse(readFileSync7(configPath, "utf8"));
3103
3154
  const cloud = raw.cloud;
3104
3155
  if (cloud?.apiKey) {
3105
3156
  key = cloud.apiKey;
@@ -3258,9 +3309,9 @@ var init_license = __esm({
3258
3309
  "src/lib/license.ts"() {
3259
3310
  "use strict";
3260
3311
  init_config();
3261
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
3262
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
3263
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
3312
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
3313
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
3314
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
3264
3315
  API_BASE = "https://askexe.com/cloud";
3265
3316
  RETRY_DELAY_MS = 500;
3266
3317
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
@@ -3290,12 +3341,12 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
3290
3341
  });
3291
3342
 
3292
3343
  // src/lib/plan-limits.ts
3293
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
3294
- import path9 from "path";
3344
+ import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
3345
+ import path10 from "path";
3295
3346
  function getLicenseSync() {
3296
3347
  try {
3297
- if (!existsSync9(CACHE_PATH2)) return freeLicense();
3298
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
3348
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
3349
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
3299
3350
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
3300
3351
  const parts = raw.token.split(".");
3301
3352
  if (parts.length !== 3) return freeLicense();
@@ -3333,8 +3384,8 @@ function assertEmployeeLimitSync(rosterPath) {
3333
3384
  const filePath = rosterPath ?? EMPLOYEES_PATH;
3334
3385
  let count = 0;
3335
3386
  try {
3336
- if (existsSync9(filePath)) {
3337
- const raw = readFileSync7(filePath, "utf8");
3387
+ if (existsSync10(filePath)) {
3388
+ const raw = readFileSync8(filePath, "utf8");
3338
3389
  const employees = JSON.parse(raw);
3339
3390
  count = Array.isArray(employees) ? employees.length : 0;
3340
3391
  }
@@ -3363,19 +3414,19 @@ var init_plan_limits = __esm({
3363
3414
  this.name = "PlanLimitError";
3364
3415
  }
3365
3416
  };
3366
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
3417
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
3367
3418
  }
3368
3419
  });
3369
3420
 
3370
3421
  // src/lib/notifications.ts
3371
3422
  import crypto from "crypto";
3372
- import path10 from "path";
3423
+ import path11 from "path";
3373
3424
  import os6 from "os";
3374
3425
  import {
3375
- readFileSync as readFileSync8,
3426
+ readFileSync as readFileSync9,
3376
3427
  readdirSync as readdirSync2,
3377
3428
  unlinkSync as unlinkSync3,
3378
- existsSync as existsSync10,
3429
+ existsSync as existsSync11,
3379
3430
  rmdirSync
3380
3431
  } from "fs";
3381
3432
  async function writeNotification(notification) {
@@ -3488,9 +3539,9 @@ async function markDoneTaskNotificationsAsRead() {
3488
3539
  }
3489
3540
  }
3490
3541
  async function migrateJsonNotifications() {
3491
- const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path10.join(os6.homedir(), ".exe-os");
3492
- const notifDir = path10.join(base, "notifications");
3493
- if (!existsSync10(notifDir)) return 0;
3542
+ const base = process.env.EXE_OS_DIR || process.env.EXE_MEM_DIR || path11.join(os6.homedir(), ".exe-os");
3543
+ const notifDir = path11.join(base, "notifications");
3544
+ if (!existsSync11(notifDir)) return 0;
3494
3545
  let migrated = 0;
3495
3546
  try {
3496
3547
  const files = readdirSync2(notifDir).filter((f) => f.endsWith(".json"));
@@ -3498,8 +3549,8 @@ async function migrateJsonNotifications() {
3498
3549
  const client = getClient();
3499
3550
  for (const file of files) {
3500
3551
  try {
3501
- const filePath = path10.join(notifDir, file);
3502
- const data = JSON.parse(readFileSync8(filePath, "utf8"));
3552
+ const filePath = path11.join(notifDir, file);
3553
+ const data = JSON.parse(readFileSync9(filePath, "utf8"));
3503
3554
  await client.execute({
3504
3555
  sql: `INSERT OR IGNORE INTO notifications (id, agent_id, agent_role, event, project, summary, task_file, read, created_at)
3505
3556
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
@@ -3646,11 +3697,11 @@ var init_session_kill_telemetry = __esm({
3646
3697
 
3647
3698
  // src/lib/tasks-crud.ts
3648
3699
  import crypto3 from "crypto";
3649
- import path11 from "path";
3700
+ import path12 from "path";
3650
3701
  import os7 from "os";
3651
3702
  import { execSync as execSync5 } from "child_process";
3652
3703
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3653
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
3704
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
3654
3705
  async function writeCheckpoint(input) {
3655
3706
  const client = getClient();
3656
3707
  const row = await resolveTask(client, input.taskId);
@@ -3825,8 +3876,8 @@ ${laneWarning}` : laneWarning;
3825
3876
  }
3826
3877
  if (input.baseDir) {
3827
3878
  try {
3828
- await mkdir4(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3829
- await mkdir4(path11.join(input.baseDir, "exe", "research"), { recursive: true });
3879
+ await mkdir4(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3880
+ await mkdir4(path12.join(input.baseDir, "exe", "research"), { recursive: true });
3830
3881
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3831
3882
  await ensureGitignoreExe(input.baseDir);
3832
3883
  } catch {
@@ -3862,10 +3913,10 @@ ${laneWarning}` : laneWarning;
3862
3913
  });
3863
3914
  if (input.baseDir) {
3864
3915
  try {
3865
- const EXE_OS_DIR = path11.join(os7.homedir(), ".exe-os");
3866
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
3867
- const mdDir = path11.dirname(mdPath);
3868
- if (!existsSync11(mdDir)) await mkdir4(mdDir, { recursive: true });
3916
+ const EXE_OS_DIR = path12.join(os7.homedir(), ".exe-os");
3917
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
3918
+ const mdDir = path12.dirname(mdPath);
3919
+ if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
3869
3920
  const reviewer = input.reviewer ?? input.assignedBy;
3870
3921
  const mdContent = `# ${input.title}
3871
3922
 
@@ -3890,7 +3941,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
3890
3941
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
3891
3942
  `;
3892
3943
  await writeFile4(mdPath, mdContent, "utf-8");
3893
- } catch {
3944
+ } catch (err) {
3945
+ process.stderr.write(
3946
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
3947
+ `
3948
+ );
3894
3949
  }
3895
3950
  }
3896
3951
  return {
@@ -4150,9 +4205,9 @@ async function deleteTaskCore(taskId, _baseDir) {
4150
4205
  return { taskFile, assignedTo, assignedBy, taskSlug };
4151
4206
  }
4152
4207
  async function ensureArchitectureDoc(baseDir, projectName) {
4153
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
4208
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
4154
4209
  try {
4155
- if (existsSync11(archPath)) return;
4210
+ if (existsSync12(archPath)) return;
4156
4211
  const template = [
4157
4212
  `# ${projectName} \u2014 System Architecture`,
4158
4213
  "",
@@ -4185,10 +4240,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
4185
4240
  }
4186
4241
  }
4187
4242
  async function ensureGitignoreExe(baseDir) {
4188
- const gitignorePath = path11.join(baseDir, ".gitignore");
4243
+ const gitignorePath = path12.join(baseDir, ".gitignore");
4189
4244
  try {
4190
- if (existsSync11(gitignorePath)) {
4191
- const content = readFileSync9(gitignorePath, "utf-8");
4245
+ if (existsSync12(gitignorePath)) {
4246
+ const content = readFileSync10(gitignorePath, "utf-8");
4192
4247
  if (/^\/?exe\/?$/m.test(content)) return;
4193
4248
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
4194
4249
  } else {
@@ -4219,8 +4274,8 @@ var init_tasks_crud = __esm({
4219
4274
  });
4220
4275
 
4221
4276
  // src/lib/tasks-review.ts
4222
- import path12 from "path";
4223
- import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
4277
+ import path13 from "path";
4278
+ import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
4224
4279
  async function countPendingReviews(sessionScope) {
4225
4280
  const client = getClient();
4226
4281
  if (sessionScope) {
@@ -4401,11 +4456,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4401
4456
  );
4402
4457
  }
4403
4458
  try {
4404
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
4405
- if (existsSync12(cacheDir)) {
4459
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4460
+ if (existsSync13(cacheDir)) {
4406
4461
  for (const f of readdirSync3(cacheDir)) {
4407
4462
  if (f.startsWith("review-notified-")) {
4408
- unlinkSync4(path12.join(cacheDir, f));
4463
+ unlinkSync4(path13.join(cacheDir, f));
4409
4464
  }
4410
4465
  }
4411
4466
  }
@@ -4426,7 +4481,7 @@ var init_tasks_review = __esm({
4426
4481
  });
4427
4482
 
4428
4483
  // src/lib/tasks-chain.ts
4429
- import path13 from "path";
4484
+ import path14 from "path";
4430
4485
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
4431
4486
  async function cascadeUnblock(taskId, baseDir, now) {
4432
4487
  const client = getClient();
@@ -4443,7 +4498,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4443
4498
  });
4444
4499
  for (const ur of unblockedRows.rows) {
4445
4500
  try {
4446
- const ubFile = path13.join(baseDir, String(ur.task_file));
4501
+ const ubFile = path14.join(baseDir, String(ur.task_file));
4447
4502
  let ubContent = await readFile4(ubFile, "utf-8");
4448
4503
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4449
4504
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4517,7 +4572,7 @@ __export(project_name_exports, {
4517
4572
  getProjectName: () => getProjectName
4518
4573
  });
4519
4574
  import { execSync as execSync6 } from "child_process";
4520
- import path14 from "path";
4575
+ import path15 from "path";
4521
4576
  function getProjectName(cwd) {
4522
4577
  const dir = cwd ?? process.cwd();
4523
4578
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4530,7 +4585,7 @@ function getProjectName(cwd) {
4530
4585
  timeout: 2e3,
4531
4586
  stdio: ["pipe", "pipe", "pipe"]
4532
4587
  }).trim();
4533
- repoRoot = path14.dirname(gitCommonDir);
4588
+ repoRoot = path15.dirname(gitCommonDir);
4534
4589
  } catch {
4535
4590
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4536
4591
  cwd: dir,
@@ -4539,11 +4594,11 @@ function getProjectName(cwd) {
4539
4594
  stdio: ["pipe", "pipe", "pipe"]
4540
4595
  }).trim();
4541
4596
  }
4542
- _cached2 = path14.basename(repoRoot);
4597
+ _cached2 = path15.basename(repoRoot);
4543
4598
  _cachedCwd = dir;
4544
4599
  return _cached2;
4545
4600
  } catch {
4546
- _cached2 = path14.basename(dir);
4601
+ _cached2 = path15.basename(dir);
4547
4602
  _cachedCwd = dir;
4548
4603
  return _cached2;
4549
4604
  }
@@ -5020,8 +5075,8 @@ __export(tasks_exports, {
5020
5075
  updateTaskStatus: () => updateTaskStatus,
5021
5076
  writeCheckpoint: () => writeCheckpoint
5022
5077
  });
5023
- import path15 from "path";
5024
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync5 } from "fs";
5078
+ import path16 from "path";
5079
+ import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
5025
5080
  async function createTask(input) {
5026
5081
  const result = await createTaskCore(input);
5027
5082
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -5040,11 +5095,11 @@ async function updateTask(input) {
5040
5095
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
5041
5096
  try {
5042
5097
  const agent = String(row.assigned_to);
5043
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
5044
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
5098
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
5099
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
5045
5100
  if (input.status === "in_progress") {
5046
- mkdirSync5(cacheDir, { recursive: true });
5047
- writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5101
+ mkdirSync6(cacheDir, { recursive: true });
5102
+ writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
5048
5103
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
5049
5104
  try {
5050
5105
  unlinkSync5(cachePath);
@@ -5511,13 +5566,13 @@ __export(tmux_routing_exports, {
5511
5566
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
5512
5567
  });
5513
5568
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
5514
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync13, appendFileSync } from "fs";
5515
- import path16 from "path";
5569
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, existsSync as existsSync14, appendFileSync } from "fs";
5570
+ import path17 from "path";
5516
5571
  import os8 from "os";
5517
5572
  import { fileURLToPath as fileURLToPath2 } from "url";
5518
5573
  import { unlinkSync as unlinkSync6 } from "fs";
5519
5574
  function spawnLockPath(sessionName) {
5520
- return path16.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5575
+ return path17.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
5521
5576
  }
5522
5577
  function isProcessAlive(pid) {
5523
5578
  try {
@@ -5528,13 +5583,13 @@ function isProcessAlive(pid) {
5528
5583
  }
5529
5584
  }
5530
5585
  function acquireSpawnLock2(sessionName) {
5531
- if (!existsSync13(SPAWN_LOCK_DIR)) {
5532
- mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
5586
+ if (!existsSync14(SPAWN_LOCK_DIR)) {
5587
+ mkdirSync7(SPAWN_LOCK_DIR, { recursive: true });
5533
5588
  }
5534
5589
  const lockFile = spawnLockPath(sessionName);
5535
- if (existsSync13(lockFile)) {
5590
+ if (existsSync14(lockFile)) {
5536
5591
  try {
5537
- const lock = JSON.parse(readFileSync10(lockFile, "utf8"));
5592
+ const lock = JSON.parse(readFileSync11(lockFile, "utf8"));
5538
5593
  const age = Date.now() - lock.timestamp;
5539
5594
  if (isProcessAlive(lock.pid) && age < 6e4) {
5540
5595
  return false;
@@ -5542,7 +5597,7 @@ function acquireSpawnLock2(sessionName) {
5542
5597
  } catch {
5543
5598
  }
5544
5599
  }
5545
- writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5600
+ writeFileSync7(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
5546
5601
  return true;
5547
5602
  }
5548
5603
  function releaseSpawnLock2(sessionName) {
@@ -5554,13 +5609,13 @@ function releaseSpawnLock2(sessionName) {
5554
5609
  function resolveBehaviorsExporterScript() {
5555
5610
  try {
5556
5611
  const thisFile = fileURLToPath2(import.meta.url);
5557
- const scriptPath = path16.join(
5558
- path16.dirname(thisFile),
5612
+ const scriptPath = path17.join(
5613
+ path17.dirname(thisFile),
5559
5614
  "..",
5560
5615
  "bin",
5561
5616
  "exe-export-behaviors.js"
5562
5617
  );
5563
- return existsSync13(scriptPath) ? scriptPath : null;
5618
+ return existsSync14(scriptPath) ? scriptPath : null;
5564
5619
  } catch {
5565
5620
  return null;
5566
5621
  }
@@ -5626,12 +5681,12 @@ function extractRootExe(name) {
5626
5681
  return parts.length > 0 ? parts[parts.length - 1] : null;
5627
5682
  }
5628
5683
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5629
- if (!existsSync13(SESSION_CACHE)) {
5630
- mkdirSync6(SESSION_CACHE, { recursive: true });
5684
+ if (!existsSync14(SESSION_CACHE)) {
5685
+ mkdirSync7(SESSION_CACHE, { recursive: true });
5631
5686
  }
5632
5687
  const rootExe = extractRootExe(parentExe) ?? parentExe;
5633
- const filePath = path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5634
- writeFileSync6(filePath, JSON.stringify({
5688
+ const filePath = path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
5689
+ writeFileSync7(filePath, JSON.stringify({
5635
5690
  parentExe: rootExe,
5636
5691
  dispatchedBy: dispatchedBy || rootExe,
5637
5692
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -5639,7 +5694,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
5639
5694
  }
5640
5695
  function getParentExe(sessionKey) {
5641
5696
  try {
5642
- const data = JSON.parse(readFileSync10(path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5697
+ const data = JSON.parse(readFileSync11(path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
5643
5698
  return data.parentExe || null;
5644
5699
  } catch {
5645
5700
  return null;
@@ -5647,8 +5702,8 @@ function getParentExe(sessionKey) {
5647
5702
  }
5648
5703
  function getDispatchedBy(sessionKey) {
5649
5704
  try {
5650
- const data = JSON.parse(readFileSync10(
5651
- path16.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5705
+ const data = JSON.parse(readFileSync11(
5706
+ path17.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
5652
5707
  "utf8"
5653
5708
  ));
5654
5709
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -5709,32 +5764,50 @@ async function verifyPaneAtCapacity(sessionName) {
5709
5764
  }
5710
5765
  function readDebounceState() {
5711
5766
  try {
5712
- if (!existsSync13(DEBOUNCE_FILE)) return {};
5713
- return JSON.parse(readFileSync10(DEBOUNCE_FILE, "utf8"));
5767
+ if (!existsSync14(DEBOUNCE_FILE)) return {};
5768
+ const raw = JSON.parse(readFileSync11(DEBOUNCE_FILE, "utf8"));
5769
+ const state = {};
5770
+ for (const [key, val] of Object.entries(raw)) {
5771
+ if (typeof val === "number") {
5772
+ state[key] = { lastSent: val, pending: 0 };
5773
+ } else if (val && typeof val === "object" && "lastSent" in val) {
5774
+ state[key] = val;
5775
+ }
5776
+ }
5777
+ return state;
5714
5778
  } catch {
5715
5779
  return {};
5716
5780
  }
5717
5781
  }
5718
5782
  function writeDebounceState(state) {
5719
5783
  try {
5720
- if (!existsSync13(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
5721
- writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
5784
+ if (!existsSync14(SESSION_CACHE)) mkdirSync7(SESSION_CACHE, { recursive: true });
5785
+ writeFileSync7(DEBOUNCE_FILE, JSON.stringify(state));
5722
5786
  } catch {
5723
5787
  }
5724
5788
  }
5725
5789
  function isDebounced(targetSession) {
5726
5790
  const state = readDebounceState();
5727
- const lastSent = state[targetSession] ?? 0;
5728
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
5791
+ const entry = state[targetSession];
5792
+ const lastSent = entry?.lastSent ?? 0;
5793
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
5794
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
5795
+ state[targetSession].pending++;
5796
+ writeDebounceState(state);
5797
+ return true;
5798
+ }
5799
+ return false;
5729
5800
  }
5730
5801
  function recordDebounce(targetSession) {
5731
5802
  const state = readDebounceState();
5732
- state[targetSession] = Date.now();
5803
+ const batched = state[targetSession]?.pending ?? 0;
5804
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
5733
5805
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
5734
5806
  for (const key of Object.keys(state)) {
5735
- if ((state[key] ?? 0) < cutoff) delete state[key];
5807
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
5736
5808
  }
5737
5809
  writeDebounceState(state);
5810
+ return batched;
5738
5811
  }
5739
5812
  function logIntercom(msg) {
5740
5813
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -5779,7 +5852,7 @@ function sendIntercom(targetSession) {
5779
5852
  return "skipped_exe";
5780
5853
  }
5781
5854
  if (isDebounced(targetSession)) {
5782
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
5855
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
5783
5856
  return "debounced";
5784
5857
  }
5785
5858
  try {
@@ -5791,14 +5864,14 @@ function sendIntercom(targetSession) {
5791
5864
  const sessionState = getSessionState(targetSession);
5792
5865
  if (sessionState === "no_claude") {
5793
5866
  queueIntercom(targetSession, "claude not running in session");
5794
- recordDebounce(targetSession);
5795
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
5867
+ const batched2 = recordDebounce(targetSession);
5868
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
5796
5869
  return "queued";
5797
5870
  }
5798
5871
  if (sessionState === "thinking" || sessionState === "tool") {
5799
5872
  queueIntercom(targetSession, "session busy at send time");
5800
- recordDebounce(targetSession);
5801
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
5873
+ const batched2 = recordDebounce(targetSession);
5874
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
5802
5875
  return "queued";
5803
5876
  }
5804
5877
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -5806,8 +5879,8 @@ function sendIntercom(targetSession) {
5806
5879
  transport.sendKeys(targetSession, "q");
5807
5880
  }
5808
5881
  transport.sendKeys(targetSession, "/exe-intercom");
5809
- recordDebounce(targetSession);
5810
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
5882
+ const batched = recordDebounce(targetSession);
5883
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
5811
5884
  return "delivered";
5812
5885
  } catch {
5813
5886
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -5909,26 +5982,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5909
5982
  const transport = getTransport();
5910
5983
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
5911
5984
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
5912
- const logDir = path16.join(os8.homedir(), ".exe-os", "session-logs");
5913
- const logFile = path16.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5914
- if (!existsSync13(logDir)) {
5915
- mkdirSync6(logDir, { recursive: true });
5985
+ const logDir = path17.join(os8.homedir(), ".exe-os", "session-logs");
5986
+ const logFile = path17.join(logDir, `${instanceLabel}-${Date.now()}.log`);
5987
+ if (!existsSync14(logDir)) {
5988
+ mkdirSync7(logDir, { recursive: true });
5916
5989
  }
5917
5990
  transport.kill(sessionName);
5918
5991
  let cleanupSuffix = "";
5919
5992
  try {
5920
5993
  const thisFile = fileURLToPath2(import.meta.url);
5921
- const cleanupScript = path16.join(path16.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5922
- if (existsSync13(cleanupScript)) {
5994
+ const cleanupScript = path17.join(path17.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
5995
+ if (existsSync14(cleanupScript)) {
5923
5996
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
5924
5997
  }
5925
5998
  } catch {
5926
5999
  }
5927
6000
  try {
5928
- const claudeJsonPath = path16.join(os8.homedir(), ".claude.json");
6001
+ const claudeJsonPath = path17.join(os8.homedir(), ".claude.json");
5929
6002
  let claudeJson = {};
5930
6003
  try {
5931
- claudeJson = JSON.parse(readFileSync10(claudeJsonPath, "utf8"));
6004
+ claudeJson = JSON.parse(readFileSync11(claudeJsonPath, "utf8"));
5932
6005
  } catch {
5933
6006
  }
5934
6007
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -5936,17 +6009,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5936
6009
  const trustDir = opts?.cwd ?? projectDir;
5937
6010
  if (!projects[trustDir]) projects[trustDir] = {};
5938
6011
  projects[trustDir].hasTrustDialogAccepted = true;
5939
- writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
6012
+ writeFileSync7(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
5940
6013
  } catch {
5941
6014
  }
5942
6015
  try {
5943
- const settingsDir = path16.join(os8.homedir(), ".claude", "projects");
6016
+ const settingsDir = path17.join(os8.homedir(), ".claude", "projects");
5944
6017
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
5945
- const projSettingsDir = path16.join(settingsDir, normalizedKey);
5946
- const settingsPath = path16.join(projSettingsDir, "settings.json");
6018
+ const projSettingsDir = path17.join(settingsDir, normalizedKey);
6019
+ const settingsPath = path17.join(projSettingsDir, "settings.json");
5947
6020
  let settings = {};
5948
6021
  try {
5949
- settings = JSON.parse(readFileSync10(settingsPath, "utf8"));
6022
+ settings = JSON.parse(readFileSync11(settingsPath, "utf8"));
5950
6023
  } catch {
5951
6024
  }
5952
6025
  const perms = settings.permissions ?? {};
@@ -5974,20 +6047,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5974
6047
  if (changed) {
5975
6048
  perms.allow = allow;
5976
6049
  settings.permissions = perms;
5977
- mkdirSync6(projSettingsDir, { recursive: true });
5978
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
6050
+ mkdirSync7(projSettingsDir, { recursive: true });
6051
+ writeFileSync7(settingsPath, JSON.stringify(settings, null, 2) + "\n");
5979
6052
  }
5980
6053
  } catch {
5981
6054
  }
5982
6055
  const spawnCwd = opts?.cwd ?? projectDir;
5983
6056
  const useExeAgent = !!(opts?.model && opts?.provider);
5984
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
6057
+ const agentRtConfig = getAgentRuntime(employeeName);
6058
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
6059
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
6060
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
5985
6061
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
5986
6062
  let identityFlag = "";
5987
6063
  let behaviorsFlag = "";
5988
6064
  let legacyFallbackWarned = false;
5989
6065
  if (!useExeAgent && !useBinSymlink) {
5990
- const identityPath = path16.join(
6066
+ const identityPath = path17.join(
5991
6067
  os8.homedir(),
5992
6068
  ".exe-os",
5993
6069
  "identity",
@@ -5997,13 +6073,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5997
6073
  const hasAgentFlag = claudeSupportsAgentFlag();
5998
6074
  if (hasAgentFlag) {
5999
6075
  identityFlag = ` --agent ${employeeName}`;
6000
- } else if (existsSync13(identityPath)) {
6076
+ } else if (existsSync14(identityPath)) {
6001
6077
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
6002
6078
  legacyFallbackWarned = true;
6003
6079
  }
6004
6080
  const behaviorsFile = exportBehaviorsSync(
6005
6081
  employeeName,
6006
- path16.basename(spawnCwd),
6082
+ path17.basename(spawnCwd),
6007
6083
  sessionName
6008
6084
  );
6009
6085
  if (behaviorsFile) {
@@ -6018,16 +6094,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6018
6094
  }
6019
6095
  let sessionContextFlag = "";
6020
6096
  try {
6021
- const ctxDir = path16.join(os8.homedir(), ".exe-os", "session-cache");
6022
- mkdirSync6(ctxDir, { recursive: true });
6023
- const ctxFile = path16.join(ctxDir, `session-context-${sessionName}.md`);
6097
+ const ctxDir = path17.join(os8.homedir(), ".exe-os", "session-cache");
6098
+ mkdirSync7(ctxDir, { recursive: true });
6099
+ const ctxFile = path17.join(ctxDir, `session-context-${sessionName}.md`);
6024
6100
  const ctxContent = [
6025
6101
  `## Session Context`,
6026
6102
  `You are running in tmux session: ${sessionName}.`,
6027
6103
  `Your parent coordinator session is ${exeSession}.`,
6028
6104
  `Your employees (if any) use the -${exeSession} suffix.`
6029
6105
  ].join("\n");
6030
- writeFileSync6(ctxFile, ctxContent);
6106
+ writeFileSync7(ctxFile, ctxContent);
6031
6107
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
6032
6108
  } catch {
6033
6109
  }
@@ -6041,9 +6117,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6041
6117
  }
6042
6118
  }
6043
6119
  }
6120
+ if (useCodex) {
6121
+ const codexCfg = RUNTIME_TABLE.codex;
6122
+ if (codexCfg?.apiKeyEnv) {
6123
+ const keyVal = process.env[codexCfg.apiKeyEnv];
6124
+ if (keyVal) {
6125
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
6126
+ }
6127
+ }
6128
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
6129
+ }
6130
+ if (useOpencode) {
6131
+ const ocCfg = PROVIDER_TABLE.opencode;
6132
+ if (ocCfg?.apiKeyEnv) {
6133
+ const keyVal = process.env[ocCfg.apiKeyEnv];
6134
+ if (keyVal) {
6135
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
6136
+ }
6137
+ }
6138
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
6139
+ }
6140
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
6141
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
6142
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
6143
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
6144
+ }
6145
+ }
6044
6146
  let spawnCommand;
6045
6147
  if (useExeAgent) {
6046
6148
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
6149
+ } else if (useCodex) {
6150
+ process.stderr.write(
6151
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
6152
+ `
6153
+ );
6154
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
6155
+ } else if (useOpencode) {
6156
+ const binName = `${employeeName}-opencode`;
6157
+ process.stderr.write(
6158
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
6159
+ `
6160
+ );
6161
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
6047
6162
  } else if (useBinSymlink) {
6048
6163
  const binName = `${employeeName}-${ccProvider}`;
6049
6164
  process.stderr.write(
@@ -6065,11 +6180,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6065
6180
  transport.pipeLog(sessionName, logFile);
6066
6181
  try {
6067
6182
  const mySession = getMySession();
6068
- const dispatchInfo = path16.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6069
- writeFileSync6(dispatchInfo, JSON.stringify({
6183
+ const dispatchInfo = path17.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
6184
+ writeFileSync7(dispatchInfo, JSON.stringify({
6070
6185
  dispatchedBy: mySession,
6071
6186
  rootExe: exeSession,
6072
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
6187
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
6188
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
6189
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
6073
6190
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
6074
6191
  }));
6075
6192
  } catch {
@@ -6087,6 +6204,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6087
6204
  booted = true;
6088
6205
  break;
6089
6206
  }
6207
+ } else if (useCodex) {
6208
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
6209
+ booted = true;
6210
+ break;
6211
+ }
6090
6212
  } else {
6091
6213
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
6092
6214
  booted = true;
@@ -6098,9 +6220,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6098
6220
  }
6099
6221
  if (!booted) {
6100
6222
  releaseSpawnLock2(sessionName);
6101
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
6223
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
6224
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
6102
6225
  }
6103
- if (!useExeAgent) {
6226
+ if (!useExeAgent && !useCodex) {
6104
6227
  try {
6105
6228
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
6106
6229
  } catch {
@@ -6127,17 +6250,19 @@ var init_tmux_routing = __esm({
6127
6250
  init_cc_agent_support();
6128
6251
  init_mcp_prefix();
6129
6252
  init_provider_table();
6253
+ init_agent_config();
6254
+ init_runtime_table();
6130
6255
  init_intercom_queue();
6131
6256
  init_plan_limits();
6132
6257
  init_employees();
6133
- SPAWN_LOCK_DIR = path16.join(os8.homedir(), ".exe-os", "spawn-locks");
6134
- SESSION_CACHE = path16.join(os8.homedir(), ".exe-os", "session-cache");
6258
+ SPAWN_LOCK_DIR = path17.join(os8.homedir(), ".exe-os", "spawn-locks");
6259
+ SESSION_CACHE = path17.join(os8.homedir(), ".exe-os", "session-cache");
6135
6260
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
6136
6261
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
6137
6262
  VERIFY_PANE_LINES = 200;
6138
6263
  INTERCOM_DEBOUNCE_MS = 3e4;
6139
- INTERCOM_LOG2 = path16.join(os8.homedir(), ".exe-os", "intercom.log");
6140
- DEBOUNCE_FILE = path16.join(SESSION_CACHE, "intercom-debounce.json");
6264
+ INTERCOM_LOG2 = path17.join(os8.homedir(), ".exe-os", "intercom.log");
6265
+ DEBOUNCE_FILE = path17.join(SESSION_CACHE, "intercom-debounce.json");
6141
6266
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
6142
6267
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
6143
6268
  }
@@ -6172,158 +6297,8 @@ var task_scanner_exports = {};
6172
6297
  __export(task_scanner_exports, {
6173
6298
  PRIORITY_RE: () => PRIORITY_RE,
6174
6299
  STATUS_RE: () => STATUS_RE,
6175
- TITLE_RE: () => TITLE_RE,
6176
- formatJson: () => formatJson,
6177
- formatMandatory: () => formatMandatory,
6178
- formatText: () => formatText,
6179
- scanAgentTasks: () => scanAgentTasks
6300
+ TITLE_RE: () => TITLE_RE
6180
6301
  });
6181
- import { readdirSync as readdirSync5, readFileSync as readFileSync12, existsSync as existsSync14, statSync as statSync2 } from "fs";
6182
- import { execSync as execSync9 } from "child_process";
6183
- import path18 from "path";
6184
- import os9 from "os";
6185
- function getProjectRoot() {
6186
- try {
6187
- return execSync9("git rev-parse --show-toplevel", {
6188
- encoding: "utf8",
6189
- stdio: ["pipe", "pipe", "pipe"],
6190
- timeout: 5e3
6191
- }).trim();
6192
- } catch {
6193
- return process.cwd();
6194
- }
6195
- }
6196
- function getSessionScope() {
6197
- try {
6198
- const out = execSync9("tmux display-message -p '#{session_name}' 2>/dev/null", {
6199
- encoding: "utf8",
6200
- timeout: 1e3
6201
- }).trim();
6202
- if (!out) return "default";
6203
- const dashIdx = out.lastIndexOf("-");
6204
- return dashIdx > 0 ? out.slice(dashIdx + 1) : out;
6205
- } catch {
6206
- return "default";
6207
- }
6208
- }
6209
- function scanDir(dirPath, seen) {
6210
- const open = [];
6211
- const inProgress = [];
6212
- let done = 0;
6213
- if (!existsSync14(dirPath)) return { open, inProgress, done };
6214
- try {
6215
- const files = readdirSync5(dirPath).filter((f) => f.endsWith(".md"));
6216
- for (const f of files) {
6217
- if (seen.has(f)) continue;
6218
- seen.add(f);
6219
- try {
6220
- const content = readFileSync12(path18.join(dirPath, f), "utf8");
6221
- const statusMatch = content.match(STATUS_RE);
6222
- const status = statusMatch ? statusMatch[1].toLowerCase() : null;
6223
- if (status === "done") {
6224
- done++;
6225
- continue;
6226
- }
6227
- if (status !== "open" && status !== "in_progress") continue;
6228
- const priMatch = content.match(PRIORITY_RE);
6229
- const titleMatch = content.match(TITLE_RE);
6230
- const task = {
6231
- file: f,
6232
- title: titleMatch ? titleMatch[1] : f.replace(".md", ""),
6233
- priority: priMatch ? priMatch[1] : "P2",
6234
- status,
6235
- slug: f.replace(".md", "")
6236
- };
6237
- if (status === "in_progress") {
6238
- inProgress.push(task);
6239
- } else {
6240
- open.push(task);
6241
- }
6242
- } catch {
6243
- }
6244
- }
6245
- } catch {
6246
- }
6247
- return { open, inProgress, done };
6248
- }
6249
- function scanAgentTasks(agentId) {
6250
- const open = [];
6251
- const inProgress = [];
6252
- let done = 0;
6253
- const seen = /* @__PURE__ */ new Set();
6254
- const scope = getSessionScope();
6255
- const centralDir = path18.join(os9.homedir(), ".exe-os", "tasks", scope, agentId);
6256
- const central = scanDir(centralDir, seen);
6257
- open.push(...central.open);
6258
- inProgress.push(...central.inProgress);
6259
- done += central.done;
6260
- const legacyDir = path18.join(getProjectRoot(), "exe", agentId);
6261
- const legacy = scanDir(legacyDir, seen);
6262
- open.push(...legacy.open);
6263
- inProgress.push(...legacy.inProgress);
6264
- done += legacy.done;
6265
- const total = open.length + inProgress.length + done;
6266
- open.sort((a, b) => a.priority.localeCompare(b.priority));
6267
- inProgress.sort((a, b) => a.priority.localeCompare(b.priority));
6268
- return { open, inProgress, done, total };
6269
- }
6270
- function formatText(agentId, result) {
6271
- const lines = [];
6272
- if (result.inProgress.length > 0) {
6273
- lines.push(`IN_PROGRESS (${result.inProgress.length}):`);
6274
- for (const t of result.inProgress) {
6275
- lines.push(` [${t.priority}] ${t.title} \u2014 exe/${agentId}/${t.file}`);
6276
- }
6277
- }
6278
- if (result.open.length > 0) {
6279
- lines.push(`OPEN (${result.open.length}):`);
6280
- for (const t of result.open) {
6281
- lines.push(` [${t.priority}] ${t.title} \u2014 exe/${agentId}/${t.file}`);
6282
- }
6283
- }
6284
- lines.push(`DONE: ${result.done}`);
6285
- return lines.join("\n");
6286
- }
6287
- function formatMandatory(agentId, result) {
6288
- const { open, inProgress } = result;
6289
- if (open.length === 0 && inProgress.length === 0) return "";
6290
- const lines = [];
6291
- if (inProgress.length > 0) {
6292
- const current = inProgress[0];
6293
- let stale = false;
6294
- try {
6295
- const stat = statSync2(path18.join(getProjectRoot(), "exe", agentId, current.file));
6296
- const ageMin = (Date.now() - stat.mtimeMs) / 6e4;
6297
- if (ageMin > 30) stale = true;
6298
- } catch {
6299
- }
6300
- if (stale) {
6301
- lines.push(`MANDATORY: Update task status for: ${current.title} [${current.priority}] (exe/${agentId}/${current.file})`);
6302
- lines.push("This task has been in_progress for over 30 minutes without updates.");
6303
- lines.push("If work is done, mark done. If blocked, update status to blocked.");
6304
- } else {
6305
- lines.push(`Continue working on: ${current.title} [${current.priority}] (exe/${agentId}/${current.file})`);
6306
- }
6307
- if (open.length > 0) {
6308
- lines.push("Queued: " + open.map((t) => `${t.title} [${t.priority}]`).join(", "));
6309
- }
6310
- } else {
6311
- const top = open[0];
6312
- lines.push(`MANDATORY: You have ${open.length} unstarted task(s).`);
6313
- lines.push(`Highest priority: ${top.title} [${top.priority}]`);
6314
- lines.push(`File: exe/${agentId}/${top.file}`);
6315
- lines.push("Read this task file and START WORKING NOW.");
6316
- }
6317
- return lines.join("\n");
6318
- }
6319
- function formatJson(result) {
6320
- return JSON.stringify({
6321
- open: result.open.map((t) => ({ file: t.file, title: t.title, priority: t.priority })),
6322
- in_progress: result.inProgress.map((t) => ({ file: t.file, title: t.title, priority: t.priority })),
6323
- done: result.done,
6324
- total: result.total
6325
- });
6326
- }
6327
6302
  var STATUS_RE, PRIORITY_RE, TITLE_RE;
6328
6303
  var init_task_scanner = __esm({
6329
6304
  "src/lib/task-scanner.ts"() {
@@ -6344,15 +6319,15 @@ __export(worker_gate_exports, {
6344
6319
  tryAcquireBackfillLock: () => tryAcquireBackfillLock,
6345
6320
  tryAcquireWorkerSlot: () => tryAcquireWorkerSlot
6346
6321
  });
6347
- import { readdirSync as readdirSync6, writeFileSync as writeFileSync8, unlinkSync as unlinkSync8, mkdirSync as mkdirSync8, existsSync as existsSync15 } from "fs";
6322
+ import { readdirSync as readdirSync5, writeFileSync as writeFileSync9, unlinkSync as unlinkSync8, mkdirSync as mkdirSync9, existsSync as existsSync15 } from "fs";
6348
6323
  import path19 from "path";
6349
6324
  function tryAcquireWorkerSlot() {
6350
6325
  try {
6351
- mkdirSync8(WORKER_PID_DIR, { recursive: true });
6326
+ mkdirSync9(WORKER_PID_DIR, { recursive: true });
6352
6327
  const reservationId = `res-${process.pid}-${Date.now()}`;
6353
6328
  const reservationPath = path19.join(WORKER_PID_DIR, `${reservationId}.pid`);
6354
- writeFileSync8(reservationPath, String(process.pid));
6355
- const files = readdirSync6(WORKER_PID_DIR);
6329
+ writeFileSync9(reservationPath, String(process.pid));
6330
+ const files = readdirSync5(WORKER_PID_DIR);
6356
6331
  let alive = 0;
6357
6332
  for (const f of files) {
6358
6333
  if (!f.endsWith(".pid")) continue;
@@ -6391,8 +6366,8 @@ function tryAcquireWorkerSlot() {
6391
6366
  }
6392
6367
  function registerWorkerPid(pid) {
6393
6368
  try {
6394
- mkdirSync8(WORKER_PID_DIR, { recursive: true });
6395
- writeFileSync8(path19.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
6369
+ mkdirSync9(WORKER_PID_DIR, { recursive: true });
6370
+ writeFileSync9(path19.join(WORKER_PID_DIR, `worker-${pid}.pid`), String(pid));
6396
6371
  } catch {
6397
6372
  }
6398
6373
  }
@@ -6404,7 +6379,7 @@ function cleanupWorkerPid() {
6404
6379
  }
6405
6380
  function tryAcquireBackfillLock() {
6406
6381
  try {
6407
- mkdirSync8(WORKER_PID_DIR, { recursive: true });
6382
+ mkdirSync9(WORKER_PID_DIR, { recursive: true });
6408
6383
  if (existsSync15(BACKFILL_LOCK)) {
6409
6384
  try {
6410
6385
  const pid = parseInt(
@@ -6421,7 +6396,7 @@ function tryAcquireBackfillLock() {
6421
6396
  } catch {
6422
6397
  }
6423
6398
  }
6424
- writeFileSync8(BACKFILL_LOCK, String(process.pid));
6399
+ writeFileSync9(BACKFILL_LOCK, String(process.pid));
6425
6400
  return true;
6426
6401
  } catch {
6427
6402
  return true;
@@ -6542,7 +6517,7 @@ __export(crdt_sync_exports, {
6542
6517
  rebuildFromDb: () => rebuildFromDb
6543
6518
  });
6544
6519
  import * as Y from "yjs";
6545
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync16, mkdirSync as mkdirSync9, unlinkSync as unlinkSync9 } from "fs";
6520
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync16, mkdirSync as mkdirSync10, unlinkSync as unlinkSync9 } from "fs";
6546
6521
  import path20 from "path";
6547
6522
  import { homedir } from "os";
6548
6523
  function getStatePath() {
@@ -6700,9 +6675,9 @@ function persistState() {
6700
6675
  try {
6701
6676
  const sp = getStatePath();
6702
6677
  const dir = path20.dirname(sp);
6703
- if (!existsSync16(dir)) mkdirSync9(dir, { recursive: true });
6678
+ if (!existsSync16(dir)) mkdirSync10(dir, { recursive: true });
6704
6679
  const state = Y.encodeStateAsUpdate(doc);
6705
- writeFileSync9(sp, Buffer.from(state));
6680
+ writeFileSync10(sp, Buffer.from(state));
6706
6681
  } catch {
6707
6682
  }
6708
6683
  }
@@ -6777,7 +6752,7 @@ __export(cloud_sync_exports, {
6777
6752
  mergeRosterFromRemote: () => mergeRosterFromRemote,
6778
6753
  recordRosterDeletion: () => recordRosterDeletion
6779
6754
  });
6780
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync17, readdirSync as readdirSync7, mkdirSync as mkdirSync10, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
6755
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync17, readdirSync as readdirSync6, mkdirSync as mkdirSync11, appendFileSync as appendFileSync2, unlinkSync as unlinkSync10, openSync as openSync2, closeSync as closeSync2 } from "fs";
6781
6756
  import crypto7 from "crypto";
6782
6757
  import path21 from "path";
6783
6758
  import { homedir as homedir2 } from "os";
@@ -6796,7 +6771,7 @@ async function withRosterLock(fn) {
6796
6771
  try {
6797
6772
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
6798
6773
  closeSync2(fd);
6799
- writeFileSync10(ROSTER_LOCK_PATH, String(Date.now()));
6774
+ writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
6800
6775
  } catch (err) {
6801
6776
  if (err.code === "EEXIST") {
6802
6777
  try {
@@ -6807,7 +6782,7 @@ async function withRosterLock(fn) {
6807
6782
  unlinkSync10(ROSTER_LOCK_PATH);
6808
6783
  const fd = openSync2(ROSTER_LOCK_PATH, "wx");
6809
6784
  closeSync2(fd);
6810
- writeFileSync10(ROSTER_LOCK_PATH, String(Date.now()));
6785
+ writeFileSync11(ROSTER_LOCK_PATH, String(Date.now()));
6811
6786
  } catch (retryErr) {
6812
6787
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
6813
6788
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -7183,7 +7158,7 @@ async function cloudSync(config) {
7183
7158
  rosterResult.employees = employees.length;
7184
7159
  const idDir = path21.join(EXE_AI_DIR, "identity");
7185
7160
  if (existsSync17(idDir)) {
7186
- rosterResult.identities = readdirSync7(idDir).filter((f) => f.endsWith(".md")).length;
7161
+ rosterResult.identities = readdirSync6(idDir).filter((f) => f.endsWith(".md")).length;
7187
7162
  }
7188
7163
  } catch {
7189
7164
  }
@@ -7209,13 +7184,13 @@ function recordRosterDeletion(name) {
7209
7184
  } catch {
7210
7185
  }
7211
7186
  if (!deletions.includes(name)) deletions.push(name);
7212
- writeFileSync10(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
7187
+ writeFileSync11(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
7213
7188
  }
7214
7189
  function consumeRosterDeletions() {
7215
7190
  try {
7216
7191
  if (!existsSync17(ROSTER_DELETIONS_PATH)) return [];
7217
7192
  const deletions = JSON.parse(readFileSync14(ROSTER_DELETIONS_PATH, "utf-8"));
7218
- writeFileSync10(ROSTER_DELETIONS_PATH, "[]");
7193
+ writeFileSync11(ROSTER_DELETIONS_PATH, "[]");
7219
7194
  return deletions;
7220
7195
  } catch {
7221
7196
  return [];
@@ -7234,7 +7209,7 @@ function buildRosterBlob(paths) {
7234
7209
  }
7235
7210
  const identities = {};
7236
7211
  if (existsSync17(identityDir)) {
7237
- for (const file of readdirSync7(identityDir).filter((f) => f.endsWith(".md"))) {
7212
+ for (const file of readdirSync6(identityDir).filter((f) => f.endsWith(".md"))) {
7238
7213
  try {
7239
7214
  identities[file] = readFileSync14(path21.join(identityDir, file), "utf-8");
7240
7215
  } catch {
@@ -7248,10 +7223,18 @@ function buildRosterBlob(paths) {
7248
7223
  } catch {
7249
7224
  }
7250
7225
  }
7226
+ let agentConfig;
7227
+ const agentConfigPath = path21.join(EXE_AI_DIR, "agent-config.json");
7228
+ if (existsSync17(agentConfigPath)) {
7229
+ try {
7230
+ agentConfig = JSON.parse(readFileSync14(agentConfigPath, "utf-8"));
7231
+ } catch {
7232
+ }
7233
+ }
7251
7234
  const deletedNames = consumeRosterDeletions();
7252
- const content = JSON.stringify({ roster, identities, config, deletedNames });
7235
+ const content = JSON.stringify({ roster, identities, config, agentConfig, deletedNames });
7253
7236
  const hash = crypto7.createHash("sha256").update(content).digest("hex").slice(0, 16);
7254
- return { roster, identities, config, deletedNames, version: hash };
7237
+ return { roster, identities, config, agentConfig, deletedNames, version: hash };
7255
7238
  }
7256
7239
  async function cloudPushRoster(config) {
7257
7240
  assertSecureEndpoint(config.endpoint);
@@ -7330,8 +7313,8 @@ function mergeConfig(remoteConfig, configPath) {
7330
7313
  }
7331
7314
  const merged = { ...remoteConfig, ...local };
7332
7315
  const dir = path21.dirname(cfgPath);
7333
- if (!existsSync17(dir)) mkdirSync10(dir, { recursive: true });
7334
- writeFileSync10(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
7316
+ if (!existsSync17(dir)) mkdirSync11(dir, { recursive: true });
7317
+ writeFileSync11(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
7335
7318
  }
7336
7319
  async function mergeRosterFromRemote(remote, paths) {
7337
7320
  return withRosterLock(async () => {
@@ -7357,7 +7340,7 @@ async function mergeRosterFromRemote(remote, paths) {
7357
7340
  ) ?? lookupKey;
7358
7341
  const remoteIdentity = remote.identities[matchedKey];
7359
7342
  if (remoteIdentity) {
7360
- if (!existsSync17(identityDir)) mkdirSync10(identityDir, { recursive: true });
7343
+ if (!existsSync17(identityDir)) mkdirSync11(identityDir, { recursive: true });
7361
7344
  const idPath = path21.join(identityDir, `${remoteEmp.name}.md`);
7362
7345
  let localIdentity = null;
7363
7346
  try {
@@ -7365,7 +7348,7 @@ async function mergeRosterFromRemote(remote, paths) {
7365
7348
  } catch {
7366
7349
  }
7367
7350
  if (localIdentity !== remoteIdentity) {
7368
- writeFileSync10(idPath, remoteIdentity, "utf-8");
7351
+ writeFileSync11(idPath, remoteIdentity, "utf-8");
7369
7352
  identitiesUpdated++;
7370
7353
  }
7371
7354
  }
@@ -7389,6 +7372,21 @@ async function mergeRosterFromRemote(remote, paths) {
7389
7372
  } catch {
7390
7373
  }
7391
7374
  }
7375
+ if (remote.agentConfig && Object.keys(remote.agentConfig).length > 0) {
7376
+ try {
7377
+ const agentConfigPath = path21.join(EXE_AI_DIR, "agent-config.json");
7378
+ let local = {};
7379
+ if (existsSync17(agentConfigPath)) {
7380
+ try {
7381
+ local = JSON.parse(readFileSync14(agentConfigPath, "utf-8"));
7382
+ } catch {
7383
+ }
7384
+ }
7385
+ const merged = { ...remote.agentConfig, ...local };
7386
+ writeFileSync11(agentConfigPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
7387
+ } catch {
7388
+ }
7389
+ }
7392
7390
  return { added, identitiesUpdated };
7393
7391
  });
7394
7392
  }
@@ -7838,7 +7836,7 @@ __export(schedules_exports, {
7838
7836
  parseHumanCron: () => parseHumanCron
7839
7837
  });
7840
7838
  import crypto8 from "crypto";
7841
- import { execSync as execSync10 } from "child_process";
7839
+ import { execSync as execSync9 } from "child_process";
7842
7840
  async function ensureDb() {
7843
7841
  if (!isInitialized()) {
7844
7842
  await initStore();
@@ -7981,7 +7979,7 @@ function addToCrontab(id, cron, prompt, projectDir) {
7981
7979
  const cwd = projectDir ? `cd ${JSON.stringify(projectDir)} && ` : "";
7982
7980
  const escapedPrompt = prompt.replace(/"/g, '\\"');
7983
7981
  const entry = `${cron} ${cwd}claude -p --dangerously-skip-permissions "${escapedPrompt}" # exe-schedule:${id}`;
7984
- execSync10(
7982
+ execSync9(
7985
7983
  `(crontab -l 2>/dev/null; echo ${JSON.stringify(entry)}) | crontab -`,
7986
7984
  { timeout: 5e3, stdio: "ignore" }
7987
7985
  );
@@ -7990,7 +7988,7 @@ function addToCrontab(id, cron, prompt, projectDir) {
7990
7988
  }
7991
7989
  function removeFromCrontab(id) {
7992
7990
  try {
7993
- execSync10(
7991
+ execSync9(
7994
7992
  `crontab -l 2>/dev/null | grep -v "exe-schedule:${id}" | crontab -`,
7995
7993
  { timeout: 5e3, stdio: "ignore" }
7996
7994
  );
@@ -8009,8 +8007,8 @@ var init_schedules = __esm({
8009
8007
  init_employees();
8010
8008
  import path22 from "path";
8011
8009
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
8012
- import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync8, unlinkSync as unlinkSync11 } from "fs";
8013
- import os10 from "os";
8010
+ import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync7, unlinkSync as unlinkSync11 } from "fs";
8011
+ import os9 from "os";
8014
8012
 
8015
8013
  // src/lib/employee-templates.ts
8016
8014
  init_global_procedures();
@@ -8486,24 +8484,24 @@ init_notifications();
8486
8484
 
8487
8485
  // src/adapters/claude/active-agent.ts
8488
8486
  init_config();
8489
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync7, readdirSync as readdirSync4 } from "fs";
8487
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8, unlinkSync as unlinkSync7, readdirSync as readdirSync4 } from "fs";
8490
8488
  import { execSync as execSync8 } from "child_process";
8491
- import path17 from "path";
8489
+ import path18 from "path";
8492
8490
 
8493
8491
  // src/adapters/claude/session-key.ts
8494
8492
  init_session_key();
8495
8493
 
8496
8494
  // src/adapters/claude/active-agent.ts
8497
8495
  init_employees();
8498
- var CACHE_DIR = path17.join(EXE_AI_DIR, "session-cache");
8496
+ var CACHE_DIR = path18.join(EXE_AI_DIR, "session-cache");
8499
8497
  var STALE_MS = 24 * 60 * 60 * 1e3;
8500
8498
  function getMarkerPath() {
8501
- return path17.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
8499
+ return path18.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
8502
8500
  }
8503
8501
  function writeActiveAgent(agentId, agentRole) {
8504
8502
  try {
8505
- mkdirSync7(CACHE_DIR, { recursive: true });
8506
- writeFileSync7(
8503
+ mkdirSync8(CACHE_DIR, { recursive: true });
8504
+ writeFileSync8(
8507
8505
  getMarkerPath(),
8508
8506
  JSON.stringify({ agentId, agentRole, startedAt: (/* @__PURE__ */ new Date()).toISOString() })
8509
8507
  );
@@ -8513,11 +8511,11 @@ function writeActiveAgent(agentId, agentRole) {
8513
8511
  function cleanupSessionMarkers() {
8514
8512
  const key = getSessionKey();
8515
8513
  try {
8516
- unlinkSync7(path17.join(CACHE_DIR, `active-agent-${key}.json`));
8514
+ unlinkSync7(path18.join(CACHE_DIR, `active-agent-${key}.json`));
8517
8515
  } catch {
8518
8516
  }
8519
8517
  try {
8520
- unlinkSync7(path17.join(CACHE_DIR, "active-agent-undefined.json"));
8518
+ unlinkSync7(path18.join(CACHE_DIR, "active-agent-undefined.json"));
8521
8519
  } catch {
8522
8520
  }
8523
8521
  }
@@ -8599,18 +8597,18 @@ async function boot(options) {
8599
8597
  } catch {
8600
8598
  }
8601
8599
  try {
8602
- const { readdirSync: readdirSync9, readFileSync: readFs } = await import("fs");
8600
+ const { readdirSync: readdirSync8, readFileSync: readFs } = await import("fs");
8603
8601
  const { STATUS_RE: STATUS_RE2, PRIORITY_RE: PRIORITY_RE2, TITLE_RE: TITLE_RE2 } = await Promise.resolve().then(() => (init_task_scanner(), task_scanner_exports));
8604
8602
  const { getProjectName: getProjectName2 } = await Promise.resolve().then(() => (init_project_name(), project_name_exports));
8605
8603
  const exeDir = "exe";
8606
- const entries = readdirSync9(exeDir, { withFileTypes: true });
8604
+ const entries = readdirSync8(exeDir, { withFileTypes: true });
8607
8605
  const employeeDirs = entries.filter((e) => e.isDirectory() && !["output", "research"].includes(e.name));
8608
8606
  for (const dir of employeeDirs) {
8609
8607
  const employee = dir.name;
8610
8608
  const taskDir = path22.join(exeDir, employee);
8611
8609
  let files;
8612
8610
  try {
8613
- files = readdirSync9(taskDir).filter((f) => f.endsWith(".md"));
8611
+ files = readdirSync8(taskDir).filter((f) => f.endsWith(".md"));
8614
8612
  } catch {
8615
8613
  continue;
8616
8614
  }
@@ -8706,7 +8704,7 @@ async function boot(options) {
8706
8704
  for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
8707
8705
  const reviewDir = path22.join(process.cwd(), "exe", reviewDirName);
8708
8706
  if (existsSync18(reviewDir)) {
8709
- for (const f of readdirSync8(reviewDir)) {
8707
+ for (const f of readdirSync7(reviewDir)) {
8710
8708
  if (f.startsWith("review-") && f.endsWith(".md")) {
8711
8709
  try {
8712
8710
  unlinkSync11(path22.join(reviewDir, f));
@@ -8759,8 +8757,8 @@ async function boot(options) {
8759
8757
  if (existsSync18(filePath)) {
8760
8758
  let content = readFileSync15(filePath, "utf8");
8761
8759
  content = content.replace(/\*\*Status:\*\* needs_review/, "**Status:** done");
8762
- const { writeFileSync: writeFileSync11 } = await import("fs");
8763
- writeFileSync11(filePath, content);
8760
+ const { writeFileSync: writeFileSync12 } = await import("fs");
8761
+ writeFileSync12(filePath, content);
8764
8762
  }
8765
8763
  } catch {
8766
8764
  }
@@ -9205,8 +9203,8 @@ async function boot(options) {
9205
9203
  updatedAt: String(row.updated_at)
9206
9204
  }));
9207
9205
  try {
9208
- const { execSync: execSync11 } = await import("child_process");
9209
- const gitLog = execSync11(
9206
+ const { execSync: execSync10 } = await import("child_process");
9207
+ const gitLog = execSync10(
9210
9208
  "git log --oneline -10 --grep='Co-Authored-By: Claude' --grep='task(' --all-match 2>/dev/null || git log --oneline -5 --grep='Co-Authored-By: Claude' 2>/dev/null || git log --oneline -5 --grep='^task(' 2>/dev/null",
9211
9209
  {
9212
9210
  encoding: "utf8",
@@ -9230,7 +9228,7 @@ async function boot(options) {
9230
9228
  ]);
9231
9229
  try {
9232
9230
  const configPath = path22.join(
9233
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os10.homedir(), ".exe-os"),
9231
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os9.homedir(), ".exe-os"),
9234
9232
  "config.json"
9235
9233
  );
9236
9234
  if (existsSync18(configPath)) {
@@ -9429,7 +9427,7 @@ ${brief}`;
9429
9427
  console.log(brief);
9430
9428
  try {
9431
9429
  const configPath2 = path22.join(
9432
- process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os10.homedir(), ".exe-os"),
9430
+ process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path22.join(os9.homedir(), ".exe-os"),
9433
9431
  "config.json"
9434
9432
  );
9435
9433
  if (existsSync18(configPath2)) {
@@ -9493,11 +9491,11 @@ async function updateIdleKillSuspectStreak(client, killsToday, liveSessions, tod
9493
9491
  }
9494
9492
  function runSplash() {
9495
9493
  try {
9496
- const { execSync: execSync11 } = __require("child_process");
9494
+ const { execSync: execSync10 } = __require("child_process");
9497
9495
  const { loadConfigSync: loadConfigSync2 } = (init_config(), __toCommonJS(config_exports));
9498
9496
  const config = loadConfigSync2();
9499
9497
  if (!config.splashEffect) return;
9500
- execSync11(
9498
+ execSync10(
9501
9499
  'echo "EXE OS" | python3 -m terminaltexteffects decrypt --typing-speed 2 --ciphertext-colors 6B4C9A F5D76E --final-gradient-stops F5D76E F0EDE8 --final-gradient-direction vertical',
9502
9500
  { stdio: "inherit", timeout: 5e3 }
9503
9501
  );