@vellumai/assistant 0.3.14 → 0.3.16

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 (295) hide show
  1. package/ARCHITECTURE.md +142 -0
  2. package/Dockerfile +2 -2
  3. package/README.md +5 -5
  4. package/docs/architecture/http-token-refresh.md +252 -0
  5. package/docs/architecture/memory.md +5 -4
  6. package/docs/architecture/scheduling.md +4 -88
  7. package/docs/runbook-trusted-contacts.md +283 -0
  8. package/docs/trusted-contact-access.md +247 -0
  9. package/package.json +1 -1
  10. package/scripts/ipc/check-swift-decoder-drift.ts +2 -0
  11. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2 -6
  12. package/src/__tests__/access-request-decision.test.ts +331 -0
  13. package/src/__tests__/asset-materialize-tool.test.ts +7 -7
  14. package/src/__tests__/asset-search-tool.test.ts +15 -15
  15. package/src/__tests__/attachments-store.test.ts +13 -13
  16. package/src/__tests__/call-controller.test.ts +150 -4
  17. package/src/__tests__/call-conversation-messages.test.ts +2 -2
  18. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  19. package/src/__tests__/call-start-guardian-guard.test.ts +93 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +108 -12
  21. package/src/__tests__/channel-guardian.test.ts +16 -14
  22. package/src/__tests__/checker.test.ts +24 -0
  23. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +2 -2
  24. package/src/__tests__/config-watcher.test.ts +358 -0
  25. package/src/__tests__/conversation-pairing.test.ts +24 -24
  26. package/src/__tests__/conversation-store.test.ts +36 -36
  27. package/src/__tests__/date-context.test.ts +179 -1
  28. package/src/__tests__/db-migration-rollback.test.ts +4 -7
  29. package/src/__tests__/deterministic-verification-control-plane.test.ts +5 -5
  30. package/src/__tests__/emit-signal-routing-intent.test.ts +179 -0
  31. package/src/__tests__/gateway-only-guard.test.ts +188 -0
  32. package/src/__tests__/guardian-action-conversation-turn.test.ts +451 -0
  33. package/src/__tests__/guardian-action-copy-generator.test.ts +197 -0
  34. package/src/__tests__/guardian-action-followup-executor.test.ts +379 -0
  35. package/src/__tests__/guardian-action-followup-store.test.ts +376 -0
  36. package/src/__tests__/guardian-action-late-reply.test.ts +294 -0
  37. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +71 -0
  38. package/src/__tests__/guardian-action-sweep.test.ts +9 -9
  39. package/src/__tests__/guardian-control-plane-policy.test.ts +1 -3
  40. package/src/__tests__/guardian-outbound-http.test.ts +202 -10
  41. package/src/__tests__/guardian-verification-intent-routing.test.ts +179 -0
  42. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +141 -0
  43. package/src/__tests__/handlers-telegram-config.test.ts +6 -6
  44. package/src/__tests__/hooks-runner.test.ts +13 -4
  45. package/src/__tests__/ingress-routes-http.test.ts +443 -0
  46. package/src/__tests__/intent-routing.test.ts +14 -0
  47. package/src/__tests__/ipc-snapshot.test.ts +2 -5
  48. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  49. package/src/__tests__/memory-regressions.test.ts +16 -12
  50. package/src/__tests__/non-member-access-request.test.ts +282 -0
  51. package/src/__tests__/notification-decision-strategy.test.ts +136 -0
  52. package/src/__tests__/notification-routing-intent.test.ts +11 -2
  53. package/src/__tests__/notification-thread-candidates.test.ts +166 -0
  54. package/src/__tests__/recording-intent-fallback.test.ts +0 -1
  55. package/src/__tests__/recording-intent-handler.test.ts +6 -3
  56. package/src/__tests__/recording-intent.test.ts +3 -2
  57. package/src/__tests__/recording-state-machine.test.ts +337 -26
  58. package/src/__tests__/registry.test.ts +17 -8
  59. package/src/__tests__/relay-server.test.ts +105 -0
  60. package/src/__tests__/reminder.test.ts +13 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +4 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +50 -0
  63. package/src/__tests__/server-history-render.test.ts +8 -8
  64. package/src/__tests__/session-agent-loop.test.ts +1 -0
  65. package/src/__tests__/session-runtime-assembly.test.ts +49 -0
  66. package/src/__tests__/session-skill-tools.test.ts +1 -0
  67. package/src/__tests__/skill-projection.benchmark.test.ts +11 -3
  68. package/src/__tests__/slack-channel-config.test.ts +230 -0
  69. package/src/__tests__/subagent-manager-notify.test.ts +4 -4
  70. package/src/__tests__/swarm-session-integration.test.ts +2 -2
  71. package/src/__tests__/system-prompt.test.ts +43 -0
  72. package/src/__tests__/task-management-tools.test.ts +3 -3
  73. package/src/__tests__/task-tools.test.ts +3 -3
  74. package/src/__tests__/trust-store.test.ts +17 -1
  75. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +491 -0
  76. package/src/__tests__/trusted-contact-multichannel.test.ts +409 -0
  77. package/src/__tests__/trusted-contact-verification.test.ts +360 -0
  78. package/src/__tests__/update-bulletin-format.test.ts +119 -0
  79. package/src/__tests__/update-bulletin-state.test.ts +129 -0
  80. package/src/__tests__/update-bulletin.test.ts +260 -0
  81. package/src/__tests__/update-template-contract.test.ts +29 -0
  82. package/src/agent/loop.ts +2 -2
  83. package/src/amazon/client.ts +2 -3
  84. package/src/calls/call-controller.ts +115 -34
  85. package/src/calls/call-conversation-messages.ts +2 -2
  86. package/src/calls/call-domain.ts +10 -3
  87. package/src/calls/call-pointer-messages.ts +17 -5
  88. package/src/calls/guardian-action-sweep.ts +77 -36
  89. package/src/calls/relay-server.ts +51 -12
  90. package/src/calls/twilio-routes.ts +3 -1
  91. package/src/calls/types.ts +1 -1
  92. package/src/calls/voice-session-bridge.ts +4 -4
  93. package/src/cli/core-commands.ts +3 -3
  94. package/src/cli/map.ts +8 -5
  95. package/src/config/bundled-skills/phone-calls/SKILL.md +16 -1
  96. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  97. package/src/config/bundled-skills/tasks/TOOLS.json +4 -4
  98. package/src/config/bundled-skills/time-based-actions/SKILL.md +11 -1
  99. package/src/config/computer-use-prompt.ts +1 -0
  100. package/src/config/core-schema.ts +16 -0
  101. package/src/config/env-registry.ts +1 -0
  102. package/src/config/env.ts +16 -1
  103. package/src/config/memory-schema.ts +5 -0
  104. package/src/config/schema.ts +4 -0
  105. package/src/config/system-prompt.ts +69 -2
  106. package/src/config/templates/BOOTSTRAP.md +1 -1
  107. package/src/config/templates/IDENTITY.md +8 -4
  108. package/src/config/templates/SOUL.md +14 -0
  109. package/src/config/templates/UPDATES.md +16 -0
  110. package/src/config/templates/USER.md +5 -1
  111. package/src/config/types.ts +1 -0
  112. package/src/config/update-bulletin-format.ts +52 -0
  113. package/src/config/update-bulletin-state.ts +49 -0
  114. package/src/config/update-bulletin.ts +82 -0
  115. package/src/config/vellum-skills/catalog.json +6 -0
  116. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  117. package/src/config/vellum-skills/guardian-verify-setup/SKILL.md +44 -10
  118. package/src/config/vellum-skills/telegram-setup/SKILL.md +4 -4
  119. package/src/config/vellum-skills/trusted-contacts/SKILL.md +147 -0
  120. package/src/config/vellum-skills/twilio-setup/SKILL.md +2 -2
  121. package/src/context/window-manager.ts +43 -3
  122. package/src/daemon/config-watcher.ts +1 -0
  123. package/src/daemon/connection-policy.ts +21 -1
  124. package/src/daemon/daemon-control.ts +164 -7
  125. package/src/daemon/date-context.ts +174 -1
  126. package/src/daemon/guardian-action-generators.ts +175 -0
  127. package/src/daemon/guardian-verification-intent.ts +120 -0
  128. package/src/daemon/handlers/apps.ts +1 -3
  129. package/src/daemon/handlers/config-channels.ts +8 -8
  130. package/src/daemon/handlers/config-heartbeat.ts +1 -1
  131. package/src/daemon/handlers/config-inbox.ts +55 -159
  132. package/src/daemon/handlers/config-ingress.ts +1 -1
  133. package/src/daemon/handlers/config-integrations.ts +1 -1
  134. package/src/daemon/handlers/config-platform.ts +1 -1
  135. package/src/daemon/handlers/config-scheduling.ts +2 -2
  136. package/src/daemon/handlers/config-slack-channel.ts +190 -0
  137. package/src/daemon/handlers/config-telegram.ts +1 -1
  138. package/src/daemon/handlers/config-twilio.ts +1 -1
  139. package/src/daemon/handlers/config-voice.ts +100 -0
  140. package/src/daemon/handlers/config.ts +3 -0
  141. package/src/daemon/handlers/index.ts +1 -1
  142. package/src/daemon/handlers/misc.ts +84 -6
  143. package/src/daemon/handlers/navigate-settings.ts +27 -0
  144. package/src/daemon/handlers/recording.ts +270 -144
  145. package/src/daemon/handlers/sessions.ts +107 -24
  146. package/src/daemon/handlers/subagents.ts +3 -3
  147. package/src/daemon/handlers/work-items.ts +10 -7
  148. package/src/daemon/ipc-contract/integrations.ts +9 -1
  149. package/src/daemon/ipc-contract/messages.ts +4 -0
  150. package/src/daemon/ipc-contract/sessions.ts +1 -1
  151. package/src/daemon/ipc-contract/settings.ts +26 -0
  152. package/src/daemon/ipc-contract/shared.ts +2 -0
  153. package/src/daemon/ipc-contract/work-items.ts +1 -7
  154. package/src/daemon/ipc-contract-inventory.json +5 -1
  155. package/src/daemon/ipc-contract.ts +5 -1
  156. package/src/daemon/lifecycle.ts +306 -266
  157. package/src/daemon/recording-executor.ts +1 -1
  158. package/src/daemon/recording-intent.ts +0 -41
  159. package/src/daemon/response-tier.ts +2 -2
  160. package/src/daemon/server.ts +6 -6
  161. package/src/daemon/session-agent-loop-handlers.ts +34 -9
  162. package/src/daemon/session-agent-loop.ts +15 -8
  163. package/src/daemon/session-history.ts +3 -2
  164. package/src/daemon/session-media-retry.ts +3 -0
  165. package/src/daemon/session-messaging.ts +38 -4
  166. package/src/daemon/session-notifiers.ts +2 -2
  167. package/src/daemon/session-process.ts +256 -23
  168. package/src/daemon/session-queue-manager.ts +2 -0
  169. package/src/daemon/session-runtime-assembly.ts +39 -0
  170. package/src/daemon/session-skill-tools.ts +13 -4
  171. package/src/daemon/session-tool-setup.ts +6 -7
  172. package/src/daemon/session.ts +19 -8
  173. package/src/daemon/tls-certs.ts +55 -13
  174. package/src/daemon/tool-side-effects.ts +13 -5
  175. package/src/gallery/default-gallery.ts +32 -9
  176. package/src/influencer/client.ts +2 -1
  177. package/src/memory/channel-delivery-store.ts +37 -567
  178. package/src/memory/channel-guardian-store.ts +66 -1317
  179. package/src/memory/conflict-store.ts +4 -4
  180. package/src/memory/conversation-attention-store.ts +4 -7
  181. package/src/memory/conversation-crud.ts +668 -0
  182. package/src/memory/conversation-queries.ts +361 -0
  183. package/src/memory/conversation-store.ts +45 -983
  184. package/src/memory/db-connection.ts +3 -0
  185. package/src/memory/db-init.ts +25 -0
  186. package/src/memory/delivery-channels.ts +175 -0
  187. package/src/memory/delivery-crud.ts +211 -0
  188. package/src/memory/delivery-status.ts +199 -0
  189. package/src/memory/embedding-backend.ts +70 -4
  190. package/src/memory/embedding-local.ts +12 -2
  191. package/src/memory/entity-extractor.ts +3 -8
  192. package/src/memory/fts-reconciler.ts +121 -0
  193. package/src/memory/guardian-action-store.ts +366 -3
  194. package/src/memory/guardian-approvals.ts +569 -0
  195. package/src/memory/guardian-bindings.ts +130 -0
  196. package/src/memory/guardian-rate-limits.ts +196 -0
  197. package/src/memory/guardian-verification.ts +520 -0
  198. package/src/memory/job-handlers/index-maintenance.ts +2 -1
  199. package/src/memory/job-utils.ts +8 -5
  200. package/src/memory/jobs-store.ts +66 -6
  201. package/src/memory/jobs-worker.ts +23 -1
  202. package/src/memory/migrations/030-guardian-action-followup.ts +21 -0
  203. package/src/memory/migrations/030-guardian-verification-purpose.ts +17 -0
  204. package/src/memory/migrations/031-conversations-thread-type-index.ts +5 -0
  205. package/src/memory/migrations/100-core-tables.ts +1 -1
  206. package/src/memory/migrations/101-watchers-and-logs.ts +4 -0
  207. package/src/memory/migrations/108-tasks-and-work-items.ts +1 -1
  208. package/src/memory/migrations/112-assistant-inbox.ts +1 -1
  209. package/src/memory/migrations/113-late-migrations.ts +1 -1
  210. package/src/memory/migrations/116-messages-fts.ts +13 -0
  211. package/src/memory/migrations/119-schema-indexes-and-columns.ts +37 -0
  212. package/src/memory/migrations/120-fk-cascade-rebuilds.ts +161 -0
  213. package/src/memory/migrations/index.ts +8 -3
  214. package/src/memory/migrations/validate-migration-state.ts +114 -15
  215. package/src/memory/qdrant-circuit-breaker.ts +105 -0
  216. package/src/memory/retriever.ts +46 -13
  217. package/src/memory/schema-migration.ts +3 -0
  218. package/src/memory/schema.ts +25 -7
  219. package/src/memory/search/semantic.ts +8 -90
  220. package/src/notifications/README.md +1 -1
  221. package/src/notifications/broadcaster.ts +20 -2
  222. package/src/notifications/conversation-pairing.ts +3 -3
  223. package/src/notifications/decision-engine.ts +173 -8
  224. package/src/notifications/deliveries-store.ts +27 -8
  225. package/src/notifications/preferences-store.ts +7 -7
  226. package/src/notifications/thread-candidates.ts +234 -0
  227. package/src/notifications/types.ts +18 -0
  228. package/src/permissions/defaults.ts +11 -1
  229. package/src/permissions/prompter.ts +17 -0
  230. package/src/permissions/trust-store.ts +2 -0
  231. package/src/providers/failover.ts +19 -0
  232. package/src/providers/registry.ts +46 -1
  233. package/src/runtime/approval-message-composer.ts +1 -1
  234. package/src/runtime/channel-guardian-service.ts +15 -3
  235. package/src/runtime/channel-retry-sweep.ts +7 -2
  236. package/src/runtime/guardian-action-conversation-turn.ts +85 -0
  237. package/src/runtime/guardian-action-followup-executor.ts +301 -0
  238. package/src/runtime/guardian-action-message-composer.ts +245 -0
  239. package/src/runtime/guardian-outbound-actions.ts +35 -15
  240. package/src/runtime/guardian-verification-templates.ts +15 -9
  241. package/src/runtime/http-errors.ts +93 -0
  242. package/src/runtime/http-server.ts +140 -51
  243. package/src/runtime/http-types.ts +53 -0
  244. package/src/runtime/ingress-service.ts +237 -0
  245. package/src/runtime/middleware/error-handler.ts +4 -3
  246. package/src/runtime/middleware/rate-limiter.ts +160 -0
  247. package/src/runtime/middleware/request-logger.ts +71 -0
  248. package/src/runtime/middleware/twilio-validation.ts +7 -6
  249. package/src/runtime/pending-interactions.ts +12 -0
  250. package/src/runtime/routes/access-request-decision.ts +215 -0
  251. package/src/runtime/routes/app-routes.ts +25 -18
  252. package/src/runtime/routes/approval-routes.ts +18 -47
  253. package/src/runtime/routes/attachment-routes.ts +15 -41
  254. package/src/runtime/routes/call-routes.ts +20 -20
  255. package/src/runtime/routes/channel-delivery-routes.ts +6 -5
  256. package/src/runtime/routes/contact-routes.ts +4 -9
  257. package/src/runtime/routes/conversation-attention-routes.ts +5 -4
  258. package/src/runtime/routes/conversation-routes.ts +26 -57
  259. package/src/runtime/routes/debug-routes.ts +71 -0
  260. package/src/runtime/routes/events-routes.ts +3 -2
  261. package/src/runtime/routes/guardian-approval-interception.ts +221 -0
  262. package/src/runtime/routes/identity-routes.ts +14 -10
  263. package/src/runtime/routes/inbound-conversation.ts +3 -2
  264. package/src/runtime/routes/inbound-message-handler.ts +527 -62
  265. package/src/runtime/routes/ingress-routes.ts +174 -0
  266. package/src/runtime/routes/integration-routes.ts +82 -20
  267. package/src/runtime/routes/pairing-routes.ts +11 -10
  268. package/src/runtime/routes/secret-routes.ts +10 -18
  269. package/src/runtime/verification-rate-limiter.ts +83 -0
  270. package/src/schedule/schedule-store.ts +13 -1
  271. package/src/schedule/scheduler.ts +2 -2
  272. package/src/security/secret-ingress.ts +5 -2
  273. package/src/security/secret-scanner.ts +72 -6
  274. package/src/subagent/manager.ts +6 -4
  275. package/src/swarm/plan-validator.ts +4 -1
  276. package/src/tasks/task-runner.ts +3 -1
  277. package/src/tools/browser/api-map.ts +9 -6
  278. package/src/tools/calls/call-start.ts +20 -0
  279. package/src/tools/executor.ts +50 -568
  280. package/src/tools/permission-checker.ts +272 -0
  281. package/src/tools/registry.ts +14 -6
  282. package/src/tools/reminder/reminder-store.ts +7 -7
  283. package/src/tools/reminder/reminder.ts +6 -3
  284. package/src/tools/secret-detection-handler.ts +301 -0
  285. package/src/tools/subagent/message.ts +1 -1
  286. package/src/tools/system/voice-config.ts +62 -0
  287. package/src/tools/tasks/index.ts +3 -3
  288. package/src/tools/tasks/work-item-list.ts +3 -3
  289. package/src/tools/tasks/work-item-update.ts +4 -5
  290. package/src/tools/tool-approval-handler.ts +192 -0
  291. package/src/tools/tool-manifest.ts +2 -0
  292. package/src/watcher/watcher-store.ts +9 -9
  293. package/src/work-items/work-item-runner.ts +9 -6
  294. /package/src/memory/migrations/{026-embeddings-nullable-vector-json.ts → 026a-embeddings-nullable-vector-json.ts} +0 -0
  295. /package/src/memory/migrations/{027-guardian-bootstrap-token.ts → 027a-guardian-bootstrap-token.ts} +0 -0
@@ -4,12 +4,77 @@ import { getOllamaBaseUrlEnv } from '../config/env.js';
4
4
  import type { AssistantConfig } from '../config/types.js';
5
5
  import { getLogger } from '../util/logger.js';
6
6
  import { GeminiEmbeddingBackend } from './embedding-gemini.js';
7
- import { LocalEmbeddingBackend } from './embedding-local.js';
8
7
  import { OllamaEmbeddingBackend } from './embedding-ollama.js';
9
8
  import { OpenAIEmbeddingBackend } from './embedding-openai.js';
10
9
 
11
10
  const log = getLogger('memory-embeddings');
12
11
 
12
+ // Tracks whether the local embedding backend has permanently failed to load
13
+ // (e.g., onnxruntime-node missing in a compiled binary). Once set, `auto` mode
14
+ // skips `local` as primary, avoiding repeated fallback latency and cost.
15
+ let localBackendBroken = false;
16
+
17
+ /**
18
+ * Lazy wrapper around LocalEmbeddingBackend that dynamically imports the
19
+ * module on first use. This avoids eagerly loading @huggingface/transformers
20
+ * (which statically imports onnxruntime-node) at module evaluation time.
21
+ * In compiled binaries where onnxruntime-node isn't bundled, the static
22
+ * import would crash the entire daemon at startup. By deferring the import,
23
+ * the failure is contained and other embedding backends can be used instead.
24
+ */
25
+
26
+ class LazyLocalEmbeddingBackend implements EmbeddingBackend {
27
+ readonly provider = 'local' as const;
28
+ readonly model: string;
29
+ private delegate: EmbeddingBackend | null = null;
30
+ private initPromise: Promise<EmbeddingBackend> | null = null;
31
+
32
+ constructor(model: string) {
33
+ this.model = model;
34
+ }
35
+
36
+ async embed(texts: string[], options?: EmbeddingRequestOptions): Promise<number[][]> {
37
+ const backend = await this.getDelegate();
38
+ try {
39
+ return await backend.embed(texts, options);
40
+ } catch (err) {
41
+ // The onnxruntime-node failure surfaces here during the first embed() call
42
+ // (via LocalEmbeddingBackend.initialize()). Mark broken so auto mode stops
43
+ // selecting local on subsequent requests.
44
+ if (!localBackendBroken && isInitializationError(err)) {
45
+ localBackendBroken = true;
46
+ log.warn({ err }, 'Local embedding backend permanently unavailable; auto mode will skip it');
47
+ }
48
+ throw err;
49
+ }
50
+ }
51
+
52
+ private async getDelegate(): Promise<EmbeddingBackend> {
53
+ if (this.delegate) return this.delegate;
54
+ if (!this.initPromise) {
55
+ this.initPromise = (async () => {
56
+ try {
57
+ const { LocalEmbeddingBackend } = await import('./embedding-local.js');
58
+ this.delegate = new LocalEmbeddingBackend(this.model);
59
+ return this.delegate;
60
+ } catch (err) {
61
+ localBackendBroken = true;
62
+ log.warn({ err }, 'Local embedding backend permanently unavailable; auto mode will skip it');
63
+ throw err;
64
+ }
65
+ })();
66
+ }
67
+ return this.initPromise;
68
+ }
69
+ }
70
+
71
+ /** Detect errors thrown by LocalEmbeddingBackend.initialize() so we can
72
+ * distinguish permanent init failures from transient embed-time errors. */
73
+ function isInitializationError(err: unknown): boolean {
74
+ if (!(err instanceof Error)) return false;
75
+ return err.message.includes('Local embedding backend unavailable');
76
+ }
77
+
13
78
  /** Global cache of embedding backend instances, keyed by "provider:model". */
14
79
  const backendCache = new Map<string, EmbeddingBackend>();
15
80
 
@@ -69,6 +134,7 @@ export function clearEmbeddingBackendCache(): void {
69
134
  backendCache.clear();
70
135
  vectorCache.clear();
71
136
  vectorCacheBytes = 0;
137
+ localBackendBroken = false;
72
138
  }
73
139
 
74
140
  function cacheKey(provider: string, model: string): string {
@@ -106,7 +172,7 @@ export function selectEmbeddingBackend(config: AssistantConfig): EmbeddingBacken
106
172
  if (requested === 'local') {
107
173
  return {
108
174
  backend: getCachedOrCreate('local', config.memory.embeddings.localModel,
109
- () => new LocalEmbeddingBackend(config.memory.embeddings.localModel)),
175
+ () => new LazyLocalEmbeddingBackend(config.memory.embeddings.localModel)),
110
176
  reason: null,
111
177
  };
112
178
  }
@@ -128,10 +194,10 @@ export function selectEmbeddingBackend(config: AssistantConfig): EmbeddingBacken
128
194
  for (const provider of order) {
129
195
  switch (provider) {
130
196
  case 'local':
131
- // Local embeddings are always available (model downloaded on first use)
197
+ if (localBackendBroken) continue;
132
198
  return {
133
199
  backend: getCachedOrCreate('local', config.memory.embeddings.localModel,
134
- () => new LocalEmbeddingBackend(config.memory.embeddings.localModel)),
200
+ () => new LazyLocalEmbeddingBackend(config.memory.embeddings.localModel)),
135
201
  reason: null,
136
202
  };
137
203
  case 'openai':
@@ -56,8 +56,18 @@ export class LocalEmbeddingBackend implements EmbeddingBackend {
56
56
 
57
57
  private async initialize(): Promise<void> {
58
58
  log.info({ model: this.model }, 'Loading local embedding model (first load downloads the model)');
59
- const { pipeline } = await import('@huggingface/transformers');
60
- this.extractor = await pipeline('feature-extraction', this.model, {
59
+ let transformers: typeof import('@huggingface/transformers');
60
+ try {
61
+ transformers = await import('@huggingface/transformers');
62
+ } catch (err) {
63
+ // onnxruntime-node is not bundled in compiled binaries, so the import
64
+ // fails at runtime. Surface a clear error so callers can fall back to
65
+ // another embedding backend.
66
+ throw new Error(
67
+ `Local embedding backend unavailable: failed to load @huggingface/transformers (${err instanceof Error ? err.message : String(err)})`,
68
+ );
69
+ }
70
+ this.extractor = await transformers.pipeline('feature-extraction', this.model, {
61
71
  dtype: 'fp32',
62
72
  }) as unknown as FeatureExtractionPipeline;
63
73
  log.info({ model: this.model }, 'Local embedding model loaded');
@@ -4,7 +4,7 @@ import type { MemoryEntityConfig } from '../config/types.js';
4
4
  import { createTimeout, extractToolUse, getConfiguredProvider, userMessage } from '../providers/provider-send-message.js';
5
5
  import { getLogger } from '../util/logger.js';
6
6
  import { truncate } from '../util/truncate.js';
7
- import { getDb } from './db.js';
7
+ import { getDb, rawAll } from './db.js';
8
8
  import { memoryEntities, memoryEntityRelations, memoryItemEntities } from './schema.js';
9
9
 
10
10
  const log = getLogger('memory-entity-extractor');
@@ -426,20 +426,15 @@ function parseExtractedRelations(
426
426
  function findEntityCandidates(nameOrAlias: string): Array<typeof memoryEntities.$inferSelect> {
427
427
  const normalized = normalizeEntityName(nameOrAlias);
428
428
  if (!normalized) return [];
429
- const db = getDb();
430
429
  const nameLower = normalized.toLowerCase();
431
430
 
432
- const raw = (db as unknown as {
433
- $client: { query: (q: string) => { all: (...params: unknown[]) => unknown[] } };
434
- }).$client;
435
-
436
- return raw.query(`
431
+ return rawAll<typeof memoryEntities.$inferSelect>(`
437
432
  SELECT DISTINCT me.* FROM memory_entities me
438
433
  WHERE LOWER(me.name) = ?
439
434
  UNION
440
435
  SELECT DISTINCT me.* FROM memory_entities me, json_each(me.aliases) je
441
436
  WHERE me.aliases IS NOT NULL AND LOWER(je.value) = ?
442
- `).all(nameLower, nameLower) as Array<typeof memoryEntities.$inferSelect>;
437
+ `, nameLower, nameLower);
443
438
  }
444
439
 
445
440
  /**
@@ -0,0 +1,121 @@
1
+ import { getLogger } from '../util/logger.js';
2
+ import { rawGet, rawRun } from './db.js';
3
+
4
+ const log = getLogger('fts-reconciler');
5
+
6
+ export interface FtsReconciliationResult {
7
+ table: string;
8
+ baseCount: number;
9
+ ftsCount: number;
10
+ missingInserted: number;
11
+ orphansRemoved: number;
12
+ staleRefreshed: number;
13
+ }
14
+
15
+ /**
16
+ * Reconcile a single FTS index against its base table. Detects missing entries
17
+ * (rows in the base table with no corresponding FTS row) and orphaned entries
18
+ * (FTS rows whose base table row no longer exists), then repairs both.
19
+ *
20
+ * This is lighter than a full rebuild — it only touches the delta rather than
21
+ * wiping and re-inserting the entire index.
22
+ */
23
+ function reconcileTable(opts: {
24
+ ftsTable: string;
25
+ ftsIdColumn: string;
26
+ ftsContentColumn: string;
27
+ baseTable: string;
28
+ baseIdColumn: string;
29
+ baseContentColumn: string;
30
+ }): FtsReconciliationResult {
31
+ const { ftsTable, ftsIdColumn, ftsContentColumn, baseTable, baseIdColumn, baseContentColumn } = opts;
32
+
33
+ const baseCount = (rawGet<{ c: number }>(`SELECT COUNT(*) AS c FROM ${baseTable}`) ?? { c: 0 }).c;
34
+ const ftsCount = (rawGet<{ c: number }>(`SELECT COUNT(*) AS c FROM ${ftsTable}`) ?? { c: 0 }).c;
35
+
36
+ // Find base table rows missing from the FTS index
37
+ const missingInserted = rawRun(/*sql*/ `
38
+ INSERT INTO ${ftsTable}(${ftsIdColumn}, ${ftsContentColumn})
39
+ SELECT b.${baseIdColumn}, b.${baseContentColumn}
40
+ FROM ${baseTable} b
41
+ LEFT JOIN ${ftsTable} f ON f.${ftsIdColumn} = b.${baseIdColumn}
42
+ WHERE f.${ftsIdColumn} IS NULL
43
+ `);
44
+
45
+ // Find FTS rows whose base table row no longer exists
46
+ const orphansRemoved = rawRun(/*sql*/ `
47
+ DELETE FROM ${ftsTable}
48
+ WHERE ${ftsIdColumn} IN (
49
+ SELECT f.${ftsIdColumn}
50
+ FROM ${ftsTable} f
51
+ LEFT JOIN ${baseTable} b ON b.${baseIdColumn} = f.${ftsIdColumn}
52
+ WHERE b.${baseIdColumn} IS NULL
53
+ )
54
+ `);
55
+
56
+ // Refresh FTS rows whose content is stale (base row was updated but the
57
+ // update trigger didn't fire or was missing). Delete-then-insert is the
58
+ // standard FTS5 update pattern.
59
+ const staleDeleted = rawRun(/*sql*/ `
60
+ DELETE FROM ${ftsTable}
61
+ WHERE ${ftsIdColumn} IN (
62
+ SELECT f.${ftsIdColumn}
63
+ FROM ${ftsTable} f
64
+ JOIN ${baseTable} b ON b.${baseIdColumn} = f.${ftsIdColumn}
65
+ WHERE b.${baseContentColumn} IS NOT f.${ftsContentColumn}
66
+ )
67
+ `);
68
+ if (staleDeleted > 0) {
69
+ rawRun(/*sql*/ `
70
+ INSERT INTO ${ftsTable}(${ftsIdColumn}, ${ftsContentColumn})
71
+ SELECT b.${baseIdColumn}, b.${baseContentColumn}
72
+ FROM ${baseTable} b
73
+ LEFT JOIN ${ftsTable} f ON f.${ftsIdColumn} = b.${baseIdColumn}
74
+ WHERE f.${ftsIdColumn} IS NULL
75
+ `);
76
+ }
77
+
78
+ return { table: ftsTable, baseCount, ftsCount, missingInserted, orphansRemoved, staleRefreshed: staleDeleted };
79
+ }
80
+
81
+ /**
82
+ * Reconcile all FTS indexes. Returns results for each table so callers can
83
+ * inspect what was repaired.
84
+ */
85
+ export function reconcileFtsIndexes(): FtsReconciliationResult[] {
86
+ const results: FtsReconciliationResult[] = [];
87
+
88
+ // memory_segment_fts tracks memory_segments
89
+ const memResult = reconcileTable({
90
+ ftsTable: 'memory_segment_fts',
91
+ ftsIdColumn: 'segment_id',
92
+ ftsContentColumn: 'text',
93
+ baseTable: 'memory_segments',
94
+ baseIdColumn: 'id',
95
+ baseContentColumn: 'text',
96
+ });
97
+ results.push(memResult);
98
+ if (memResult.missingInserted > 0 || memResult.orphansRemoved > 0 || memResult.staleRefreshed > 0) {
99
+ log.info(memResult, 'Reconciled memory_segment_fts');
100
+ } else {
101
+ log.debug(memResult, 'memory_segment_fts is in sync');
102
+ }
103
+
104
+ // messages_fts tracks messages
105
+ const msgResult = reconcileTable({
106
+ ftsTable: 'messages_fts',
107
+ ftsIdColumn: 'message_id',
108
+ ftsContentColumn: 'content',
109
+ baseTable: 'messages',
110
+ baseIdColumn: 'id',
111
+ baseContentColumn: 'content',
112
+ });
113
+ results.push(msgResult);
114
+ if (msgResult.missingInserted > 0 || msgResult.orphansRemoved > 0 || msgResult.staleRefreshed > 0) {
115
+ log.info(msgResult, 'Reconciled messages_fts');
116
+ } else {
117
+ log.debug(msgResult, 'messages_fts is in sync');
118
+ }
119
+
120
+ return results;
121
+ }