@askexenow/exe-os 0.9.92 → 0.9.94

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 +118 -13
  10. package/dist/bin/cli.js +1605 -456
  11. package/dist/bin/customer-readiness.js +51 -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 +111 -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 +133 -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 +132 -18
  22. package/dist/bin/exe-heartbeat.js +118 -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 +118 -13
  27. package/dist/bin/exe-pending-notifications.js +118 -13
  28. package/dist/bin/exe-pending-reviews.js +118 -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 +133 -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 +118 -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 +133 -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 +133 -18
  43. package/dist/bin/scan-tasks.js +133 -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 +57 -1
  47. package/dist/bin/update.js +1 -1
  48. package/dist/gateway/index.js +133 -18
  49. package/dist/hooks/bug-report-worker.js +133 -18
  50. package/dist/hooks/codex-stop-task-finalizer.js +123 -14
  51. package/dist/hooks/commit-complete.js +133 -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 +310 -50
  57. package/dist/hooks/post-tool-combined.js +433 -13
  58. package/dist/hooks/pre-compact.js +133 -18
  59. package/dist/hooks/pre-tool-use.js +118 -13
  60. package/dist/hooks/prompt-submit.js +191 -19
  61. package/dist/hooks/session-end.js +133 -18
  62. package/dist/hooks/session-start.js +143 -13
  63. package/dist/hooks/stop.js +118 -13
  64. package/dist/hooks/subagent-stop.js +118 -13
  65. package/dist/hooks/summary-worker.js +96 -7
  66. package/dist/index.js +133 -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 +913 -42
  74. package/dist/lib/hybrid-search.js +100 -9
  75. package/dist/lib/license.js +1 -1
  76. package/dist/lib/messaging.js +40 -4
  77. package/dist/lib/schedules.js +54 -3
  78. package/dist/lib/store.js +75 -9
  79. package/dist/lib/tasks.js +58 -9
  80. package/dist/lib/tmux-routing.js +58 -9
  81. package/dist/mcp/server.js +875 -42
  82. package/dist/mcp/tools/create-task.js +67 -12
  83. package/dist/mcp/tools/list-tasks.js +46 -5
  84. package/dist/mcp/tools/send-message.js +40 -4
  85. package/dist/mcp/tools/update-task.js +58 -9
  86. package/dist/runtime/index.js +133 -18
  87. package/dist/tui/App.js +132 -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 },
@@ -2218,6 +2219,7 @@ function resolveExeSession() {
2218
2219
  const mySession = getMySession();
2219
2220
  if (!mySession) return null;
2220
2221
  const fromSessionName = extractRootExe(mySession);
2222
+ let candidate = null;
2221
2223
  try {
2222
2224
  const key = getSessionKey();
2223
2225
  const parentExe = getParentExe(key);
@@ -2228,13 +2230,47 @@ function resolveExeSession() {
2228
2230
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
2229
2231
  `
2230
2232
  );
2231
- return fromSessionName;
2233
+ candidate = fromSessionName;
2234
+ } else {
2235
+ candidate = fromCache;
2232
2236
  }
2233
- return fromCache;
2234
2237
  }
2235
2238
  } catch {
2236
2239
  }
2237
- return fromSessionName ?? mySession;
2240
+ if (!candidate) {
2241
+ candidate = fromSessionName ?? mySession;
2242
+ }
2243
+ if (candidate && isRootSession(candidate)) {
2244
+ try {
2245
+ const transport = getTransport();
2246
+ const liveSessions = transport.listSessions();
2247
+ if (!liveSessions.includes(candidate)) {
2248
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
2249
+ if (liveRoots.length === 1) {
2250
+ process.stderr.write(
2251
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
2252
+ `
2253
+ );
2254
+ return liveRoots[0];
2255
+ } else if (liveRoots.length > 1) {
2256
+ const base = candidate.replace(/\d+$/, "");
2257
+ const match = liveRoots.find((s) => s.startsWith(base));
2258
+ const chosen = match ?? liveRoots[0];
2259
+ process.stderr.write(
2260
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
2261
+ `
2262
+ );
2263
+ return chosen;
2264
+ }
2265
+ process.stderr.write(
2266
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
2267
+ `
2268
+ );
2269
+ }
2270
+ } catch {
2271
+ }
2272
+ }
2273
+ return candidate;
2238
2274
  }
2239
2275
  function isEmployeeAlive(sessionName) {
2240
2276
  return getTransport().isAlive(sessionName);
@@ -2636,7 +2672,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2636
2672
  }
2637
2673
  const spawnCwd = opts?.cwd ?? projectDir;
2638
2674
  const useExeAgent = !!(opts?.model && opts?.provider);
2639
- const agentRtConfig = getAgentRuntime(employeeName);
2675
+ const baseRtConfig = getAgentRuntime(employeeName);
2676
+ const agentRtConfig = {
2677
+ ...baseRtConfig,
2678
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
2679
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
2680
+ };
2640
2681
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
2641
2682
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
2642
2683
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -3274,8 +3315,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3274
3315
  const complexity = input.complexity ?? "standard";
3275
3316
  const sessionScope = earlySessionScope;
3276
3317
  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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3318
+ 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)
3319
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3279
3320
  args: [
3280
3321
  id,
3281
3322
  input.title,
@@ -3295,6 +3336,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3295
3336
  0,
3296
3337
  null,
3297
3338
  sessionScope,
3339
+ input.spawnRuntime ?? null,
3340
+ input.spawnModel ?? null,
3298
3341
  now,
3299
3342
  now
3300
3343
  ]
@@ -3351,7 +3394,9 @@ ${input.context}
3351
3394
  budgetTokens: input.budgetTokens ?? null,
3352
3395
  budgetFallbackModel: input.budgetFallbackModel ?? null,
3353
3396
  tokensUsed: 0,
3354
- tokensWarnedAt: null
3397
+ tokensWarnedAt: null,
3398
+ spawnRuntime: input.spawnRuntime ?? null,
3399
+ spawnModel: input.spawnModel ?? null
3355
3400
  };
3356
3401
  }
3357
3402
  async function listTasks(input) {
@@ -3401,7 +3446,9 @@ async function listTasks(input) {
3401
3446
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
3402
3447
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
3403
3448
  tokensUsed: Number(r.tokens_used ?? 0),
3404
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
3449
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
3450
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
3451
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
3405
3452
  }));
3406
3453
  }
3407
3454
  function isTmuxSessionAlive(identifier) {
@@ -4868,6 +4915,8 @@ async function updateTask(input) {
4868
4915
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
4869
4916
  tokensUsed: Number(row.tokens_used ?? 0),
4870
4917
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
4918
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
4919
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
4871
4920
  nextTask
4872
4921
  };
4873
4922
  }
@@ -5721,10 +5770,12 @@ function registerCreateTask(server) {
5721
5770
  parent_task_id: z.string().optional().describe("Parent task ID or slug. Links this task as a subtask."),
5722
5771
  reviewer: z.string().optional().describe("Who should review this task when done. Defaults to assigner."),
5723
5772
  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")
5773
+ budget_fallback_model: z.string().optional().describe("Model to route to when budget is exhausted"),
5774
+ spawn_runtime: z.string().optional().describe("Override runtime for spawned session (e.g., 'claude', 'codex', 'opencode')"),
5775
+ spawn_model: z.string().optional().describe("Override model for spawned session (e.g., 'claude-sonnet-4.6', 'claude-haiku-4.5')")
5725
5776
  }
5726
5777
  },
5727
- async ({ title, assigned_to, project_name, priority, complexity, context, blocked_by, parent_task_id, reviewer, budget_tokens, budget_fallback_model }) => {
5778
+ 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
5779
  if (!isCoordinatorName(assigned_to)) {
5729
5780
  const license = getLicenseSync();
5730
5781
  if (license.plan === "free") {
@@ -5752,6 +5803,8 @@ function registerCreateTask(server) {
5752
5803
  reviewer,
5753
5804
  budgetTokens: budget_tokens,
5754
5805
  budgetFallbackModel: budget_fallback_model,
5806
+ spawnRuntime: spawn_runtime,
5807
+ spawnModel: spawn_model,
5755
5808
  // Skip internal dispatch — we handle it below with autoInstance
5756
5809
  // support. Without this, createTask fires dispatchTaskToEmployee
5757
5810
  // (no autoInstance) in parallel, racing with our ensureEmployee
@@ -5789,7 +5842,9 @@ function registerCreateTask(server) {
5789
5842
  const cfg = loadConfigSync2();
5790
5843
  const result = ensureEmployee(assigned_to, exeSession, process.cwd(), {
5791
5844
  autoInstance: useAutoInstance,
5792
- maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0
5845
+ maxAutoInstances: useAutoInstance ? cfg.sessionLifecycle.maxAutoInstances : void 0,
5846
+ runtimeOverride: spawn_runtime ?? void 0,
5847
+ modelOverride: spawn_model ?? void 0
5793
5848
  });
5794
5849
  switch (result.status) {
5795
5850
  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;
@@ -596,6 +600,7 @@ function resolveExeSession() {
596
600
  const mySession = getMySession();
597
601
  if (!mySession) return null;
598
602
  const fromSessionName = extractRootExe(mySession);
603
+ let candidate = null;
599
604
  try {
600
605
  const key = getSessionKey();
601
606
  const parentExe = getParentExe(key);
@@ -606,13 +611,47 @@ function resolveExeSession() {
606
611
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
607
612
  `
608
613
  );
609
- return fromSessionName;
614
+ candidate = fromSessionName;
615
+ } else {
616
+ candidate = fromCache;
610
617
  }
611
- return fromCache;
612
618
  }
613
619
  } catch {
614
620
  }
615
- return fromSessionName ?? mySession;
621
+ if (!candidate) {
622
+ candidate = fromSessionName ?? mySession;
623
+ }
624
+ if (candidate && isRootSession(candidate)) {
625
+ try {
626
+ const transport = getTransport();
627
+ const liveSessions = transport.listSessions();
628
+ if (!liveSessions.includes(candidate)) {
629
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
630
+ if (liveRoots.length === 1) {
631
+ process.stderr.write(
632
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
633
+ `
634
+ );
635
+ return liveRoots[0];
636
+ } else if (liveRoots.length > 1) {
637
+ const base = candidate.replace(/\d+$/, "");
638
+ const match = liveRoots.find((s) => s.startsWith(base));
639
+ const chosen = match ?? liveRoots[0];
640
+ process.stderr.write(
641
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
642
+ `
643
+ );
644
+ return chosen;
645
+ }
646
+ process.stderr.write(
647
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
648
+ `
649
+ );
650
+ }
651
+ } catch {
652
+ }
653
+ }
654
+ return candidate;
616
655
  }
617
656
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
618
657
  var init_tmux_routing = __esm({
@@ -801,7 +840,9 @@ async function listTasks(input) {
801
840
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
802
841
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
803
842
  tokensUsed: Number(r.tokens_used ?? 0),
804
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
843
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
844
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
845
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
805
846
  }));
806
847
  }
807
848
  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
 
@@ -686,6 +687,7 @@ function resolveExeSession() {
686
687
  const mySession = getMySession();
687
688
  if (!mySession) return null;
688
689
  const fromSessionName = extractRootExe(mySession);
690
+ let candidate = null;
689
691
  try {
690
692
  const key = getSessionKey();
691
693
  const parentExe = getParentExe(key);
@@ -696,13 +698,47 @@ function resolveExeSession() {
696
698
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
697
699
  `
698
700
  );
699
- return fromSessionName;
701
+ candidate = fromSessionName;
702
+ } else {
703
+ candidate = fromCache;
700
704
  }
701
- return fromCache;
702
705
  }
703
706
  } catch {
704
707
  }
705
- return fromSessionName ?? mySession;
708
+ if (!candidate) {
709
+ candidate = fromSessionName ?? mySession;
710
+ }
711
+ if (candidate && isRootSession(candidate)) {
712
+ try {
713
+ const transport = getTransport();
714
+ const liveSessions = transport.listSessions();
715
+ if (!liveSessions.includes(candidate)) {
716
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
717
+ if (liveRoots.length === 1) {
718
+ process.stderr.write(
719
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
720
+ `
721
+ );
722
+ return liveRoots[0];
723
+ } else if (liveRoots.length > 1) {
724
+ const base = candidate.replace(/\d+$/, "");
725
+ const match = liveRoots.find((s) => s.startsWith(base));
726
+ const chosen = match ?? liveRoots[0];
727
+ process.stderr.write(
728
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
729
+ `
730
+ );
731
+ return chosen;
732
+ }
733
+ process.stderr.write(
734
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
735
+ `
736
+ );
737
+ }
738
+ } catch {
739
+ }
740
+ }
741
+ return candidate;
706
742
  }
707
743
  function isEmployeeAlive(sessionName) {
708
744
  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 },
@@ -1981,6 +1982,7 @@ function resolveExeSession() {
1981
1982
  const mySession = getMySession();
1982
1983
  if (!mySession) return null;
1983
1984
  const fromSessionName = extractRootExe(mySession);
1985
+ let candidate = null;
1984
1986
  try {
1985
1987
  const key = getSessionKey();
1986
1988
  const parentExe = getParentExe(key);
@@ -1991,13 +1993,47 @@ function resolveExeSession() {
1991
1993
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
1992
1994
  `
1993
1995
  );
1994
- return fromSessionName;
1996
+ candidate = fromSessionName;
1997
+ } else {
1998
+ candidate = fromCache;
1995
1999
  }
1996
- return fromCache;
1997
2000
  }
1998
2001
  } catch {
1999
2002
  }
2000
- return fromSessionName ?? mySession;
2003
+ if (!candidate) {
2004
+ candidate = fromSessionName ?? mySession;
2005
+ }
2006
+ if (candidate && isRootSession(candidate)) {
2007
+ try {
2008
+ const transport = getTransport();
2009
+ const liveSessions = transport.listSessions();
2010
+ if (!liveSessions.includes(candidate)) {
2011
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
2012
+ if (liveRoots.length === 1) {
2013
+ process.stderr.write(
2014
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
2015
+ `
2016
+ );
2017
+ return liveRoots[0];
2018
+ } else if (liveRoots.length > 1) {
2019
+ const base = candidate.replace(/\d+$/, "");
2020
+ const match = liveRoots.find((s) => s.startsWith(base));
2021
+ const chosen = match ?? liveRoots[0];
2022
+ process.stderr.write(
2023
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
2024
+ `
2025
+ );
2026
+ return chosen;
2027
+ }
2028
+ process.stderr.write(
2029
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
2030
+ `
2031
+ );
2032
+ }
2033
+ } catch {
2034
+ }
2035
+ }
2036
+ return candidate;
2001
2037
  }
2002
2038
  function isEmployeeAlive(sessionName) {
2003
2039
  return getTransport().isAlive(sessionName);
@@ -2399,7 +2435,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
2399
2435
  }
2400
2436
  const spawnCwd = opts?.cwd ?? projectDir;
2401
2437
  const useExeAgent = !!(opts?.model && opts?.provider);
2402
- const agentRtConfig = getAgentRuntime(employeeName);
2438
+ const baseRtConfig = getAgentRuntime(employeeName);
2439
+ const agentRtConfig = {
2440
+ ...baseRtConfig,
2441
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
2442
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
2443
+ };
2403
2444
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
2404
2445
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
2405
2446
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -3037,8 +3078,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3037
3078
  const complexity = input.complexity ?? "standard";
3038
3079
  const sessionScope = earlySessionScope;
3039
3080
  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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3081
+ 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)
3082
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3042
3083
  args: [
3043
3084
  id,
3044
3085
  input.title,
@@ -3058,6 +3099,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
3058
3099
  0,
3059
3100
  null,
3060
3101
  sessionScope,
3102
+ input.spawnRuntime ?? null,
3103
+ input.spawnModel ?? null,
3061
3104
  now,
3062
3105
  now
3063
3106
  ]
@@ -3114,7 +3157,9 @@ ${input.context}
3114
3157
  budgetTokens: input.budgetTokens ?? null,
3115
3158
  budgetFallbackModel: input.budgetFallbackModel ?? null,
3116
3159
  tokensUsed: 0,
3117
- tokensWarnedAt: null
3160
+ tokensWarnedAt: null,
3161
+ spawnRuntime: input.spawnRuntime ?? null,
3162
+ spawnModel: input.spawnModel ?? null
3118
3163
  };
3119
3164
  }
3120
3165
  async function listTasks(input) {
@@ -3164,7 +3209,9 @@ async function listTasks(input) {
3164
3209
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
3165
3210
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
3166
3211
  tokensUsed: Number(r.tokens_used ?? 0),
3167
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
3212
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
3213
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
3214
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
3168
3215
  }));
3169
3216
  }
3170
3217
  function isTmuxSessionAlive(identifier) {
@@ -4631,6 +4678,8 @@ async function updateTask(input) {
4631
4678
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
4632
4679
  tokensUsed: Number(row.tokens_used ?? 0),
4633
4680
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
4681
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
4682
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
4634
4683
  nextTask
4635
4684
  };
4636
4685
  }