@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.
Files changed (57) hide show
  1. package/dist/bin/cleanup-stale-review-tasks.js +57 -19
  2. package/dist/bin/cli.js +510 -340
  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 +50 -5
  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
@@ -2219,18 +2219,69 @@ var init_provider_table = __esm({
2219
2219
  }
2220
2220
  });
2221
2221
 
2222
- // src/lib/intercom-queue.ts
2223
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2222
+ // src/lib/runtime-table.ts
2223
+ var RUNTIME_TABLE, DEFAULT_RUNTIME;
2224
+ var init_runtime_table = __esm({
2225
+ "src/lib/runtime-table.ts"() {
2226
+ "use strict";
2227
+ RUNTIME_TABLE = {
2228
+ codex: {
2229
+ binary: "codex",
2230
+ launchMode: "exec",
2231
+ autoApproveFlag: "--full-auto",
2232
+ inlineFlag: "--no-alt-screen",
2233
+ apiKeyEnv: "OPENAI_API_KEY",
2234
+ defaultModel: "gpt-5.4"
2235
+ }
2236
+ };
2237
+ DEFAULT_RUNTIME = "claude";
2238
+ }
2239
+ });
2240
+
2241
+ // src/lib/agent-config.ts
2242
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
2224
2243
  import path7 from "path";
2244
+ function loadAgentConfig() {
2245
+ if (!existsSync7(AGENT_CONFIG_PATH)) return {};
2246
+ try {
2247
+ return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf-8"));
2248
+ } catch {
2249
+ return {};
2250
+ }
2251
+ }
2252
+ function getAgentRuntime(agentId) {
2253
+ const config = loadAgentConfig();
2254
+ const entry = config[agentId];
2255
+ if (entry) return entry;
2256
+ return { runtime: DEFAULT_RUNTIME, model: DEFAULT_MODELS[DEFAULT_RUNTIME] };
2257
+ }
2258
+ var AGENT_CONFIG_PATH, DEFAULT_MODELS;
2259
+ var init_agent_config = __esm({
2260
+ "src/lib/agent-config.ts"() {
2261
+ "use strict";
2262
+ init_config();
2263
+ init_runtime_table();
2264
+ AGENT_CONFIG_PATH = path7.join(EXE_AI_DIR, "agent-config.json");
2265
+ DEFAULT_MODELS = {
2266
+ claude: "claude-opus-4",
2267
+ codex: RUNTIME_TABLE.codex?.defaultModel ?? "gpt-5.4",
2268
+ opencode: "minimax-m2.7"
2269
+ };
2270
+ }
2271
+ });
2272
+
2273
+ // src/lib/intercom-queue.ts
2274
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2275
+ import path8 from "path";
2225
2276
  import os6 from "os";
2226
2277
  function ensureDir() {
2227
- const dir = path7.dirname(QUEUE_PATH);
2228
- if (!existsSync7(dir)) mkdirSync3(dir, { recursive: true });
2278
+ const dir = path8.dirname(QUEUE_PATH);
2279
+ if (!existsSync8(dir)) mkdirSync4(dir, { recursive: true });
2229
2280
  }
2230
2281
  function readQueue() {
2231
2282
  try {
2232
- if (!existsSync7(QUEUE_PATH)) return [];
2233
- return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
2283
+ if (!existsSync8(QUEUE_PATH)) return [];
2284
+ return JSON.parse(readFileSync6(QUEUE_PATH, "utf8"));
2234
2285
  } catch {
2235
2286
  return [];
2236
2287
  }
@@ -2238,7 +2289,7 @@ function readQueue() {
2238
2289
  function writeQueue(queue) {
2239
2290
  ensureDir();
2240
2291
  const tmp = `${QUEUE_PATH}.tmp`;
2241
- writeFileSync3(tmp, JSON.stringify(queue, null, 2));
2292
+ writeFileSync4(tmp, JSON.stringify(queue, null, 2));
2242
2293
  renameSync3(tmp, QUEUE_PATH);
2243
2294
  }
2244
2295
  function queueIntercom(targetSession, reason) {
@@ -2262,25 +2313,25 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
2262
2313
  var init_intercom_queue = __esm({
2263
2314
  "src/lib/intercom-queue.ts"() {
2264
2315
  "use strict";
2265
- QUEUE_PATH = path7.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2316
+ QUEUE_PATH = path8.join(os6.homedir(), ".exe-os", "intercom-queue.json");
2266
2317
  TTL_MS = 60 * 60 * 1e3;
2267
- INTERCOM_LOG = path7.join(os6.homedir(), ".exe-os", "intercom.log");
2318
+ INTERCOM_LOG = path8.join(os6.homedir(), ".exe-os", "intercom.log");
2268
2319
  }
2269
2320
  });
2270
2321
 
2271
2322
  // src/lib/license.ts
2272
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
2323
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
2273
2324
  import { randomUUID as randomUUID2 } from "crypto";
2274
- import path8 from "path";
2325
+ import path9 from "path";
2275
2326
  import { jwtVerify, importSPKI } from "jose";
2276
2327
  var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
2277
2328
  var init_license = __esm({
2278
2329
  "src/lib/license.ts"() {
2279
2330
  "use strict";
2280
2331
  init_config();
2281
- LICENSE_PATH = path8.join(EXE_AI_DIR, "license.key");
2282
- CACHE_PATH = path8.join(EXE_AI_DIR, "license-cache.json");
2283
- DEVICE_ID_PATH = path8.join(EXE_AI_DIR, "device-id");
2332
+ LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
2333
+ CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
2334
+ DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
2284
2335
  PLAN_LIMITS = {
2285
2336
  free: { devices: 1, employees: 1, memories: 5e3 },
2286
2337
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2292,12 +2343,12 @@ var init_license = __esm({
2292
2343
  });
2293
2344
 
2294
2345
  // src/lib/plan-limits.ts
2295
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
2296
- import path9 from "path";
2346
+ import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
2347
+ import path10 from "path";
2297
2348
  function getLicenseSync() {
2298
2349
  try {
2299
- if (!existsSync9(CACHE_PATH2)) return freeLicense();
2300
- const raw = JSON.parse(readFileSync7(CACHE_PATH2, "utf8"));
2350
+ if (!existsSync10(CACHE_PATH2)) return freeLicense();
2351
+ const raw = JSON.parse(readFileSync8(CACHE_PATH2, "utf8"));
2301
2352
  if (!raw.token || typeof raw.token !== "string") return freeLicense();
2302
2353
  const parts = raw.token.split(".");
2303
2354
  if (parts.length !== 3) return freeLicense();
@@ -2335,8 +2386,8 @@ function assertEmployeeLimitSync(rosterPath) {
2335
2386
  const filePath = rosterPath ?? EMPLOYEES_PATH;
2336
2387
  let count = 0;
2337
2388
  try {
2338
- if (existsSync9(filePath)) {
2339
- const raw = readFileSync7(filePath, "utf8");
2389
+ if (existsSync10(filePath)) {
2390
+ const raw = readFileSync8(filePath, "utf8");
2340
2391
  const employees = JSON.parse(raw);
2341
2392
  count = Array.isArray(employees) ? employees.length : 0;
2342
2393
  }
@@ -2365,7 +2416,7 @@ var init_plan_limits = __esm({
2365
2416
  this.name = "PlanLimitError";
2366
2417
  }
2367
2418
  };
2368
- CACHE_PATH2 = path9.join(EXE_AI_DIR, "license-cache.json");
2419
+ CACHE_PATH2 = path10.join(EXE_AI_DIR, "license-cache.json");
2369
2420
  }
2370
2421
  });
2371
2422
 
@@ -2713,13 +2764,13 @@ __export(tmux_routing_exports, {
2713
2764
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
2714
2765
  });
2715
2766
  import { execFileSync as execFileSync2, execSync as execSync4 } from "child_process";
2716
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
2717
- import path10 from "path";
2767
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync11, appendFileSync } from "fs";
2768
+ import path11 from "path";
2718
2769
  import os7 from "os";
2719
2770
  import { fileURLToPath } from "url";
2720
2771
  import { unlinkSync as unlinkSync3 } from "fs";
2721
2772
  function spawnLockPath(sessionName) {
2722
- return path10.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2773
+ return path11.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
2723
2774
  }
2724
2775
  function isProcessAlive(pid) {
2725
2776
  try {
@@ -2730,13 +2781,13 @@ function isProcessAlive(pid) {
2730
2781
  }
2731
2782
  }
2732
2783
  function acquireSpawnLock(sessionName) {
2733
- if (!existsSync10(SPAWN_LOCK_DIR)) {
2734
- mkdirSync5(SPAWN_LOCK_DIR, { recursive: true });
2784
+ if (!existsSync11(SPAWN_LOCK_DIR)) {
2785
+ mkdirSync6(SPAWN_LOCK_DIR, { recursive: true });
2735
2786
  }
2736
2787
  const lockFile = spawnLockPath(sessionName);
2737
- if (existsSync10(lockFile)) {
2788
+ if (existsSync11(lockFile)) {
2738
2789
  try {
2739
- const lock = JSON.parse(readFileSync8(lockFile, "utf8"));
2790
+ const lock = JSON.parse(readFileSync9(lockFile, "utf8"));
2740
2791
  const age = Date.now() - lock.timestamp;
2741
2792
  if (isProcessAlive(lock.pid) && age < 6e4) {
2742
2793
  return false;
@@ -2744,7 +2795,7 @@ function acquireSpawnLock(sessionName) {
2744
2795
  } catch {
2745
2796
  }
2746
2797
  }
2747
- writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2798
+ writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
2748
2799
  return true;
2749
2800
  }
2750
2801
  function releaseSpawnLock(sessionName) {
@@ -2756,13 +2807,13 @@ function releaseSpawnLock(sessionName) {
2756
2807
  function resolveBehaviorsExporterScript() {
2757
2808
  try {
2758
2809
  const thisFile = fileURLToPath(import.meta.url);
2759
- const scriptPath = path10.join(
2760
- path10.dirname(thisFile),
2810
+ const scriptPath = path11.join(
2811
+ path11.dirname(thisFile),
2761
2812
  "..",
2762
2813
  "bin",
2763
2814
  "exe-export-behaviors.js"
2764
2815
  );
2765
- return existsSync10(scriptPath) ? scriptPath : null;
2816
+ return existsSync11(scriptPath) ? scriptPath : null;
2766
2817
  } catch {
2767
2818
  return null;
2768
2819
  }
@@ -2828,12 +2879,12 @@ function extractRootExe(name) {
2828
2879
  return parts.length > 0 ? parts[parts.length - 1] : null;
2829
2880
  }
2830
2881
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
2831
- if (!existsSync10(SESSION_CACHE)) {
2832
- mkdirSync5(SESSION_CACHE, { recursive: true });
2882
+ if (!existsSync11(SESSION_CACHE)) {
2883
+ mkdirSync6(SESSION_CACHE, { recursive: true });
2833
2884
  }
2834
2885
  const rootExe = extractRootExe(parentExe) ?? parentExe;
2835
- const filePath = path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
2836
- writeFileSync5(filePath, JSON.stringify({
2886
+ const filePath = path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
2887
+ writeFileSync6(filePath, JSON.stringify({
2837
2888
  parentExe: rootExe,
2838
2889
  dispatchedBy: dispatchedBy || rootExe,
2839
2890
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2841,7 +2892,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
2841
2892
  }
2842
2893
  function getParentExe(sessionKey) {
2843
2894
  try {
2844
- const data = JSON.parse(readFileSync8(path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2895
+ const data = JSON.parse(readFileSync9(path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`), "utf8"));
2845
2896
  return data.parentExe || null;
2846
2897
  } catch {
2847
2898
  return null;
@@ -2849,8 +2900,8 @@ function getParentExe(sessionKey) {
2849
2900
  }
2850
2901
  function getDispatchedBy(sessionKey) {
2851
2902
  try {
2852
- const data = JSON.parse(readFileSync8(
2853
- path10.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2903
+ const data = JSON.parse(readFileSync9(
2904
+ path11.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`),
2854
2905
  "utf8"
2855
2906
  ));
2856
2907
  return data.dispatchedBy ?? data.parentExe ?? null;
@@ -2911,32 +2962,50 @@ async function verifyPaneAtCapacity(sessionName) {
2911
2962
  }
2912
2963
  function readDebounceState() {
2913
2964
  try {
2914
- if (!existsSync10(DEBOUNCE_FILE)) return {};
2915
- return JSON.parse(readFileSync8(DEBOUNCE_FILE, "utf8"));
2965
+ if (!existsSync11(DEBOUNCE_FILE)) return {};
2966
+ const raw = JSON.parse(readFileSync9(DEBOUNCE_FILE, "utf8"));
2967
+ const state = {};
2968
+ for (const [key, val] of Object.entries(raw)) {
2969
+ if (typeof val === "number") {
2970
+ state[key] = { lastSent: val, pending: 0 };
2971
+ } else if (val && typeof val === "object" && "lastSent" in val) {
2972
+ state[key] = val;
2973
+ }
2974
+ }
2975
+ return state;
2916
2976
  } catch {
2917
2977
  return {};
2918
2978
  }
2919
2979
  }
2920
2980
  function writeDebounceState(state) {
2921
2981
  try {
2922
- if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
2923
- writeFileSync5(DEBOUNCE_FILE, JSON.stringify(state));
2982
+ if (!existsSync11(SESSION_CACHE)) mkdirSync6(SESSION_CACHE, { recursive: true });
2983
+ writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
2924
2984
  } catch {
2925
2985
  }
2926
2986
  }
2927
2987
  function isDebounced(targetSession) {
2928
2988
  const state = readDebounceState();
2929
- const lastSent = state[targetSession] ?? 0;
2930
- return Date.now() - lastSent < INTERCOM_DEBOUNCE_MS;
2989
+ const entry = state[targetSession];
2990
+ const lastSent = entry?.lastSent ?? 0;
2991
+ if (Date.now() - lastSent < INTERCOM_DEBOUNCE_MS) {
2992
+ if (!state[targetSession]) state[targetSession] = { lastSent, pending: 0 };
2993
+ state[targetSession].pending++;
2994
+ writeDebounceState(state);
2995
+ return true;
2996
+ }
2997
+ return false;
2931
2998
  }
2932
2999
  function recordDebounce(targetSession) {
2933
3000
  const state = readDebounceState();
2934
- state[targetSession] = Date.now();
3001
+ const batched = state[targetSession]?.pending ?? 0;
3002
+ state[targetSession] = { lastSent: Date.now(), pending: 0 };
2935
3003
  const cutoff = Date.now() - DEBOUNCE_CLEANUP_AGE_MS;
2936
3004
  for (const key of Object.keys(state)) {
2937
- if ((state[key] ?? 0) < cutoff) delete state[key];
3005
+ if ((state[key]?.lastSent ?? 0) < cutoff) delete state[key];
2938
3006
  }
2939
3007
  writeDebounceState(state);
3008
+ return batched;
2940
3009
  }
2941
3010
  function logIntercom(msg) {
2942
3011
  const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${msg}
@@ -2981,7 +3050,7 @@ function sendIntercom(targetSession) {
2981
3050
  return "skipped_exe";
2982
3051
  }
2983
3052
  if (isDebounced(targetSession)) {
2984
- logIntercom(`DEBOUNCE \u2192 ${targetSession} (cross-process file debounce)`);
3053
+ logIntercom(`DEBOUNCE \u2192 ${targetSession} (nudge batched, task safe in DB)`);
2985
3054
  return "debounced";
2986
3055
  }
2987
3056
  try {
@@ -2993,14 +3062,14 @@ function sendIntercom(targetSession) {
2993
3062
  const sessionState = getSessionState(targetSession);
2994
3063
  if (sessionState === "no_claude") {
2995
3064
  queueIntercom(targetSession, "claude not running in session");
2996
- recordDebounce(targetSession);
2997
- logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
3065
+ const batched2 = recordDebounce(targetSession);
3066
+ logIntercom(`QUEUED \u2192 ${targetSession} (no claude process)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
2998
3067
  return "queued";
2999
3068
  }
3000
3069
  if (sessionState === "thinking" || sessionState === "tool") {
3001
3070
  queueIntercom(targetSession, "session busy at send time");
3002
- recordDebounce(targetSession);
3003
- logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
3071
+ const batched2 = recordDebounce(targetSession);
3072
+ logIntercom(`QUEUED \u2192 ${targetSession} (session busy)${batched2 > 0 ? ` [${batched2} batched]` : ""}`);
3004
3073
  return "queued";
3005
3074
  }
3006
3075
  if (transport.isPaneInCopyMode(targetSession)) {
@@ -3008,8 +3077,8 @@ function sendIntercom(targetSession) {
3008
3077
  transport.sendKeys(targetSession, "q");
3009
3078
  }
3010
3079
  transport.sendKeys(targetSession, "/exe-intercom");
3011
- recordDebounce(targetSession);
3012
- logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
3080
+ const batched = recordDebounce(targetSession);
3081
+ logIntercom(`DELIVERED \u2192 ${targetSession}${batched > 0 ? ` [${batched} nudges batched during debounce]` : ""} (fire-and-forget)`);
3013
3082
  return "delivered";
3014
3083
  } catch {
3015
3084
  logIntercom(`FAIL \u2192 ${targetSession}`);
@@ -3111,26 +3180,26 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3111
3180
  const transport = getTransport();
3112
3181
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3113
3182
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3114
- const logDir = path10.join(os7.homedir(), ".exe-os", "session-logs");
3115
- const logFile = path10.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3116
- if (!existsSync10(logDir)) {
3117
- mkdirSync5(logDir, { recursive: true });
3183
+ const logDir = path11.join(os7.homedir(), ".exe-os", "session-logs");
3184
+ const logFile = path11.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3185
+ if (!existsSync11(logDir)) {
3186
+ mkdirSync6(logDir, { recursive: true });
3118
3187
  }
3119
3188
  transport.kill(sessionName);
3120
3189
  let cleanupSuffix = "";
3121
3190
  try {
3122
3191
  const thisFile = fileURLToPath(import.meta.url);
3123
- const cleanupScript = path10.join(path10.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3124
- if (existsSync10(cleanupScript)) {
3192
+ const cleanupScript = path11.join(path11.dirname(thisFile), "..", "bin", "exe-session-cleanup.js");
3193
+ if (existsSync11(cleanupScript)) {
3125
3194
  cleanupSuffix = `; ${process.execPath} "${cleanupScript}" "${employeeName}" "${exeSession}"`;
3126
3195
  }
3127
3196
  } catch {
3128
3197
  }
3129
3198
  try {
3130
- const claudeJsonPath = path10.join(os7.homedir(), ".claude.json");
3199
+ const claudeJsonPath = path11.join(os7.homedir(), ".claude.json");
3131
3200
  let claudeJson = {};
3132
3201
  try {
3133
- claudeJson = JSON.parse(readFileSync8(claudeJsonPath, "utf8"));
3202
+ claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
3134
3203
  } catch {
3135
3204
  }
3136
3205
  if (!claudeJson.projects) claudeJson.projects = {};
@@ -3138,17 +3207,17 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3138
3207
  const trustDir = opts?.cwd ?? projectDir;
3139
3208
  if (!projects[trustDir]) projects[trustDir] = {};
3140
3209
  projects[trustDir].hasTrustDialogAccepted = true;
3141
- writeFileSync5(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
3210
+ writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
3142
3211
  } catch {
3143
3212
  }
3144
3213
  try {
3145
- const settingsDir = path10.join(os7.homedir(), ".claude", "projects");
3214
+ const settingsDir = path11.join(os7.homedir(), ".claude", "projects");
3146
3215
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
3147
- const projSettingsDir = path10.join(settingsDir, normalizedKey);
3148
- const settingsPath = path10.join(projSettingsDir, "settings.json");
3216
+ const projSettingsDir = path11.join(settingsDir, normalizedKey);
3217
+ const settingsPath = path11.join(projSettingsDir, "settings.json");
3149
3218
  let settings = {};
3150
3219
  try {
3151
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
3220
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
3152
3221
  } catch {
3153
3222
  }
3154
3223
  const perms = settings.permissions ?? {};
@@ -3176,20 +3245,23 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3176
3245
  if (changed) {
3177
3246
  perms.allow = allow;
3178
3247
  settings.permissions = perms;
3179
- mkdirSync5(projSettingsDir, { recursive: true });
3180
- writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3248
+ mkdirSync6(projSettingsDir, { recursive: true });
3249
+ writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
3181
3250
  }
3182
3251
  } catch {
3183
3252
  }
3184
3253
  const spawnCwd = opts?.cwd ?? projectDir;
3185
3254
  const useExeAgent = !!(opts?.model && opts?.provider);
3186
- const ccProvider = useExeAgent ? DEFAULT_PROVIDER : detectActiveProvider();
3255
+ const agentRtConfig = getAgentRuntime(employeeName);
3256
+ const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
3257
+ const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
3258
+ const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
3187
3259
  const useBinSymlink = ccProvider !== DEFAULT_PROVIDER;
3188
3260
  let identityFlag = "";
3189
3261
  let behaviorsFlag = "";
3190
3262
  let legacyFallbackWarned = false;
3191
3263
  if (!useExeAgent && !useBinSymlink) {
3192
- const identityPath = path10.join(
3264
+ const identityPath = path11.join(
3193
3265
  os7.homedir(),
3194
3266
  ".exe-os",
3195
3267
  "identity",
@@ -3199,13 +3271,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3199
3271
  const hasAgentFlag = claudeSupportsAgentFlag();
3200
3272
  if (hasAgentFlag) {
3201
3273
  identityFlag = ` --agent ${employeeName}`;
3202
- } else if (existsSync10(identityPath)) {
3274
+ } else if (existsSync11(identityPath)) {
3203
3275
  identityFlag = ` --append-system-prompt-file ${identityPath}`;
3204
3276
  legacyFallbackWarned = true;
3205
3277
  }
3206
3278
  const behaviorsFile = exportBehaviorsSync(
3207
3279
  employeeName,
3208
- path10.basename(spawnCwd),
3280
+ path11.basename(spawnCwd),
3209
3281
  sessionName
3210
3282
  );
3211
3283
  if (behaviorsFile) {
@@ -3220,16 +3292,16 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3220
3292
  }
3221
3293
  let sessionContextFlag = "";
3222
3294
  try {
3223
- const ctxDir = path10.join(os7.homedir(), ".exe-os", "session-cache");
3224
- mkdirSync5(ctxDir, { recursive: true });
3225
- const ctxFile = path10.join(ctxDir, `session-context-${sessionName}.md`);
3295
+ const ctxDir = path11.join(os7.homedir(), ".exe-os", "session-cache");
3296
+ mkdirSync6(ctxDir, { recursive: true });
3297
+ const ctxFile = path11.join(ctxDir, `session-context-${sessionName}.md`);
3226
3298
  const ctxContent = [
3227
3299
  `## Session Context`,
3228
3300
  `You are running in tmux session: ${sessionName}.`,
3229
3301
  `Your parent coordinator session is ${exeSession}.`,
3230
3302
  `Your employees (if any) use the -${exeSession} suffix.`
3231
3303
  ].join("\n");
3232
- writeFileSync5(ctxFile, ctxContent);
3304
+ writeFileSync6(ctxFile, ctxContent);
3233
3305
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
3234
3306
  } catch {
3235
3307
  }
@@ -3243,9 +3315,48 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3243
3315
  }
3244
3316
  }
3245
3317
  }
3318
+ if (useCodex) {
3319
+ const codexCfg = RUNTIME_TABLE.codex;
3320
+ if (codexCfg?.apiKeyEnv) {
3321
+ const keyVal = process.env[codexCfg.apiKeyEnv];
3322
+ if (keyVal) {
3323
+ envPrefix = `${envPrefix} ${codexCfg.apiKeyEnv}=${keyVal}`;
3324
+ }
3325
+ }
3326
+ envPrefix = `${envPrefix} EXE_AGENT_MODEL=${agentRtConfig.model}`;
3327
+ }
3328
+ if (useOpencode) {
3329
+ const ocCfg = PROVIDER_TABLE.opencode;
3330
+ if (ocCfg?.apiKeyEnv) {
3331
+ const keyVal = process.env[ocCfg.apiKeyEnv];
3332
+ if (keyVal) {
3333
+ envPrefix = `${envPrefix} ${ocCfg.apiKeyEnv}=${keyVal}`;
3334
+ }
3335
+ }
3336
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
3337
+ }
3338
+ if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
3339
+ const defaultClaudeModel = DEFAULT_MODELS.claude;
3340
+ if (agentRtConfig.runtime === "claude" && agentRtConfig.model !== defaultClaudeModel) {
3341
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
3342
+ }
3343
+ }
3246
3344
  let spawnCommand;
3247
3345
  if (useExeAgent) {
3248
3346
  spawnCommand = `${envPrefix} exe-agent --employee ${employeeName} --model ${opts.model} --provider ${opts.provider}${cleanupSuffix}`;
3347
+ } else if (useCodex) {
3348
+ process.stderr.write(
3349
+ `[tmux-routing] agent-config: ${employeeName} \u2192 codex (${agentRtConfig.model})
3350
+ `
3351
+ );
3352
+ spawnCommand = `${envPrefix} exe-start-codex --agent ${employeeName}${cleanupSuffix}`;
3353
+ } else if (useOpencode) {
3354
+ const binName = `${employeeName}-opencode`;
3355
+ process.stderr.write(
3356
+ `[tmux-routing] agent-config: ${employeeName} \u2192 opencode (${agentRtConfig.model})
3357
+ `
3358
+ );
3359
+ spawnCommand = `${envPrefix} ${binName}${cleanupSuffix}`;
3249
3360
  } else if (useBinSymlink) {
3250
3361
  const binName = `${employeeName}-${ccProvider}`;
3251
3362
  process.stderr.write(
@@ -3267,11 +3378,13 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3267
3378
  transport.pipeLog(sessionName, logFile);
3268
3379
  try {
3269
3380
  const mySession = getMySession();
3270
- const dispatchInfo = path10.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3271
- writeFileSync5(dispatchInfo, JSON.stringify({
3381
+ const dispatchInfo = path11.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
3382
+ writeFileSync6(dispatchInfo, JSON.stringify({
3272
3383
  dispatchedBy: mySession,
3273
3384
  rootExe: exeSession,
3274
- provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
3385
+ provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : useCodex ? "openai" : useOpencode ? "opencode" : "anthropic",
3386
+ runtime: useCodex ? "codex" : useOpencode ? "opencode" : useExeAgent ? "exe-agent" : "claude",
3387
+ model: useCodex ? agentRtConfig.model : useOpencode ? agentRtConfig.model : void 0,
3275
3388
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3276
3389
  }));
3277
3390
  } catch {
@@ -3289,6 +3402,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3289
3402
  booted = true;
3290
3403
  break;
3291
3404
  }
3405
+ } else if (useCodex) {
3406
+ if (pane.includes("codex") || pane.includes("Codex") || pane.includes("exe-start-codex")) {
3407
+ booted = true;
3408
+ break;
3409
+ }
3292
3410
  } else {
3293
3411
  if (pane.includes("Claude Code") || pane.includes("\u276F")) {
3294
3412
  booted = true;
@@ -3300,9 +3418,10 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3300
3418
  }
3301
3419
  if (!booted) {
3302
3420
  releaseSpawnLock(sessionName);
3303
- return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
3421
+ const runtimeLabel = useExeAgent ? "exe-agent" : useCodex ? "codex" : "claude";
3422
+ return { sessionName, error: `${runtimeLabel} did not boot within 15s` };
3304
3423
  }
3305
- if (!useExeAgent) {
3424
+ if (!useExeAgent && !useCodex) {
3306
3425
  try {
3307
3426
  transport.sendKeys(sessionName, `/exe-call ${employeeName}`);
3308
3427
  } catch {
@@ -3329,17 +3448,19 @@ var init_tmux_routing = __esm({
3329
3448
  init_cc_agent_support();
3330
3449
  init_mcp_prefix();
3331
3450
  init_provider_table();
3451
+ init_agent_config();
3452
+ init_runtime_table();
3332
3453
  init_intercom_queue();
3333
3454
  init_plan_limits();
3334
3455
  init_employees();
3335
- SPAWN_LOCK_DIR = path10.join(os7.homedir(), ".exe-os", "spawn-locks");
3336
- SESSION_CACHE = path10.join(os7.homedir(), ".exe-os", "session-cache");
3456
+ SPAWN_LOCK_DIR = path11.join(os7.homedir(), ".exe-os", "spawn-locks");
3457
+ SESSION_CACHE = path11.join(os7.homedir(), ".exe-os", "session-cache");
3337
3458
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
3338
3459
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
3339
3460
  VERIFY_PANE_LINES = 200;
3340
3461
  INTERCOM_DEBOUNCE_MS = 3e4;
3341
- INTERCOM_LOG2 = path10.join(os7.homedir(), ".exe-os", "intercom.log");
3342
- DEBOUNCE_FILE = path10.join(SESSION_CACHE, "intercom-debounce.json");
3462
+ INTERCOM_LOG2 = path11.join(os7.homedir(), ".exe-os", "intercom.log");
3463
+ DEBOUNCE_FILE = path11.join(SESSION_CACHE, "intercom-debounce.json");
3343
3464
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
3344
3465
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
3345
3466
  }
@@ -3371,11 +3492,11 @@ var init_task_scope = __esm({
3371
3492
 
3372
3493
  // src/lib/tasks-crud.ts
3373
3494
  import crypto3 from "crypto";
3374
- import path11 from "path";
3495
+ import path12 from "path";
3375
3496
  import os8 from "os";
3376
3497
  import { execSync as execSync5 } from "child_process";
3377
3498
  import { mkdir as mkdir4, writeFile as writeFile4, appendFile } from "fs/promises";
3378
- import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
3499
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
3379
3500
  async function writeCheckpoint(input) {
3380
3501
  const client = getClient();
3381
3502
  const row = await resolveTask(client, input.taskId);
@@ -3550,8 +3671,8 @@ ${laneWarning}` : laneWarning;
3550
3671
  }
3551
3672
  if (input.baseDir) {
3552
3673
  try {
3553
- await mkdir4(path11.join(input.baseDir, "exe", "output"), { recursive: true });
3554
- await mkdir4(path11.join(input.baseDir, "exe", "research"), { recursive: true });
3674
+ await mkdir4(path12.join(input.baseDir, "exe", "output"), { recursive: true });
3675
+ await mkdir4(path12.join(input.baseDir, "exe", "research"), { recursive: true });
3555
3676
  await ensureArchitectureDoc(input.baseDir, input.projectName);
3556
3677
  await ensureGitignoreExe(input.baseDir);
3557
3678
  } catch {
@@ -3587,10 +3708,10 @@ ${laneWarning}` : laneWarning;
3587
3708
  });
3588
3709
  if (input.baseDir) {
3589
3710
  try {
3590
- const EXE_OS_DIR = path11.join(os8.homedir(), ".exe-os");
3591
- const mdPath = path11.join(EXE_OS_DIR, taskFile);
3592
- const mdDir = path11.dirname(mdPath);
3593
- if (!existsSync11(mdDir)) await mkdir4(mdDir, { recursive: true });
3711
+ const EXE_OS_DIR = path12.join(os8.homedir(), ".exe-os");
3712
+ const mdPath = path12.join(EXE_OS_DIR, taskFile);
3713
+ const mdDir = path12.dirname(mdPath);
3714
+ if (!existsSync12(mdDir)) await mkdir4(mdDir, { recursive: true });
3594
3715
  const reviewer = input.reviewer ?? input.assignedBy;
3595
3716
  const mdContent = `# ${input.title}
3596
3717
 
@@ -3615,7 +3736,11 @@ If you skip this, your reviewer will not know you're done and your work won't be
3615
3736
  Do NOT let a failed commit or any error prevent you from calling update_task(done).
3616
3737
  `;
3617
3738
  await writeFile4(mdPath, mdContent, "utf-8");
3618
- } catch {
3739
+ } catch (err) {
3740
+ process.stderr.write(
3741
+ `[create-task] WARNING: .md file write failed for ${taskFile}: ${err instanceof Error ? err.message : String(err)}
3742
+ `
3743
+ );
3619
3744
  }
3620
3745
  }
3621
3746
  return {
@@ -3875,9 +4000,9 @@ async function deleteTaskCore(taskId, _baseDir) {
3875
4000
  return { taskFile, assignedTo, assignedBy, taskSlug };
3876
4001
  }
3877
4002
  async function ensureArchitectureDoc(baseDir, projectName) {
3878
- const archPath = path11.join(baseDir, "exe", "ARCHITECTURE.md");
4003
+ const archPath = path12.join(baseDir, "exe", "ARCHITECTURE.md");
3879
4004
  try {
3880
- if (existsSync11(archPath)) return;
4005
+ if (existsSync12(archPath)) return;
3881
4006
  const template = [
3882
4007
  `# ${projectName} \u2014 System Architecture`,
3883
4008
  "",
@@ -3910,10 +4035,10 @@ async function ensureArchitectureDoc(baseDir, projectName) {
3910
4035
  }
3911
4036
  }
3912
4037
  async function ensureGitignoreExe(baseDir) {
3913
- const gitignorePath = path11.join(baseDir, ".gitignore");
4038
+ const gitignorePath = path12.join(baseDir, ".gitignore");
3914
4039
  try {
3915
- if (existsSync11(gitignorePath)) {
3916
- const content = readFileSync9(gitignorePath, "utf-8");
4040
+ if (existsSync12(gitignorePath)) {
4041
+ const content = readFileSync10(gitignorePath, "utf-8");
3917
4042
  if (/^\/?exe\/?$/m.test(content)) return;
3918
4043
  await appendFile(gitignorePath, "\n# Employee task assignments (private)\n/exe/\n");
3919
4044
  } else {
@@ -3944,8 +4069,8 @@ var init_tasks_crud = __esm({
3944
4069
  });
3945
4070
 
3946
4071
  // src/lib/tasks-review.ts
3947
- import path12 from "path";
3948
- import { existsSync as existsSync12, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
4072
+ import path13 from "path";
4073
+ import { existsSync as existsSync13, readdirSync as readdirSync3, unlinkSync as unlinkSync4 } from "fs";
3949
4074
  async function countPendingReviews(sessionScope) {
3950
4075
  const client = getClient();
3951
4076
  if (sessionScope) {
@@ -4126,11 +4251,11 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
4126
4251
  );
4127
4252
  }
4128
4253
  try {
4129
- const cacheDir = path12.join(EXE_AI_DIR, "session-cache");
4130
- if (existsSync12(cacheDir)) {
4254
+ const cacheDir = path13.join(EXE_AI_DIR, "session-cache");
4255
+ if (existsSync13(cacheDir)) {
4131
4256
  for (const f of readdirSync3(cacheDir)) {
4132
4257
  if (f.startsWith("review-notified-")) {
4133
- unlinkSync4(path12.join(cacheDir, f));
4258
+ unlinkSync4(path13.join(cacheDir, f));
4134
4259
  }
4135
4260
  }
4136
4261
  }
@@ -4151,7 +4276,7 @@ var init_tasks_review = __esm({
4151
4276
  });
4152
4277
 
4153
4278
  // src/lib/tasks-chain.ts
4154
- import path13 from "path";
4279
+ import path14 from "path";
4155
4280
  import { readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
4156
4281
  async function cascadeUnblock(taskId, baseDir, now) {
4157
4282
  const client = getClient();
@@ -4168,7 +4293,7 @@ async function cascadeUnblock(taskId, baseDir, now) {
4168
4293
  });
4169
4294
  for (const ur of unblockedRows.rows) {
4170
4295
  try {
4171
- const ubFile = path13.join(baseDir, String(ur.task_file));
4296
+ const ubFile = path14.join(baseDir, String(ur.task_file));
4172
4297
  let ubContent = await readFile4(ubFile, "utf-8");
4173
4298
  ubContent = ubContent.replace(/\*\*Status:\*\* blocked/, "**Status:** open");
4174
4299
  ubContent = ubContent.replace(/\n\*\*Blocked by:\*\*.*\n/, "\n");
@@ -4237,7 +4362,7 @@ var init_tasks_chain = __esm({
4237
4362
 
4238
4363
  // src/lib/project-name.ts
4239
4364
  import { execSync as execSync6 } from "child_process";
4240
- import path14 from "path";
4365
+ import path15 from "path";
4241
4366
  function getProjectName(cwd) {
4242
4367
  const dir = cwd ?? process.cwd();
4243
4368
  if (_cached2 && _cachedCwd === dir) return _cached2;
@@ -4250,7 +4375,7 @@ function getProjectName(cwd) {
4250
4375
  timeout: 2e3,
4251
4376
  stdio: ["pipe", "pipe", "pipe"]
4252
4377
  }).trim();
4253
- repoRoot = path14.dirname(gitCommonDir);
4378
+ repoRoot = path15.dirname(gitCommonDir);
4254
4379
  } catch {
4255
4380
  repoRoot = execSync6("git rev-parse --show-toplevel", {
4256
4381
  cwd: dir,
@@ -4259,11 +4384,11 @@ function getProjectName(cwd) {
4259
4384
  stdio: ["pipe", "pipe", "pipe"]
4260
4385
  }).trim();
4261
4386
  }
4262
- _cached2 = path14.basename(repoRoot);
4387
+ _cached2 = path15.basename(repoRoot);
4263
4388
  _cachedCwd = dir;
4264
4389
  return _cached2;
4265
4390
  } catch {
4266
- _cached2 = path14.basename(dir);
4391
+ _cached2 = path15.basename(dir);
4267
4392
  _cachedCwd = dir;
4268
4393
  return _cached2;
4269
4394
  }
@@ -4736,8 +4861,8 @@ __export(tasks_exports, {
4736
4861
  updateTaskStatus: () => updateTaskStatus,
4737
4862
  writeCheckpoint: () => writeCheckpoint
4738
4863
  });
4739
- import path15 from "path";
4740
- import { writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, unlinkSync as unlinkSync5 } from "fs";
4864
+ import path16 from "path";
4865
+ import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync7, unlinkSync as unlinkSync5 } from "fs";
4741
4866
  async function createTask(input) {
4742
4867
  const result = await createTaskCore(input);
4743
4868
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -4756,11 +4881,11 @@ async function updateTask(input) {
4756
4881
  const { row, taskFile, now, taskId } = await updateTaskStatus(input);
4757
4882
  try {
4758
4883
  const agent = String(row.assigned_to);
4759
- const cacheDir = path15.join(EXE_AI_DIR, "session-cache");
4760
- const cachePath = path15.join(cacheDir, `current-task-${agent}.json`);
4884
+ const cacheDir = path16.join(EXE_AI_DIR, "session-cache");
4885
+ const cachePath = path16.join(cacheDir, `current-task-${agent}.json`);
4761
4886
  if (input.status === "in_progress") {
4762
- mkdirSync6(cacheDir, { recursive: true });
4763
- writeFileSync6(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4887
+ mkdirSync7(cacheDir, { recursive: true });
4888
+ writeFileSync7(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
4764
4889
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
4765
4890
  try {
4766
4891
  unlinkSync5(cachePath);