create-walle 0.9.21 → 0.9.23

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 (500) hide show
  1. package/README.md +27 -5
  2. package/package.json +2 -2
  3. package/template/CLAUDE.md +2 -2
  4. package/template/LICENSE +1 -1
  5. package/template/bin/ctm-dev-cleanup.js +24 -3
  6. package/template/bin/ctm-launch.sh +13 -0
  7. package/template/bin/dev.sh +156 -18
  8. package/template/bin/node-bin.sh +84 -0
  9. package/template/bin/pin-node.sh +51 -0
  10. package/template/claude-task-manager/api-prompts.js +1203 -182
  11. package/template/claude-task-manager/api-reviews.js +109 -15
  12. package/template/claude-task-manager/approval-agent.js +1360 -280
  13. package/template/claude-task-manager/bin/restart-ctm.sh +64 -23
  14. package/template/claude-task-manager/bin/storage-migration-supervisor.js +338 -0
  15. package/template/claude-task-manager/db.js +4417 -295
  16. package/template/claude-task-manager/docs/app-update-refresh-protocol.md +69 -0
  17. package/template/claude-task-manager/docs/approval-ai-refinement.md +138 -0
  18. package/template/claude-task-manager/docs/approval-rescue-loop.md +74 -0
  19. package/template/claude-task-manager/docs/codex-operational-warning-health.md +107 -0
  20. package/template/claude-task-manager/docs/codex-resume-state-guard-design.md +17 -12
  21. package/template/claude-task-manager/docs/codex-terminal-render-controller-handoff.md +311 -0
  22. package/template/claude-task-manager/docs/coding-agent-hooks-architecture.md +418 -0
  23. package/template/claude-task-manager/docs/conversation-import-freshness.md +20 -0
  24. package/template/claude-task-manager/docs/google-workspace-auth-health.md +77 -0
  25. package/template/claude-task-manager/docs/image-paste-ux.md +13 -0
  26. package/template/claude-task-manager/docs/ipad-web-preview.md +88 -0
  27. package/template/claude-task-manager/docs/main-loop-offload-architecture.md +66 -0
  28. package/template/claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md +274 -519
  29. package/template/claude-task-manager/docs/mobile-live-streaming.md +27 -5
  30. package/template/claude-task-manager/docs/mobile-remote-submission-lifecycle.md +69 -0
  31. package/template/claude-task-manager/docs/phone-access-design.md +53 -15
  32. package/template/claude-task-manager/docs/phone-passkey-identity.md +122 -0
  33. package/template/claude-task-manager/docs/phone-setup.md +3 -0
  34. package/template/claude-task-manager/docs/prompt-editing-tree-design.md +25 -1
  35. package/template/claude-task-manager/docs/remote-desktop-access-design.md +268 -0
  36. package/template/claude-task-manager/docs/restart-lifecycle-architecture.md +95 -0
  37. package/template/claude-task-manager/docs/runtime-work-control-plane.md +53 -0
  38. package/template/claude-task-manager/docs/session-interactive-wait-surfaces.md +38 -0
  39. package/template/claude-task-manager/docs/session-needs-you-dismissal.md +84 -0
  40. package/template/claude-task-manager/docs/session-render-state-management-design.md +91 -3
  41. package/template/claude-task-manager/docs/session-standup-command-center-design.md +25 -1
  42. package/template/claude-task-manager/docs/session-title-authority.md +32 -0
  43. package/template/claude-task-manager/docs/session-workspace-binding.md +33 -0
  44. package/template/claude-task-manager/docs/skill-intent-resolution-design.md +72 -0
  45. package/template/claude-task-manager/docs/walle-mcp-supervisor-health.md +86 -0
  46. package/template/claude-task-manager/docs/walle-relay-phone-access-design.md +24 -15
  47. package/template/claude-task-manager/docs/walle-session-history-hydration.md +114 -0
  48. package/template/claude-task-manager/docs/walle-session-input-queue.md +104 -0
  49. package/template/claude-task-manager/docs/walle-session-model-catalog.md +90 -0
  50. package/template/claude-task-manager/docs/walle-session-model-preferences.md +15 -6
  51. package/template/claude-task-manager/git-utils.js +897 -27
  52. package/template/claude-task-manager/lib/agent-capabilities.js +33 -0
  53. package/template/claude-task-manager/lib/agent-cli-cache.js +37 -7
  54. package/template/claude-task-manager/lib/agent-hooks-installer.js +26 -2
  55. package/template/claude-task-manager/lib/agent-presets.js +17 -1
  56. package/template/claude-task-manager/lib/all-sessions-query.js +108 -0
  57. package/template/claude-task-manager/lib/approval-ai-refinement.js +488 -0
  58. package/template/claude-task-manager/lib/approval-self-adapt.js +168 -0
  59. package/template/claude-task-manager/lib/async-semaphore.js +44 -0
  60. package/template/claude-task-manager/lib/auth-context.js +5 -0
  61. package/template/claude-task-manager/lib/auth-rate-limit.js +47 -4
  62. package/template/claude-task-manager/lib/auth-rules.js +29 -2
  63. package/template/claude-task-manager/lib/auto-approval-verifier.js +129 -16
  64. package/template/claude-task-manager/lib/background-llm.js +144 -17
  65. package/template/claude-task-manager/lib/branch-inventory.js +212 -0
  66. package/template/claude-task-manager/lib/claude-desktop-sessions.js +15 -3
  67. package/template/claude-task-manager/lib/coalesce-sync-frames.js +151 -0
  68. package/template/claude-task-manager/lib/codex-launch-health.js +762 -0
  69. package/template/claude-task-manager/lib/codex-transcript-pager.js +51 -0
  70. package/template/claude-task-manager/lib/codex-zst.js +124 -0
  71. package/template/claude-task-manager/lib/coding-agent-models.js +233 -30
  72. package/template/claude-task-manager/lib/connection-health.js +232 -0
  73. package/template/claude-task-manager/lib/conversation-blob-parser.js +42 -0
  74. package/template/claude-task-manager/lib/conversation-tail-merge.js +89 -26
  75. package/template/claude-task-manager/lib/ctm-session-context-api.js +39 -10
  76. package/template/claude-task-manager/lib/cursor-conversation-store.js +354 -0
  77. package/template/claude-task-manager/lib/db-owner-worker-client.js +315 -0
  78. package/template/claude-task-manager/lib/document-review.js +141 -6
  79. package/template/claude-task-manager/lib/escalation-review.js +152 -0
  80. package/template/claude-task-manager/lib/graceful-shutdown.js +159 -0
  81. package/template/claude-task-manager/lib/headless-term-service.js +678 -0
  82. package/template/claude-task-manager/lib/heavy-worker-fallback.js +38 -0
  83. package/template/claude-task-manager/lib/jsonl-conversation-parser.js +542 -0
  84. package/template/claude-task-manager/lib/jsonl-range-reader.js +112 -0
  85. package/template/claude-task-manager/lib/main-db-census.js +216 -0
  86. package/template/claude-task-manager/lib/message-pagination.js +106 -4
  87. package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +750 -26
  88. package/template/claude-task-manager/lib/mobile-auth-api.js +274 -7
  89. package/template/claude-task-manager/lib/mobile-auth-store.js +592 -10
  90. package/template/claude-task-manager/lib/mobile-notification-dispatcher.js +15 -0
  91. package/template/claude-task-manager/lib/model-overview-brain-fallback.js +311 -0
  92. package/template/claude-task-manager/lib/model-overview-cache.js +141 -0
  93. package/template/claude-task-manager/lib/models-health-routing-notice.js +126 -0
  94. package/template/claude-task-manager/lib/node-pin-guard.js +93 -0
  95. package/template/claude-task-manager/lib/perf-tracker.js +242 -6
  96. package/template/claude-task-manager/lib/permission-match.js +76 -0
  97. package/template/claude-task-manager/lib/permission-sync.js +133 -20
  98. package/template/claude-task-manager/lib/process-title.js +35 -0
  99. package/template/claude-task-manager/lib/prompt-executions-query.js +25 -0
  100. package/template/claude-task-manager/lib/prompt-index-disk-cache.js +44 -0
  101. package/template/claude-task-manager/lib/prompt-intent.js +132 -0
  102. package/template/claude-task-manager/lib/provider-user-context.js +34 -0
  103. package/template/claude-task-manager/lib/read-pool-client.js +313 -0
  104. package/template/claude-task-manager/lib/readpool-breaker.js +31 -0
  105. package/template/claude-task-manager/lib/recent-sessions-breaker.js +12 -0
  106. package/template/claude-task-manager/lib/remote-feedback-client.js +72 -0
  107. package/template/claude-task-manager/lib/remote-relay-protocol.js +37 -4
  108. package/template/claude-task-manager/lib/remote-relay-store.js +159 -0
  109. package/template/claude-task-manager/lib/remote-submission-observer.js +278 -0
  110. package/template/claude-task-manager/lib/restart-guard.js +109 -0
  111. package/template/claude-task-manager/lib/restore-interruption-detector.js +439 -0
  112. package/template/claude-task-manager/lib/restore-policy.js +13 -0
  113. package/template/claude-task-manager/lib/restore-resume-batch.js +74 -0
  114. package/template/claude-task-manager/lib/restore-runtime.js +68 -0
  115. package/template/claude-task-manager/lib/restore-storm.js +34 -0
  116. package/template/claude-task-manager/lib/resume-cwd.js +36 -0
  117. package/template/claude-task-manager/lib/resume-preflight.js +313 -0
  118. package/template/claude-task-manager/lib/runtime-work-registry.js +444 -0
  119. package/template/claude-task-manager/lib/sanitize-openai-auth.js +31 -0
  120. package/template/claude-task-manager/lib/scheduler.js +21 -1
  121. package/template/claude-task-manager/lib/scrollback-snapshot-store.js +159 -0
  122. package/template/claude-task-manager/lib/serial-task-queue.js +64 -0
  123. package/template/claude-task-manager/lib/server-listeners.js +239 -0
  124. package/template/claude-task-manager/lib/session-capture.js +42 -7
  125. package/template/claude-task-manager/lib/session-content-backfill.js +131 -0
  126. package/template/claude-task-manager/lib/session-history.js +388 -43
  127. package/template/claude-task-manager/lib/session-host-manager.js +287 -0
  128. package/template/claude-task-manager/lib/session-image-refs.js +209 -0
  129. package/template/claude-task-manager/lib/session-jobs.js +399 -59
  130. package/template/claude-task-manager/lib/session-prompt-index.js +137 -0
  131. package/template/claude-task-manager/lib/session-restore.js +53 -0
  132. package/template/claude-task-manager/lib/session-standup.js +123 -23
  133. package/template/claude-task-manager/lib/session-state-bus.js +14 -0
  134. package/template/claude-task-manager/lib/session-stream.js +64 -16
  135. package/template/claude-task-manager/lib/session-timeline-summary.js +260 -0
  136. package/template/claude-task-manager/lib/session-token-usage.js +494 -0
  137. package/template/claude-task-manager/lib/session-workspace-binding.js +356 -0
  138. package/template/claude-task-manager/lib/setup-network-config.js +9 -0
  139. package/template/claude-task-manager/lib/size-cap.js +45 -0
  140. package/template/claude-task-manager/lib/size-cap.test.js +62 -0
  141. package/template/claude-task-manager/lib/skill-autocomplete.js +180 -1
  142. package/template/claude-task-manager/lib/skill-intent-resolver.js +304 -0
  143. package/template/claude-task-manager/lib/sqlite-driver.js +19 -3
  144. package/template/claude-task-manager/lib/standup-attention.js +7 -3
  145. package/template/claude-task-manager/lib/status-authority.js +39 -0
  146. package/template/claude-task-manager/lib/status-hooks.js +4 -0
  147. package/template/claude-task-manager/lib/storage-migration.js +235 -0
  148. package/template/claude-task-manager/lib/structured-capture.js +298 -0
  149. package/template/claude-task-manager/lib/sync-io-census.js +163 -0
  150. package/template/claude-task-manager/lib/tailscale-setup.js +6 -0
  151. package/template/claude-task-manager/lib/terminal-activity-evidence.js +33 -0
  152. package/template/claude-task-manager/lib/terminal-choice.js +364 -0
  153. package/template/claude-task-manager/lib/terminal-control-sanitize.js +17 -0
  154. package/template/claude-task-manager/lib/terminal-fingerprint.js +48 -0
  155. package/template/claude-task-manager/lib/terminal-output-flush.js +84 -0
  156. package/template/claude-task-manager/lib/timeline-order.js +122 -0
  157. package/template/claude-task-manager/lib/transcript-store.js +348 -43
  158. package/template/claude-task-manager/lib/transport-security.js +84 -1
  159. package/template/claude-task-manager/lib/wait-state.js +184 -0
  160. package/template/claude-task-manager/lib/walle-client.js +47 -5
  161. package/template/claude-task-manager/lib/walle-ctm-history.js +564 -4
  162. package/template/claude-task-manager/lib/walle-external-actions.js +135 -16
  163. package/template/claude-task-manager/lib/walle-history-hydration.js +46 -0
  164. package/template/claude-task-manager/lib/walle-native-health.js +403 -0
  165. package/template/claude-task-manager/lib/walle-repair.js +701 -0
  166. package/template/claude-task-manager/lib/walle-session-cache.js +109 -0
  167. package/template/claude-task-manager/lib/walle-session-context.js +57 -21
  168. package/template/claude-task-manager/lib/walle-session-model-catalog.js +34 -0
  169. package/template/claude-task-manager/lib/walle-supervisor.js +539 -63
  170. package/template/claude-task-manager/lib/walle-transcript.js +52 -0
  171. package/template/claude-task-manager/lib/worktree-active-sync.js +11 -7
  172. package/template/claude-task-manager/lib/worktree-cwd.js +32 -1
  173. package/template/claude-task-manager/package.json +1 -1
  174. package/template/claude-task-manager/prompt-harvest.js +89 -66
  175. package/template/claude-task-manager/providers/claude-code.js +51 -3
  176. package/template/claude-task-manager/providers/cursor.js +140 -45
  177. package/template/claude-task-manager/public/css/reviews.css +551 -61
  178. package/template/claude-task-manager/public/css/setup.css +191 -0
  179. package/template/claude-task-manager/public/css/walle-session.css +865 -10
  180. package/template/claude-task-manager/public/css/walle.css +154 -0
  181. package/template/claude-task-manager/public/designs/ai-providers-consolidation-v2.html +830 -0
  182. package/template/claude-task-manager/public/index.html +18516 -2058
  183. package/template/claude-task-manager/public/ipad.html +363 -0
  184. package/template/claude-task-manager/public/js/document-review-links.js +301 -0
  185. package/template/claude-task-manager/public/js/image-normalize.js +69 -36
  186. package/template/claude-task-manager/public/js/message-renderer.js +1265 -77
  187. package/template/claude-task-manager/public/js/prompts.js +66 -29
  188. package/template/claude-task-manager/public/js/reviews.js +901 -133
  189. package/template/claude-task-manager/public/js/session-activity-utils.js +11 -1
  190. package/template/claude-task-manager/public/js/session-search-utils.js +94 -10
  191. package/template/claude-task-manager/public/js/session-status-precedence.js +23 -5
  192. package/template/claude-task-manager/public/js/setup.js +1273 -176
  193. package/template/claude-task-manager/public/js/stream-view.js +691 -73
  194. package/template/claude-task-manager/public/js/terminal-reconciler.js +210 -0
  195. package/template/claude-task-manager/public/js/walle-session.js +2455 -158
  196. package/template/claude-task-manager/public/js/walle.js +455 -28
  197. package/template/claude-task-manager/public/m/app.css +2909 -262
  198. package/template/claude-task-manager/public/m/app.js +6601 -398
  199. package/template/claude-task-manager/public/m/claim.html +224 -17
  200. package/template/claude-task-manager/public/m/index.html +117 -21
  201. package/template/claude-task-manager/public/m/sw.js +3 -1
  202. package/template/claude-task-manager/public/manifest.json +2 -2
  203. package/template/claude-task-manager/public/prompts.html +30 -14
  204. package/template/claude-task-manager/queue-engine.js +507 -28
  205. package/template/claude-task-manager/scripts/repair-claude-session-images.js +27 -8
  206. package/template/claude-task-manager/server.js +14341 -2197
  207. package/template/claude-task-manager/session-integrity.js +160 -18
  208. package/template/claude-task-manager/session-search-ranking.js +1 -0
  209. package/template/claude-task-manager/session-utils.js +25 -5
  210. package/template/claude-task-manager/workers/approval-blocklist.js +96 -6
  211. package/template/claude-task-manager/workers/approval-widget-validator.js +14 -8
  212. package/template/claude-task-manager/workers/conversation-import-worker.js +11 -50
  213. package/template/claude-task-manager/workers/db-owner-worker.js +386 -0
  214. package/template/claude-task-manager/workers/harvest-worker.js +9 -55
  215. package/template/claude-task-manager/workers/headless-term-worker.js +9 -530
  216. package/template/claude-task-manager/workers/read-pool-worker.js +387 -0
  217. package/template/claude-task-manager/workers/scrollback-worker.js +11 -72
  218. package/template/claude-task-manager/workers/session-host-process.js +146 -0
  219. package/template/claude-task-manager/workers/session-integrity-worker.js +10 -54
  220. package/template/claude-task-manager/workers/state-detectors/base.js +18 -1
  221. package/template/claude-task-manager/workers/state-detectors/claude-code.js +182 -9
  222. package/template/claude-task-manager/workers/state-detectors/codex.js +150 -2
  223. package/template/claude-task-manager/workers/state-detectors/cursor.js +127 -0
  224. package/template/claude-task-manager/workers/state-detectors/gemini.js +21 -0
  225. package/template/claude-task-manager/workers/state-detectors/index.js +29 -0
  226. package/template/claude-task-manager/workers/state-detectors/opencode.js +103 -0
  227. package/template/docs/design/markdown-review-pane.md +206 -0
  228. package/template/docs/designs/2026-05-17-portkey-gateway-provider-ux.md +129 -38
  229. package/template/docs/designs/2026-05-20-mobile-worktree-finish-command.md +27 -0
  230. package/template/docs/designs/2026-05-22-ai-configuration-consolidation.md +248 -0
  231. package/template/docs/designs/ai-configuration-consolidation-mock.html +812 -0
  232. package/template/docs/private-memory-and-pii-policy.md +69 -0
  233. package/template/package.json +2 -1
  234. package/template/scripts/check-private-data.js +201 -0
  235. package/template/shared/sqlite-owner-guard.js +30 -0
  236. package/template/shared/sqlite-owner-write-queue.js +225 -0
  237. package/template/shared/sqlite-storage-policy.js +111 -0
  238. package/template/shared/sqlite-write-lock.js +428 -0
  239. package/template/wall-e/agent-runners/claude-code.js +5 -0
  240. package/template/wall-e/agent.js +166 -22
  241. package/template/wall-e/api-walle.js +524 -70
  242. package/template/wall-e/auth/provider-flows.js +11 -1
  243. package/template/wall-e/bin/walle-mcp-stdio.js +341 -17
  244. package/template/wall-e/brain.js +1614 -141
  245. package/template/wall-e/chat/attachment-blocks.js +96 -0
  246. package/template/wall-e/chat/attachments.js +2 -1
  247. package/template/wall-e/chat/capability-resolver.js +7 -7
  248. package/template/wall-e/chat/context-messages.js +28 -0
  249. package/template/wall-e/chat/conversation-frame.js +630 -0
  250. package/template/wall-e/chat/provider-messages.js +125 -0
  251. package/template/wall-e/chat.js +1002 -233
  252. package/template/wall-e/coding/acceptance-contract.js +170 -0
  253. package/template/wall-e/coding/acp-adapter.js +1 -1
  254. package/template/wall-e/coding/agent-catalog.js +3 -0
  255. package/template/wall-e/coding/artifact-store.js +93 -0
  256. package/template/wall-e/coding/capability-router.js +120 -0
  257. package/template/wall-e/coding/coding-run-controller.js +423 -0
  258. package/template/wall-e/coding/compaction-service.js +157 -12
  259. package/template/wall-e/coding/frontend-verification.js +258 -0
  260. package/template/wall-e/coding/lifecycle-hooks.js +75 -0
  261. package/template/wall-e/coding/local-preview-contract.js +157 -0
  262. package/template/wall-e/coding/permission-service.js +57 -13
  263. package/template/wall-e/coding/prompt-bundle.js +19 -1
  264. package/template/wall-e/coding/prompt-section-registry.js +227 -0
  265. package/template/wall-e/coding/provider-compat.js +15 -0
  266. package/template/wall-e/coding/runtime-events.js +224 -0
  267. package/template/wall-e/coding/runtime-mode.js +3 -0
  268. package/template/wall-e/coding/side-git-snapshot.js +160 -4
  269. package/template/wall-e/coding/snapshot-service.js +143 -1
  270. package/template/wall-e/coding/stream-processor.js +388 -34
  271. package/template/wall-e/coding/task-tool.js +141 -4
  272. package/template/wall-e/coding/tool-execution-controller.js +365 -0
  273. package/template/wall-e/coding/tool-registry.js +43 -5
  274. package/template/wall-e/coding/user-hooks.js +217 -0
  275. package/template/wall-e/coding-orchestrator.js +1330 -221
  276. package/template/wall-e/coding-prompts.js +20 -4
  277. package/template/wall-e/context/context-builder.js +15 -2
  278. package/template/wall-e/decision/confidence.js +1 -1
  279. package/template/wall-e/docs/coding-acceptance-contract.md +41 -0
  280. package/template/wall-e/docs/external-action-controller.md +26 -6
  281. package/template/wall-e/docs/telemetry-lifecycle.md +8 -2
  282. package/template/wall-e/embeddings.js +591 -53
  283. package/template/wall-e/external-action-controller.js +12 -0
  284. package/template/wall-e/http/auth.js +1 -0
  285. package/template/wall-e/http/chat-api.js +46 -11
  286. package/template/wall-e/http/model-admin.js +836 -34
  287. package/template/wall-e/lib/boot-profile.js +88 -0
  288. package/template/wall-e/lib/event-loop-monitor.js +93 -0
  289. package/template/wall-e/lib/service-health.js +194 -0
  290. package/template/wall-e/llm/anthropic.js +130 -5
  291. package/template/wall-e/llm/client.js +266 -63
  292. package/template/wall-e/llm/default-fallback.js +382 -0
  293. package/template/wall-e/llm/health.js +19 -0
  294. package/template/wall-e/llm/message-guard.js +78 -0
  295. package/template/wall-e/llm/model-catalog.js +252 -1
  296. package/template/wall-e/llm/openai.js +26 -4
  297. package/template/wall-e/llm/portkey-sync.js +654 -0
  298. package/template/wall-e/llm/provider-error.js +30 -2
  299. package/template/wall-e/llm/registry.js +5 -1
  300. package/template/wall-e/llm/request-compat.js +67 -0
  301. package/template/wall-e/loops/backfill.js +79 -23
  302. package/template/wall-e/loops/brain-optimize.js +67 -0
  303. package/template/wall-e/loops/ingest.js +25 -10
  304. package/template/wall-e/loops/question-digest.js +160 -0
  305. package/template/wall-e/loops/reflect.js +6 -4
  306. package/template/wall-e/loops/think.js +39 -12
  307. package/template/wall-e/mcp-server.js +318 -36
  308. package/template/wall-e/memory/ctm-context-client.js +52 -14
  309. package/template/wall-e/memory/ctm-operational-context.js +237 -0
  310. package/template/wall-e/memory/ctm-prompt-executions-client.js +128 -0
  311. package/template/wall-e/memory/ctm-session-context.js +111 -63
  312. package/template/wall-e/prompts/coding/deepseek.txt +3 -0
  313. package/template/wall-e/prompts/coding/gemini.txt +6 -0
  314. package/template/wall-e/prompts/coding/gpt.txt +6 -0
  315. package/template/wall-e/prompts/coding/local.txt +7 -0
  316. package/template/wall-e/runtime/decision-hooks.js +115 -0
  317. package/template/wall-e/runtime/devbox-gateway.js +82 -8
  318. package/template/wall-e/runtime/prompt-manifest.js +86 -0
  319. package/template/wall-e/runtime/tool-executor.js +269 -0
  320. package/template/wall-e/runtime/tool-result-envelope.js +138 -0
  321. package/template/wall-e/runtime/transcript-projection.js +60 -0
  322. package/template/wall-e/runtime/walle-runtime.js +224 -0
  323. package/template/wall-e/scripts/db-optimize/migrate.js +162 -0
  324. package/template/wall-e/scripts/db-optimize/recall-eval.js +117 -0
  325. package/template/wall-e/server.js +15 -0
  326. package/template/wall-e/session-files.js +9 -0
  327. package/template/wall-e/skills/_bundled/google-calendar/run.js +1 -1
  328. package/template/wall-e/skills/_bundled/gws-workspace/run.js +1 -1
  329. package/template/wall-e/skills/_bundled/slack-mentions/run.js +76 -6
  330. package/template/wall-e/skills/claude-code-reader.js +7 -3
  331. package/template/wall-e/skills/script-skill-runner.js +10 -0
  332. package/template/wall-e/skills/skill-planner.js +38 -0
  333. package/template/wall-e/tools/builtin-middleware.js +19 -9
  334. package/template/wall-e/tools/local-tools.js +1428 -16
  335. package/template/wall-e/tools/permission-checker.js +73 -5
  336. package/template/wall-e/tools/question-manager.js +117 -7
  337. package/template/wall-e/training/harvester.js +12 -28
  338. package/template/wall-e/training/replay.js +25 -80
  339. package/template/website/index.html +10 -10
  340. package/template/wall-e/eval/ab-test.js +0 -203
  341. package/template/wall-e/eval/agent-runner.js +0 -772
  342. package/template/wall-e/eval/agent-scorer.js +0 -461
  343. package/template/wall-e/eval/aggregator.js +0 -414
  344. package/template/wall-e/eval/allowed-test-commands.js +0 -34
  345. package/template/wall-e/eval/benchmark-generator.js +0 -113
  346. package/template/wall-e/eval/benchmarks/chat-eval.json +0 -1662
  347. package/template/wall-e/eval/benchmarks/chat.json +0 -82
  348. package/template/wall-e/eval/benchmarks/coding-agent-real.json +0 -1
  349. package/template/wall-e/eval/benchmarks/coding-agent.json +0 -1581
  350. package/template/wall-e/eval/benchmarks/coding.json +0 -122
  351. package/template/wall-e/eval/benchmarks/memory-retrieval.json +0 -234
  352. package/template/wall-e/eval/benchmarks/reasoning.json +0 -82
  353. package/template/wall-e/eval/benchmarks/swebench-lite-30.json +0 -212
  354. package/template/wall-e/eval/benchmarks.js +0 -669
  355. package/template/wall-e/eval/cc-replay.js +0 -719
  356. package/template/wall-e/eval/chat-eval.js +0 -525
  357. package/template/wall-e/eval/check-keys.js +0 -15
  358. package/template/wall-e/eval/check-providers.js +0 -42
  359. package/template/wall-e/eval/codex-cli-baseline.js +0 -669
  360. package/template/wall-e/eval/coding-agent-real.js +0 -570
  361. package/template/wall-e/eval/context-compactor.js +0 -251
  362. package/template/wall-e/eval/debug-agent003.js +0 -68
  363. package/template/wall-e/eval/diagnostics.js +0 -216
  364. package/template/wall-e/eval/eval-orchestrator.js +0 -642
  365. package/template/wall-e/eval/evaluate.js +0 -202
  366. package/template/wall-e/eval/evaluator.js +0 -373
  367. package/template/wall-e/eval/exporter.js +0 -212
  368. package/template/wall-e/eval/fixtures/express-basic/package.json +0 -9
  369. package/template/wall-e/eval/fixtures/express-basic/server.js +0 -115
  370. package/template/wall-e/eval/fixtures/express-basic/test.js +0 -83
  371. package/template/wall-e/eval/fixtures/express-buggy/package.json +0 -9
  372. package/template/wall-e/eval/fixtures/express-buggy/server.js +0 -113
  373. package/template/wall-e/eval/fixtures/express-buggy/test.js +0 -83
  374. package/template/wall-e/eval/fixtures/express-buggy-items/package.json +0 -9
  375. package/template/wall-e/eval/fixtures/express-buggy-items/server.js +0 -112
  376. package/template/wall-e/eval/fixtures/express-buggy-items/test.js +0 -83
  377. package/template/wall-e/eval/fixtures/express-buggy-search/package.json +0 -9
  378. package/template/wall-e/eval/fixtures/express-buggy-search/server.js +0 -121
  379. package/template/wall-e/eval/fixtures/express-buggy-search/test.js +0 -83
  380. package/template/wall-e/eval/fixtures/express-rename-data/data.js +0 -34
  381. package/template/wall-e/eval/fixtures/express-rename-data/package.json +0 -9
  382. package/template/wall-e/eval/fixtures/express-rename-data/server.js +0 -97
  383. package/template/wall-e/eval/fixtures/express-rename-data/test.js +0 -88
  384. package/template/wall-e/eval/fixtures/express-xss/package.json +0 -12
  385. package/template/wall-e/eval/fixtures/express-xss/server.js +0 -90
  386. package/template/wall-e/eval/fixtures/express-xss/test.js +0 -67
  387. package/template/wall-e/eval/fixtures/express-xss/views/profile.ejs +0 -9
  388. package/template/wall-e/eval/fixtures/fullstack-app/config/default.js +0 -9
  389. package/template/wall-e/eval/fixtures/fullstack-app/config/test.js +0 -13
  390. package/template/wall-e/eval/fixtures/fullstack-app/package.json +0 -11
  391. package/template/wall-e/eval/fixtures/fullstack-app/public/css/style.css +0 -137
  392. package/template/wall-e/eval/fixtures/fullstack-app/public/index.html +0 -46
  393. package/template/wall-e/eval/fixtures/fullstack-app/public/js/app.js +0 -121
  394. package/template/wall-e/eval/fixtures/fullstack-app/public/js/auth.js +0 -71
  395. package/template/wall-e/eval/fixtures/fullstack-app/public/js/items.js +0 -80
  396. package/template/wall-e/eval/fixtures/fullstack-app/public/js/users.js +0 -46
  397. package/template/wall-e/eval/fixtures/fullstack-app/public/login.html +0 -45
  398. package/template/wall-e/eval/fixtures/fullstack-app/public/register.html +0 -38
  399. package/template/wall-e/eval/fixtures/fullstack-app/scripts/migrate.js +0 -23
  400. package/template/wall-e/eval/fixtures/fullstack-app/scripts/seed.js +0 -46
  401. package/template/wall-e/eval/fixtures/fullstack-app/server/db.js +0 -99
  402. package/template/wall-e/eval/fixtures/fullstack-app/server/index.js +0 -94
  403. package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/auth.js +0 -19
  404. package/template/wall-e/eval/fixtures/fullstack-app/server/middleware/logger.js +0 -19
  405. package/template/wall-e/eval/fixtures/fullstack-app/server/router.js +0 -50
  406. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/auth.js +0 -69
  407. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/health.js +0 -23
  408. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/items.js +0 -88
  409. package/template/wall-e/eval/fixtures/fullstack-app/server/routes/users.js +0 -75
  410. package/template/wall-e/eval/fixtures/fullstack-app/server/test.js +0 -198
  411. package/template/wall-e/eval/fixtures/fullstack-app/server/utils/response.js +0 -34
  412. package/template/wall-e/eval/fixtures/fullstack-app/server/utils/validate.js +0 -26
  413. package/template/wall-e/eval/fixtures/fullstack-app/server.js +0 -8
  414. package/template/wall-e/eval/fixtures/fullstack-app/test.js +0 -12
  415. package/template/wall-e/eval/fixtures/monorepo-basic/package.json +0 -8
  416. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/data.js +0 -58
  417. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/middleware.js +0 -46
  418. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/package.json +0 -8
  419. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/routes.js +0 -64
  420. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/server.js +0 -56
  421. package/template/wall-e/eval/fixtures/monorepo-basic/packages/api/test.js +0 -116
  422. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/commands.js +0 -61
  423. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/index.js +0 -62
  424. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/output.js +0 -43
  425. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/package.json +0 -11
  426. package/template/wall-e/eval/fixtures/monorepo-basic/packages/cli/test.js +0 -44
  427. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/formatters.js +0 -43
  428. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/index.js +0 -12
  429. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/package.json +0 -5
  430. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/test.js +0 -55
  431. package/template/wall-e/eval/fixtures/monorepo-basic/packages/shared/validators.js +0 -29
  432. package/template/wall-e/eval/fixtures/monorepo-basic/test.js +0 -46
  433. package/template/wall-e/eval/fixtures/node-cli/index.js +0 -78
  434. package/template/wall-e/eval/fixtures/node-cli/package.json +0 -10
  435. package/template/wall-e/eval/fixtures/node-cli/test.js +0 -57
  436. package/template/wall-e/eval/fixtures/node-typed/package.json +0 -8
  437. package/template/wall-e/eval/fixtures/node-typed/src/handlers.js +0 -31
  438. package/template/wall-e/eval/fixtures/node-typed/src/utils.js +0 -33
  439. package/template/wall-e/eval/fixtures/node-typed/test.js +0 -36
  440. package/template/wall-e/eval/fixtures/python-flask/app.py +0 -14
  441. package/template/wall-e/eval/fixtures/python-flask/requirements.txt +0 -2
  442. package/template/wall-e/eval/fixtures/python-flask/test_app.py +0 -25
  443. package/template/wall-e/eval/fixtures/wall-e-subset/brain.js +0 -105
  444. package/template/wall-e/eval/fixtures/wall-e-subset/eval/aggregator.js +0 -101
  445. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/chat.json +0 -20
  446. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks/coding.json +0 -32
  447. package/template/wall-e/eval/fixtures/wall-e-subset/eval/benchmarks.js +0 -64
  448. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/package.json +0 -6
  449. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/server.js +0 -31
  450. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/test.js +0 -18
  451. package/template/wall-e/eval/fixtures/wall-e-subset/eval/fixtures/simple-project/utils.js +0 -34
  452. package/template/wall-e/eval/fixtures/wall-e-subset/eval/runner.js +0 -104
  453. package/template/wall-e/eval/fixtures/wall-e-subset/eval/scorer.js +0 -73
  454. package/template/wall-e/eval/fixtures/wall-e-subset/eval/test.js +0 -134
  455. package/template/wall-e/eval/fixtures/wall-e-subset/llm/client.js +0 -99
  456. package/template/wall-e/eval/fixtures/wall-e-subset/llm/providers.js +0 -63
  457. package/template/wall-e/eval/fixtures/wall-e-subset/llm/test.js +0 -70
  458. package/template/wall-e/eval/fixtures/wall-e-subset/package.json +0 -10
  459. package/template/wall-e/eval/fixtures/wall-e-subset/test.js +0 -86
  460. package/template/wall-e/eval/harvester.js +0 -685
  461. package/template/wall-e/eval/head-to-head.js +0 -388
  462. package/template/wall-e/eval/humaneval-adapter.js +0 -321
  463. package/template/wall-e/eval/list-models.js +0 -31
  464. package/template/wall-e/eval/livecodebench-adapter.js +0 -291
  465. package/template/wall-e/eval/mail-integration.js +0 -443
  466. package/template/wall-e/eval/manifest.js +0 -186
  467. package/template/wall-e/eval/meta-harness/adapters/coding-agent.js +0 -57
  468. package/template/wall-e/eval/meta-harness/bootstrap-snapshot.js +0 -149
  469. package/template/wall-e/eval/meta-harness/candidate-store.js +0 -117
  470. package/template/wall-e/eval/meta-harness/cli.js +0 -86
  471. package/template/wall-e/eval/meta-harness/domain-spec.js +0 -154
  472. package/template/wall-e/eval/meta-harness/domains/coding-agent.domain.json +0 -84
  473. package/template/wall-e/eval/meta-harness/examples/env-bootstrap-candidate.js +0 -29
  474. package/template/wall-e/eval/meta-harness/experience-store.js +0 -174
  475. package/template/wall-e/eval/meta-harness/frontier.js +0 -96
  476. package/template/wall-e/eval/meta-harness/harness-interface.js +0 -90
  477. package/template/wall-e/eval/meta-harness/leakage-guard.js +0 -80
  478. package/template/wall-e/eval/meta-harness/optimizer.js +0 -207
  479. package/template/wall-e/eval/meta-harness/proposer-runner.js +0 -110
  480. package/template/wall-e/eval/meta-harness/reporting.js +0 -58
  481. package/template/wall-e/eval/meta-harness/telemetry.js +0 -27
  482. package/template/wall-e/eval/meta-harness/validation.js +0 -81
  483. package/template/wall-e/eval/promoter.js +0 -228
  484. package/template/wall-e/eval/provider-normalizer.js +0 -33
  485. package/template/wall-e/eval/replay.js +0 -395
  486. package/template/wall-e/eval/run-agent-benchmarks.js +0 -386
  487. package/template/wall-e/eval/run-codex-cli-baseline.js +0 -177
  488. package/template/wall-e/eval/run-coding-agent-real.js +0 -187
  489. package/template/wall-e/eval/run-eval.js +0 -435
  490. package/template/wall-e/eval/run-model-comparison.js +0 -142
  491. package/template/wall-e/eval/session-evaluator.js +0 -187
  492. package/template/wall-e/eval/session-miner.js +0 -207
  493. package/template/wall-e/eval/session-retrieval-benchmark.js +0 -150
  494. package/template/wall-e/eval/session-transcripts.js +0 -509
  495. package/template/wall-e/eval/shadow.js +0 -161
  496. package/template/wall-e/eval/swebench-adapter.js +0 -345
  497. package/template/wall-e/eval/swebench-docker.js +0 -192
  498. package/template/wall-e/eval/train.py +0 -320
  499. package/template/wall-e/eval/trainer.js +0 -232
  500. package/template/wall-e/eval/weekly-eval-loop.js +0 -241
@@ -65,39 +65,163 @@ const EMBEDDING_MODELS = {
65
65
  'voyage-3-lite': { dimensions: 512, provider: 'voyage', maxBatch: 128, key: 'voyage', label: 'Voyage AI', cost: '$0.02/1M', desc: 'Anthropic recommended' },
66
66
  'text-embedding-3-small': { dimensions: 1536, provider: 'openai', maxBatch: 2048, key: 'openai', label: 'OpenAI', cost: '$0.02/1M', desc: 'text-embedding-3-small' },
67
67
  'text-embedding-3-large': { dimensions: 3072, provider: 'openai', maxBatch: 2048, key: 'openai_lg', label: 'OpenAI Large', cost: '$0.13/1M', desc: 'text-embedding-3-large' },
68
- 'nomic-embed-text': { dimensions: 768, provider: 'ollama', maxBatch: 32, key: 'ollama', label: 'Ollama (local)', cost: 'Free', desc: 'nomic-embed-text, private' },
68
+ 'nomic-embed-text': { dimensions: 768, provider: 'ollama', maxBatch: 32, key: 'ollama', label: 'Ollama (local)', cost: 'Free', desc: 'nomic-embed-text, private', quantize: 'int8' },
69
69
  'mxbai-embed-large': { dimensions: 1024, provider: 'ollama', maxBatch: 32, key: 'ollama_lg', label: 'Ollama Large', cost: 'Free', desc: 'mxbai-embed-large, private' },
70
70
  };
71
71
 
72
- /**
73
- * Determine the active embedding model.
74
- * Priority: explicit env var > kv_store preference > auto-detect from API keys.
75
- */
76
- function getEmbeddingModel() {
77
- // Explicit override (for testing / CI)
78
- const explicit = process.env.WALLE_EMBEDDING_MODEL;
79
- if (explicit) return EMBEDDING_MODELS[explicit] ? explicit : null;
72
+ const EMBEDDING_POLICY = {
73
+ LOCAL_FIRST: 'local_first',
74
+ LOCAL_ONLY: 'local_only',
75
+ CLOUD_ALLOWED: 'cloud_allowed',
76
+ OFF: 'off',
77
+ };
78
+ const DEFAULT_EMBEDDING_POLICY = EMBEDDING_POLICY.LOCAL_FIRST;
79
+ const EMBEDDING_POLICY_VALUES = new Set(Object.values(EMBEDDING_POLICY));
80
+ const EMBEDDING_LOCAL_MODELS = ['nomic-embed-text', 'mxbai-embed-large'];
81
+ const EMBEDDING_CLOUD_MODELS = ['text-embedding-004', 'text-embedding-3-small', 'voyage-3-lite'];
82
+ const EMBEDDING_AUTO_MODEL_ORDER = [...EMBEDDING_LOCAL_MODELS, ...EMBEDDING_CLOUD_MODELS];
83
+
84
+ function _getKv(key) {
85
+ try { return _getAdapter().getKv(key); } catch { return null; }
86
+ }
87
+
88
+ function _setKv(key, value) {
89
+ try { _getAdapter().setKv(key, value); return true; } catch { return false; }
90
+ }
91
+
92
+ function _normalizeEmbeddingPolicy(value) {
93
+ const normalized = String(value || '').trim().toLowerCase();
94
+ return EMBEDDING_POLICY_VALUES.has(normalized) ? normalized : DEFAULT_EMBEDDING_POLICY;
95
+ }
96
+
97
+ function getEmbeddingPolicy() {
98
+ return _normalizeEmbeddingPolicy(_getKv('embedding:auto_policy'));
99
+ }
100
+
101
+ function _getEmbeddingPolicyRecord() {
102
+ const raw = _getKv('embedding:auto_policy');
103
+ return {
104
+ policy: _normalizeEmbeddingPolicy(raw),
105
+ source: raw && EMBEDDING_POLICY_VALUES.has(String(raw).trim().toLowerCase()) ? 'saved' : 'default',
106
+ };
107
+ }
80
108
 
81
- // User preference from setup page (stored in kv_store)
82
- // IMPORTANT: Honor the user's explicit choice even if the provider is temporarily
83
- // unreachable (e.g. Ollama not running). Only skip if the model is unknown.
84
- // Auto-detect fallthrough should only apply when there is NO stored preference.
109
+ function _isLocalProvider(provider) {
110
+ return provider === 'ollama';
111
+ }
112
+
113
+ function _isCloudProvider(provider) {
114
+ return Boolean(provider) && !_isLocalProvider(provider);
115
+ }
116
+
117
+ function _policyAllowsConfig(config, policy) {
118
+ if (!config || policy === EMBEDDING_POLICY.OFF) return false;
119
+ if (policy === EMBEDDING_POLICY.CLOUD_ALLOWED) return true;
120
+ return _isLocalProvider(config.provider);
121
+ }
122
+
123
+ function _providerReachableForModel(modelName) {
124
+ const config = EMBEDDING_MODELS[modelName];
125
+ return config ? _hasApiKey(config.provider) : false;
126
+ }
127
+
128
+ function _embeddingCountForModel(modelName) {
129
+ try {
130
+ const db = _getAdapter().getDb();
131
+ const row = db.prepare('SELECT count(*) as c FROM embedding_map WHERE model = ?').get(modelName);
132
+ return row?.c || 0;
133
+ } catch {
134
+ return 0;
135
+ }
136
+ }
137
+
138
+ function _findExistingIndexedModel(policy, policySource) {
85
139
  try {
86
- const pref = _getAdapter().getKv('embedding:active_model');
87
- if (pref && EMBEDDING_MODELS[pref]) {
88
- return pref;
140
+ const db = _getAdapter().getDb();
141
+ const rows = db.prepare('SELECT model, count(*) as c FROM embedding_map GROUP BY model ORDER BY c DESC').all();
142
+ for (const row of rows) {
143
+ const config = EMBEDDING_MODELS[row.model];
144
+ if (!config) continue;
145
+ // Backward compatibility: installs that already built a cloud index before
146
+ // the policy setting existed should keep using it until the user changes
147
+ // policy or manually switches providers.
148
+ if (policySource === 'default' || _policyAllowsConfig(config, policy)) return row.model;
89
149
  }
90
150
  } catch {}
151
+ return null;
152
+ }
91
153
 
92
- // Auto-detect: prefer free providers first, check both env and brain DB
93
- if (_hasApiKey('google')) return 'text-embedding-004';
94
- if (_hasApiKey('voyage')) return 'voyage-3-lite';
95
- if (_hasApiKey('openai')) return 'text-embedding-3-small';
96
- if (_hasApiKey('ollama')) return 'nomic-embed-text';
97
-
154
+ function _firstAvailableModel(models, policy) {
155
+ for (const model of models) {
156
+ const config = EMBEDDING_MODELS[model];
157
+ if (!config || !_policyAllowsConfig(config, policy)) continue;
158
+ if (_hasApiKey(config.provider)) return model;
159
+ }
98
160
  return null;
99
161
  }
100
162
 
163
+ function _selectAutoCandidate(policy) {
164
+ if (policy === EMBEDDING_POLICY.OFF) return null;
165
+ return _firstAvailableModel(
166
+ policy === EMBEDDING_POLICY.CLOUD_ALLOWED ? EMBEDDING_AUTO_MODEL_ORDER : EMBEDDING_LOCAL_MODELS,
167
+ policy
168
+ );
169
+ }
170
+
171
+ function getActiveEmbeddingModelInfo() {
172
+ const explicit = process.env.WALLE_EMBEDDING_MODEL;
173
+ if (explicit) {
174
+ return EMBEDDING_MODELS[explicit]
175
+ ? { model: explicit, source: 'env', policy: getEmbeddingPolicy(), policySource: 'env' }
176
+ : { model: null, source: 'invalid_env', policy: getEmbeddingPolicy(), policySource: 'env' };
177
+ }
178
+
179
+ const policyRecord = _getEmbeddingPolicyRecord();
180
+ if (policyRecord.policy === EMBEDDING_POLICY.OFF) {
181
+ return { model: null, source: 'off', policy: policyRecord.policy, policySource: policyRecord.source };
182
+ }
183
+
184
+ const pref = _getKv('embedding:active_model');
185
+ const selectionMode = _getKv('embedding:selection_mode') || (pref ? 'manual' : 'auto');
186
+ if (pref && EMBEDDING_MODELS[pref]) {
187
+ const config = EMBEDDING_MODELS[pref];
188
+ const blockedByLocalOnly = policyRecord.source === 'saved'
189
+ && policyRecord.policy === EMBEDDING_POLICY.LOCAL_ONLY
190
+ && !_policyAllowsConfig(config, policyRecord.policy);
191
+ if (blockedByLocalOnly) {
192
+ // The explicit Local only policy is stronger than an older cloud manual
193
+ // choice. The saved model is left in kv_store for audit/history, but it is
194
+ // not effective while this policy is active.
195
+ } else if (selectionMode !== 'auto') {
196
+ return { model: pref, source: selectionMode, policy: policyRecord.policy, policySource: policyRecord.source };
197
+ } else if (_policyAllowsConfig(config, policyRecord.policy) && _providerReachableForModel(pref)) {
198
+ return { model: pref, source: 'auto', policy: policyRecord.policy, policySource: policyRecord.source };
199
+ }
200
+ }
201
+
202
+ const indexedModel = _findExistingIndexedModel(policyRecord.policy, policyRecord.source);
203
+ if (indexedModel) {
204
+ return { model: indexedModel, source: 'existing_index', policy: policyRecord.policy, policySource: policyRecord.source };
205
+ }
206
+
207
+ const candidate = _selectAutoCandidate(policyRecord.policy);
208
+ return {
209
+ model: candidate,
210
+ source: candidate ? 'auto_candidate' : 'none',
211
+ policy: policyRecord.policy,
212
+ policySource: policyRecord.source,
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Determine the active embedding model.
218
+ * Priority: explicit env var > manual/saved preference > compatible existing
219
+ * index > auto policy candidate. Cloud candidates require explicit opt-in.
220
+ */
221
+ function getEmbeddingModel() {
222
+ return getActiveEmbeddingModelInfo().model || null;
223
+ }
224
+
101
225
  function _hasApiKey(provider) {
102
226
  // Check process.env first, then fall back to model_providers table in brain DB
103
227
  switch (provider) {
@@ -137,6 +261,142 @@ function _isOllamaAvailable() {
137
261
  }
138
262
  }
139
263
 
264
+ // --- Telemetry ---
265
+
266
+ const EMBEDDING_SETUP_STATUS_TELEMETRY_INTERVAL_MS = 6 * 60 * 60 * 1000;
267
+ const EMBEDDING_USAGE_TELEMETRY_INTERVAL_MS = 60 * 60 * 1000;
268
+ let _lastSetupStatusTelemetry = { key: '', ts: 0 };
269
+ let _lastUsageTelemetryByKey = new Map();
270
+
271
+ function _countBucket(value) {
272
+ const n = Number(value) || 0;
273
+ if (n <= 0) return '0';
274
+ if (n < 10) return '1-9';
275
+ if (n < 100) return '10-99';
276
+ if (n < 1000) return '100-999';
277
+ if (n < 10000) return '1k-9k';
278
+ if (n < 100000) return '10k-99k';
279
+ return '100k+';
280
+ }
281
+
282
+ function _safeTelemetry() {
283
+ try { return require('./telemetry'); } catch { return null; }
284
+ }
285
+
286
+ function _modelTelemetryFields(modelName) {
287
+ const cfg = modelName && EMBEDDING_MODELS[modelName] ? EMBEDDING_MODELS[modelName] : null;
288
+ return {
289
+ model: cfg ? modelName : 'none',
290
+ provider: cfg ? cfg.provider : 'none',
291
+ model_key: cfg ? cfg.key : 'none',
292
+ cost_tier: cfg ? (cfg.cost === 'Free' ? 'free' : 'paid') : 'none',
293
+ };
294
+ }
295
+
296
+ function _embeddingTelemetrySnapshot() {
297
+ const info = getActiveEmbeddingModelInfo();
298
+ let model = info.model || null;
299
+ let providerReachable = false;
300
+ let stats = { total: 0, memories: 0, knowledge: 0 };
301
+ try {
302
+ const cfg = model && EMBEDDING_MODELS[model] ? EMBEDDING_MODELS[model] : null;
303
+ providerReachable = cfg ? !!_hasApiKey(cfg.provider) : false;
304
+ } catch {}
305
+ try {
306
+ if (model) stats = getEmbeddingStats();
307
+ } catch {}
308
+
309
+ let state = 'ready';
310
+ if (!_vecLoaded) state = 'sqlite_vec_unavailable';
311
+ else if (!model) state = 'no_provider';
312
+ else if (!providerReachable) state = 'provider_unreachable';
313
+
314
+ return {
315
+ state,
316
+ enabled: state === 'ready' ? 1 : 0,
317
+ sqlite_vec: _vecLoaded ? 1 : 0,
318
+ provider_reachable: providerReachable ? 1 : 0,
319
+ policy: info.policy || DEFAULT_EMBEDDING_POLICY,
320
+ selection_source: info.source || 'none',
321
+ ..._modelTelemetryFields(model),
322
+ embedded_bucket: _countBucket(stats.total),
323
+ memory_bucket: _countBucket(stats.memories),
324
+ knowledge_bucket: _countBucket(stats.knowledge),
325
+ };
326
+ }
327
+
328
+ function trackEmbeddingStatus(reason = 'boot', options = {}) {
329
+ const telemetry = options.telemetry || _safeTelemetry();
330
+ if (!telemetry || typeof telemetry.track !== 'function') return;
331
+ telemetry.track('embedding_status', {
332
+ reason,
333
+ ..._embeddingTelemetrySnapshot(),
334
+ });
335
+ }
336
+
337
+ function trackEmbeddingSetupView() {
338
+ const snapshot = _embeddingTelemetrySnapshot();
339
+ const key = [
340
+ snapshot.state,
341
+ snapshot.provider,
342
+ snapshot.model,
343
+ snapshot.embedded_bucket,
344
+ snapshot.memory_bucket,
345
+ ].join('|');
346
+ const now = Date.now();
347
+ if (_lastSetupStatusTelemetry.key === key && now - _lastSetupStatusTelemetry.ts < EMBEDDING_SETUP_STATUS_TELEMETRY_INTERVAL_MS) {
348
+ return;
349
+ }
350
+ _lastSetupStatusTelemetry = { key, ts: now };
351
+ trackEmbeddingStatus('setup_view');
352
+ }
353
+
354
+ function _switchFailureReason(result) {
355
+ const message = String(result && result.message || '').toLowerCase();
356
+ if (message.includes('unknown model')) return 'unknown_model';
357
+ if (message.includes('no api key')) return 'missing_key';
358
+ return result && result.ok ? 'success' : 'failed';
359
+ }
360
+
361
+ function _trackEmbeddingProviderSwitch(previousModel, requestedModel, result) {
362
+ const telemetry = _safeTelemetry();
363
+ if (!telemetry || typeof telemetry.track !== 'function') return;
364
+ const fromFields = _modelTelemetryFields(previousModel);
365
+ const toFields = _modelTelemetryFields(requestedModel);
366
+ telemetry.track('embedding_provider_switch', {
367
+ ok: result && result.ok ? 1 : 0,
368
+ reason: _switchFailureReason(result),
369
+ from_provider: fromFields.provider,
370
+ from_model: fromFields.model,
371
+ to_provider: toFields.provider,
372
+ to_model: toFields.model,
373
+ same_provider: fromFields.provider === toFields.provider ? 1 : 0,
374
+ });
375
+ if (result && result.ok) trackEmbeddingStatus('provider_switch', { telemetry });
376
+ }
377
+
378
+ function _trackEmbeddingUsage(operation, resultCount, status = 'ok') {
379
+ const telemetry = _safeTelemetry();
380
+ if (!telemetry || typeof telemetry.track !== 'function') return;
381
+ const snapshot = _embeddingTelemetrySnapshot();
382
+ const safeOperation = String(operation || 'unknown').replace(/[^a-z0-9_-]/gi, '_').slice(0, 64) || 'unknown';
383
+ const safeStatus = String(status || 'ok').replace(/[^a-z0-9_-]/gi, '_').slice(0, 32) || 'ok';
384
+ const resultBucket = _countBucket(resultCount);
385
+ const key = [safeOperation, safeStatus, snapshot.provider, snapshot.model, resultBucket].join('|');
386
+ const now = Date.now();
387
+ const last = _lastUsageTelemetryByKey.get(key) || 0;
388
+ if (now - last < EMBEDDING_USAGE_TELEMETRY_INTERVAL_MS) return;
389
+ _lastUsageTelemetryByKey.set(key, now);
390
+ telemetry.track('embedding_usage', {
391
+ operation: safeOperation,
392
+ status: safeStatus,
393
+ result_bucket: resultBucket,
394
+ state: snapshot.state,
395
+ provider: snapshot.provider,
396
+ model: snapshot.model,
397
+ });
398
+ }
399
+
140
400
  function getModelConfig() {
141
401
  const model = getEmbeddingModel();
142
402
  return model ? { name: model, ...EMBEDDING_MODELS[model] } : null;
@@ -154,7 +414,9 @@ function getDimensions() {
154
414
  * Used by the setup page UI.
155
415
  */
156
416
  function getProviderStatus() {
157
- const activeModel = getEmbeddingModel();
417
+ const activeInfo = getActiveEmbeddingModelInfo();
418
+ const activeModel = activeInfo.model;
419
+ const policy = activeInfo.policy || getEmbeddingPolicy();
158
420
  const db = _getAdapter().getDb();
159
421
 
160
422
  return Object.entries(EMBEDDING_MODELS).map(([model, config]) => {
@@ -186,12 +448,171 @@ function getProviderStatus() {
186
448
  hasKey,
187
449
  isActive,
188
450
  reachable,
451
+ selectionSource: isActive ? activeInfo.source : undefined,
452
+ autoEligible: _policyAllowsConfig(config, policy),
453
+ cloudRequiresOptIn: _isCloudProvider(config.provider) && policy !== EMBEDDING_POLICY.CLOUD_ALLOWED,
189
454
  embeddingCount: count,
190
455
  sizeBytes: sizeEstimate,
191
456
  };
192
457
  });
193
458
  }
194
459
 
460
+ function _resetBackfillForModelChange() {
461
+ try {
462
+ _setKv('backfill:embed:cursor', '0');
463
+ _setKv('backfill:done', null);
464
+ } catch {}
465
+ }
466
+
467
+ function _ensureVecTableForModel(modelName) {
468
+ const config = EMBEDDING_MODELS[modelName];
469
+ if (!config || !ensureVecLoaded()) return false;
470
+ const db = _getAdapter().getDb();
471
+ const vecTable = _vecTableName(config.key);
472
+ const existing = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(vecTable);
473
+ if (!existing) {
474
+ db.prepare(`CREATE VIRTUAL TABLE "${vecTable}" USING vec0(embedding ${_vecColType(config)})`).run();
475
+ }
476
+ return true;
477
+ }
478
+
479
+ function _trackEmbeddingAutoSelect(reason, result) {
480
+ const telemetry = _safeTelemetry();
481
+ if (!telemetry || typeof telemetry.track !== 'function') return;
482
+ telemetry.track('embedding_auto_select', {
483
+ reason,
484
+ ok: result.ok ? 1 : 0,
485
+ status: result.status || '',
486
+ policy: result.policy || getEmbeddingPolicy(),
487
+ model: result.model || 'none',
488
+ provider: result.provider || 'none',
489
+ selection_source: result.selectionSource || 'none',
490
+ });
491
+ }
492
+
493
+ function autoSelectProvider(reason = 'auto') {
494
+ const info = getActiveEmbeddingModelInfo();
495
+ const config = info.model ? EMBEDDING_MODELS[info.model] : null;
496
+ if (!info.model || !config) {
497
+ const result = {
498
+ ok: false,
499
+ status: info.source === 'off' ? 'off' : 'waiting',
500
+ reason: info.source,
501
+ policy: info.policy || getEmbeddingPolicy(),
502
+ selectionSource: info.source || 'none',
503
+ };
504
+ _trackEmbeddingAutoSelect(reason, result);
505
+ return result;
506
+ }
507
+ if (!['auto_candidate', 'auto'].includes(info.source)) {
508
+ return {
509
+ ok: false,
510
+ status: 'preserved',
511
+ reason: info.source,
512
+ model: info.model,
513
+ provider: config.provider,
514
+ policy: info.policy,
515
+ selectionSource: info.source,
516
+ };
517
+ }
518
+
519
+ const previous = _getKv('embedding:active_model');
520
+ _setKv('embedding:active_model', info.model);
521
+ _setKv('embedding:selection_mode', 'auto');
522
+ _ensureVecTableForModel(info.model);
523
+ if (previous !== info.model) _resetBackfillForModelChange();
524
+
525
+ const result = {
526
+ ok: true,
527
+ status: previous === info.model ? 'unchanged' : 'selected',
528
+ model: info.model,
529
+ provider: config.provider,
530
+ policy: info.policy,
531
+ selectionSource: 'auto',
532
+ };
533
+ _trackEmbeddingAutoSelect(reason, result);
534
+ if (previous !== info.model) trackEmbeddingStatus('auto_select');
535
+ return result;
536
+ }
537
+
538
+ function getEmbeddingSetupState() {
539
+ const policyRecord = _getEmbeddingPolicyRecord();
540
+ const activeInfo = getActiveEmbeddingModelInfo();
541
+ const activeConfig = activeInfo.model ? EMBEDDING_MODELS[activeInfo.model] : null;
542
+ const localAvailable = Boolean(_firstAvailableModel(EMBEDDING_LOCAL_MODELS, EMBEDDING_POLICY.LOCAL_ONLY));
543
+ const cloudAvailableModel = _firstAvailableModel(EMBEDDING_CLOUD_MODELS, EMBEDDING_POLICY.CLOUD_ALLOWED);
544
+ const cloudAvailable = Boolean(cloudAvailableModel);
545
+ const cloudOptInAvailable = !activeInfo.model
546
+ && cloudAvailable
547
+ && policyRecord.policy !== EMBEDDING_POLICY.CLOUD_ALLOWED
548
+ && policyRecord.policy !== EMBEDDING_POLICY.OFF;
549
+
550
+ let status = 'waiting_for_provider';
551
+ let message = 'Memory Search will turn on automatically when a local embedding provider is available.';
552
+ if (policyRecord.policy === EMBEDDING_POLICY.OFF) {
553
+ status = 'off';
554
+ message = 'Memory Search is off.';
555
+ } else if (activeInfo.model) {
556
+ status = activeInfo.source === 'manual' ? 'manual_active' : 'active';
557
+ message = `${activeConfig.label} is powering Memory Search.`;
558
+ } else if (cloudOptInAvailable) {
559
+ status = 'cloud_opt_in_available';
560
+ message = 'A cloud embedding provider is available. Opt in to enable semantic Memory Search.';
561
+ } else if (!localAvailable && !cloudAvailable) {
562
+ status = 'no_provider';
563
+ message = 'Add a local embedding provider or cloud API key to enable semantic Memory Search.';
564
+ }
565
+
566
+ return {
567
+ policy: policyRecord.policy,
568
+ policySource: policyRecord.source,
569
+ status,
570
+ message,
571
+ activeModel: activeInfo.model || null,
572
+ activeProvider: activeConfig ? activeConfig.provider : null,
573
+ activeLabel: activeConfig ? activeConfig.label : null,
574
+ selectionSource: activeInfo.source || 'none',
575
+ localAvailable,
576
+ cloudAvailable,
577
+ cloudAvailableModel,
578
+ cloudOptInAvailable,
579
+ totalIndexed: activeInfo.model ? _embeddingCountForModel(activeInfo.model) : 0,
580
+ };
581
+ }
582
+
583
+ function setEmbeddingPolicy(policy) {
584
+ const normalized = String(policy || '').trim().toLowerCase();
585
+ if (!EMBEDDING_POLICY_VALUES.has(normalized)) {
586
+ return { ok: false, message: `Unknown Memory Search policy: ${policy}` };
587
+ }
588
+ const previousPolicy = getEmbeddingPolicy();
589
+ const previousModel = _getKv('embedding:active_model');
590
+ const previousConfig = previousModel && EMBEDDING_MODELS[previousModel] ? EMBEDDING_MODELS[previousModel] : null;
591
+ _setKv('embedding:auto_policy', normalized);
592
+ if (normalized !== EMBEDDING_POLICY.OFF && _getKv('embedding:selection_mode') === 'auto') {
593
+ _setKv('embedding:active_model', null);
594
+ }
595
+ if ((normalized === EMBEDDING_POLICY.LOCAL_ONLY || normalized === EMBEDDING_POLICY.OFF)
596
+ && previousConfig
597
+ && _isCloudProvider(previousConfig.provider)) {
598
+ _setKv('embedding:active_model', null);
599
+ _setKv('embedding:selection_mode', 'auto');
600
+ _resetBackfillForModelChange();
601
+ }
602
+ const auto = autoSelectProvider('policy_update');
603
+ const setup = getEmbeddingSetupState();
604
+ const telemetry = _safeTelemetry();
605
+ try {
606
+ telemetry?.track?.('embedding_policy_update', {
607
+ from: previousPolicy,
608
+ to: normalized,
609
+ status: setup.status,
610
+ active_provider: setup.activeProvider || 'none',
611
+ });
612
+ } catch {}
613
+ return { ok: true, policy: normalized, auto, setup };
614
+ }
615
+
195
616
  // --- Embedding computation ---
196
617
 
197
618
  async function computeEmbedding(text) {
@@ -425,6 +846,33 @@ function _vecTableName(modelKey) {
425
846
  return `embedding_vec_${modelKey}`;
426
847
  }
427
848
 
849
+ // vec0 column type for a model: int8[N] for models that opt into quantization
850
+ // (config.quantize==='int8'), float[N] otherwise. int8 stores 1 byte/dim vs 4 — ~6x
851
+ // smaller on disk + faster KNN, at ~95% recall@10 for unit-normalized vectors (nomic).
852
+ function _vecColType(config) {
853
+ return `${config.quantize === 'int8' ? 'int8' : 'float'}[${config.dimensions}]`;
854
+ }
855
+
856
+ // The bind expression that turns a JSON-array param into the stored/queried vector.
857
+ // Derived from the ACTUAL declared table type (not just config) so the code is correct
858
+ // whether the table is still float32 (pre-migration) or already int8 (post-migration) —
859
+ // no fragile coupling between the schema and a flag. 'unit' assumes inputs in [-1,1]
860
+ // (true for L2-normalized models like nomic-embed-text), the only mode sqlite-vec supports.
861
+ function _declaredVecType(db, table) {
862
+ try {
863
+ const r = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name=?").get(table);
864
+ if (!r || !r.sql) return null;
865
+ if (/\bint8\s*\[/.test(r.sql)) return 'int8';
866
+ if (/\bfloat\s*\[/.test(r.sql)) return 'float';
867
+ } catch {}
868
+ return null;
869
+ }
870
+ function _vecBindExpr(db, config) {
871
+ return _declaredVecType(db, _vecTableName(config.key)) === 'int8'
872
+ ? "vec_quantize_int8(vec_f32(?),'unit')"
873
+ : 'vec_f32(?)';
874
+ }
875
+
428
876
  /**
429
877
  * Create embedding tables for the active model.
430
878
  * Each model gets its own vec table. The mapping table is shared.
@@ -454,8 +902,8 @@ function createEmbeddingTables() {
454
902
  const vecTable = _vecTableName(config.key);
455
903
  const existing = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(vecTable);
456
904
  if (!existing) {
457
- db.prepare(`CREATE VIRTUAL TABLE "${vecTable}" USING vec0(embedding float[${config.dimensions}])`).run();
458
- console.log(`[embeddings] Created vec table: ${vecTable} (${config.dimensions}d)`);
905
+ db.prepare(`CREATE VIRTUAL TABLE "${vecTable}" USING vec0(embedding ${_vecColType(config)})`).run();
906
+ console.log(`[embeddings] Created vec table: ${vecTable} (${_vecColType(config)})`);
459
907
  }
460
908
 
461
909
  // Migrate from old single-table layout if it exists
@@ -543,7 +991,7 @@ function storeEmbedding(entityId, entityType, embedding) {
543
991
  rowid = info.lastInsertRowid;
544
992
  }
545
993
 
546
- db.prepare(`INSERT INTO "${vecTable}"(rowid, embedding) VALUES (?, vec_f32(?))`).run(
994
+ db.prepare(`INSERT INTO "${vecTable}"(rowid, embedding) VALUES (?, ${_vecBindExpr(db, config)})`).run(
547
995
  BigInt(rowid), JSON.stringify(embedding)
548
996
  );
549
997
  });
@@ -632,6 +1080,75 @@ function deleteEmbeddingsForModel(modelName) {
632
1080
  }
633
1081
  }
634
1082
 
1083
+ // Delete all embeddings for one entity (its embedding_map rows across models + the
1084
+ // matching vec-table rows keyed by rowid). Call this whenever an entity is hard-deleted
1085
+ // so we don't leak orphaned vectors (the source of the audited 47,649 orphans — skills
1086
+ // that re-sync by `DELETE FROM memories WHERE id = ?` left the embeddings behind).
1087
+ function deleteEmbeddingsForEntity(entityId) {
1088
+ if (!entityId) return { deleted: 0 };
1089
+ // Load vec0 into this connection if not already (skill subprocesses may not have
1090
+ // touched the embeddings module yet, but their brain.initDb() opened the DB).
1091
+ if (!ensureVecLoaded()) return { deleted: 0 };
1092
+ const db = _getAdapter().getDb();
1093
+ try {
1094
+ const rows = db.prepare('SELECT rowid, model FROM embedding_map WHERE entity_id = ?').all(entityId);
1095
+ if (!rows.length) return { deleted: 0 };
1096
+ db.transaction(() => {
1097
+ for (const r of rows) {
1098
+ const cfg = EMBEDDING_MODELS[r.model];
1099
+ if (cfg) {
1100
+ try { db.prepare(`DELETE FROM "${_vecTableName(cfg.key)}" WHERE rowid = ?`).run(BigInt(r.rowid)); } catch {}
1101
+ }
1102
+ }
1103
+ db.prepare('DELETE FROM embedding_map WHERE entity_id = ?').run(entityId);
1104
+ })();
1105
+ return { deleted: rows.length };
1106
+ } catch (err) {
1107
+ return { deleted: 0, error: err.message };
1108
+ }
1109
+ }
1110
+
1111
+ // One-time / periodic sweep: delete embeddings whose entity no longer exists. Only sweeps
1112
+ // entity types whose source table we can verify (memory→memories, knowledge→knowledge);
1113
+ // other types are left untouched. Batched per-transaction so each holds the cross-process
1114
+ // write lock only briefly (lock-friendly, per the CTM lesson). entity_type is parameterized;
1115
+ // the source-table names are internal constants (no user input → no injection).
1116
+ const _ORPHAN_ENTITY_TABLES = { memory: 'memories', knowledge: 'knowledge' };
1117
+ function pruneOrphanEmbeddings({ batchSize = 200 } = {}) {
1118
+ if (!ensureVecLoaded()) return { deleted: 0 };
1119
+ const db = _getAdapter().getDb();
1120
+ const batch = Math.max(50, Math.min(2000, Number(batchSize) || 200));
1121
+ let deleted = 0;
1122
+ for (const [etype, table] of Object.entries(_ORPHAN_ENTITY_TABLES)) {
1123
+ // Guard: only sweep if the source table exists, else skip (never mass-delete blind).
1124
+ const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table);
1125
+ if (!hasTable) continue;
1126
+ for (let i = 0; i < 5000; i += 1) { // batch backstop
1127
+ const orphans = db.prepare(
1128
+ `SELECT em.rowid AS rowid, em.model AS model
1129
+ FROM embedding_map em
1130
+ WHERE em.entity_type = ?
1131
+ AND NOT EXISTS (SELECT 1 FROM ${table} t WHERE t.id = em.entity_id)
1132
+ LIMIT ?`
1133
+ ).all(etype, batch);
1134
+ if (!orphans.length) break;
1135
+ db.transaction(() => {
1136
+ for (const o of orphans) {
1137
+ const cfg = EMBEDDING_MODELS[o.model];
1138
+ if (cfg) {
1139
+ try { db.prepare(`DELETE FROM "${_vecTableName(cfg.key)}" WHERE rowid = ?`).run(BigInt(o.rowid)); } catch {}
1140
+ }
1141
+ db.prepare('DELETE FROM embedding_map WHERE rowid = ?').run(o.rowid);
1142
+ }
1143
+ })();
1144
+ deleted += orphans.length;
1145
+ if (orphans.length < batch) break;
1146
+ }
1147
+ }
1148
+ if (deleted) console.log(`[embeddings] pruneOrphanEmbeddings removed ${deleted} orphaned embeddings`);
1149
+ return { deleted };
1150
+ }
1151
+
635
1152
  // --- Vector search ---
636
1153
 
637
1154
  function searchSimilarMemories(queryEmbedding, limit = 10, filters = {}) {
@@ -649,7 +1166,7 @@ function searchSimilarMemories(queryEmbedding, limit = 10, filters = {}) {
649
1166
  FROM "${vecTable}" v
650
1167
  JOIN embedding_map em ON em.rowid = v.rowid
651
1168
  JOIN memories m ON m.id = em.entity_id
652
- WHERE v.embedding MATCH vec_f32(?) AND k = ?
1169
+ WHERE v.embedding MATCH ${_vecBindExpr(db, config)} AND k = ?
653
1170
  AND em.entity_type = 'memory' AND em.model = ?
654
1171
  `;
655
1172
  const params = [JSON.stringify(queryEmbedding), kFetch, config.name];
@@ -661,9 +1178,12 @@ function searchSimilarMemories(queryEmbedding, limit = 10, filters = {}) {
661
1178
  sql += ' ORDER BY v.distance LIMIT ?';
662
1179
  params.push(limit);
663
1180
 
664
- return db.prepare(sql).all(...params);
1181
+ const rows = db.prepare(sql).all(...params);
1182
+ _trackEmbeddingUsage('memory_search', rows.length);
1183
+ return rows;
665
1184
  } catch (err) {
666
1185
  console.error('[embeddings] Vector search failed:', err.message);
1186
+ _trackEmbeddingUsage('memory_search', 0, 'error');
667
1187
  return [];
668
1188
  }
669
1189
  }
@@ -678,19 +1198,22 @@ function searchSimilarKnowledge(queryEmbedding, limit = 10) {
678
1198
  const vecTable = _vecTableName(config.key);
679
1199
  const kFetch = Math.min(limit * 3, 100);
680
1200
 
681
- return db.prepare(`
1201
+ const rows = db.prepare(`
682
1202
  SELECT kn.*, v.distance
683
1203
  FROM "${vecTable}" v
684
1204
  JOIN embedding_map em ON em.rowid = v.rowid
685
1205
  JOIN knowledge kn ON kn.id = em.entity_id
686
- WHERE v.embedding MATCH vec_f32(?) AND k = ?
1206
+ WHERE v.embedding MATCH ${_vecBindExpr(db, config)} AND k = ?
687
1207
  AND em.entity_type = 'knowledge' AND em.model = ?
688
1208
  AND kn.status = 'active'
689
1209
  ORDER BY v.distance
690
1210
  LIMIT ?
691
1211
  `).all(JSON.stringify(queryEmbedding), kFetch, config.name, limit);
1212
+ _trackEmbeddingUsage('knowledge_search', rows.length);
1213
+ return rows;
692
1214
  } catch (err) {
693
1215
  console.error('[embeddings] Knowledge vector search failed:', err.message);
1216
+ _trackEmbeddingUsage('knowledge_search', 0, 'error');
694
1217
  return [];
695
1218
  }
696
1219
  }
@@ -714,7 +1237,7 @@ function searchSimilarEntities(queryEmbedding, limit = 10, entityType = null) {
714
1237
  SELECT em.entity_id, em.entity_type, v.distance
715
1238
  FROM "${vecTable}" v
716
1239
  JOIN embedding_map em ON em.rowid = v.rowid
717
- WHERE v.embedding MATCH vec_f32(?) AND k = ?
1240
+ WHERE v.embedding MATCH ${_vecBindExpr(db, config)} AND k = ?
718
1241
  AND em.model = ?
719
1242
  `;
720
1243
  const params = [JSON.stringify(queryEmbedding), kFetch, config.name];
@@ -724,9 +1247,12 @@ function searchSimilarEntities(queryEmbedding, limit = 10, entityType = null) {
724
1247
  sql += ' ORDER BY v.distance LIMIT ?';
725
1248
  params.push(limit);
726
1249
 
727
- return db.prepare(sql).all(...params);
1250
+ const rows = db.prepare(sql).all(...params);
1251
+ _trackEmbeddingUsage(entityType ? `${entityType}_entity_search` : 'entity_search', rows.length);
1252
+ return rows;
728
1253
  } catch (err) {
729
1254
  console.error('[embeddings] Entity vector search failed:', err.message);
1255
+ _trackEmbeddingUsage(entityType ? `${entityType}_entity_search` : 'entity_search', 0, 'error');
730
1256
  return [];
731
1257
  }
732
1258
  }
@@ -765,51 +1291,57 @@ function reciprocalRankFusion(lexicalResults, vectorResults, limit, k = 60) {
765
1291
  * @returns {{ ok: boolean, message: string }}
766
1292
  */
767
1293
  function switchProvider(modelName) {
1294
+ const previousModel = getEmbeddingModel();
768
1295
  const config = EMBEDDING_MODELS[modelName];
769
- if (!config) return { ok: false, message: `Unknown model: ${modelName}` };
770
- if (!_hasApiKey(config.provider)) return { ok: false, message: `No API key for ${config.label}` };
1296
+ if (!config) {
1297
+ const result = { ok: false, message: `Unknown model: ${modelName}` };
1298
+ _trackEmbeddingProviderSwitch(previousModel, modelName, result);
1299
+ return result;
1300
+ }
1301
+ if (!_hasApiKey(config.provider)) {
1302
+ const result = { ok: false, message: `No API key for ${config.label}` };
1303
+ _trackEmbeddingProviderSwitch(previousModel, modelName, result);
1304
+ return result;
1305
+ }
771
1306
 
772
1307
  // Save preference
773
- _getAdapter().setKv('embedding:active_model', modelName);
1308
+ _setKv('embedding:active_model', modelName);
1309
+ _setKv('embedding:selection_mode', 'manual');
774
1310
 
775
1311
  // Reset cached dimensions so they're recalculated
776
1312
  _dimensions = null;
777
1313
 
778
1314
  // Create the new vec table if needed
779
- if (ensureVecLoaded()) {
780
- const db = _getAdapter().getDb();
781
- const vecTable = _vecTableName(config.key);
782
- const existing = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`).get(vecTable);
783
- if (!existing) {
784
- db.prepare(`CREATE VIRTUAL TABLE "${vecTable}" USING vec0(embedding float[${config.dimensions}])`).run();
785
- }
786
- }
1315
+ _ensureVecTableForModel(modelName);
787
1316
 
788
1317
  // Reset backfill cursors so the new model gets backfilled
789
- try {
790
- _getAdapter().setKv('backfill:embed:cursor', '0');
791
- _getAdapter().setKv('backfill:done', null);
792
- } catch {}
1318
+ _resetBackfillForModelChange();
793
1319
 
794
1320
  console.log(`[embeddings] Switched to ${config.label} (${modelName}, ${config.dimensions}d)`);
795
- return { ok: true, message: `Switched to ${config.label}` };
1321
+ const result = { ok: true, message: `Switched to ${config.label}` };
1322
+ _trackEmbeddingProviderSwitch(previousModel, modelName, result);
1323
+ return result;
796
1324
  }
797
1325
 
798
1326
  // --- Initialization ---
799
1327
 
800
1328
  function init(adapter) {
1329
+ // Sub-phase timing so the boot profile pinpoints what's slow inside embeddings.init
1330
+ // (on the live brain this whole call was ~15s; isolate which step). No-op if profiler absent.
1331
+ let _boot; try { _boot = require('./lib/boot-profile'); } catch { _boot = { measure: (_l, f) => f() }; }
801
1332
  if (adapter) _adapter = adapter;
802
- if (!ensureVecLoaded()) {
1333
+ if (!_boot.measure('embeddings.ensureVecLoaded', () => ensureVecLoaded())) {
803
1334
  console.log('[embeddings] sqlite-vec not available — vector search disabled');
804
1335
  return false;
805
1336
  }
1337
+ _boot.measure('embeddings.autoSelectProvider', () => autoSelectProvider('boot'));
806
1338
  if (!getEmbeddingModel()) {
807
- console.log('[embeddings] No embedding API key found (set GOOGLE_API_KEY, VOYAGE_API_KEY, OPENAI_API_KEY, or OLLAMA_BASE_URL) vector search disabled');
1339
+ console.log('[embeddings] No embedding provider active vector search disabled until local provider is available or cloud embeddings are allowed');
808
1340
  return false;
809
1341
  }
810
- const ok = createEmbeddingTables();
1342
+ const ok = _boot.measure('embeddings.createEmbeddingTables', () => createEmbeddingTables());
811
1343
  if (ok) {
812
- const stats = getEmbeddingStats();
1344
+ const stats = _boot.measure('embeddings.getEmbeddingStats', () => getEmbeddingStats());
813
1345
  const config = getModelConfig();
814
1346
  console.log(`[embeddings] Initialized (${stats.total} embeddings, model: ${config.name}, dims: ${config.dimensions}, provider: ${config.label})`);
815
1347
  }
@@ -822,14 +1354,20 @@ function _resetForTesting() {
822
1354
  _adapter = null;
823
1355
  _defaultAdapter = null;
824
1356
  _ollamaAvailableCache = { value: false, ts: 0 };
1357
+ _lastSetupStatusTelemetry = { key: '', ts: 0 };
1358
+ _lastUsageTelemetryByKey = new Map();
825
1359
  }
826
1360
 
827
1361
  module.exports = {
828
1362
  init, ensureVecLoaded, isAvailable, createEmbeddingTables,
829
- getEmbeddingModel, getModelConfig, getDimensions, EMBEDDING_MODELS,
1363
+ getEmbeddingModel, getActiveEmbeddingModelInfo, getEmbeddingPolicy, setEmbeddingPolicy,
1364
+ getEmbeddingSetupState, autoSelectProvider,
1365
+ getModelConfig, getDimensions, EMBEDDING_MODELS, EMBEDDING_POLICY,
1366
+ trackEmbeddingStatus, trackEmbeddingSetupView,
830
1367
  computeEmbedding, batchComputeEmbeddings,
831
1368
  storeEmbedding, hasEmbedding, getEmbeddingStats, getAllEmbeddingStats,
832
1369
  getProviderStatus, switchProvider, deleteEmbeddingsForModel,
1370
+ deleteEmbeddingsForEntity, pruneOrphanEmbeddings,
833
1371
  searchSimilarMemories, searchSimilarKnowledge, searchSimilarEntities, reciprocalRankFusion,
834
1372
  _resetForTesting,
835
1373
  };