@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
package/dist/tui/App.js CHANGED
@@ -3586,6 +3586,20 @@ async function ensureSchema() {
3586
3586
  });
3587
3587
  } catch {
3588
3588
  }
3589
+ try {
3590
+ await client.execute({
3591
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3592
+ args: []
3593
+ });
3594
+ } catch {
3595
+ }
3596
+ try {
3597
+ await client.execute({
3598
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3599
+ args: []
3600
+ });
3601
+ } catch {
3602
+ }
3589
3603
  await client.executeMultiple(`
3590
3604
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3591
3605
  content_text,
@@ -3837,6 +3851,22 @@ async function ensureSchema() {
3837
3851
  );
3838
3852
  } catch {
3839
3853
  }
3854
+ for (const col of [
3855
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3856
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3857
+ ]) {
3858
+ try {
3859
+ await client.execute(col);
3860
+ } catch {
3861
+ }
3862
+ }
3863
+ try {
3864
+ await client.execute({
3865
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3866
+ args: []
3867
+ });
3868
+ } catch {
3869
+ }
3840
3870
  try {
3841
3871
  await client.execute({
3842
3872
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3879,6 +3909,13 @@ async function ensureSchema() {
3879
3909
  } catch {
3880
3910
  }
3881
3911
  }
3912
+ try {
3913
+ await client.execute({
3914
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3915
+ args: []
3916
+ });
3917
+ } catch {
3918
+ }
3882
3919
  try {
3883
3920
  await client.execute({
3884
3921
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -4368,7 +4405,7 @@ var init_license = __esm({
4368
4405
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
4369
4406
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
4370
4407
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
4371
- API_BASE = "https://askexe.com/cloud";
4408
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4372
4409
  RETRY_DELAY_MS = 500;
4373
4410
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4374
4411
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -5021,8 +5058,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
5021
5058
  const complexity = input.complexity ?? "standard";
5022
5059
  const sessionScope = earlySessionScope;
5023
5060
  await client.execute({
5024
- 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)
5025
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5061
+ 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)
5062
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5026
5063
  args: [
5027
5064
  id,
5028
5065
  input.title,
@@ -5042,6 +5079,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
5042
5079
  0,
5043
5080
  null,
5044
5081
  sessionScope,
5082
+ input.spawnRuntime ?? null,
5083
+ input.spawnModel ?? null,
5045
5084
  now,
5046
5085
  now
5047
5086
  ]
@@ -5098,7 +5137,9 @@ ${input.context}
5098
5137
  budgetTokens: input.budgetTokens ?? null,
5099
5138
  budgetFallbackModel: input.budgetFallbackModel ?? null,
5100
5139
  tokensUsed: 0,
5101
- tokensWarnedAt: null
5140
+ tokensWarnedAt: null,
5141
+ spawnRuntime: input.spawnRuntime ?? null,
5142
+ spawnModel: input.spawnModel ?? null
5102
5143
  };
5103
5144
  }
5104
5145
  async function listTasks(input) {
@@ -5148,7 +5189,9 @@ async function listTasks(input) {
5148
5189
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
5149
5190
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
5150
5191
  tokensUsed: Number(r.tokens_used ?? 0),
5151
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
5192
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
5193
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
5194
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
5152
5195
  }));
5153
5196
  }
5154
5197
  function isTmuxSessionAlive(identifier) {
@@ -6445,6 +6488,8 @@ async function updateTask(input) {
6445
6488
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
6446
6489
  tokensUsed: Number(row.tokens_used ?? 0),
6447
6490
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
6491
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
6492
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
6448
6493
  nextTask
6449
6494
  };
6450
6495
  }
@@ -6937,9 +6982,13 @@ function getDispatchedBy(sessionKey) {
6937
6982
  }
6938
6983
  }
6939
6984
  function resolveExeSession() {
6985
+ if (process.env.EXE_SESSION_NAME) {
6986
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6987
+ }
6940
6988
  const mySession = getMySession();
6941
6989
  if (!mySession) return null;
6942
6990
  const fromSessionName = extractRootExe(mySession);
6991
+ let candidate = null;
6943
6992
  try {
6944
6993
  const key = getSessionKey();
6945
6994
  const parentExe = getParentExe(key);
@@ -6950,13 +6999,47 @@ function resolveExeSession() {
6950
6999
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6951
7000
  `
6952
7001
  );
6953
- return fromSessionName;
7002
+ candidate = fromSessionName;
7003
+ } else {
7004
+ candidate = fromCache;
6954
7005
  }
6955
- return fromCache;
6956
7006
  }
6957
7007
  } catch {
6958
7008
  }
6959
- return fromSessionName ?? mySession;
7009
+ if (!candidate) {
7010
+ candidate = fromSessionName ?? mySession;
7011
+ }
7012
+ if (candidate && isRootSession(candidate)) {
7013
+ try {
7014
+ const transport = getTransport();
7015
+ const liveSessions = transport.listSessions();
7016
+ if (!liveSessions.includes(candidate)) {
7017
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
7018
+ if (liveRoots.length === 1) {
7019
+ process.stderr.write(
7020
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
7021
+ `
7022
+ );
7023
+ return liveRoots[0];
7024
+ } else if (liveRoots.length > 1) {
7025
+ const base = candidate.replace(/\d+$/, "");
7026
+ const match = liveRoots.find((s) => s.startsWith(base));
7027
+ const chosen = match ?? liveRoots[0];
7028
+ process.stderr.write(
7029
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
7030
+ `
7031
+ );
7032
+ return chosen;
7033
+ }
7034
+ process.stderr.write(
7035
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
7036
+ `
7037
+ );
7038
+ }
7039
+ } catch {
7040
+ }
7041
+ }
7042
+ return candidate;
6960
7043
  }
6961
7044
  function isEmployeeAlive(sessionName) {
6962
7045
  return getTransport().isAlive(sessionName);
@@ -7358,7 +7441,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7358
7441
  }
7359
7442
  const spawnCwd = opts?.cwd ?? projectDir;
7360
7443
  const useExeAgent = !!(opts?.model && opts?.provider);
7361
- const agentRtConfig = getAgentRuntime(employeeName);
7444
+ const baseRtConfig = getAgentRuntime(employeeName);
7445
+ const agentRtConfig = {
7446
+ ...baseRtConfig,
7447
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
7448
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
7449
+ };
7362
7450
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
7363
7451
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
7364
7452
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -8991,6 +9079,20 @@ var init_platform_procedures = __esm({
8991
9079
  priority: "p1",
8992
9080
  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."
8993
9081
  },
9082
+ // --- Tool guidance ---
9083
+ {
9084
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
9085
+ domain: "tools",
9086
+ priority: "p2",
9087
+ 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."
9088
+ },
9089
+ // --- Release awareness ---
9090
+ {
9091
+ title: "What's New check \u2014 surface new features after update",
9092
+ domain: "support",
9093
+ priority: "p1",
9094
+ 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."
9095
+ },
8994
9096
  // --- Platform vs Customer ownership ---
8995
9097
  {
8996
9098
  title: "What the platform provides vs what you customize",
@@ -9079,13 +9181,13 @@ var init_platform_procedures = __esm({
9079
9181
  title: "MCP tools \u2014 memory, decision, and search",
9080
9182
  domain: "tool-use",
9081
9183
  priority: "p1",
9082
- 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.`
9184
+ 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.`
9083
9185
  },
9084
9186
  {
9085
9187
  title: "MCP tools \u2014 task orchestration",
9086
9188
  domain: "tool-use",
9087
9189
  priority: "p1",
9088
- 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.'
9190
+ 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.'
9089
9191
  },
9090
9192
  {
9091
9193
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -9115,7 +9217,7 @@ var init_platform_procedures = __esm({
9115
9217
  title: "MCP tools \u2014 admin, config, and operations",
9116
9218
  domain: "tool-use",
9117
9219
  priority: "p1",
9118
- 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.'
9220
+ 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.'
9119
9221
  }
9120
9222
  ];
9121
9223
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -12936,6 +13038,8 @@ async function writeMemory(record) {
12936
13038
  source_type: record.source_type ?? null,
12937
13039
  tier: record.tier ?? classifyTier(record),
12938
13040
  supersedes_id: record.supersedes_id ?? null,
13041
+ valid_from: record.valid_from ?? record.timestamp,
13042
+ invalid_at: record.invalid_at ?? null,
12939
13043
  draft: record.draft ? 1 : 0,
12940
13044
  memory_type: memoryType,
12941
13045
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -12954,7 +13058,8 @@ async function writeMemory(record) {
12954
13058
  token_cost: record.token_cost ?? null,
12955
13059
  audience: record.audience ?? null,
12956
13060
  language_type: record.language_type ?? inferLanguageType(record),
12957
- parent_memory_id: record.parent_memory_id ?? null
13061
+ parent_memory_id: record.parent_memory_id ?? null,
13062
+ procedure_for: record.procedure_for ?? null
12958
13063
  };
12959
13064
  _pendingRecords.push(dbRow);
12960
13065
  orgBus.emit({
@@ -13009,6 +13114,8 @@ async function flushBatch() {
13009
13114
  const sourceType = row.source_type ?? null;
13010
13115
  const tier = row.tier ?? 3;
13011
13116
  const supersedesId = row.supersedes_id ?? null;
13117
+ const validFrom = row.valid_from ?? row.timestamp;
13118
+ const invalidAt = row.invalid_at ?? null;
13012
13119
  const draft = row.draft ? 1 : 0;
13013
13120
  const memoryType = row.memory_type ?? "raw";
13014
13121
  const trajectory = row.trajectory ?? null;
@@ -13028,15 +13135,16 @@ async function flushBatch() {
13028
13135
  const audience = row.audience ?? null;
13029
13136
  const languageType = row.language_type ?? null;
13030
13137
  const parentMemoryId = row.parent_memory_id ?? null;
13138
+ const procedureFor = row.procedure_for ?? null;
13031
13139
  const cols = `id, agent_id, agent_role, session_id, timestamp,
13032
13140
  tool_name, project_name,
13033
13141
  has_error, raw_text, vector, version, task_id, importance, status,
13034
13142
  confidence, last_accessed,
13035
13143
  workspace_id, document_id, user_id, char_offset, page_number,
13036
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
13144
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
13037
13145
  intent, outcome, domain, referenced_entities, retrieval_count,
13038
13146
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
13039
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
13147
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
13040
13148
  const metaArgs = [
13041
13149
  intent,
13042
13150
  outcome,
@@ -13052,7 +13160,8 @@ async function flushBatch() {
13052
13160
  tokenCost,
13053
13161
  audience,
13054
13162
  languageType,
13055
- parentMemoryId
13163
+ parentMemoryId,
13164
+ procedureFor
13056
13165
  ];
13057
13166
  const baseArgs = [
13058
13167
  row.id,
@@ -13081,6 +13190,8 @@ async function flushBatch() {
13081
13190
  sourceType,
13082
13191
  tier,
13083
13192
  supersedesId,
13193
+ validFrom,
13194
+ invalidAt,
13084
13195
  draft,
13085
13196
  memoryType,
13086
13197
  trajectory,
@@ -13088,8 +13199,8 @@ async function flushBatch() {
13088
13199
  ];
13089
13200
  return {
13090
13201
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
13091
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
13092
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
13202
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
13203
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
13093
13204
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
13094
13205
  };
13095
13206
  };
@@ -13205,6 +13316,12 @@ async function searchMemories(queryVector, agentId, options) {
13205
13316
  AND vector IS NOT NULL${statusFilter}${draftFilter}
13206
13317
  AND COALESCE(confidence, 0.7) >= 0.3`;
13207
13318
  const args = [agentId];
13319
+ if (options?.asOf) {
13320
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
13321
+ args.push(options.asOf, options.asOf);
13322
+ } else {
13323
+ sql += ` AND invalid_at IS NULL`;
13324
+ }
13208
13325
  const scope = buildWikiScopeFilter(options, "");
13209
13326
  sql += scope.clause;
13210
13327
  args.push(...scope.args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.9.93",
3
+ "version": "0.9.95",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",