@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
@@ -3175,6 +3175,20 @@ async function ensureSchema() {
3175
3175
  });
3176
3176
  } catch {
3177
3177
  }
3178
+ try {
3179
+ await client.execute({
3180
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3181
+ args: []
3182
+ });
3183
+ } catch {
3184
+ }
3185
+ try {
3186
+ await client.execute({
3187
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3188
+ args: []
3189
+ });
3190
+ } catch {
3191
+ }
3178
3192
  await client.executeMultiple(`
3179
3193
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3180
3194
  content_text,
@@ -3426,6 +3440,22 @@ async function ensureSchema() {
3426
3440
  );
3427
3441
  } catch {
3428
3442
  }
3443
+ for (const col of [
3444
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3445
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3446
+ ]) {
3447
+ try {
3448
+ await client.execute(col);
3449
+ } catch {
3450
+ }
3451
+ }
3452
+ try {
3453
+ await client.execute({
3454
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3455
+ args: []
3456
+ });
3457
+ } catch {
3458
+ }
3429
3459
  try {
3430
3460
  await client.execute({
3431
3461
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3468,6 +3498,13 @@ async function ensureSchema() {
3468
3498
  } catch {
3469
3499
  }
3470
3500
  }
3501
+ try {
3502
+ await client.execute({
3503
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3504
+ args: []
3505
+ });
3506
+ } catch {
3507
+ }
3471
3508
  try {
3472
3509
  await client.execute({
3473
3510
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -4606,6 +4643,20 @@ var init_platform_procedures = __esm({
4606
4643
  priority: "p1",
4607
4644
  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."
4608
4645
  },
4646
+ // --- Tool guidance ---
4647
+ {
4648
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
4649
+ domain: "tools",
4650
+ priority: "p2",
4651
+ 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."
4652
+ },
4653
+ // --- Release awareness ---
4654
+ {
4655
+ title: "What's New check \u2014 surface new features after update",
4656
+ domain: "support",
4657
+ priority: "p1",
4658
+ 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."
4659
+ },
4609
4660
  // --- Platform vs Customer ownership ---
4610
4661
  {
4611
4662
  title: "What the platform provides vs what you customize",
@@ -4694,13 +4745,13 @@ var init_platform_procedures = __esm({
4694
4745
  title: "MCP tools \u2014 memory, decision, and search",
4695
4746
  domain: "tool-use",
4696
4747
  priority: "p1",
4697
- 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.`
4748
+ 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.`
4698
4749
  },
4699
4750
  {
4700
4751
  title: "MCP tools \u2014 task orchestration",
4701
4752
  domain: "tool-use",
4702
4753
  priority: "p1",
4703
- 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.'
4754
+ 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.`
4704
4755
  },
4705
4756
  {
4706
4757
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -4730,7 +4781,7 @@ var init_platform_procedures = __esm({
4730
4781
  title: "MCP tools \u2014 admin, config, and operations",
4731
4782
  domain: "tool-use",
4732
4783
  priority: "p1",
4733
- 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.'
4784
+ 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.'
4734
4785
  }
4735
4786
  ];
4736
4787
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5414,6 +5465,8 @@ async function writeMemory(record) {
5414
5465
  source_type: record.source_type ?? null,
5415
5466
  tier: record.tier ?? classifyTier(record),
5416
5467
  supersedes_id: record.supersedes_id ?? null,
5468
+ valid_from: record.valid_from ?? record.timestamp,
5469
+ invalid_at: record.invalid_at ?? null,
5417
5470
  draft: record.draft ? 1 : 0,
5418
5471
  memory_type: memoryType,
5419
5472
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5432,7 +5485,8 @@ async function writeMemory(record) {
5432
5485
  token_cost: record.token_cost ?? null,
5433
5486
  audience: record.audience ?? null,
5434
5487
  language_type: record.language_type ?? inferLanguageType(record),
5435
- parent_memory_id: record.parent_memory_id ?? null
5488
+ parent_memory_id: record.parent_memory_id ?? null,
5489
+ procedure_for: record.procedure_for ?? null
5436
5490
  };
5437
5491
  _pendingRecords.push(dbRow);
5438
5492
  orgBus.emit({
@@ -5487,6 +5541,8 @@ async function flushBatch() {
5487
5541
  const sourceType = row.source_type ?? null;
5488
5542
  const tier = row.tier ?? 3;
5489
5543
  const supersedesId = row.supersedes_id ?? null;
5544
+ const validFrom = row.valid_from ?? row.timestamp;
5545
+ const invalidAt = row.invalid_at ?? null;
5490
5546
  const draft = row.draft ? 1 : 0;
5491
5547
  const memoryType = row.memory_type ?? "raw";
5492
5548
  const trajectory = row.trajectory ?? null;
@@ -5506,15 +5562,16 @@ async function flushBatch() {
5506
5562
  const audience = row.audience ?? null;
5507
5563
  const languageType = row.language_type ?? null;
5508
5564
  const parentMemoryId = row.parent_memory_id ?? null;
5565
+ const procedureFor = row.procedure_for ?? null;
5509
5566
  const cols = `id, agent_id, agent_role, session_id, timestamp,
5510
5567
  tool_name, project_name,
5511
5568
  has_error, raw_text, vector, version, task_id, importance, status,
5512
5569
  confidence, last_accessed,
5513
5570
  workspace_id, document_id, user_id, char_offset, page_number,
5514
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5571
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
5515
5572
  intent, outcome, domain, referenced_entities, retrieval_count,
5516
5573
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
5517
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
5574
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
5518
5575
  const metaArgs = [
5519
5576
  intent,
5520
5577
  outcome,
@@ -5530,7 +5587,8 @@ async function flushBatch() {
5530
5587
  tokenCost,
5531
5588
  audience,
5532
5589
  languageType,
5533
- parentMemoryId
5590
+ parentMemoryId,
5591
+ procedureFor
5534
5592
  ];
5535
5593
  const baseArgs = [
5536
5594
  row.id,
@@ -5559,6 +5617,8 @@ async function flushBatch() {
5559
5617
  sourceType,
5560
5618
  tier,
5561
5619
  supersedesId,
5620
+ validFrom,
5621
+ invalidAt,
5562
5622
  draft,
5563
5623
  memoryType,
5564
5624
  trajectory,
@@ -5566,8 +5626,8 @@ async function flushBatch() {
5566
5626
  ];
5567
5627
  return {
5568
5628
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5569
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5570
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5629
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5630
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5571
5631
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5572
5632
  };
5573
5633
  };
@@ -5683,6 +5743,12 @@ async function searchMemories(queryVector, agentId, options) {
5683
5743
  AND vector IS NOT NULL${statusFilter}${draftFilter}
5684
5744
  AND COALESCE(confidence, 0.7) >= 0.3`;
5685
5745
  const args = [agentId];
5746
+ if (options?.asOf) {
5747
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
5748
+ args.push(options.asOf, options.asOf);
5749
+ } else {
5750
+ sql += ` AND invalid_at IS NULL`;
5751
+ }
5686
5752
  const scope = buildWikiScopeFilter(options, "");
5687
5753
  sql += scope.clause;
5688
5754
  args.push(...scope.args);
@@ -7007,6 +7073,19 @@ __export(hybrid_search_exports, {
7007
7073
  rrfMerge: () => rrfMerge,
7008
7074
  rrfMergeMulti: () => rrfMergeMulti
7009
7075
  });
7076
+ function buildTemporalFilter(options, columnPrefix) {
7077
+ const asOf = options?.asOf;
7078
+ if (asOf) {
7079
+ return {
7080
+ clause: ` AND (${columnPrefix}valid_from IS NULL OR ${columnPrefix}valid_from <= ?) AND (${columnPrefix}invalid_at IS NULL OR ${columnPrefix}invalid_at > ?)`,
7081
+ args: [asOf, asOf]
7082
+ };
7083
+ }
7084
+ return {
7085
+ clause: ` AND ${columnPrefix}invalid_at IS NULL`,
7086
+ args: []
7087
+ };
7088
+ }
7010
7089
  function appendMemoryTypeFilter(sql, args, column, options) {
7011
7090
  if (options?.memoryTypes && options.memoryTypes.length > 0) {
7012
7091
  const uniqueTypes = [...new Set(options.memoryTypes)];
@@ -7239,6 +7318,9 @@ async function estimateCardinality(agentId, options) {
7239
7318
  AND COALESCE(status, 'active') = 'active'
7240
7319
  AND COALESCE(confidence, 0.7) >= 0.3`;
7241
7320
  const args = [agentId];
7321
+ const temporal = buildTemporalFilter(options, "");
7322
+ sql += temporal.clause;
7323
+ args.push(...temporal.args);
7242
7324
  const rawVisibility = buildRawVisibilityFilter(options, "");
7243
7325
  sql += rawVisibility.clause;
7244
7326
  args.push(...rawVisibility.args);
@@ -7367,6 +7449,9 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
7367
7449
  AND m.agent_id = ?${statusFilter}${draftFilter}
7368
7450
  AND COALESCE(m.confidence, 0.7) >= 0.3`;
7369
7451
  const args = [matchExpr, agentId];
7452
+ const temporal = buildTemporalFilter(options, "m.");
7453
+ sql += temporal.clause;
7454
+ args.push(...temporal.args);
7370
7455
  const scope = buildWikiScopeFilter(options, "m.");
7371
7456
  sql += scope.clause;
7372
7457
  args.push(...scope.args);
@@ -7479,6 +7564,9 @@ async function recentRecords(agentId, options, limit, textFilter) {
7479
7564
  WHERE agent_id = ?${statusFilter}${draftFilter}
7480
7565
  AND COALESCE(confidence, 0.7) >= 0.3`;
7481
7566
  const args = [agentId];
7567
+ const temporal = buildTemporalFilter(options, "");
7568
+ sql += temporal.clause;
7569
+ args.push(...temporal.args);
7482
7570
  const scope = buildWikiScopeFilter(options, "");
7483
7571
  sql += scope.clause;
7484
7572
  args.push(...scope.args);
@@ -7579,6 +7667,9 @@ async function trajectoryBypass(queryText, agentId, options, limit) {
7579
7667
  AND json_extract(trajectory, '$.tool') = ?
7580
7668
  AND agent_id = ?${statusFilter}${draftFilter}`;
7581
7669
  const args = [toolName, agentId];
7670
+ const temporal = buildTemporalFilter(options, "");
7671
+ sql += temporal.clause;
7672
+ args.push(...temporal.args);
7582
7673
  const rawVisibility = buildRawVisibilityFilter(options, "");
7583
7674
  sql += rawVisibility.clause;
7584
7675
  args.push(...rawVisibility.args);
@@ -8090,7 +8181,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
8090
8181
  import os8 from "os";
8091
8182
  import path15 from "path";
8092
8183
  import { jwtVerify, importSPKI } from "jose";
8093
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
8184
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE;
8094
8185
  var init_license = __esm({
8095
8186
  "src/lib/license.ts"() {
8096
8187
  "use strict";
@@ -8098,6 +8189,7 @@ var init_license = __esm({
8098
8189
  LICENSE_PATH = path15.join(EXE_AI_DIR, "license.key");
8099
8190
  CACHE_PATH = path15.join(EXE_AI_DIR, "license-cache.json");
8100
8191
  DEVICE_ID_PATH = path15.join(EXE_AI_DIR, "device-id");
8192
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
8101
8193
  }
8102
8194
  });
8103
8195
 
@@ -8141,6 +8233,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
8141
8233
  function getMySession() {
8142
8234
  return getTransport().getMySession();
8143
8235
  }
8236
+ function isRootSession(name) {
8237
+ return name.length > 0 && !name.includes("-");
8238
+ }
8144
8239
  function extractRootExe(name) {
8145
8240
  if (!name) return null;
8146
8241
  if (!name.includes("-")) return name;
@@ -8159,6 +8254,7 @@ function resolveExeSession() {
8159
8254
  const mySession = getMySession();
8160
8255
  if (!mySession) return null;
8161
8256
  const fromSessionName = extractRootExe(mySession);
8257
+ let candidate = null;
8162
8258
  try {
8163
8259
  const key = getSessionKey();
8164
8260
  const parentExe = getParentExe(key);
@@ -8169,13 +8265,47 @@ function resolveExeSession() {
8169
8265
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
8170
8266
  `
8171
8267
  );
8172
- return fromSessionName;
8268
+ candidate = fromSessionName;
8269
+ } else {
8270
+ candidate = fromCache;
8173
8271
  }
8174
- return fromCache;
8175
8272
  }
8176
8273
  } catch {
8177
8274
  }
8178
- return fromSessionName ?? mySession;
8275
+ if (!candidate) {
8276
+ candidate = fromSessionName ?? mySession;
8277
+ }
8278
+ if (candidate && isRootSession(candidate)) {
8279
+ try {
8280
+ const transport = getTransport();
8281
+ const liveSessions = transport.listSessions();
8282
+ if (!liveSessions.includes(candidate)) {
8283
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
8284
+ if (liveRoots.length === 1) {
8285
+ process.stderr.write(
8286
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
8287
+ `
8288
+ );
8289
+ return liveRoots[0];
8290
+ } else if (liveRoots.length > 1) {
8291
+ const base = candidate.replace(/\d+$/, "");
8292
+ const match = liveRoots.find((s) => s.startsWith(base));
8293
+ const chosen = match ?? liveRoots[0];
8294
+ process.stderr.write(
8295
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
8296
+ `
8297
+ );
8298
+ return chosen;
8299
+ }
8300
+ process.stderr.write(
8301
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
8302
+ `
8303
+ );
8304
+ }
8305
+ } catch {
8306
+ }
8307
+ }
8308
+ return candidate;
8179
8309
  }
8180
8310
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
8181
8311
  var init_tmux_routing = __esm({
@@ -3097,6 +3097,20 @@ async function ensureSchema() {
3097
3097
  });
3098
3098
  } catch {
3099
3099
  }
3100
+ try {
3101
+ await client.execute({
3102
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3103
+ args: []
3104
+ });
3105
+ } catch {
3106
+ }
3107
+ try {
3108
+ await client.execute({
3109
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3110
+ args: []
3111
+ });
3112
+ } catch {
3113
+ }
3100
3114
  await client.executeMultiple(`
3101
3115
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3102
3116
  content_text,
@@ -3348,6 +3362,22 @@ async function ensureSchema() {
3348
3362
  );
3349
3363
  } catch {
3350
3364
  }
3365
+ for (const col of [
3366
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3367
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3368
+ ]) {
3369
+ try {
3370
+ await client.execute(col);
3371
+ } catch {
3372
+ }
3373
+ }
3374
+ try {
3375
+ await client.execute({
3376
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3377
+ args: []
3378
+ });
3379
+ } catch {
3380
+ }
3351
3381
  try {
3352
3382
  await client.execute({
3353
3383
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3390,6 +3420,13 @@ async function ensureSchema() {
3390
3420
  } catch {
3391
3421
  }
3392
3422
  }
3423
+ try {
3424
+ await client.execute({
3425
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3426
+ args: []
3427
+ });
3428
+ } catch {
3429
+ }
3393
3430
  try {
3394
3431
  await client.execute({
3395
3432
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3450,7 +3487,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3450
3487
  import os7 from "os";
3451
3488
  import path10 from "path";
3452
3489
  import { jwtVerify, importSPKI } from "jose";
3453
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
3490
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE;
3454
3491
  var init_license = __esm({
3455
3492
  "src/lib/license.ts"() {
3456
3493
  "use strict";
@@ -3458,6 +3495,7 @@ var init_license = __esm({
3458
3495
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3459
3496
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3460
3497
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3498
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3461
3499
  }
3462
3500
  });
3463
3501
 
@@ -3501,6 +3539,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
3501
3539
  function getMySession() {
3502
3540
  return getTransport().getMySession();
3503
3541
  }
3542
+ function isRootSession(name) {
3543
+ return name.length > 0 && !name.includes("-");
3544
+ }
3504
3545
  function extractRootExe(name) {
3505
3546
  if (!name) return null;
3506
3547
  if (!name.includes("-")) return name;
@@ -3519,6 +3560,7 @@ function resolveExeSession() {
3519
3560
  const mySession = getMySession();
3520
3561
  if (!mySession) return null;
3521
3562
  const fromSessionName = extractRootExe(mySession);
3563
+ let candidate = null;
3522
3564
  try {
3523
3565
  const key = getSessionKey();
3524
3566
  const parentExe = getParentExe(key);
@@ -3529,13 +3571,47 @@ function resolveExeSession() {
3529
3571
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3530
3572
  `
3531
3573
  );
3532
- return fromSessionName;
3574
+ candidate = fromSessionName;
3575
+ } else {
3576
+ candidate = fromCache;
3533
3577
  }
3534
- return fromCache;
3535
3578
  }
3536
3579
  } catch {
3537
3580
  }
3538
- return fromSessionName ?? mySession;
3581
+ if (!candidate) {
3582
+ candidate = fromSessionName ?? mySession;
3583
+ }
3584
+ if (candidate && isRootSession(candidate)) {
3585
+ try {
3586
+ const transport = getTransport();
3587
+ const liveSessions = transport.listSessions();
3588
+ if (!liveSessions.includes(candidate)) {
3589
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
3590
+ if (liveRoots.length === 1) {
3591
+ process.stderr.write(
3592
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
3593
+ `
3594
+ );
3595
+ return liveRoots[0];
3596
+ } else if (liveRoots.length > 1) {
3597
+ const base = candidate.replace(/\d+$/, "");
3598
+ const match = liveRoots.find((s) => s.startsWith(base));
3599
+ const chosen = match ?? liveRoots[0];
3600
+ process.stderr.write(
3601
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
3602
+ `
3603
+ );
3604
+ return chosen;
3605
+ }
3606
+ process.stderr.write(
3607
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
3608
+ `
3609
+ );
3610
+ }
3611
+ } catch {
3612
+ }
3613
+ }
3614
+ return candidate;
3539
3615
  }
3540
3616
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3541
3617
  var init_tmux_routing = __esm({
@@ -4680,6 +4756,20 @@ var init_platform_procedures = __esm({
4680
4756
  priority: "p1",
4681
4757
  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."
4682
4758
  },
4759
+ // --- Tool guidance ---
4760
+ {
4761
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
4762
+ domain: "tools",
4763
+ priority: "p2",
4764
+ 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."
4765
+ },
4766
+ // --- Release awareness ---
4767
+ {
4768
+ title: "What's New check \u2014 surface new features after update",
4769
+ domain: "support",
4770
+ priority: "p1",
4771
+ 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."
4772
+ },
4683
4773
  // --- Platform vs Customer ownership ---
4684
4774
  {
4685
4775
  title: "What the platform provides vs what you customize",
@@ -4768,13 +4858,13 @@ var init_platform_procedures = __esm({
4768
4858
  title: "MCP tools \u2014 memory, decision, and search",
4769
4859
  domain: "tool-use",
4770
4860
  priority: "p1",
4771
- 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.`
4861
+ 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.`
4772
4862
  },
4773
4863
  {
4774
4864
  title: "MCP tools \u2014 task orchestration",
4775
4865
  domain: "tool-use",
4776
4866
  priority: "p1",
4777
- 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.'
4867
+ 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.`
4778
4868
  },
4779
4869
  {
4780
4870
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -4804,7 +4894,7 @@ var init_platform_procedures = __esm({
4804
4894
  title: "MCP tools \u2014 admin, config, and operations",
4805
4895
  domain: "tool-use",
4806
4896
  priority: "p1",
4807
- 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.'
4897
+ 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.'
4808
4898
  }
4809
4899
  ];
4810
4900
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5488,6 +5578,8 @@ async function writeMemory(record) {
5488
5578
  source_type: record.source_type ?? null,
5489
5579
  tier: record.tier ?? classifyTier(record),
5490
5580
  supersedes_id: record.supersedes_id ?? null,
5581
+ valid_from: record.valid_from ?? record.timestamp,
5582
+ invalid_at: record.invalid_at ?? null,
5491
5583
  draft: record.draft ? 1 : 0,
5492
5584
  memory_type: memoryType,
5493
5585
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5506,7 +5598,8 @@ async function writeMemory(record) {
5506
5598
  token_cost: record.token_cost ?? null,
5507
5599
  audience: record.audience ?? null,
5508
5600
  language_type: record.language_type ?? inferLanguageType(record),
5509
- parent_memory_id: record.parent_memory_id ?? null
5601
+ parent_memory_id: record.parent_memory_id ?? null,
5602
+ procedure_for: record.procedure_for ?? null
5510
5603
  };
5511
5604
  _pendingRecords.push(dbRow);
5512
5605
  orgBus.emit({
@@ -5561,6 +5654,8 @@ async function flushBatch() {
5561
5654
  const sourceType = row.source_type ?? null;
5562
5655
  const tier = row.tier ?? 3;
5563
5656
  const supersedesId = row.supersedes_id ?? null;
5657
+ const validFrom = row.valid_from ?? row.timestamp;
5658
+ const invalidAt = row.invalid_at ?? null;
5564
5659
  const draft = row.draft ? 1 : 0;
5565
5660
  const memoryType = row.memory_type ?? "raw";
5566
5661
  const trajectory = row.trajectory ?? null;
@@ -5580,15 +5675,16 @@ async function flushBatch() {
5580
5675
  const audience = row.audience ?? null;
5581
5676
  const languageType = row.language_type ?? null;
5582
5677
  const parentMemoryId = row.parent_memory_id ?? null;
5678
+ const procedureFor = row.procedure_for ?? null;
5583
5679
  const cols = `id, agent_id, agent_role, session_id, timestamp,
5584
5680
  tool_name, project_name,
5585
5681
  has_error, raw_text, vector, version, task_id, importance, status,
5586
5682
  confidence, last_accessed,
5587
5683
  workspace_id, document_id, user_id, char_offset, page_number,
5588
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
5684
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
5589
5685
  intent, outcome, domain, referenced_entities, retrieval_count,
5590
5686
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
5591
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
5687
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
5592
5688
  const metaArgs = [
5593
5689
  intent,
5594
5690
  outcome,
@@ -5604,7 +5700,8 @@ async function flushBatch() {
5604
5700
  tokenCost,
5605
5701
  audience,
5606
5702
  languageType,
5607
- parentMemoryId
5703
+ parentMemoryId,
5704
+ procedureFor
5608
5705
  ];
5609
5706
  const baseArgs = [
5610
5707
  row.id,
@@ -5633,6 +5730,8 @@ async function flushBatch() {
5633
5730
  sourceType,
5634
5731
  tier,
5635
5732
  supersedesId,
5733
+ validFrom,
5734
+ invalidAt,
5636
5735
  draft,
5637
5736
  memoryType,
5638
5737
  trajectory,
@@ -5640,8 +5739,8 @@ async function flushBatch() {
5640
5739
  ];
5641
5740
  return {
5642
5741
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
5643
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5644
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5742
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
5743
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5645
5744
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
5646
5745
  };
5647
5746
  };
@@ -5757,6 +5856,12 @@ async function searchMemories(queryVector, agentId, options) {
5757
5856
  AND vector IS NOT NULL${statusFilter}${draftFilter}
5758
5857
  AND COALESCE(confidence, 0.7) >= 0.3`;
5759
5858
  const args = [agentId];
5859
+ if (options?.asOf) {
5860
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
5861
+ args.push(options.asOf, options.asOf);
5862
+ } else {
5863
+ sql += ` AND invalid_at IS NULL`;
5864
+ }
5760
5865
  const scope = buildWikiScopeFilter(options, "");
5761
5866
  sql += scope.clause;
5762
5867
  args.push(...scope.args);