@askexenow/exe-os 0.9.93 → 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 +1558 -466
  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 +5 -6
  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
@@ -3436,6 +3436,20 @@ async function ensureSchema() {
3436
3436
  });
3437
3437
  } catch {
3438
3438
  }
3439
+ try {
3440
+ await client.execute({
3441
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3442
+ args: []
3443
+ });
3444
+ } catch {
3445
+ }
3446
+ try {
3447
+ await client.execute({
3448
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3449
+ args: []
3450
+ });
3451
+ } catch {
3452
+ }
3439
3453
  await client.executeMultiple(`
3440
3454
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3441
3455
  content_text,
@@ -3687,6 +3701,22 @@ async function ensureSchema() {
3687
3701
  );
3688
3702
  } catch {
3689
3703
  }
3704
+ for (const col of [
3705
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3706
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3707
+ ]) {
3708
+ try {
3709
+ await client.execute(col);
3710
+ } catch {
3711
+ }
3712
+ }
3713
+ try {
3714
+ await client.execute({
3715
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3716
+ args: []
3717
+ });
3718
+ } catch {
3719
+ }
3690
3720
  try {
3691
3721
  await client.execute({
3692
3722
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3729,6 +3759,13 @@ async function ensureSchema() {
3729
3759
  } catch {
3730
3760
  }
3731
3761
  }
3762
+ try {
3763
+ await client.execute({
3764
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3765
+ args: []
3766
+ });
3767
+ } catch {
3768
+ }
3732
3769
  try {
3733
3770
  await client.execute({
3734
3771
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -4880,6 +4917,20 @@ var init_platform_procedures = __esm({
4880
4917
  priority: "p1",
4881
4918
  content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
4882
4919
  },
4920
+ // --- Tool guidance ---
4921
+ {
4922
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
4923
+ domain: "tools",
4924
+ priority: "p2",
4925
+ content: "The company_actions tool executes business actions through gateway connectors (e.g. send WhatsApp, trigger workflows, update CRM). It routes through the exe-gateway on the VPS. Actions are defined by the customer's gateway configuration \u2014 each connector (WhatsApp, Shopify, email, etc.) exposes specific actions. Use query_company_brain to find available data first, then company_actions to act on it. Requires gateway auth token. Read-only founders should NOT use this \u2014 it mutates external state."
4926
+ },
4927
+ // --- Release awareness ---
4928
+ {
4929
+ title: "What's New check \u2014 surface new features after update",
4930
+ domain: "support",
4931
+ priority: "p1",
4932
+ content: "Once per session (COO boot only, never repeat), check if the installed exe-os version is newer than the last session. If it is, read the bundled release-notes.json (at the package root) and surface a brief summary to the founder: 'Updated to exe-os vX.Y.Z \u2014 N new features, M fixes.' List the top 3 features by name. This helps the founder know what they got from the update. If release-notes.json doesn't exist or the version hasn't changed, skip silently. Never repeat this check in the same session."
4933
+ },
4883
4934
  // --- Platform vs Customer ownership ---
4884
4935
  {
4885
4936
  title: "What the platform provides vs what you customize",
@@ -4968,13 +5019,13 @@ var init_platform_procedures = __esm({
4968
5019
  title: "MCP tools \u2014 memory, decision, and search",
4969
5020
  domain: "tool-use",
4970
5021
  priority: "p1",
4971
- content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
5022
+ content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). Supports as_of param for bi-temporal queries (what did I know at time X?), kind param to filter by memory type (decision, procedure, observation, raw, conversation, behavior). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. Supports kind param and procedure_for domain tag for procedure-type memories. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="supersede") / supersede: replace an old memory with a new version (old_id + new text). decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
4972
5023
  },
4973
5024
  {
4974
5025
  title: "MCP tools \u2014 task orchestration",
4975
5026
  domain: "tool-use",
4976
5027
  priority: "p1",
4977
- content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
5028
+ content: `task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Supports spawn_runtime and spawn_model params to override the agent's default runtime/model for a specific task. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.`
4978
5029
  },
4979
5030
  {
4980
5031
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -5004,7 +5055,7 @@ var init_platform_procedures = __esm({
5004
5055
  title: "MCP tools \u2014 admin, config, and operations",
5005
5056
  domain: "tool-use",
5006
5057
  priority: "p1",
5007
- content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. mcp_ping(): daemon health + license status + tool usage stats.'
5058
+ content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. Returns top-K tools ranked by relevance. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role identity. Returns drift score + recommendations. mcp_ping(): daemon health + license status + tool usage stats.'
5008
5059
  }
5009
5060
  ];
5010
5061
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5688,6 +5739,8 @@ async function writeMemory(record) {
5688
5739
  source_type: record.source_type ?? null,
5689
5740
  tier: record.tier ?? classifyTier(record),
5690
5741
  supersedes_id: record.supersedes_id ?? null,
5742
+ valid_from: record.valid_from ?? record.timestamp,
5743
+ invalid_at: record.invalid_at ?? null,
5691
5744
  draft: record.draft ? 1 : 0,
5692
5745
  memory_type: memoryType,
5693
5746
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5706,7 +5759,8 @@ async function writeMemory(record) {
5706
5759
  token_cost: record.token_cost ?? null,
5707
5760
  audience: record.audience ?? null,
5708
5761
  language_type: record.language_type ?? inferLanguageType(record),
5709
- parent_memory_id: record.parent_memory_id ?? null
5762
+ parent_memory_id: record.parent_memory_id ?? null,
5763
+ procedure_for: record.procedure_for ?? null
5710
5764
  };
5711
5765
  _pendingRecords.push(dbRow);
5712
5766
  orgBus.emit({
@@ -5761,6 +5815,8 @@ async function flushBatch() {
5761
5815
  const sourceType = row.source_type ?? null;
5762
5816
  const tier = row.tier ?? 3;
5763
5817
  const supersedesId = row.supersedes_id ?? null;
5818
+ const validFrom = row.valid_from ?? row.timestamp;
5819
+ const invalidAt = row.invalid_at ?? null;
5764
5820
  const draft = row.draft ? 1 : 0;
5765
5821
  const memoryType = row.memory_type ?? "raw";
5766
5822
  const trajectory = row.trajectory ?? null;
@@ -5780,15 +5836,16 @@ async function flushBatch() {
5780
5836
  const audience = row.audience ?? null;
5781
5837
  const languageType = row.language_type ?? null;
5782
5838
  const parentMemoryId = row.parent_memory_id ?? null;
5839
+ const procedureFor = row.procedure_for ?? null;
5783
5840
  const cols = `id, agent_id, agent_role, session_id, timestamp,
5784
5841
  tool_name, project_name,
5785
5842
  has_error, raw_text, vector, version, task_id, importance, status,
5786
5843
  confidence, last_accessed,
5787
5844
  workspace_id, document_id, user_id, char_offset, page_number,
5788
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5845
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
5789
5846
  intent, outcome, domain, referenced_entities, retrieval_count,
5790
5847
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
5791
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
5848
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
5792
5849
  const metaArgs = [
5793
5850
  intent,
5794
5851
  outcome,
@@ -5804,7 +5861,8 @@ async function flushBatch() {
5804
5861
  tokenCost,
5805
5862
  audience,
5806
5863
  languageType,
5807
- parentMemoryId
5864
+ parentMemoryId,
5865
+ procedureFor
5808
5866
  ];
5809
5867
  const baseArgs = [
5810
5868
  row.id,
@@ -5833,6 +5891,8 @@ async function flushBatch() {
5833
5891
  sourceType,
5834
5892
  tier,
5835
5893
  supersedesId,
5894
+ validFrom,
5895
+ invalidAt,
5836
5896
  draft,
5837
5897
  memoryType,
5838
5898
  trajectory,
@@ -5840,8 +5900,8 @@ async function flushBatch() {
5840
5900
  ];
5841
5901
  return {
5842
5902
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5843
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5844
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5903
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5904
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5845
5905
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5846
5906
  };
5847
5907
  };
@@ -5957,6 +6017,12 @@ async function searchMemories(queryVector, agentId, options) {
5957
6017
  AND vector IS NOT NULL${statusFilter}${draftFilter}
5958
6018
  AND COALESCE(confidence, 0.7) >= 0.3`;
5959
6019
  const args = [agentId];
6020
+ if (options?.asOf) {
6021
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
6022
+ args.push(options.asOf, options.asOf);
6023
+ } else {
6024
+ sql += ` AND invalid_at IS NULL`;
6025
+ }
5960
6026
  const scope = buildWikiScopeFilter(options, "");
5961
6027
  sql += scope.clause;
5962
6028
  args.push(...scope.args);
@@ -7712,7 +7778,7 @@ var init_license = __esm({
7712
7778
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
7713
7779
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
7714
7780
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
7715
- API_BASE = "https://askexe.com/cloud";
7781
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
7716
7782
  RETRY_DELAY_MS = 500;
7717
7783
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
7718
7784
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -10463,8 +10529,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
10463
10529
  const complexity = input.complexity ?? "standard";
10464
10530
  const sessionScope = earlySessionScope;
10465
10531
  await client.execute({
10466
- 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)
10467
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
10532
+ 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)
10533
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
10468
10534
  args: [
10469
10535
  id,
10470
10536
  input.title,
@@ -10484,6 +10550,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
10484
10550
  0,
10485
10551
  null,
10486
10552
  sessionScope,
10553
+ input.spawnRuntime ?? null,
10554
+ input.spawnModel ?? null,
10487
10555
  now,
10488
10556
  now
10489
10557
  ]
@@ -10540,7 +10608,9 @@ ${input.context}
10540
10608
  budgetTokens: input.budgetTokens ?? null,
10541
10609
  budgetFallbackModel: input.budgetFallbackModel ?? null,
10542
10610
  tokensUsed: 0,
10543
- tokensWarnedAt: null
10611
+ tokensWarnedAt: null,
10612
+ spawnRuntime: input.spawnRuntime ?? null,
10613
+ spawnModel: input.spawnModel ?? null
10544
10614
  };
10545
10615
  }
10546
10616
  async function listTasks(input) {
@@ -10590,7 +10660,9 @@ async function listTasks(input) {
10590
10660
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
10591
10661
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
10592
10662
  tokensUsed: Number(r.tokens_used ?? 0),
10593
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
10663
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
10664
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
10665
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
10594
10666
  }));
10595
10667
  }
10596
10668
  function isTmuxSessionAlive(identifier) {
@@ -11819,6 +11891,8 @@ async function updateTask(input) {
11819
11891
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
11820
11892
  tokensUsed: Number(row.tokens_used ?? 0),
11821
11893
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
11894
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
11895
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
11822
11896
  nextTask
11823
11897
  };
11824
11898
  }
@@ -12314,6 +12388,7 @@ function resolveExeSession() {
12314
12388
  const mySession = getMySession();
12315
12389
  if (!mySession) return null;
12316
12390
  const fromSessionName = extractRootExe(mySession);
12391
+ let candidate = null;
12317
12392
  try {
12318
12393
  const key = getSessionKey();
12319
12394
  const parentExe = getParentExe(key);
@@ -12324,13 +12399,47 @@ function resolveExeSession() {
12324
12399
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
12325
12400
  `
12326
12401
  );
12327
- return fromSessionName;
12402
+ candidate = fromSessionName;
12403
+ } else {
12404
+ candidate = fromCache;
12328
12405
  }
12329
- return fromCache;
12330
12406
  }
12331
12407
  } catch {
12332
12408
  }
12333
- return fromSessionName ?? mySession;
12409
+ if (!candidate) {
12410
+ candidate = fromSessionName ?? mySession;
12411
+ }
12412
+ if (candidate && isRootSession(candidate)) {
12413
+ try {
12414
+ const transport = getTransport();
12415
+ const liveSessions = transport.listSessions();
12416
+ if (!liveSessions.includes(candidate)) {
12417
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
12418
+ if (liveRoots.length === 1) {
12419
+ process.stderr.write(
12420
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
12421
+ `
12422
+ );
12423
+ return liveRoots[0];
12424
+ } else if (liveRoots.length > 1) {
12425
+ const base = candidate.replace(/\d+$/, "");
12426
+ const match = liveRoots.find((s) => s.startsWith(base));
12427
+ const chosen = match ?? liveRoots[0];
12428
+ process.stderr.write(
12429
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
12430
+ `
12431
+ );
12432
+ return chosen;
12433
+ }
12434
+ process.stderr.write(
12435
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
12436
+ `
12437
+ );
12438
+ }
12439
+ } catch {
12440
+ }
12441
+ }
12442
+ return candidate;
12334
12443
  }
12335
12444
  function isEmployeeAlive(sessionName) {
12336
12445
  return getTransport().isAlive(sessionName);
@@ -12732,7 +12841,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
12732
12841
  }
12733
12842
  const spawnCwd = opts?.cwd ?? projectDir;
12734
12843
  const useExeAgent = !!(opts?.model && opts?.provider);
12735
- const agentRtConfig = getAgentRuntime(employeeName);
12844
+ const baseRtConfig = getAgentRuntime(employeeName);
12845
+ const agentRtConfig = {
12846
+ ...baseRtConfig,
12847
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
12848
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
12849
+ };
12736
12850
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
12737
12851
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
12738
12852
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -2804,6 +2804,20 @@ async function ensureSchema() {
2804
2804
  });
2805
2805
  } catch {
2806
2806
  }
2807
+ try {
2808
+ await client.execute({
2809
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2810
+ args: []
2811
+ });
2812
+ } catch {
2813
+ }
2814
+ try {
2815
+ await client.execute({
2816
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2817
+ args: []
2818
+ });
2819
+ } catch {
2820
+ }
2807
2821
  await client.executeMultiple(`
2808
2822
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2809
2823
  content_text,
@@ -3055,6 +3069,22 @@ async function ensureSchema() {
3055
3069
  );
3056
3070
  } catch {
3057
3071
  }
3072
+ for (const col of [
3073
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3074
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3075
+ ]) {
3076
+ try {
3077
+ await client.execute(col);
3078
+ } catch {
3079
+ }
3080
+ }
3081
+ try {
3082
+ await client.execute({
3083
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3084
+ args: []
3085
+ });
3086
+ } catch {
3087
+ }
3058
3088
  try {
3059
3089
  await client.execute({
3060
3090
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3097,6 +3127,13 @@ async function ensureSchema() {
3097
3127
  } catch {
3098
3128
  }
3099
3129
  }
3130
+ try {
3131
+ await client.execute({
3132
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3133
+ args: []
3134
+ });
3135
+ } catch {
3136
+ }
3100
3137
  try {
3101
3138
  await client.execute({
3102
3139
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -4235,6 +4272,20 @@ var init_platform_procedures = __esm({
4235
4272
  priority: "p1",
4236
4273
  content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
4237
4274
  },
4275
+ // --- Tool guidance ---
4276
+ {
4277
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
4278
+ domain: "tools",
4279
+ priority: "p2",
4280
+ content: "The company_actions tool executes business actions through gateway connectors (e.g. send WhatsApp, trigger workflows, update CRM). It routes through the exe-gateway on the VPS. Actions are defined by the customer's gateway configuration \u2014 each connector (WhatsApp, Shopify, email, etc.) exposes specific actions. Use query_company_brain to find available data first, then company_actions to act on it. Requires gateway auth token. Read-only founders should NOT use this \u2014 it mutates external state."
4281
+ },
4282
+ // --- Release awareness ---
4283
+ {
4284
+ title: "What's New check \u2014 surface new features after update",
4285
+ domain: "support",
4286
+ priority: "p1",
4287
+ content: "Once per session (COO boot only, never repeat), check if the installed exe-os version is newer than the last session. If it is, read the bundled release-notes.json (at the package root) and surface a brief summary to the founder: 'Updated to exe-os vX.Y.Z \u2014 N new features, M fixes.' List the top 3 features by name. This helps the founder know what they got from the update. If release-notes.json doesn't exist or the version hasn't changed, skip silently. Never repeat this check in the same session."
4288
+ },
4238
4289
  // --- Platform vs Customer ownership ---
4239
4290
  {
4240
4291
  title: "What the platform provides vs what you customize",
@@ -4323,13 +4374,13 @@ var init_platform_procedures = __esm({
4323
4374
  title: "MCP tools \u2014 memory, decision, and search",
4324
4375
  domain: "tool-use",
4325
4376
  priority: "p1",
4326
- content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
4377
+ content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). Supports as_of param for bi-temporal queries (what did I know at time X?), kind param to filter by memory type (decision, procedure, observation, raw, conversation, behavior). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. Supports kind param and procedure_for domain tag for procedure-type memories. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="supersede") / supersede: replace an old memory with a new version (old_id + new text). decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
4327
4378
  },
4328
4379
  {
4329
4380
  title: "MCP tools \u2014 task orchestration",
4330
4381
  domain: "tool-use",
4331
4382
  priority: "p1",
4332
- content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
4383
+ content: `task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Supports spawn_runtime and spawn_model params to override the agent's default runtime/model for a specific task. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.`
4333
4384
  },
4334
4385
  {
4335
4386
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -4359,7 +4410,7 @@ var init_platform_procedures = __esm({
4359
4410
  title: "MCP tools \u2014 admin, config, and operations",
4360
4411
  domain: "tool-use",
4361
4412
  priority: "p1",
4362
- content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. mcp_ping(): daemon health + license status + tool usage stats.'
4413
+ content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. Returns top-K tools ranked by relevance. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role identity. Returns drift score + recommendations. mcp_ping(): daemon health + license status + tool usage stats.'
4363
4414
  }
4364
4415
  ];
4365
4416
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5043,6 +5094,8 @@ async function writeMemory(record) {
5043
5094
  source_type: record.source_type ?? null,
5044
5095
  tier: record.tier ?? classifyTier(record),
5045
5096
  supersedes_id: record.supersedes_id ?? null,
5097
+ valid_from: record.valid_from ?? record.timestamp,
5098
+ invalid_at: record.invalid_at ?? null,
5046
5099
  draft: record.draft ? 1 : 0,
5047
5100
  memory_type: memoryType,
5048
5101
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5061,7 +5114,8 @@ async function writeMemory(record) {
5061
5114
  token_cost: record.token_cost ?? null,
5062
5115
  audience: record.audience ?? null,
5063
5116
  language_type: record.language_type ?? inferLanguageType(record),
5064
- parent_memory_id: record.parent_memory_id ?? null
5117
+ parent_memory_id: record.parent_memory_id ?? null,
5118
+ procedure_for: record.procedure_for ?? null
5065
5119
  };
5066
5120
  _pendingRecords.push(dbRow);
5067
5121
  orgBus.emit({
@@ -5116,6 +5170,8 @@ async function flushBatch() {
5116
5170
  const sourceType = row.source_type ?? null;
5117
5171
  const tier = row.tier ?? 3;
5118
5172
  const supersedesId = row.supersedes_id ?? null;
5173
+ const validFrom = row.valid_from ?? row.timestamp;
5174
+ const invalidAt = row.invalid_at ?? null;
5119
5175
  const draft = row.draft ? 1 : 0;
5120
5176
  const memoryType = row.memory_type ?? "raw";
5121
5177
  const trajectory = row.trajectory ?? null;
@@ -5135,15 +5191,16 @@ async function flushBatch() {
5135
5191
  const audience = row.audience ?? null;
5136
5192
  const languageType = row.language_type ?? null;
5137
5193
  const parentMemoryId = row.parent_memory_id ?? null;
5194
+ const procedureFor = row.procedure_for ?? null;
5138
5195
  const cols = `id, agent_id, agent_role, session_id, timestamp,
5139
5196
  tool_name, project_name,
5140
5197
  has_error, raw_text, vector, version, task_id, importance, status,
5141
5198
  confidence, last_accessed,
5142
5199
  workspace_id, document_id, user_id, char_offset, page_number,
5143
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5200
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
5144
5201
  intent, outcome, domain, referenced_entities, retrieval_count,
5145
5202
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
5146
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
5203
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
5147
5204
  const metaArgs = [
5148
5205
  intent,
5149
5206
  outcome,
@@ -5159,7 +5216,8 @@ async function flushBatch() {
5159
5216
  tokenCost,
5160
5217
  audience,
5161
5218
  languageType,
5162
- parentMemoryId
5219
+ parentMemoryId,
5220
+ procedureFor
5163
5221
  ];
5164
5222
  const baseArgs = [
5165
5223
  row.id,
@@ -5188,6 +5246,8 @@ async function flushBatch() {
5188
5246
  sourceType,
5189
5247
  tier,
5190
5248
  supersedesId,
5249
+ validFrom,
5250
+ invalidAt,
5191
5251
  draft,
5192
5252
  memoryType,
5193
5253
  trajectory,
@@ -5195,8 +5255,8 @@ async function flushBatch() {
5195
5255
  ];
5196
5256
  return {
5197
5257
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5198
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5199
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5258
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5259
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5200
5260
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5201
5261
  };
5202
5262
  };
@@ -5312,6 +5372,12 @@ async function searchMemories(queryVector, agentId, options) {
5312
5372
  AND vector IS NOT NULL${statusFilter}${draftFilter}
5313
5373
  AND COALESCE(confidence, 0.7) >= 0.3`;
5314
5374
  const args = [agentId];
5375
+ if (options?.asOf) {
5376
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
5377
+ args.push(options.asOf, options.asOf);
5378
+ } else {
5379
+ sql += ` AND invalid_at IS NULL`;
5380
+ }
5315
5381
  const scope = buildWikiScopeFilter(options, "");
5316
5382
  sql += scope.clause;
5317
5383
  args.push(...scope.args);
@@ -5780,7 +5846,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
5780
5846
  import os8 from "os";
5781
5847
  import path11 from "path";
5782
5848
  import { jwtVerify, importSPKI } from "jose";
5783
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
5849
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE;
5784
5850
  var init_license = __esm({
5785
5851
  "src/lib/license.ts"() {
5786
5852
  "use strict";
@@ -5788,6 +5854,7 @@ var init_license = __esm({
5788
5854
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
5789
5855
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
5790
5856
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
5857
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
5791
5858
  }
5792
5859
  });
5793
5860
 
@@ -5831,6 +5898,9 @@ import { fileURLToPath as fileURLToPath3 } from "url";
5831
5898
  function getMySession() {
5832
5899
  return getTransport().getMySession();
5833
5900
  }
5901
+ function isRootSession(name) {
5902
+ return name.length > 0 && !name.includes("-");
5903
+ }
5834
5904
  function extractRootExe(name) {
5835
5905
  if (!name) return null;
5836
5906
  if (!name.includes("-")) return name;
@@ -5849,6 +5919,7 @@ function resolveExeSession() {
5849
5919
  const mySession = getMySession();
5850
5920
  if (!mySession) return null;
5851
5921
  const fromSessionName = extractRootExe(mySession);
5922
+ let candidate = null;
5852
5923
  try {
5853
5924
  const key = getSessionKey();
5854
5925
  const parentExe = getParentExe(key);
@@ -5859,13 +5930,47 @@ function resolveExeSession() {
5859
5930
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
5860
5931
  `
5861
5932
  );
5862
- return fromSessionName;
5933
+ candidate = fromSessionName;
5934
+ } else {
5935
+ candidate = fromCache;
5863
5936
  }
5864
- return fromCache;
5865
5937
  }
5866
5938
  } catch {
5867
5939
  }
5868
- return fromSessionName ?? mySession;
5940
+ if (!candidate) {
5941
+ candidate = fromSessionName ?? mySession;
5942
+ }
5943
+ if (candidate && isRootSession(candidate)) {
5944
+ try {
5945
+ const transport = getTransport();
5946
+ const liveSessions = transport.listSessions();
5947
+ if (!liveSessions.includes(candidate)) {
5948
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
5949
+ if (liveRoots.length === 1) {
5950
+ process.stderr.write(
5951
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
5952
+ `
5953
+ );
5954
+ return liveRoots[0];
5955
+ } else if (liveRoots.length > 1) {
5956
+ const base = candidate.replace(/\d+$/, "");
5957
+ const match = liveRoots.find((s) => s.startsWith(base));
5958
+ const chosen = match ?? liveRoots[0];
5959
+ process.stderr.write(
5960
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
5961
+ `
5962
+ );
5963
+ return chosen;
5964
+ }
5965
+ process.stderr.write(
5966
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
5967
+ `
5968
+ );
5969
+ }
5970
+ } catch {
5971
+ }
5972
+ }
5973
+ return candidate;
5869
5974
  }
5870
5975
  function isExeSession(sessionName) {
5871
5976
  const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));