@askexenow/exe-os 0.9.93 → 0.9.95

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 (88) hide show
  1. package/deploy/compose/docker-compose.yml +1 -0
  2. package/dist/bin/agentic-ontology-backfill.js +65 -8
  3. package/dist/bin/agentic-reflection-backfill.js +54 -3
  4. package/dist/bin/agentic-semantic-label.js +54 -3
  5. package/dist/bin/backfill-conversations.js +69 -9
  6. package/dist/bin/backfill-responses.js +69 -9
  7. package/dist/bin/backfill-vectors.js +54 -3
  8. package/dist/bin/bulk-sync-postgres.js +66 -8
  9. package/dist/bin/cleanup-stale-review-tasks.js +121 -13
  10. package/dist/bin/cli.js +1561 -466
  11. package/dist/bin/customer-readiness.js +61 -0
  12. package/dist/bin/exe-agent.js +17 -3
  13. package/dist/bin/exe-assign.js +75 -9
  14. package/dist/bin/exe-boot.js +114 -12
  15. package/dist/bin/exe-call.js +17 -3
  16. package/dist/bin/exe-cloud.js +76 -10
  17. package/dist/bin/exe-dispatch.js +136 -18
  18. package/dist/bin/exe-doctor.js +75 -9
  19. package/dist/bin/exe-export-behaviors.js +75 -9
  20. package/dist/bin/exe-forget.js +94 -9
  21. package/dist/bin/exe-gateway.js +135 -18
  22. package/dist/bin/exe-heartbeat.js +121 -13
  23. package/dist/bin/exe-kill.js +75 -9
  24. package/dist/bin/exe-launch-agent.js +75 -9
  25. package/dist/bin/exe-new-employee.js +18 -4
  26. package/dist/bin/exe-pending-messages.js +121 -13
  27. package/dist/bin/exe-pending-notifications.js +121 -13
  28. package/dist/bin/exe-pending-reviews.js +121 -13
  29. package/dist/bin/exe-rename.js +75 -9
  30. package/dist/bin/exe-review.js +75 -9
  31. package/dist/bin/exe-search.js +100 -9
  32. package/dist/bin/exe-session-cleanup.js +136 -18
  33. package/dist/bin/exe-settings.js +1 -0
  34. package/dist/bin/exe-start-codex.js +65 -8
  35. package/dist/bin/exe-start-opencode.js +65 -8
  36. package/dist/bin/exe-status.js +121 -13
  37. package/dist/bin/exe-support.js +1 -0
  38. package/dist/bin/exe-team.js +75 -9
  39. package/dist/bin/git-sweep.js +136 -18
  40. package/dist/bin/graph-backfill.js +65 -8
  41. package/dist/bin/graph-export.js +75 -9
  42. package/dist/bin/intercom-check.js +136 -18
  43. package/dist/bin/scan-tasks.js +136 -18
  44. package/dist/bin/setup.js +55 -4
  45. package/dist/bin/shard-migrate.js +65 -8
  46. package/dist/bin/stack-update.js +5 -6
  47. package/dist/bin/update.js +1 -1
  48. package/dist/gateway/index.js +136 -18
  49. package/dist/hooks/bug-report-worker.js +136 -18
  50. package/dist/hooks/codex-stop-task-finalizer.js +126 -14
  51. package/dist/hooks/commit-complete.js +136 -18
  52. package/dist/hooks/error-recall.js +100 -9
  53. package/dist/hooks/ingest.js +75 -9
  54. package/dist/hooks/instructions-loaded.js +75 -9
  55. package/dist/hooks/notification.js +75 -9
  56. package/dist/hooks/post-compact.js +313 -50
  57. package/dist/hooks/post-tool-combined.js +436 -13
  58. package/dist/hooks/pre-compact.js +136 -18
  59. package/dist/hooks/pre-tool-use.js +121 -13
  60. package/dist/hooks/prompt-submit.js +194 -19
  61. package/dist/hooks/session-end.js +136 -18
  62. package/dist/hooks/session-start.js +146 -13
  63. package/dist/hooks/stop.js +121 -13
  64. package/dist/hooks/subagent-stop.js +121 -13
  65. package/dist/hooks/summary-worker.js +99 -7
  66. package/dist/index.js +136 -18
  67. package/dist/lib/cloud-sync.js +38 -0
  68. package/dist/lib/consolidation.js +3 -1
  69. package/dist/lib/database.js +37 -0
  70. package/dist/lib/db.js +37 -0
  71. package/dist/lib/device-registry.js +37 -0
  72. package/dist/lib/employee-templates.js +17 -3
  73. package/dist/lib/exe-daemon.js +916 -42
  74. package/dist/lib/hybrid-search.js +100 -9
  75. package/dist/lib/license.js +1 -1
  76. package/dist/lib/messaging.js +43 -4
  77. package/dist/lib/schedules.js +54 -3
  78. package/dist/lib/store.js +75 -9
  79. package/dist/lib/tasks.js +61 -9
  80. package/dist/lib/tmux-routing.js +61 -9
  81. package/dist/mcp/server.js +878 -42
  82. package/dist/mcp/tools/create-task.js +70 -12
  83. package/dist/mcp/tools/list-tasks.js +49 -5
  84. package/dist/mcp/tools/send-message.js +43 -4
  85. package/dist/mcp/tools/update-task.js +61 -9
  86. package/dist/runtime/index.js +136 -18
  87. package/dist/tui/App.js +135 -18
  88. package/package.json +1 -1
@@ -1201,7 +1201,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
1201
1201
  import os6 from "os";
1202
1202
  import path7 from "path";
1203
1203
  import { jwtVerify, importSPKI } from "jose";
1204
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
1204
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
1205
1205
  var init_license = __esm({
1206
1206
  "src/lib/license.ts"() {
1207
1207
  "use strict";
@@ -1209,6 +1209,7 @@ var init_license = __esm({
1209
1209
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
1210
1210
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
1211
1211
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
1212
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
1212
1213
  PLAN_LIMITS = {
1213
1214
  free: { devices: 1, employees: 1, memories: 5e3 },
1214
1215
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -2215,9 +2216,13 @@ function getDispatchedBy(sessionKey) {
2215
2216
  }
2216
2217
  }
2217
2218
  function resolveExeSession() {
2219
+ if (process.env.EXE_SESSION_NAME) {
2220
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
2221
+ }
2218
2222
  const mySession = getMySession();
2219
2223
  if (!mySession) return null;
2220
2224
  const fromSessionName = extractRootExe(mySession);
2225
+ let candidate = null;
2221
2226
  try {
2222
2227
  const key = getSessionKey();
2223
2228
  const parentExe = getParentExe(key);
@@ -2228,13 +2233,47 @@ function resolveExeSession() {
2228
2233
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
2229
2234
  `
2230
2235
  );
2231
- return fromSessionName;
2236
+ candidate = fromSessionName;
2237
+ } else {
2238
+ candidate = fromCache;
2232
2239
  }
2233
- return fromCache;
2234
2240
  }
2235
2241
  } catch {
2236
2242
  }
2237
- return fromSessionName ?? mySession;
2243
+ if (!candidate) {
2244
+ candidate = fromSessionName ?? mySession;
2245
+ }
2246
+ if (candidate && isRootSession(candidate)) {
2247
+ try {
2248
+ const transport = getTransport();
2249
+ const liveSessions = transport.listSessions();
2250
+ if (!liveSessions.includes(candidate)) {
2251
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
2252
+ if (liveRoots.length === 1) {
2253
+ process.stderr.write(
2254
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
2255
+ `
2256
+ );
2257
+ return liveRoots[0];
2258
+ } else if (liveRoots.length > 1) {
2259
+ const base = candidate.replace(/\d+$/, "");
2260
+ const match = liveRoots.find((s) => s.startsWith(base));
2261
+ const chosen = match ?? liveRoots[0];
2262
+ process.stderr.write(
2263
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
2264
+ `
2265
+ );
2266
+ return chosen;
2267
+ }
2268
+ process.stderr.write(
2269
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
2270
+ `
2271
+ );
2272
+ }
2273
+ } catch {
2274
+ }
2275
+ }
2276
+ return candidate;
2238
2277
  }
2239
2278
  function isEmployeeAlive(sessionName) {
2240
2279
  return getTransport().isAlive(sessionName);
@@ -2636,7 +2675,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2636
2675
  }
2637
2676
  const spawnCwd = opts?.cwd ?? projectDir;
2638
2677
  const useExeAgent = !!(opts?.model && opts?.provider);
2639
- const agentRtConfig = getAgentRuntime(employeeName);
2678
+ const baseRtConfig = getAgentRuntime(employeeName);
2679
+ const agentRtConfig = {
2680
+ ...baseRtConfig,
2681
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
2682
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
2683
+ };
2640
2684
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
2641
2685
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
2642
2686
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -3274,8 +3318,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3274
3318
  const complexity = input.complexity ?? "standard";
3275
3319
  const sessionScope = earlySessionScope;
3276
3320
  await client.execute({
3277
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
3278
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3321
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, spawn_runtime, spawn_model, created_at, updated_at)
3322
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3279
3323
  args: [
3280
3324
  id,
3281
3325
  input.title,
@@ -3295,6 +3339,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3295
3339
  0,
3296
3340
  null,
3297
3341
  sessionScope,
3342
+ input.spawnRuntime ?? null,
3343
+ input.spawnModel ?? null,
3298
3344
  now,
3299
3345
  now
3300
3346
  ]
@@ -3351,7 +3397,9 @@ ${input.context}
3351
3397
  budgetTokens: input.budgetTokens ?? null,
3352
3398
  budgetFallbackModel: input.budgetFallbackModel ?? null,
3353
3399
  tokensUsed: 0,
3354
- tokensWarnedAt: null
3400
+ tokensWarnedAt: null,
3401
+ spawnRuntime: input.spawnRuntime ?? null,
3402
+ spawnModel: input.spawnModel ?? null
3355
3403
  };
3356
3404
  }
3357
3405
  async function listTasks(input) {
@@ -3401,7 +3449,9 @@ async function listTasks(input) {
3401
3449
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
3402
3450
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
3403
3451
  tokensUsed: Number(r.tokens_used ?? 0),
3404
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
3452
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
3453
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
3454
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
3405
3455
  }));
3406
3456
  }
3407
3457
  function isTmuxSessionAlive(identifier) {
@@ -4868,6 +4918,8 @@ async function updateTask(input) {
4868
4918
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
4869
4919
  tokensUsed: Number(row.tokens_used ?? 0),
4870
4920
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
4921
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
4922
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
4871
4923
  nextTask
4872
4924
  };
4873
4925
  }
@@ -5721,10 +5773,12 @@ function registerCreateTask(server) {
5721
5773
  parent_task_id: z.string().optional().describe("Parent task ID or slug. Links this task as a subtask."),
5722
5774
  reviewer: z.string().optional().describe("Who should review this task when done. Defaults to assigner."),
5723
5775
  budget_tokens: z.number().optional().describe("Max tokens allowed for this task (null = unlimited)"),
5724
- budget_fallback_model: z.string().optional().describe("Model to route to when budget is exhausted")
5776
+ budget_fallback_model: z.string().optional().describe("Model to route to when budget is exhausted"),
5777
+ spawn_runtime: z.string().optional().describe("Override runtime for spawned session (e.g., 'claude', 'codex', 'opencode')"),
5778
+ spawn_model: z.string().optional().describe("Override model for spawned session (e.g., 'claude-sonnet-4.6', 'claude-haiku-4.5')")
5725
5779
  }
5726
5780
  },
5727
- async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model }) => {
5781
+ async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model, spawn_runtime, spawn_model }) => {
5728
5782
  if (!isCoordinatorName(assigned_to)) {
5729
5783
  const license = getLicenseSync();
5730
5784
  if (license.plan === "free") {
@@ -5752,6 +5806,8 @@ function registerCreateTask(server) {
5752
5806
  reviewer,
5753
5807
  budgetTokens: budget_tokens,
5754
5808
  budgetFallbackModel: budget_fallback_model,
5809
+ spawnRuntime: spawn_runtime,
5810
+ spawnModel: spawn_model,
5755
5811
  // Skip internal dispatch — we handle it below with autoInstance
5756
5812
  // support. Without this, createTask fires dispatchTaskToEmployee
5757
5813
  // (no autoInstance) in parallel, racing with our ensureEmployee
@@ -5789,7 +5845,9 @@ function registerCreateTask(server) {
5789
5845
  const cfg = loadConfigSync2();
5790
5846
  const result = ensureEmployee(assigned_to, exeSession, process.cwd(), {
5791
5847
  autoInstance: useAutoInstance,
5792
- maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0
5848
+ maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0,
5849
+ runtimeOverride: spawn_runtime ?? void 0,
5850
+ modelOverride: spawn_model ?? void 0
5793
5851
  });
5794
5852
  switch (result.status) {
5795
5853
  case "intercom_sent":
@@ -527,7 +527,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
527
527
  import os6 from "os";
528
528
  import path7 from "path";
529
529
  import { jwtVerify, importSPKI } from "jose";
530
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
530
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE;
531
531
  var init_license = __esm({
532
532
  "src/lib/license.ts"() {
533
533
  "use strict";
@@ -535,6 +535,7 @@ var init_license = __esm({
535
535
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
536
536
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
537
537
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
538
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
538
539
  }
539
540
  });
540
541
 
@@ -578,6 +579,9 @@ import { fileURLToPath } from "url";
578
579
  function getMySession() {
579
580
  return getTransport().getMySession();
580
581
  }
582
+ function isRootSession(name) {
583
+ return name.length > 0 && !name.includes("-");
584
+ }
581
585
  function extractRootExe(name) {
582
586
  if (!name) return null;
583
587
  if (!name.includes("-")) return name;
@@ -593,9 +597,13 @@ function getParentExe(sessionKey) {
593
597
  }
594
598
  }
595
599
  function resolveExeSession() {
600
+ if (process.env.EXE_SESSION_NAME) {
601
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
602
+ }
596
603
  const mySession = getMySession();
597
604
  if (!mySession) return null;
598
605
  const fromSessionName = extractRootExe(mySession);
606
+ let candidate = null;
599
607
  try {
600
608
  const key = getSessionKey();
601
609
  const parentExe = getParentExe(key);
@@ -606,13 +614,47 @@ function resolveExeSession() {
606
614
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
607
615
  `
608
616
  );
609
- return fromSessionName;
617
+ candidate = fromSessionName;
618
+ } else {
619
+ candidate = fromCache;
610
620
  }
611
- return fromCache;
612
621
  }
613
622
  } catch {
614
623
  }
615
- return fromSessionName ?? mySession;
624
+ if (!candidate) {
625
+ candidate = fromSessionName ?? mySession;
626
+ }
627
+ if (candidate && isRootSession(candidate)) {
628
+ try {
629
+ const transport = getTransport();
630
+ const liveSessions = transport.listSessions();
631
+ if (!liveSessions.includes(candidate)) {
632
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
633
+ if (liveRoots.length === 1) {
634
+ process.stderr.write(
635
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
636
+ `
637
+ );
638
+ return liveRoots[0];
639
+ } else if (liveRoots.length > 1) {
640
+ const base = candidate.replace(/\d+$/, "");
641
+ const match = liveRoots.find((s) => s.startsWith(base));
642
+ const chosen = match ?? liveRoots[0];
643
+ process.stderr.write(
644
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
645
+ `
646
+ );
647
+ return chosen;
648
+ }
649
+ process.stderr.write(
650
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
651
+ `
652
+ );
653
+ }
654
+ } catch {
655
+ }
656
+ }
657
+ return candidate;
616
658
  }
617
659
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
618
660
  var init_tmux_routing = __esm({
@@ -801,7 +843,9 @@ async function listTasks(input) {
801
843
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
802
844
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
803
845
  tokensUsed: Number(r.tokens_used ?? 0),
804
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
846
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
847
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
848
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
805
849
  }));
806
850
  }
807
851
  var LANE_KEYWORDS, KEYWORD_INDEX;
@@ -590,7 +590,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
590
590
  import os6 from "os";
591
591
  import path7 from "path";
592
592
  import { jwtVerify, importSPKI } from "jose";
593
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
593
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE;
594
594
  var init_license = __esm({
595
595
  "src/lib/license.ts"() {
596
596
  "use strict";
@@ -598,6 +598,7 @@ var init_license = __esm({
598
598
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
599
599
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
600
600
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
601
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
601
602
  }
602
603
  });
603
604
 
@@ -683,9 +684,13 @@ function getParentExe(sessionKey) {
683
684
  }
684
685
  }
685
686
  function resolveExeSession() {
687
+ if (process.env.EXE_SESSION_NAME) {
688
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
689
+ }
686
690
  const mySession = getMySession();
687
691
  if (!mySession) return null;
688
692
  const fromSessionName = extractRootExe(mySession);
693
+ let candidate = null;
689
694
  try {
690
695
  const key = getSessionKey();
691
696
  const parentExe = getParentExe(key);
@@ -696,13 +701,47 @@ function resolveExeSession() {
696
701
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
697
702
  `
698
703
  );
699
- return fromSessionName;
704
+ candidate = fromSessionName;
705
+ } else {
706
+ candidate = fromCache;
700
707
  }
701
- return fromCache;
702
708
  }
703
709
  } catch {
704
710
  }
705
- return fromSessionName ?? mySession;
711
+ if (!candidate) {
712
+ candidate = fromSessionName ?? mySession;
713
+ }
714
+ if (candidate && isRootSession(candidate)) {
715
+ try {
716
+ const transport = getTransport();
717
+ const liveSessions = transport.listSessions();
718
+ if (!liveSessions.includes(candidate)) {
719
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
720
+ if (liveRoots.length === 1) {
721
+ process.stderr.write(
722
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
723
+ `
724
+ );
725
+ return liveRoots[0];
726
+ } else if (liveRoots.length > 1) {
727
+ const base = candidate.replace(/\d+$/, "");
728
+ const match = liveRoots.find((s) => s.startsWith(base));
729
+ const chosen = match ?? liveRoots[0];
730
+ process.stderr.write(
731
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
732
+ `
733
+ );
734
+ return chosen;
735
+ }
736
+ process.stderr.write(
737
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
738
+ `
739
+ );
740
+ }
741
+ } catch {
742
+ }
743
+ }
744
+ return candidate;
706
745
  }
707
746
  function isEmployeeAlive(sessionName) {
708
747
  return getTransport().isAlive(sessionName);
@@ -964,7 +964,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
964
964
  import os6 from "os";
965
965
  import path7 from "path";
966
966
  import { jwtVerify, importSPKI } from "jose";
967
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
967
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
968
968
  var init_license = __esm({
969
969
  "src/lib/license.ts"() {
970
970
  "use strict";
@@ -972,6 +972,7 @@ var init_license = __esm({
972
972
  LICENSE_PATH = path7.join(EXE_AI_DIR, "license.key");
973
973
  CACHE_PATH = path7.join(EXE_AI_DIR, "license-cache.json");
974
974
  DEVICE_ID_PATH = path7.join(EXE_AI_DIR, "device-id");
975
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
975
976
  PLAN_LIMITS = {
976
977
  free: { devices: 1, employees: 1, memories: 5e3 },
977
978
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -1978,9 +1979,13 @@ function getDispatchedBy(sessionKey) {
1978
1979
  }
1979
1980
  }
1980
1981
  function resolveExeSession() {
1982
+ if (process.env.EXE_SESSION_NAME) {
1983
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
1984
+ }
1981
1985
  const mySession = getMySession();
1982
1986
  if (!mySession) return null;
1983
1987
  const fromSessionName = extractRootExe(mySession);
1988
+ let candidate = null;
1984
1989
  try {
1985
1990
  const key = getSessionKey();
1986
1991
  const parentExe = getParentExe(key);
@@ -1991,13 +1996,47 @@ function resolveExeSession() {
1991
1996
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
1992
1997
  `
1993
1998
  );
1994
- return fromSessionName;
1999
+ candidate = fromSessionName;
2000
+ } else {
2001
+ candidate = fromCache;
1995
2002
  }
1996
- return fromCache;
1997
2003
  }
1998
2004
  } catch {
1999
2005
  }
2000
- return fromSessionName ?? mySession;
2006
+ if (!candidate) {
2007
+ candidate = fromSessionName ?? mySession;
2008
+ }
2009
+ if (candidate && isRootSession(candidate)) {
2010
+ try {
2011
+ const transport = getTransport();
2012
+ const liveSessions = transport.listSessions();
2013
+ if (!liveSessions.includes(candidate)) {
2014
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
2015
+ if (liveRoots.length === 1) {
2016
+ process.stderr.write(
2017
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
2018
+ `
2019
+ );
2020
+ return liveRoots[0];
2021
+ } else if (liveRoots.length > 1) {
2022
+ const base = candidate.replace(/\d+$/, "");
2023
+ const match = liveRoots.find((s) => s.startsWith(base));
2024
+ const chosen = match ?? liveRoots[0];
2025
+ process.stderr.write(
2026
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
2027
+ `
2028
+ );
2029
+ return chosen;
2030
+ }
2031
+ process.stderr.write(
2032
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
2033
+ `
2034
+ );
2035
+ }
2036
+ } catch {
2037
+ }
2038
+ }
2039
+ return candidate;
2001
2040
  }
2002
2041
  function isEmployeeAlive(sessionName) {
2003
2042
  return getTransport().isAlive(sessionName);
@@ -2399,7 +2438,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2399
2438
  }
2400
2439
  const spawnCwd = opts?.cwd ?? projectDir;
2401
2440
  const useExeAgent = !!(opts?.model && opts?.provider);
2402
- const agentRtConfig = getAgentRuntime(employeeName);
2441
+ const baseRtConfig = getAgentRuntime(employeeName);
2442
+ const agentRtConfig = {
2443
+ ...baseRtConfig,
2444
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
2445
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
2446
+ };
2403
2447
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
2404
2448
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
2405
2449
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -3037,8 +3081,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3037
3081
  const complexity = input.complexity ?? "standard";
3038
3082
  const sessionScope = earlySessionScope;
3039
3083
  await client.execute({
3040
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
3041
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3084
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, spawn_runtime, spawn_model, created_at, updated_at)
3085
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3042
3086
  args: [
3043
3087
  id,
3044
3088
  input.title,
@@ -3058,6 +3102,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3058
3102
  0,
3059
3103
  null,
3060
3104
  sessionScope,
3105
+ input.spawnRuntime ?? null,
3106
+ input.spawnModel ?? null,
3061
3107
  now,
3062
3108
  now
3063
3109
  ]
@@ -3114,7 +3160,9 @@ ${input.context}
3114
3160
  budgetTokens: input.budgetTokens ?? null,
3115
3161
  budgetFallbackModel: input.budgetFallbackModel ?? null,
3116
3162
  tokensUsed: 0,
3117
- tokensWarnedAt: null
3163
+ tokensWarnedAt: null,
3164
+ spawnRuntime: input.spawnRuntime ?? null,
3165
+ spawnModel: input.spawnModel ?? null
3118
3166
  };
3119
3167
  }
3120
3168
  async function listTasks(input) {
@@ -3164,7 +3212,9 @@ async function listTasks(input) {
3164
3212
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
3165
3213
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
3166
3214
  tokensUsed: Number(r.tokens_used ?? 0),
3167
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
3215
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
3216
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
3217
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
3168
3218
  }));
3169
3219
  }
3170
3220
  function isTmuxSessionAlive(identifier) {
@@ -4631,6 +4681,8 @@ async function updateTask(input) {
4631
4681
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
4632
4682
  tokensUsed: Number(row.tokens_used ?? 0),
4633
4683
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
4684
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
4685
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
4634
4686
  nextTask
4635
4687
  };
4636
4688
  }