@vellumai/assistant 0.8.1 → 0.8.3

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 (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -126,10 +126,15 @@ describe("searchWorkspaceSource", () => {
126
126
  writeWorkspaceFile(root, "api-key.md", "needle key");
127
127
  writeWorkspaceFile(root, "secret-plan.md", "needle secret");
128
128
  writeWorkspaceFile(root, "token-cache.md", "needle token");
129
+ writeWorkspaceFile(root, "apiKey.json", "needle camel key");
130
+ writeWorkspaceFile(root, "userToken.txt", "needle camel token");
131
+ writeWorkspaceFile(root, "MySecret.md", "needle camel secret");
129
132
  writeWorkspaceFile(root, "credentials.json", "needle credentials");
130
133
  writeWorkspaceFile(root, "protected/readme.md", "needle protected");
131
134
  writeWorkspaceFile(root, "gateway-security/readme.md", "needle gateway");
132
135
  writeWorkspaceFile(root, "ces-security/readme.md", "needle ces");
136
+ writeWorkspaceFile(root, "keyboard.ts", "needle keyboard");
137
+ writeWorkspaceFile(root, "tokenizer.py", "needle tokenizer");
133
138
  writeWorkspaceFile(root, "src/readme.md", "needle safe");
134
139
 
135
140
  const result = await searchWorkspaceSource("needle", makeContext(root), 10);
@@ -138,7 +143,9 @@ describe("searchWorkspaceSource", () => {
138
143
  ".hidden.md:1",
139
144
  ".notes/.format-rec.md:1",
140
145
  ".notes/cake.md:1",
146
+ "keyboard.ts:1",
141
147
  "src/readme.md:1",
148
+ "tokenizer.py:1",
142
149
  ]);
143
150
  });
144
151
 
@@ -33,6 +33,7 @@ function makePngBase64(width: number, height: number): string {
33
33
  describe("token estimator", () => {
34
34
  test("estimates text tokens from character length", () => {
35
35
  expect(estimateTextTokens("")).toBe(0);
36
+ expect(estimateTextTokens(undefined)).toBe(0);
36
37
  expect(estimateTextTokens("abcd")).toBe(1);
37
38
  expect(estimateTextTokens("abcde")).toBe(2);
38
39
  });
@@ -218,36 +219,7 @@ describe("token estimator", () => {
218
219
  expect(largeFileTokens).toBe(smallFileTokens);
219
220
  });
220
221
 
221
- // Non-Anthropic providers use base64 payload size for image estimation
222
- test("scales image token estimate with base64 payload size (non-Anthropic)", () => {
223
- const smallImageTokens = estimateContentBlockTokens(
224
- {
225
- type: "image",
226
- source: {
227
- type: "base64",
228
- media_type: "image/png",
229
- data: "a".repeat(64),
230
- },
231
- },
232
- { providerName: "openai" },
233
- );
234
- const largeImageTokens = estimateContentBlockTokens(
235
- {
236
- type: "image",
237
- source: {
238
- type: "base64",
239
- media_type: "image/png",
240
- data: "a".repeat(60_000),
241
- },
242
- },
243
- { providerName: "openai" },
244
- );
245
-
246
- expect(largeImageTokens).toBeGreaterThan(smallImageTokens);
247
- expect(largeImageTokens - smallImageTokens).toBeGreaterThan(1000);
248
- });
249
-
250
- test("estimates Anthropic image tokens from dimensions, not base64 size", () => {
222
+ test("estimates image tokens from dimensions, not base64 size", () => {
251
223
  // Build a minimal valid PNG header encoding 1920x1080 dimensions.
252
224
  // PNG header: 8-byte signature + 4-byte IHDR length + 4-byte "IHDR" + 4-byte width + 4-byte height = 24 bytes minimum
253
225
  const pngHeader = Buffer.alloc(24);
@@ -277,55 +249,49 @@ describe("token estimator", () => {
277
249
  const fullPayload = Buffer.concat([pngHeader, padding]);
278
250
  const base64Data = fullPayload.toString("base64");
279
251
 
280
- const anthropicTokens = estimateContentBlockTokens(
281
- {
282
- type: "image",
283
- source: { type: "base64", media_type: "image/png", data: base64Data },
284
- },
285
- { providerName: "anthropic" },
286
- );
287
-
288
252
  // 1920x1080 scaled to fit 1568px bounding box: dimScale = 1568/1920 = 0.8167
289
253
  // scaledWidth = round(1920 * 0.8167) = 1568, scaledHeight = round(1080 * 0.8167) = 882
290
254
  // pixels = 1568 * 882 = 1,382,976 > 1,200,000 → mpScale = sqrt(1200000/1382976) = 0.9315
291
255
  // scaledWidth = round(1568 * 0.9315) = 1461, scaledHeight = round(882 * 0.9315) = 822
292
256
  // tokens = ceil(1461 * 822 / 750) = ceil(1601.26) = ~1,602
293
- // With IMAGE_BLOCK_OVERHEAD_TOKENS and media_type overhead, still well under 5000
294
- expect(anthropicTokens).toBeLessThan(5_000);
295
-
296
- // Verify it's NOT using base64 size (which would be ~50,000+ tokens)
297
- const nonAnthropicTokens = estimateContentBlockTokens(
298
- {
299
- type: "image",
300
- source: { type: "base64", media_type: "image/png", data: base64Data },
301
- },
302
- { providerName: "openai" },
303
- );
304
- expect(nonAnthropicTokens).toBeGreaterThan(50_000);
257
+ // With IMAGE_BLOCK_OVERHEAD_TOKENS and media_type overhead, still well under 5000.
258
+ // Same result for every provider — dimension-based estimate is universal.
259
+ for (const providerName of ["anthropic", "openai", "openrouter"]) {
260
+ const tokens = estimateContentBlockTokens(
261
+ {
262
+ type: "image",
263
+ source: { type: "base64", media_type: "image/png", data: base64Data },
264
+ },
265
+ { providerName },
266
+ );
267
+ expect(tokens).toBeLessThan(5_000);
268
+ }
305
269
  });
306
270
 
307
- test("falls back to max tokens when Anthropic image dimensions can't be parsed", () => {
271
+ test("falls back to max tokens when image dimensions can't be parsed", () => {
308
272
  // Corrupted base64 that won't parse as a valid image header
309
273
  const corruptedData = Buffer.from(
310
274
  "not-a-valid-image-header-at-all",
311
275
  ).toString("base64");
312
276
 
313
- const tokens = estimateContentBlockTokens(
314
- {
315
- type: "image",
316
- source: {
317
- type: "base64",
318
- media_type: "image/png",
319
- data: corruptedData,
277
+ for (const providerName of ["anthropic", "openai", "openrouter"]) {
278
+ const tokens = estimateContentBlockTokens(
279
+ {
280
+ type: "image",
281
+ source: {
282
+ type: "base64",
283
+ media_type: "image/png",
284
+ data: corruptedData,
285
+ },
320
286
  },
321
- },
322
- { providerName: "anthropic" },
323
- );
287
+ { providerName },
288
+ );
324
289
 
325
- // Should fall back to ANTHROPIC_IMAGE_MAX_TOKENS (1,600)
326
- // Total = 16 (block overhead) + ceil(9/4) (media_type) + 1600 = 1619
327
- expect(tokens).toBeGreaterThanOrEqual(1_600);
328
- expect(tokens).toBeLessThan(2_000);
290
+ // Falls back to the per-image cap (1,600 tokens). Total = 16 (block
291
+ // overhead) + ceil(9/4) (media_type) + 1600 = 1619.
292
+ expect(tokens).toBeGreaterThanOrEqual(1_600);
293
+ expect(tokens).toBeLessThan(2_000);
294
+ }
329
295
  });
330
296
 
331
297
  test("Anthropic image tokens are the same for same-dimension images regardless of payload size", () => {
@@ -55,7 +55,10 @@ mock.module("../config/loader.js", () => ({
55
55
  pricingOverrides: [],
56
56
  },
57
57
  rateLimit: { maxRequestsPerMinute: 0 },
58
- memory: { v2: { enabled: false } },
58
+ memory: {
59
+ v2: { enabled: false },
60
+ retrieval: { scratchpadInjection: { enabled: true } },
61
+ },
59
62
  daemon: {
60
63
  startupSocketWaitMs: 5000,
61
64
  stopTimeoutMs: 5000,
@@ -84,6 +84,7 @@ mock.module("../config/loader.js", () => ({
84
84
  },
85
85
  rateLimit: { maxRequestsPerMinute: 0 },
86
86
  workspaceGit: { turnCommitMaxWaitMs: 10 },
87
+ memory: { retrieval: { scratchpadInjection: { enabled: true } } },
87
88
  ui: {},
88
89
  }),
89
90
  loadRawConfig: () => ({}),
@@ -7,8 +7,9 @@
7
7
  * 2. Token estimation significantly underestimates actual token count
8
8
  * 3. No mid-loop budget check to prevent hitting the provider limit
9
9
  *
10
- * All tests are test.todo — they document expected behavior for bugs
10
+ * Most tests are test.todo — they document expected behavior for bugs
11
11
  * to be fixed in subsequent PRs (PR 2 for tests 1–5, PR 3 for tests 6–7).
12
+ * Tests 2, 8, 9, and 10 are now active and passing against current code.
12
13
  */
13
14
  import { createRequire } from "node:module";
14
15
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
@@ -87,7 +88,9 @@ mock.module("../config/loader.js", () => ({
87
88
  llm: mockLlmConfig,
88
89
  rateLimit: { maxRequestsPerMinute: 0 },
89
90
  workspaceGit: { turnCommitMaxWaitMs: 10 },
91
+ memory: { retrieval: { scratchpadInjection: { enabled: true } } },
90
92
  ui: {},
93
+ compaction: { enabled: true, autoThreshold: 0.7 },
91
94
  }),
92
95
  loadRawConfig: () => ({}),
93
96
  saveRawConfig: () => {},
@@ -851,111 +854,108 @@ describe("session-agent-loop overflow recovery (JARVIS-110)", () => {
851
854
  // When estimation says we're within budget but the provider rejects,
852
855
  // the post-run convergence loop should kick in and recover.
853
856
  // This test should PASS against current code (when no progress is made).
854
- test.todo(
855
- "overflow recovery compacts below limit even when estimation underestimates",
856
- async () => {
857
- const events: ServerMessage[] = [];
858
- let callCount = 0;
859
- let reducerCalled = false;
857
+ test("overflow recovery compacts below limit even when estimation underestimates", async () => {
858
+ const events: ServerMessage[] = [];
859
+ let callCount = 0;
860
+ let reducerCalled = false;
860
861
 
861
- // Estimator says 185k (below 190k budget = 200k * 0.95)
862
- mockEstimateTokens = 185_000;
862
+ // Estimator says 185k (below 190k budget = 200k * 0.95)
863
+ mockEstimateTokens = 185_000;
863
864
 
864
- // Reducer successfully compacts
865
- mockReducerStepFn = (msgs: Message[]) => {
866
- reducerCalled = true;
867
- return {
865
+ // Reducer successfully compacts
866
+ mockReducerStepFn = (msgs: Message[]) => {
867
+ reducerCalled = true;
868
+ return {
869
+ messages: msgs,
870
+ tier: "forced_compaction",
871
+ state: {
872
+ appliedTiers: ["forced_compaction"],
873
+ injectionMode: "full",
874
+ exhausted: false,
875
+ },
876
+ estimatedTokens: 100_000,
877
+ compactionResult: {
878
+ compacted: true,
868
879
  messages: msgs,
869
- tier: "forced_compaction",
870
- state: {
871
- appliedTiers: ["forced_compaction"],
872
- injectionMode: "full",
873
- exhausted: false,
874
- },
875
- estimatedTokens: 100_000,
876
- compactionResult: {
877
- compacted: true,
878
- messages: msgs,
879
- compactedPersistedMessages: 10,
880
- summaryText: "Summary",
881
- previousEstimatedInputTokens: 185_000,
882
- estimatedInputTokens: 100_000,
883
- maxInputTokens: 200_000,
884
- thresholdTokens: 160_000,
885
- compactedMessages: 20,
886
- summaryCalls: 1,
887
- summaryInputTokens: 800,
888
- summaryOutputTokens: 300,
889
- summaryModel: "mock-model",
890
- },
891
- };
880
+ compactedPersistedMessages: 10,
881
+ summaryText: "Summary",
882
+ previousEstimatedInputTokens: 185_000,
883
+ estimatedInputTokens: 100_000,
884
+ maxInputTokens: 200_000,
885
+ thresholdTokens: 160_000,
886
+ compactedMessages: 20,
887
+ summaryCalls: 1,
888
+ summaryInputTokens: 800,
889
+ summaryOutputTokens: 300,
890
+ summaryModel: "mock-model",
891
+ },
892
892
  };
893
+ };
893
894
 
894
- const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
895
- callCount++;
896
- if (callCount === 1) {
897
- // Provider rejects with "prompt is too long: 242201 tokens > 200000"
898
- // even though estimator said 185k
899
- onEvent({
900
- type: "error",
901
- error: new Error(
902
- "prompt is too long: 242201 tokens > 200000 maximum",
903
- ),
904
- });
905
- onEvent({
906
- type: "usage",
907
- inputTokens: 0,
908
- outputTokens: 0,
909
- model: "test-model",
910
- providerDurationMs: 10,
911
- });
912
- // No progress — return same messages
913
- return messages;
914
- }
915
- // Second call succeeds
895
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
896
+ callCount++;
897
+ if (callCount === 1) {
898
+ // Provider rejects with "prompt is too long: 242201 tokens > 200000"
899
+ // even though estimator said 185k
916
900
  onEvent({
917
- type: "message_complete",
918
- message: {
919
- role: "assistant",
920
- content: [{ type: "text", text: "recovered" }],
921
- },
901
+ type: "error",
902
+ error: new Error(
903
+ "prompt is too long: 242201 tokens > 200000 maximum",
904
+ ),
922
905
  });
923
906
  onEvent({
924
907
  type: "usage",
925
- inputTokens: 80_000,
926
- outputTokens: 200,
908
+ inputTokens: 0,
909
+ outputTokens: 0,
927
910
  model: "test-model",
928
- providerDurationMs: 500,
911
+ providerDurationMs: 10,
929
912
  });
930
- return [
931
- ...messages,
932
- {
933
- role: "assistant" as const,
934
- content: [{ type: "text", text: "recovered" }] as ContentBlock[],
935
- },
936
- ];
937
- };
938
-
939
- const ctx = makeCtx({
940
- agentLoopRun,
941
- contextWindowManager: {
942
- shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
943
- maybeCompact: async () => ({ compacted: false }),
944
- } as unknown as AgentLoopConversationContext["contextWindowManager"],
913
+ // No progress — return same messages
914
+ return messages;
915
+ }
916
+ // Second call succeeds
917
+ onEvent({
918
+ type: "message_complete",
919
+ message: {
920
+ role: "assistant",
921
+ content: [{ type: "text", text: "recovered" }],
922
+ },
945
923
  });
924
+ onEvent({
925
+ type: "usage",
926
+ inputTokens: 80_000,
927
+ outputTokens: 200,
928
+ model: "test-model",
929
+ providerDurationMs: 500,
930
+ });
931
+ return [
932
+ ...messages,
933
+ {
934
+ role: "assistant" as const,
935
+ content: [{ type: "text", text: "recovered" }] as ContentBlock[],
936
+ },
937
+ ];
938
+ };
946
939
 
947
- await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
940
+ const ctx = makeCtx({
941
+ agentLoopRun,
942
+ contextWindowManager: {
943
+ shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
944
+ maybeCompact: async () => ({ compacted: false }),
945
+ } as unknown as AgentLoopConversationContext["contextWindowManager"],
946
+ });
948
947
 
949
- // The reducer should be called in the convergence loop
950
- expect(reducerCalled).toBe(true);
951
- // Should recover without conversation_error
952
- const conversationError = events.find(
953
- (e) => e.type === "conversation_error",
954
- );
955
- expect(conversationError).toBeUndefined();
956
- expect(callCount).toBe(2);
957
- },
958
- );
948
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
949
+
950
+ // The reducer should be called in the convergence loop
951
+ expect(reducerCalled).toBe(true);
952
+ // Should recover without conversation_error
953
+ const conversationError = events.find(
954
+ (e) => e.type === "conversation_error",
955
+ );
956
+ expect(conversationError).toBeUndefined();
957
+ expect(callCount).toBe(2);
958
+ });
959
959
 
960
960
  // ── Test 3 ────────────────────────────────────────────────────────
961
961
  // BUG: When the provider rejection reveals actual token count (e.g.,
@@ -61,7 +61,9 @@ mock.module("../config/loader.js", () => ({
61
61
  },
62
62
  rateLimit: { maxRequestsPerMinute: 0 },
63
63
  workspaceGit: { turnCommitMaxWaitMs: 10 },
64
+ memory: { retrieval: { scratchpadInjection: { enabled: true } } },
64
65
  ui: mockUiConfig,
66
+ compaction: { enabled: true, autoThreshold: 0.7 },
65
67
  }),
66
68
  loadRawConfig: () => ({}),
67
69
  saveRawConfig: () => {},
@@ -384,6 +386,7 @@ mock.module("../daemon/history-repair.js", () => ({
384
386
 
385
387
  const recordUsageMock = mock(() => {});
386
388
  const recordRequestLogMock = mock(() => {});
389
+ const backfillMessageIdOnLogsMock = mock(() => {});
387
390
  mock.module("../daemon/conversation-usage.js", () => ({
388
391
  recordUsage: recordUsageMock,
389
392
  }));
@@ -480,7 +483,7 @@ mock.module("../memory/archive-store.js", () => ({
480
483
 
481
484
  mock.module("../memory/llm-request-log-store.js", () => ({
482
485
  recordRequestLog: recordRequestLogMock,
483
- backfillMessageIdOnLogs: () => {},
486
+ backfillMessageIdOnLogs: backfillMessageIdOnLogsMock,
484
487
  }));
485
488
 
486
489
  let mockHasProactiveArtifactCompleted = true;
@@ -656,6 +659,7 @@ beforeEach(() => {
656
659
  mockInjectionBlocks = {};
657
660
  recordUsageMock.mockClear();
658
661
  recordRequestLogMock.mockClear();
662
+ backfillMessageIdOnLogsMock.mockClear();
659
663
  syncMessageToDiskMock.mockClear();
660
664
  rebuildConversationDiskViewFromDbStateMock.mockClear();
661
665
  updateMessageMetadataMock.mockClear();
@@ -2853,6 +2857,60 @@ describe("session-agent-loop", () => {
2853
2857
  );
2854
2858
  expect(conversationErrors.length).toBeGreaterThanOrEqual(1);
2855
2859
  });
2860
+
2861
+ test("pipes synthetic assistant message id into provider-error log rows via backfill", async () => {
2862
+ // Codex P1 regression test: the provider-failure turn must not leave
2863
+ // its `llm_request_logs` row orphaned. Without the backfill call in
2864
+ // the synthetic-message branch, a later turn's `handleMessageComplete`
2865
+ // sweep would wrong-attach this row to the wrong assistant message.
2866
+ const events: ServerMessage[] = [];
2867
+
2868
+ const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
2869
+ // 1) handleProviderError -> writes an `llm_request_logs` row with
2870
+ // messageId=null (the orphan we are trying to link).
2871
+ onEvent({
2872
+ type: "provider_error",
2873
+ error: new Error("upstream 500"),
2874
+ rawRequest: { model: "gpt-4.1", messages: [] },
2875
+ actualProvider: "openai",
2876
+ });
2877
+ // 2) handleError -> sets `state.providerErrorUserMessage`, which
2878
+ // activates the synthetic-message branch below the loop.
2879
+ onEvent({
2880
+ type: "error",
2881
+ error: new Error("upstream 500"),
2882
+ });
2883
+ // Provider returned no assistant content — same messages back.
2884
+ return messages;
2885
+ };
2886
+
2887
+ const ctx = makeCtx({ agentLoopRun });
2888
+ await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
2889
+
2890
+ // The orphan was written with messageId=undefined.
2891
+ expect(recordRequestLogMock).toHaveBeenCalledTimes(1);
2892
+ const recordCall = recordRequestLogMock.mock.calls[0] as unknown as [
2893
+ string,
2894
+ string,
2895
+ string,
2896
+ string | undefined,
2897
+ string | undefined,
2898
+ ];
2899
+ expect(recordCall[0]).toBe("test-conv");
2900
+ expect(recordCall[3]).toBeUndefined();
2901
+
2902
+ // The synthetic-message branch then piped the assigned message id
2903
+ // (from the mocked `addMessage` -> `{ id: "mock-msg-id" }`) into the
2904
+ // backfill primitive, scoped to this conversation.
2905
+ expect(backfillMessageIdOnLogsMock).toHaveBeenCalledTimes(1);
2906
+ const backfillCall =
2907
+ backfillMessageIdOnLogsMock.mock.calls[0] as unknown as [
2908
+ string,
2909
+ string,
2910
+ ];
2911
+ expect(backfillCall[0]).toBe("test-conv");
2912
+ expect(backfillCall[1]).toBe("mock-msg-id");
2913
+ });
2856
2914
  });
2857
2915
 
2858
2916
  describe("pkbSystemReminderBlock metadata persistence", () => {
@@ -596,12 +596,51 @@ describe("classifyConversationError", () => {
596
596
  expect(result.retryable).toBe(true);
597
597
  });
598
598
 
599
- it("classifies ProviderError with 401 as PROVIDER_NOT_CONFIGURED (non-retryable)", () => {
599
+ it("classifies ProviderError with 401 as PROVIDER_INVALID_KEY (non-retryable)", () => {
600
+ // 401 means the upstream provider rejected the configured key
601
+ // (vs. PROVIDER_NOT_CONFIGURED which is for a never-set key).
602
+ // The macOS chat renders these on different banners.
600
603
  const err = new ProviderError("Unauthorized", "anthropic", 401);
601
604
  const result = classifyConversationError(err, baseCtx);
602
- expect(result.code).toBe("PROVIDER_NOT_CONFIGURED");
605
+ expect(result.code).toBe("PROVIDER_INVALID_KEY");
603
606
  expect(result.retryable).toBe(false);
604
- expect(result.errorCategory).toBe("provider_not_configured");
607
+ expect(result.errorCategory).toBe("provider_invalid_key");
608
+ });
609
+
610
+ it("classifies ProviderError 401 with 'invalid x-api-key' message as PROVIDER_INVALID_KEY", () => {
611
+ // Regex-match branch — Anthropic's standard 401 wording.
612
+ const err = new ProviderError(
613
+ "Anthropic API error: invalid x-api-key",
614
+ "anthropic",
615
+ 401,
616
+ );
617
+ const result = classifyConversationError(err, baseCtx);
618
+ expect(result.code).toBe("PROVIDER_INVALID_KEY");
619
+ expect(result.errorCategory).toBe("provider_invalid_key");
620
+ });
621
+
622
+ it("classifies ProviderError 403 with 'invalid api key' message as PROVIDER_INVALID_KEY", () => {
623
+ const err = new ProviderError(
624
+ "OpenAI: Invalid API key",
625
+ "openai",
626
+ 403,
627
+ );
628
+ const result = classifyConversationError(err, baseCtx);
629
+ expect(result.code).toBe("PROVIDER_INVALID_KEY");
630
+ expect(result.errorCategory).toBe("provider_invalid_key");
631
+ });
632
+
633
+ it("includes connection/profile attribution in PROVIDER_INVALID_KEY when provided", () => {
634
+ const err = new ProviderError("Unauthorized", "anthropic", 401);
635
+ const result = classifyConversationError(err, {
636
+ ...baseCtx,
637
+ connectionName: "my-anthropic",
638
+ profileName: "personal",
639
+ });
640
+ expect(result.code).toBe("PROVIDER_INVALID_KEY");
641
+ expect(result.connectionName).toBe("my-anthropic");
642
+ expect(result.profileName).toBe("personal");
643
+ expect(result.userMessage).toContain("personal");
605
644
  });
606
645
 
607
646
  it("classifies direct ProviderError with 402 as provider_billing (non-retryable)", () => {
@@ -609,6 +609,88 @@ describe("forkConversation", () => {
609
609
  expect(await hydrateActivationState(db, fork.id)).toBeNull();
610
610
  expect(loadGraphMemoryState(fork.id)).toBeNull();
611
611
  });
612
+
613
+ test("does not copy memory state when the fork is truncated mid-history", async () => {
614
+ const source = createConversation("Truncated thread");
615
+ const firstMessage = await addMessage(
616
+ source.id,
617
+ "user",
618
+ "first turn",
619
+ undefined,
620
+ { skipIndexing: true },
621
+ );
622
+ await addMessage(source.id, "assistant", "first reply", undefined, {
623
+ skipIndexing: true,
624
+ });
625
+ const lastMessage = await addMessage(
626
+ source.id,
627
+ "user",
628
+ "second turn",
629
+ undefined,
630
+ { skipIndexing: true },
631
+ );
632
+
633
+ const db = getDb();
634
+ db.insert(activationState)
635
+ .values({
636
+ conversationId: source.id,
637
+ messageId: lastMessage.id,
638
+ stateJson: JSON.stringify({ "concepts/foo": 0.5 }),
639
+ everInjectedJson: JSON.stringify([{ slug: "concepts/foo", turn: 2 }]),
640
+ currentTurn: 2,
641
+ updatedAt: 1_700_000_000_000,
642
+ })
643
+ .run();
644
+ saveGraphMemoryState(
645
+ source.id,
646
+ JSON.stringify({
647
+ initialized: true,
648
+ needsReload: false,
649
+ inContext: ["node-foo"],
650
+ log: [{ nodeId: "node-foo", turn: 2 }],
651
+ currentTurn: 2,
652
+ }),
653
+ );
654
+
655
+ const fork = forkConversation({
656
+ conversationId: source.id,
657
+ throughMessageId: firstMessage.id,
658
+ });
659
+
660
+ expect(await hydrateActivationState(db, fork.id)).toBeNull();
661
+ expect(loadGraphMemoryState(fork.id)).toBeNull();
662
+ });
663
+
664
+ test("copies memory state when throughMessageId points at the last message", async () => {
665
+ const source = createConversation("Through-last thread");
666
+ const lastMessage = await addMessage(
667
+ source.id,
668
+ "user",
669
+ "only turn",
670
+ undefined,
671
+ { skipIndexing: true },
672
+ );
673
+
674
+ const db = getDb();
675
+ db.insert(activationState)
676
+ .values({
677
+ conversationId: source.id,
678
+ messageId: lastMessage.id,
679
+ stateJson: JSON.stringify({ "concepts/foo": 0.9 }),
680
+ everInjectedJson: JSON.stringify([{ slug: "concepts/foo", turn: 1 }]),
681
+ currentTurn: 1,
682
+ updatedAt: 1_700_000_000_000,
683
+ })
684
+ .run();
685
+
686
+ const fork = forkConversation({
687
+ conversationId: source.id,
688
+ throughMessageId: lastMessage.id,
689
+ });
690
+
691
+ const childState = await hydrateActivationState(db, fork.id);
692
+ expect(childState?.currentTurn).toBe(1);
693
+ });
612
694
  });
613
695
 
614
696
  describe("forkConversation + memory_retrospective_state", () => {