@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
@@ -2874,6 +2874,20 @@ async function ensureSchema() {
2874
2874
  });
2875
2875
  } catch {
2876
2876
  }
2877
+ try {
2878
+ await client.execute({
2879
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2880
+ args: []
2881
+ });
2882
+ } catch {
2883
+ }
2884
+ try {
2885
+ await client.execute({
2886
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2887
+ args: []
2888
+ });
2889
+ } catch {
2890
+ }
2877
2891
  await client.executeMultiple(`
2878
2892
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2879
2893
  content_text,
@@ -3125,6 +3139,22 @@ async function ensureSchema() {
3125
3139
  );
3126
3140
  } catch {
3127
3141
  }
3142
+ for (const col of [
3143
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3144
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3145
+ ]) {
3146
+ try {
3147
+ await client.execute(col);
3148
+ } catch {
3149
+ }
3150
+ }
3151
+ try {
3152
+ await client.execute({
3153
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3154
+ args: []
3155
+ });
3156
+ } catch {
3157
+ }
3128
3158
  try {
3129
3159
  await client.execute({
3130
3160
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3167,6 +3197,13 @@ async function ensureSchema() {
3167
3197
  } catch {
3168
3198
  }
3169
3199
  }
3200
+ try {
3201
+ await client.execute({
3202
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3203
+ args: []
3204
+ });
3205
+ } catch {
3206
+ }
3170
3207
  try {
3171
3208
  await client.execute({
3172
3209
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -4305,6 +4342,20 @@ var init_platform_procedures = __esm({
4305
4342
  priority: "p1",
4306
4343
  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."
4307
4344
  },
4345
+ // --- Tool guidance ---
4346
+ {
4347
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
4348
+ domain: "tools",
4349
+ priority: "p2",
4350
+ 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."
4351
+ },
4352
+ // --- Release awareness ---
4353
+ {
4354
+ title: "What's New check \u2014 surface new features after update",
4355
+ domain: "support",
4356
+ priority: "p1",
4357
+ 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."
4358
+ },
4308
4359
  // --- Platform vs Customer ownership ---
4309
4360
  {
4310
4361
  title: "What the platform provides vs what you customize",
@@ -4393,13 +4444,13 @@ var init_platform_procedures = __esm({
4393
4444
  title: "MCP tools \u2014 memory, decision, and search",
4394
4445
  domain: "tool-use",
4395
4446
  priority: "p1",
4396
- 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.`
4447
+ content: `memory(action="recall") / recall_my_memory: search memories (semantic + FTS). Params: as_of (bi-temporal \u2014 what did I know at time X?), kind (decision|procedure|observation|raw|conversation|behavior), retrieval_mode (all|decisions_only|procedures_only|operational|recent_high_value). memory(action="ask_team") / ask_team_memory: search a colleague's memories. memory(action="store") / store_memory: persist a memory. Params: kind, procedure_for (domain tag for procedures). memory(action="commit") / commit_memory: high-importance, survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal window. Requires session_id + target_timestamp. memory(action="get_by_id"): fetch one memory by UUID with full untruncated text. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="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.`
4397
4448
  },
4398
4449
  {
4399
4450
  title: "MCP tools \u2014 task orchestration",
4400
4451
  domain: "tool-use",
4401
4452
  priority: "p1",
4402
- 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.'
4453
+ content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Params: blocked_by (task ID for dependency), parent_task_id (subtask hierarchy), reviewer, complexity (routine|standard|complex|critical), budget_tokens (max token cap), budget_fallback_model, spawn_runtime (override runtime: claude|codex|opencode), spawn_model (override model). 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.'
4403
4454
  },
4404
4455
  {
4405
4456
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -4429,7 +4480,7 @@ var init_platform_procedures = __esm({
4429
4480
  title: "MCP tools \u2014 admin, config, and operations",
4430
4481
  domain: "tool-use",
4431
4482
  priority: "p1",
4432
- 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.'
4483
+ content: 'config(action="list_employees"): view roster. config(action="set_agent_config"): view or change per-agent runtime + model. Call with no args to show all agents. 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. Supports cloud_action param: status|sync|reupload. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="worker_gate"): check spawn slot availability \u2014 alive/stale/reserved counts vs max. Use before dispatching. config(action="auto_wake_status"): orphaned tasks, blocked tasks, auto-wake retry status. config(action="orchestration_phase"): view/change org phase (phase_1_coo|phase_2_executives|phase_3_parallel_org). 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="pending_work_summary"): pending reviews + messages + notifications in one call. diagnostics(action="rename_employee"): rename an agent across all systems (roster, identity, DB, symlinks). diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role. mcp_ping(): daemon health + license status + tool usage stats.'
4433
4484
  }
4434
4485
  ];
4435
4486
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5113,6 +5164,8 @@ async function writeMemory(record) {
5113
5164
  source_type: record.source_type ?? null,
5114
5165
  tier: record.tier ?? classifyTier(record),
5115
5166
  supersedes_id: record.supersedes_id ?? null,
5167
+ valid_from: record.valid_from ?? record.timestamp,
5168
+ invalid_at: record.invalid_at ?? null,
5116
5169
  draft: record.draft ? 1 : 0,
5117
5170
  memory_type: memoryType,
5118
5171
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5131,7 +5184,8 @@ async function writeMemory(record) {
5131
5184
  token_cost: record.token_cost ?? null,
5132
5185
  audience: record.audience ?? null,
5133
5186
  language_type: record.language_type ?? inferLanguageType(record),
5134
- parent_memory_id: record.parent_memory_id ?? null
5187
+ parent_memory_id: record.parent_memory_id ?? null,
5188
+ procedure_for: record.procedure_for ?? null
5135
5189
  };
5136
5190
  _pendingRecords.push(dbRow);
5137
5191
  orgBus.emit({
@@ -5186,6 +5240,8 @@ async function flushBatch() {
5186
5240
  const sourceType = row.source_type ?? null;
5187
5241
  const tier = row.tier ?? 3;
5188
5242
  const supersedesId = row.supersedes_id ?? null;
5243
+ const validFrom = row.valid_from ?? row.timestamp;
5244
+ const invalidAt = row.invalid_at ?? null;
5189
5245
  const draft = row.draft ? 1 : 0;
5190
5246
  const memoryType = row.memory_type ?? "raw";
5191
5247
  const trajectory = row.trajectory ?? null;
@@ -5205,15 +5261,16 @@ async function flushBatch() {
5205
5261
  const audience = row.audience ?? null;
5206
5262
  const languageType = row.language_type ?? null;
5207
5263
  const parentMemoryId = row.parent_memory_id ?? null;
5264
+ const procedureFor = row.procedure_for ?? null;
5208
5265
  const cols = `id, agent_id, agent_role, session_id, timestamp,
5209
5266
  tool_name, project_name,
5210
5267
  has_error, raw_text, vector, version, task_id, importance, status,
5211
5268
  confidence, last_accessed,
5212
5269
  workspace_id, document_id, user_id, char_offset, page_number,
5213
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5270
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
5214
5271
  intent, outcome, domain, referenced_entities, retrieval_count,
5215
5272
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
5216
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
5273
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
5217
5274
  const metaArgs = [
5218
5275
  intent,
5219
5276
  outcome,
@@ -5229,7 +5286,8 @@ async function flushBatch() {
5229
5286
  tokenCost,
5230
5287
  audience,
5231
5288
  languageType,
5232
- parentMemoryId
5289
+ parentMemoryId,
5290
+ procedureFor
5233
5291
  ];
5234
5292
  const baseArgs = [
5235
5293
  row.id,
@@ -5258,6 +5316,8 @@ async function flushBatch() {
5258
5316
  sourceType,
5259
5317
  tier,
5260
5318
  supersedesId,
5319
+ validFrom,
5320
+ invalidAt,
5261
5321
  draft,
5262
5322
  memoryType,
5263
5323
  trajectory,
@@ -5265,8 +5325,8 @@ async function flushBatch() {
5265
5325
  ];
5266
5326
  return {
5267
5327
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5268
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5269
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5328
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5329
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5270
5330
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5271
5331
  };
5272
5332
  };
@@ -5382,6 +5442,12 @@ async function searchMemories(queryVector, agentId, options) {
5382
5442
  AND vector IS NOT NULL${statusFilter}${draftFilter}
5383
5443
  AND COALESCE(confidence, 0.7) >= 0.3`;
5384
5444
  const args = [agentId];
5445
+ if (options?.asOf) {
5446
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
5447
+ args.push(options.asOf, options.asOf);
5448
+ } else {
5449
+ sql += ` AND invalid_at IS NULL`;
5450
+ }
5385
5451
  const scope = buildWikiScopeFilter(options, "");
5386
5452
  sql += scope.clause;
5387
5453
  args.push(...scope.args);
@@ -6130,7 +6196,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
6130
6196
  import os8 from "os";
6131
6197
  import path11 from "path";
6132
6198
  import { jwtVerify, importSPKI } from "jose";
6133
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
6199
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
6134
6200
  var init_license = __esm({
6135
6201
  "src/lib/license.ts"() {
6136
6202
  "use strict";
@@ -6138,6 +6204,7 @@ var init_license = __esm({
6138
6204
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
6139
6205
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
6140
6206
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
6207
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
6141
6208
  PLAN_LIMITS = {
6142
6209
  free: { devices: 1, employees: 1, memories: 5e3 },
6143
6210
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -6652,8 +6719,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
6652
6719
  const complexity = input.complexity ?? "standard";
6653
6720
  const sessionScope = earlySessionScope;
6654
6721
  await client.execute({
6655
- 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)
6656
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6722
+ 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)
6723
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6657
6724
  args: [
6658
6725
  id,
6659
6726
  input.title,
@@ -6673,6 +6740,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
6673
6740
  0,
6674
6741
  null,
6675
6742
  sessionScope,
6743
+ input.spawnRuntime ?? null,
6744
+ input.spawnModel ?? null,
6676
6745
  now,
6677
6746
  now
6678
6747
  ]
@@ -6729,7 +6798,9 @@ ${input.context}
6729
6798
  budgetTokens: input.budgetTokens ?? null,
6730
6799
  budgetFallbackModel: input.budgetFallbackModel ?? null,
6731
6800
  tokensUsed: 0,
6732
- tokensWarnedAt: null
6801
+ tokensWarnedAt: null,
6802
+ spawnRuntime: input.spawnRuntime ?? null,
6803
+ spawnModel: input.spawnModel ?? null
6733
6804
  };
6734
6805
  }
6735
6806
  async function listTasks(input) {
@@ -6779,7 +6850,9 @@ async function listTasks(input) {
6779
6850
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
6780
6851
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
6781
6852
  tokensUsed: Number(r.tokens_used ?? 0),
6782
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
6853
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
6854
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
6855
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
6783
6856
  }));
6784
6857
  }
6785
6858
  function isTmuxSessionAlive(identifier) {
@@ -7768,6 +7841,8 @@ async function updateTask(input) {
7768
7841
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
7769
7842
  tokensUsed: Number(row.tokens_used ?? 0),
7770
7843
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
7844
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
7845
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
7771
7846
  nextTask
7772
7847
  };
7773
7848
  }
@@ -8260,9 +8335,13 @@ function getDispatchedBy(sessionKey) {
8260
8335
  }
8261
8336
  }
8262
8337
  function resolveExeSession() {
8338
+ if (process.env.EXE_SESSION_NAME) {
8339
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
8340
+ }
8263
8341
  const mySession = getMySession();
8264
8342
  if (!mySession) return null;
8265
8343
  const fromSessionName = extractRootExe(mySession);
8344
+ let candidate = null;
8266
8345
  try {
8267
8346
  const key = getSessionKey();
8268
8347
  const parentExe = getParentExe(key);
@@ -8273,13 +8352,47 @@ function resolveExeSession() {
8273
8352
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
8274
8353
  `
8275
8354
  );
8276
- return fromSessionName;
8355
+ candidate = fromSessionName;
8356
+ } else {
8357
+ candidate = fromCache;
8277
8358
  }
8278
- return fromCache;
8279
8359
  }
8280
8360
  } catch {
8281
8361
  }
8282
- return fromSessionName ?? mySession;
8362
+ if (!candidate) {
8363
+ candidate = fromSessionName ?? mySession;
8364
+ }
8365
+ if (candidate && isRootSession(candidate)) {
8366
+ try {
8367
+ const transport = getTransport();
8368
+ const liveSessions = transport.listSessions();
8369
+ if (!liveSessions.includes(candidate)) {
8370
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
8371
+ if (liveRoots.length === 1) {
8372
+ process.stderr.write(
8373
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
8374
+ `
8375
+ );
8376
+ return liveRoots[0];
8377
+ } else if (liveRoots.length > 1) {
8378
+ const base = candidate.replace(/\d+$/, "");
8379
+ const match = liveRoots.find((s) => s.startsWith(base));
8380
+ const chosen = match ?? liveRoots[0];
8381
+ process.stderr.write(
8382
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
8383
+ `
8384
+ );
8385
+ return chosen;
8386
+ }
8387
+ process.stderr.write(
8388
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
8389
+ `
8390
+ );
8391
+ }
8392
+ } catch {
8393
+ }
8394
+ }
8395
+ return candidate;
8283
8396
  }
8284
8397
  function isEmployeeAlive(sessionName) {
8285
8398
  return getTransport().isAlive(sessionName);
@@ -8681,7 +8794,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
8681
8794
  }
8682
8795
  const spawnCwd = opts?.cwd ?? projectDir;
8683
8796
  const useExeAgent = !!(opts?.model && opts?.provider);
8684
- const agentRtConfig = getAgentRuntime(employeeName);
8797
+ const baseRtConfig = getAgentRuntime(employeeName);
8798
+ const agentRtConfig = {
8799
+ ...baseRtConfig,
8800
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
8801
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
8802
+ };
8685
8803
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
8686
8804
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
8687
8805
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();