@vellumai/assistant 0.8.1 → 0.8.2

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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -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 +325 -3
  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-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -5,6 +5,7 @@ import type { RecallSearchContext } from "../memory/context-search/types.js";
5
5
  import { getDb } from "../memory/db-connection.js";
6
6
  import { initializeDb } from "../memory/db-init.js";
7
7
  import { rawRun } from "../memory/raw-query.js";
8
+ import { writeSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
8
9
  initializeDb();
9
10
 
10
11
  let seedId = 0;
@@ -225,6 +226,99 @@ describe("searchConversationSource", () => {
225
226
  );
226
227
  });
227
228
 
229
+ test("preserves external_content boundaries in recall evidence", async () => {
230
+ const { conversation, message } = await seedConversation({
231
+ title: "Slack recall",
232
+ content:
233
+ '<external_content source="slack" origin="@alice">\nThe recalltoken decision came from Slack.\n</external_content>',
234
+ });
235
+
236
+ const result = await searchConversationSource(
237
+ "recalltoken",
238
+ makeContext(),
239
+ 1,
240
+ );
241
+
242
+ expect(result.evidence).toHaveLength(1);
243
+ expect(result.evidence[0]).toMatchObject({
244
+ locator: `${conversation.id}#${message.id}`,
245
+ excerpt:
246
+ '<external_content source="slack" origin="@alice">\nThe recalltoken decision came from Slack.\n</external_content>',
247
+ });
248
+ });
249
+
250
+ test("wraps raw non-guardian Slack recall evidence from metadata", async () => {
251
+ const { conversation, message } = await seedConversation({
252
+ title: "Raw Slack recall",
253
+ role: "user",
254
+ content: "The rawrecalltoken decision came from Slack.",
255
+ metadata: slackMetadata("1700000100.000000", {
256
+ provenanceTrustClass: "unknown",
257
+ }),
258
+ });
259
+
260
+ const result = await searchConversationSource(
261
+ "rawrecalltoken",
262
+ makeContext(),
263
+ 1,
264
+ );
265
+
266
+ expect(result.evidence).toHaveLength(1);
267
+ expect(result.evidence[0]).toMatchObject({
268
+ locator: `${conversation.id}#${message.id}`,
269
+ excerpt:
270
+ '<external_content source="slack" origin="@alice">\nThe rawrecalltoken decision came from Slack.\n</external_content>',
271
+ });
272
+ });
273
+
274
+ test("wraps raw non-guardian Slack recall evidence that mentions external_content", async () => {
275
+ const { conversation, message } = await seedConversation({
276
+ title: "Raw Slack tag mention recall",
277
+ role: "user",
278
+ content:
279
+ "The tagmentionrecalltoken text mentions <external_content but is raw Slack content.",
280
+ metadata: slackMetadata("1700000102.000000", {
281
+ provenanceTrustClass: "unknown",
282
+ }),
283
+ });
284
+
285
+ const result = await searchConversationSource(
286
+ "tagmentionrecalltoken",
287
+ makeContext(),
288
+ 1,
289
+ );
290
+
291
+ expect(result.evidence).toHaveLength(1);
292
+ expect(result.evidence[0]).toMatchObject({
293
+ locator: `${conversation.id}#${message.id}`,
294
+ excerpt:
295
+ '<external_content source="slack" origin="@alice">\nThe tagmentionrecalltoken text mentions <external_content but is raw Slack content.\n</external_content>',
296
+ });
297
+ });
298
+
299
+ test("does not wrap guardian Slack recall evidence", async () => {
300
+ const { conversation, message } = await seedConversation({
301
+ title: "Guardian Slack recall",
302
+ role: "user",
303
+ content: "The guardianrecalltoken decision came from Slack.",
304
+ metadata: slackMetadata("1700000101.000000", {
305
+ provenanceTrustClass: "guardian",
306
+ }),
307
+ });
308
+
309
+ const result = await searchConversationSource(
310
+ "guardianrecalltoken",
311
+ makeContext(),
312
+ 1,
313
+ );
314
+
315
+ expect(result.evidence).toHaveLength(1);
316
+ expect(result.evidence[0]).toMatchObject({
317
+ locator: `${conversation.id}#${message.id}`,
318
+ excerpt: "The guardianrecalltoken decision came from Slack.",
319
+ });
320
+ });
321
+
228
322
  test("broadens overconstrained recall queries to salient terms", async () => {
229
323
  const specific = await seedConversation({
230
324
  title: "Birthday cake plan",
@@ -258,6 +352,7 @@ function seedConversation(opts: {
258
352
  memoryScopeId?: string;
259
353
  role?: string;
260
354
  content: string;
355
+ metadata?: string;
261
356
  }) {
262
357
  const id = ++seedId;
263
358
  const now = Date.now() + id;
@@ -297,19 +392,39 @@ function seedConversation(opts: {
297
392
  conversation_id,
298
393
  role,
299
394
  content,
300
- created_at
301
- ) VALUES (?, ?, ?, ?, ?)
395
+ created_at,
396
+ metadata
397
+ ) VALUES (?, ?, ?, ?, ?, ?)
302
398
  `,
303
399
  message.id,
304
400
  conversation.id,
305
401
  opts.role ?? "assistant",
306
402
  opts.content,
307
403
  now,
404
+ opts.metadata ?? null,
308
405
  );
309
406
 
310
407
  return { conversation, message };
311
408
  }
312
409
 
410
+ function slackMetadata(
411
+ channelTs: string,
412
+ extra: Record<string, unknown>,
413
+ ): string {
414
+ return JSON.stringify({
415
+ userMessageChannel: "slack",
416
+ assistantMessageChannel: "slack",
417
+ slackMeta: writeSlackMetadata({
418
+ source: "slack",
419
+ channelId: "C0123",
420
+ channelTs,
421
+ eventKind: "message",
422
+ displayName: "@alice",
423
+ }),
424
+ ...extra,
425
+ });
426
+ }
427
+
313
428
  function makeContext(
314
429
  overrides: Partial<RecallSearchContext> = {},
315
430
  ): RecallSearchContext {
@@ -41,7 +41,6 @@ mock.module("../memory/embedding-backend.js", () => ({
41
41
  model: "test-model",
42
42
  vectors: [denseEmbedReturn],
43
43
  }),
44
- generateSparseEmbedding: () => ({ indices: [1], values: [1] }),
45
44
  }));
46
45
 
47
46
  interface QdrantHit {
@@ -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
  });
@@ -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: () => {},
@@ -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", () => {