@vellumai/assistant 0.5.13 → 0.5.15

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 (425) hide show
  1. package/.env.example +1 -6
  2. package/AGENTS.md +4 -0
  3. package/ARCHITECTURE.md +0 -1
  4. package/bunfig.toml +1 -0
  5. package/docs/architecture/memory.md +3 -3
  6. package/openapi.yaml +127 -22
  7. package/package.json +1 -1
  8. package/src/__tests__/access-request-decision.test.ts +2 -32
  9. package/src/__tests__/actor-token-service.test.ts +1 -31
  10. package/src/__tests__/anthropic-provider.test.ts +53 -40
  11. package/src/__tests__/app-git-history.test.ts +9 -17
  12. package/src/__tests__/app-git-service.test.ts +14 -20
  13. package/src/__tests__/app-store-dir-names.test.ts +10 -20
  14. package/src/__tests__/approval-cascade.test.ts +2 -19
  15. package/src/__tests__/approval-primitive.test.ts +2 -27
  16. package/src/__tests__/approval-routes-http.test.ts +2 -30
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
  19. package/src/__tests__/attachments-store.test.ts +5 -32
  20. package/src/__tests__/audit-log-rotation.test.ts +5 -36
  21. package/src/__tests__/avatar-e2e.test.ts +1 -9
  22. package/src/__tests__/avatar-generator.test.ts +1 -7
  23. package/src/__tests__/browser-fill-credential.test.ts +0 -4
  24. package/src/__tests__/browser-manager.test.ts +0 -6
  25. package/src/__tests__/call-controller.test.ts +1 -22
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -21
  27. package/src/__tests__/call-domain.test.ts +0 -25
  28. package/src/__tests__/call-pointer-messages.test.ts +0 -21
  29. package/src/__tests__/call-recovery.test.ts +0 -22
  30. package/src/__tests__/call-routes-http.test.ts +0 -24
  31. package/src/__tests__/call-store.test.ts +0 -21
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
  33. package/src/__tests__/canonical-guardian-store.test.ts +48 -21
  34. package/src/__tests__/channel-approval-routes.test.ts +6 -26
  35. package/src/__tests__/channel-approvals.test.ts +1 -38
  36. package/src/__tests__/channel-delivery-store.test.ts +0 -21
  37. package/src/__tests__/channel-guardian.test.ts +0 -26
  38. package/src/__tests__/channel-reply-delivery.test.ts +5 -0
  39. package/src/__tests__/channel-retry-sweep.test.ts +0 -21
  40. package/src/__tests__/checker.test.ts +26 -61
  41. package/src/__tests__/clawhub.test.ts +9 -25
  42. package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
  43. package/src/__tests__/config-loader-backfill.test.ts +9 -28
  44. package/src/__tests__/config-schema-cmd.test.ts +5 -25
  45. package/src/__tests__/config-schema.test.ts +21 -40
  46. package/src/__tests__/config-watcher.test.ts +4 -91
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
  48. package/src/__tests__/contacts-tools.test.ts +0 -21
  49. package/src/__tests__/context-memory-e2e.test.ts +0 -21
  50. package/src/__tests__/context-window-manager.test.ts +130 -3
  51. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
  52. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
  53. package/src/__tests__/conversation-agent-loop.test.ts +0 -4
  54. package/src/__tests__/conversation-attachments.test.ts +1 -24
  55. package/src/__tests__/conversation-attention-store.test.ts +0 -21
  56. package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
  57. package/src/__tests__/conversation-clear-safety.test.ts +0 -22
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
  59. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
  60. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
  61. package/src/__tests__/conversation-disk-view.test.ts +5 -27
  62. package/src/__tests__/conversation-error.test.ts +1 -1
  63. package/src/__tests__/conversation-fork-crud.test.ts +1 -33
  64. package/src/__tests__/conversation-fork-route.test.ts +0 -27
  65. package/src/__tests__/conversation-history-web-search.test.ts +23 -16
  66. package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
  67. package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
  68. package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
  69. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
  70. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
  71. package/src/__tests__/conversation-queue.test.ts +8 -8
  72. package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
  74. package/src/__tests__/conversation-slash-commands.test.ts +5 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +0 -4
  76. package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
  77. package/src/__tests__/conversation-speed-override.test.ts +326 -0
  78. package/src/__tests__/conversation-starter-routes.test.ts +0 -23
  79. package/src/__tests__/conversation-store.test.ts +0 -21
  80. package/src/__tests__/conversation-unread-route.test.ts +0 -24
  81. package/src/__tests__/conversation-usage.test.ts +56 -21
  82. package/src/__tests__/conversation-wipe.test.ts +0 -21
  83. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  84. package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
  85. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
  86. package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
  87. package/src/__tests__/credential-vault-unit.test.ts +9 -428
  88. package/src/__tests__/credentials-cli.test.ts +10 -10
  89. package/src/__tests__/daemon-assistant-events.test.ts +0 -19
  90. package/src/__tests__/date-context.test.ts +77 -97
  91. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
  92. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
  93. package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
  94. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
  95. package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
  96. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
  97. package/src/__tests__/email-cli.test.ts +6 -6
  98. package/src/__tests__/ephemeral-permissions.test.ts +5 -17
  99. package/src/__tests__/first-greeting.test.ts +4 -32
  100. package/src/__tests__/followup-tools.test.ts +0 -21
  101. package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
  102. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
  103. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
  104. package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
  105. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
  106. package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
  107. package/src/__tests__/guardian-action-store.test.ts +0 -21
  108. package/src/__tests__/guardian-action-sweep.test.ts +0 -21
  109. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
  110. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
  111. package/src/__tests__/guardian-dispatch.test.ts +0 -21
  112. package/src/__tests__/guardian-grant-minting.test.ts +0 -22
  113. package/src/__tests__/guardian-outbound-http.test.ts +0 -22
  114. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
  115. package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
  116. package/src/__tests__/guardian-routing-state.test.ts +0 -22
  117. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
  118. package/src/__tests__/headless-browser-interactions.test.ts +0 -4
  119. package/src/__tests__/headless-browser-navigate.test.ts +0 -4
  120. package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
  121. package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
  122. package/src/__tests__/heartbeat-service.test.ts +99 -26
  123. package/src/__tests__/hooks-blocking.test.ts +3 -3
  124. package/src/__tests__/hooks-config.test.ts +7 -7
  125. package/src/__tests__/hooks-discovery.test.ts +3 -3
  126. package/src/__tests__/hooks-integration.test.ts +5 -5
  127. package/src/__tests__/hooks-manager.test.ts +3 -3
  128. package/src/__tests__/hooks-runner.test.ts +5 -23
  129. package/src/__tests__/hooks-settings.test.ts +3 -3
  130. package/src/__tests__/hooks-templates.test.ts +3 -3
  131. package/src/__tests__/http-conversation-lineage.test.ts +0 -27
  132. package/src/__tests__/identity-intro-cache.test.ts +0 -4
  133. package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
  134. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
  135. package/src/__tests__/intent-routing.test.ts +2 -55
  136. package/src/__tests__/invite-redemption-service.test.ts +0 -21
  137. package/src/__tests__/invite-routes-http.test.ts +0 -21
  138. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
  139. package/src/__tests__/journal-context.test.ts +8 -75
  140. package/src/__tests__/list-messages-attachments.test.ts +0 -22
  141. package/src/__tests__/llm-context-route-provider.test.ts +0 -21
  142. package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
  143. package/src/__tests__/llm-usage-store.test.ts +0 -21
  144. package/src/__tests__/log-export-workspace.test.ts +1 -1
  145. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  146. package/src/__tests__/managed-store.test.ts +1 -1
  147. package/src/__tests__/mcp-cli.test.ts +7 -10
  148. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
  149. package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
  150. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
  151. package/src/__tests__/memory-recall-log-store.test.ts +0 -27
  152. package/src/__tests__/memory-recall-quality.test.ts +0 -21
  153. package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
  154. package/src/__tests__/memory-regressions.test.ts +282 -70
  155. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
  156. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
  157. package/src/__tests__/messaging-send-tool.test.ts +201 -0
  158. package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
  159. package/src/__tests__/migration-export-http.test.ts +7 -1
  160. package/src/__tests__/migration-import-commit-http.test.ts +16 -14
  161. package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
  162. package/src/__tests__/migration-validate-http.test.ts +1 -28
  163. package/src/__tests__/native-web-search.test.ts +25 -22
  164. package/src/__tests__/non-member-access-request.test.ts +0 -22
  165. package/src/__tests__/notification-guardian-path.test.ts +0 -21
  166. package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
  167. package/src/__tests__/oauth-apps-routes.test.ts +103 -2
  168. package/src/__tests__/oauth-cli.test.ts +52 -0
  169. package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
  170. package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
  171. package/src/__tests__/oauth-providers-routes.test.ts +257 -0
  172. package/src/__tests__/oauth-store.test.ts +0 -21
  173. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  174. package/src/__tests__/openai-provider.test.ts +261 -0
  175. package/src/__tests__/pairing-concurrent.test.ts +6 -6
  176. package/src/__tests__/pairing-routes.test.ts +7 -1
  177. package/src/__tests__/path-policy.test.ts +1 -1
  178. package/src/__tests__/platform.test.ts +64 -88
  179. package/src/__tests__/playbook-execution.test.ts +0 -21
  180. package/src/__tests__/playbook-tools.test.ts +0 -21
  181. package/src/__tests__/pricing.test.ts +100 -0
  182. package/src/__tests__/relay-server.test.ts +1 -25
  183. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
  184. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
  185. package/src/__tests__/runtime-events-sse.test.ts +0 -24
  186. package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
  187. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  188. package/src/__tests__/schedule-store.test.ts +0 -21
  189. package/src/__tests__/schedule-tools.test.ts +0 -21
  190. package/src/__tests__/scheduler-recurrence.test.ts +0 -21
  191. package/src/__tests__/scoped-approval-grants.test.ts +0 -21
  192. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
  193. package/src/__tests__/secret-allowlist.test.ts +1 -1
  194. package/src/__tests__/secret-ingress-channel.test.ts +0 -5
  195. package/src/__tests__/secret-ingress-cli.test.ts +0 -6
  196. package/src/__tests__/secret-ingress-http.test.ts +0 -5
  197. package/src/__tests__/secret-ingress.test.ts +0 -5
  198. package/src/__tests__/send-endpoint-busy.test.ts +0 -24
  199. package/src/__tests__/sequence-store.test.ts +0 -21
  200. package/src/__tests__/server-history-render.test.ts +0 -24
  201. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
  202. package/src/__tests__/skill-load-inline-command.test.ts +9 -0
  203. package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
  204. package/src/__tests__/skill-load-tool.test.ts +11 -0
  205. package/src/__tests__/skills-uninstall.test.ts +10 -8
  206. package/src/__tests__/skills.test.ts +1 -1
  207. package/src/__tests__/slack-channel-config.test.ts +1 -1
  208. package/src/__tests__/slack-inbound-verification.test.ts +0 -22
  209. package/src/__tests__/starter-bundle.test.ts +4 -1
  210. package/src/__tests__/suggestion-routes.test.ts +2 -0
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/terminal-tools.test.ts +1 -1
  213. package/src/__tests__/test-preload.ts +31 -0
  214. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  215. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  216. package/src/__tests__/tool-executor.test.ts +0 -20
  217. package/src/__tests__/tool-input-summary.test.ts +124 -0
  218. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
  219. package/src/__tests__/trust-store.test.ts +7 -1
  220. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
  221. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
  222. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
  223. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  224. package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
  225. package/src/__tests__/twilio-routes.test.ts +1 -1
  226. package/src/__tests__/update-bulletin.test.ts +1 -1
  227. package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
  228. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
  229. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  230. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  232. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
  234. package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
  235. package/src/agent/loop.ts +6 -9
  236. package/src/approvals/guardian-decision-primitive.ts +46 -18
  237. package/src/approvals/guardian-request-resolvers.ts +19 -2
  238. package/src/calls/active-call-lease.ts +2 -2
  239. package/src/cli/AGENTS.md +1 -1
  240. package/src/cli/commands/doctor.ts +9 -9
  241. package/src/cli/commands/memory.ts +142 -0
  242. package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
  243. package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
  244. package/src/cli/commands/oauth/connect.ts +13 -12
  245. package/src/cli/commands/oauth/index.ts +1 -1
  246. package/src/cli/commands/oauth/providers.ts +47 -62
  247. package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
  248. package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
  249. package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
  250. package/src/cli/commands/platform/connect.ts +17 -7
  251. package/src/cli/commands/platform/disconnect.ts +28 -3
  252. package/src/cli/commands/platform/index.ts +3 -3
  253. package/src/cli.ts +1 -299
  254. package/src/config/assistant-feature-flags.ts +23 -15
  255. package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
  256. package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
  257. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
  258. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
  259. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
  260. package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
  261. package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
  262. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
  263. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
  264. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  265. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
  266. package/src/config/bundled-skills/slack/SKILL.md +18 -0
  267. package/src/config/env-registry.ts +15 -11
  268. package/src/config/env.ts +1 -11
  269. package/src/config/feature-flag-registry.json +16 -0
  270. package/src/config/schema.ts +4 -0
  271. package/src/config/schemas/heartbeat.ts +6 -1
  272. package/src/config/schemas/inference.ts +14 -3
  273. package/src/config/schemas/memory-processing.ts +16 -8
  274. package/src/config/schemas/memory-retrieval.ts +3 -3
  275. package/src/config/skills.ts +1 -1
  276. package/src/context/window-manager.ts +174 -51
  277. package/src/credential-execution/executable-discovery.ts +2 -2
  278. package/src/daemon/approved-devices-store.ts +2 -2
  279. package/src/daemon/assistant-attachments.ts +2 -0
  280. package/src/daemon/config-watcher.ts +4 -50
  281. package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
  282. package/src/daemon/conversation-agent-loop.ts +12 -0
  283. package/src/daemon/conversation-error.ts +3 -5
  284. package/src/daemon/conversation-history.ts +7 -3
  285. package/src/daemon/conversation-lifecycle.ts +16 -0
  286. package/src/daemon/conversation-messaging.ts +1 -0
  287. package/src/daemon/conversation-notifiers.ts +67 -30
  288. package/src/daemon/conversation-process.ts +161 -2
  289. package/src/daemon/conversation-queue-manager.ts +2 -0
  290. package/src/daemon/conversation-runtime-assembly.ts +33 -11
  291. package/src/daemon/conversation-slash.ts +14 -3
  292. package/src/daemon/conversation-tool-setup.ts +2 -0
  293. package/src/daemon/conversation-usage.ts +32 -4
  294. package/src/daemon/conversation.ts +33 -1
  295. package/src/daemon/daemon-control.ts +32 -16
  296. package/src/daemon/date-context.ts +47 -45
  297. package/src/daemon/dictation-profile-store.ts +2 -2
  298. package/src/daemon/handlers/conversations.ts +19 -0
  299. package/src/daemon/handlers/shared.ts +14 -21
  300. package/src/daemon/lifecycle.ts +5 -7
  301. package/src/daemon/message-types/conversations.ts +2 -0
  302. package/src/daemon/message-types/guardian-actions.ts +3 -17
  303. package/src/daemon/message-types/integrations.ts +11 -1
  304. package/src/daemon/message-types/messages.ts +1 -0
  305. package/src/daemon/pairing-store.ts +2 -79
  306. package/src/daemon/server.ts +154 -8
  307. package/src/daemon/watch-handler.ts +65 -21
  308. package/src/email/guardrails.ts +3 -3
  309. package/src/heartbeat/heartbeat-service.ts +14 -7
  310. package/src/hooks/cli.ts +2 -2
  311. package/src/hooks/config.ts +2 -2
  312. package/src/hooks/discovery.ts +2 -2
  313. package/src/hooks/manager.ts +2 -2
  314. package/src/hooks/runner.ts +5 -2
  315. package/src/hooks/templates.ts +2 -2
  316. package/src/memory/admin.ts +181 -2
  317. package/src/memory/app-git-service.ts +61 -4
  318. package/src/memory/attachments-store.ts +2 -0
  319. package/src/memory/canonical-guardian-store.ts +16 -0
  320. package/src/memory/db-init.ts +8 -0
  321. package/src/memory/embedding-local.ts +5 -2
  322. package/src/memory/indexer.ts +44 -26
  323. package/src/memory/items-extractor.ts +34 -82
  324. package/src/memory/job-handlers/batch-extraction.ts +741 -0
  325. package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
  326. package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
  327. package/src/memory/jobs-store.ts +28 -0
  328. package/src/memory/jobs-worker.ts +56 -9
  329. package/src/memory/lifecycle-events-store.ts +4 -2
  330. package/src/memory/llm-request-log-store.ts +40 -2
  331. package/src/memory/llm-usage-store.ts +4 -3
  332. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
  333. package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
  334. package/src/memory/migrations/index.ts +2 -0
  335. package/src/memory/query-expansion.ts +83 -0
  336. package/src/memory/retriever.test.ts +119 -0
  337. package/src/memory/retriever.ts +513 -105
  338. package/src/memory/schema/guardian.ts +4 -0
  339. package/src/memory/schema/infrastructure.ts +1 -0
  340. package/src/memory/search/formatting.test.ts +140 -0
  341. package/src/memory/search/formatting.ts +143 -198
  342. package/src/memory/search/mmr.ts +136 -0
  343. package/src/memory/search/staleness.ts +0 -15
  344. package/src/memory/search/tier-classifier.ts +10 -21
  345. package/src/memory/search/types.ts +17 -0
  346. package/src/messaging/providers/slack/adapter.ts +51 -5
  347. package/src/notifications/broadcaster.ts +13 -0
  348. package/src/notifications/copy-composer.ts +8 -0
  349. package/src/oauth/connect-orchestrator.ts +1 -1
  350. package/src/oauth/connection-resolver.ts +2 -2
  351. package/src/oauth/provider-serializer.ts +116 -0
  352. package/src/permissions/trust-store.ts +24 -7
  353. package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
  354. package/src/prompts/journal-context.ts +50 -35
  355. package/src/prompts/persona-resolver.ts +1 -1
  356. package/src/prompts/system-prompt.ts +27 -28
  357. package/src/prompts/templates/BOOTSTRAP.md +14 -1
  358. package/src/prompts/templates/HEARTBEAT.md +10 -0
  359. package/src/prompts/templates/NOW.md +19 -25
  360. package/src/prompts/templates/SOUL.md +13 -1
  361. package/src/prompts/templates/UPDATES.md +12 -0
  362. package/src/prompts/update-bulletin.ts +1 -1
  363. package/src/providers/anthropic/client.ts +89 -18
  364. package/src/providers/model-catalog.ts +22 -2
  365. package/src/providers/model-intents.ts +2 -2
  366. package/src/providers/openai/client.ts +40 -1
  367. package/src/providers/retry.ts +23 -4
  368. package/src/providers/types.ts +2 -0
  369. package/src/runtime/assistant-scope.ts +1 -1
  370. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
  371. package/src/runtime/auth/route-policy.ts +1 -0
  372. package/src/runtime/auth/token-service.ts +51 -29
  373. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
  374. package/src/runtime/guardian-decision-types.ts +16 -10
  375. package/src/runtime/http-server.ts +3 -14
  376. package/src/runtime/http-types.ts +1 -0
  377. package/src/runtime/migrations/vbundle-builder.ts +7 -4
  378. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
  379. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  380. package/src/runtime/routes/conversation-query-routes.ts +40 -8
  381. package/src/runtime/routes/conversation-routes.ts +125 -3
  382. package/src/runtime/routes/guardian-action-routes.ts +9 -3
  383. package/src/runtime/routes/identity-routes.ts +25 -4
  384. package/src/runtime/routes/llm-context-normalization.ts +1 -0
  385. package/src/runtime/routes/log-export-routes.ts +34 -12
  386. package/src/runtime/routes/migration-routes.ts +6 -10
  387. package/src/runtime/routes/oauth-apps.ts +2 -9
  388. package/src/runtime/routes/oauth-providers.ts +60 -0
  389. package/src/runtime/routes/pairing-routes.ts +0 -8
  390. package/src/runtime/routes/settings-routes.ts +0 -1
  391. package/src/runtime/routes/telemetry-routes.ts +16 -4
  392. package/src/security/encrypted-store.ts +2 -2
  393. package/src/security/secret-allowlist.ts +3 -3
  394. package/src/signals/emit-event.ts +42 -0
  395. package/src/signals/user-message.ts +37 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
  397. package/src/telemetry/usage-telemetry-reporter.ts +23 -17
  398. package/src/tools/browser/runtime-check.ts +2 -2
  399. package/src/tools/credentials/vault.ts +2 -249
  400. package/src/tools/memory/definitions.ts +1 -1
  401. package/src/tools/memory/handlers.test.ts +50 -8
  402. package/src/tools/memory/handlers.ts +3 -1
  403. package/src/tools/side-effects.ts +1 -6
  404. package/src/tools/terminal/safe-env.ts +3 -2
  405. package/src/tools/terminal/shell.ts +11 -14
  406. package/src/tools/tool-approval-handler.ts +20 -1
  407. package/src/tools/tool-input-summary.ts +66 -0
  408. package/src/tools/types.ts +4 -0
  409. package/src/usage/types.ts +4 -0
  410. package/src/util/device-id.ts +10 -10
  411. package/src/util/platform.ts +71 -33
  412. package/src/util/pricing.ts +19 -6
  413. package/src/util/strip-comment-lines.ts +28 -0
  414. package/src/workspace/git-service.ts +8 -18
  415. package/src/workspace/migrations/003-seed-device-id.ts +6 -4
  416. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
  417. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
  418. package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
  419. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
  420. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
  421. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
  422. package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
  423. package/src/workspace/migrations/registry.ts +8 -0
  424. package/src/signals/confirm.ts +0 -82
  425. package/src/signals/trust-rule.ts +0 -174
@@ -589,7 +589,7 @@ describe("injectTemporalContext", () => {
589
589
  };
590
590
 
591
591
  const sampleContext =
592
- "<temporal_context>\nToday: 2026-02-18 (Wednesday)\nTimezone: UTC\n</temporal_context>";
592
+ "<temporal_context>\nToday: 2026-02-18 (Wed) 12:00 +00:00\nTZ: UTC\n</temporal_context>";
593
593
 
594
594
  test("prepends temporal context block to user message", () => {
595
595
  const result = injectTemporalContext(baseUserMessage, sampleContext);
@@ -729,7 +729,7 @@ describe("applyRuntimeInjections with temporalContext", () => {
729
729
  ];
730
730
 
731
731
  const sampleContext =
732
- "<temporal_context>\nToday: 2026-02-18 (Wednesday)\n</temporal_context>";
732
+ "<temporal_context>\nToday: 2026-02-18 (Wed) 12:00 +00:00\nTZ: UTC\n</temporal_context>";
733
733
 
734
734
  test("injects temporal context when provided", () => {
735
735
  const result = applyRuntimeInjections(baseMessages, {
@@ -1293,7 +1293,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1293
1293
  workspaceTopLevelContext:
1294
1294
  "<workspace_top_level>\nRoot: /sandbox\n</workspace_top_level>",
1295
1295
  temporalContext:
1296
- "<temporal_context>\nToday: 2026-03-04 (Tuesday)\n</temporal_context>",
1296
+ "<temporal_context>\nToday: 2026-03-04 (Tue) 12:00 +00:00\nTZ: UTC\n</temporal_context>",
1297
1297
  channelCommandContext: { type: "start" } as const,
1298
1298
  activeSurface: { surfaceId: "sf_1", html: "<div>test</div>" },
1299
1299
  channelCapabilities: {
@@ -1341,7 +1341,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1341
1341
  expect(allText).toContain("<turn_context>");
1342
1342
  expect(allText).toContain("<inbound_actor_context>");
1343
1343
  expect(allText).toContain("<non_interactive_context>");
1344
- expect(allText).toContain("<now_scratchpad>");
1344
+ expect(allText).toContain("<NOW.md");
1345
1345
  });
1346
1346
 
1347
1347
  test("explicit mode: 'full' behaves the same as default", () => {
@@ -1358,7 +1358,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1358
1358
  expect(allText).toContain("<temporal_context>");
1359
1359
  expect(allText).toContain("<channel_command_context>");
1360
1360
  expect(allText).toContain("<active_workspace>");
1361
- expect(allText).toContain("<now_scratchpad>");
1361
+ expect(allText).toContain("<NOW.md");
1362
1362
  });
1363
1363
 
1364
1364
  test("minimal mode skips high-token optional blocks", () => {
@@ -1376,7 +1376,7 @@ describe("applyRuntimeInjections — injection mode", () => {
1376
1376
  expect(allText).not.toContain("<temporal_context>");
1377
1377
  expect(allText).not.toContain("<channel_command_context>");
1378
1378
  expect(allText).not.toContain("<active_workspace>");
1379
- expect(allText).not.toContain("<now_scratchpad>");
1379
+ expect(allText).not.toContain("<NOW.md");
1380
1380
  });
1381
1381
 
1382
1382
  test("minimal mode preserves safety-critical blocks", () => {
@@ -1435,26 +1435,51 @@ describe("injectNowScratchpad", () => {
1435
1435
  content: [{ type: "text", text: "What should I work on?" }],
1436
1436
  };
1437
1437
 
1438
- test("appends now_scratchpad block to user message", () => {
1438
+ test("inserts NOW.md before user content", () => {
1439
1439
  const result = injectNowScratchpad(
1440
1440
  baseUserMessage,
1441
1441
  "Current focus: shipping PR 3",
1442
1442
  );
1443
1443
  expect(result.content.length).toBe(2);
1444
- // Original content comes first
1445
- expect((result.content[0] as { type: "text"; text: string }).text).toBe(
1446
- "What should I work on?",
1447
- );
1448
- // Scratchpad is appended (not prepended)
1449
- const injected = result.content[1];
1444
+ // Scratchpad comes first (before user content)
1445
+ const injected = result.content[0];
1450
1446
  expect(injected.type).toBe("text");
1451
1447
  const text = (injected as { type: "text"; text: string }).text;
1452
1448
  expect(text).toBe(
1453
- "<now_scratchpad>\nCurrent focus: shipping PR 3\n</now_scratchpad>",
1449
+ "<NOW.md Always keep this up to date>\nCurrent focus: shipping PR 3\n</NOW.md>",
1450
+ );
1451
+ // Original content comes last
1452
+ expect((result.content[1] as { type: "text"; text: string }).text).toBe(
1453
+ "What should I work on?",
1454
1454
  );
1455
1455
  });
1456
1456
 
1457
- test("preserves existing multi-block content and appends at end", () => {
1457
+ test("inserts after memory_context but before user content", () => {
1458
+ const messageWithMemory: Message = {
1459
+ role: "user",
1460
+ content: [
1461
+ { type: "text", text: "<memory_context __injected>\nrecalled notes\n</memory_context>" },
1462
+ { type: "text", text: "What should I work on?" },
1463
+ ],
1464
+ };
1465
+
1466
+ const result = injectNowScratchpad(messageWithMemory, "scratchpad notes");
1467
+ expect(result.content.length).toBe(3);
1468
+ // Memory context stays first
1469
+ expect(
1470
+ (result.content[0] as { type: "text"; text: string }).text,
1471
+ ).toContain("<memory_context");
1472
+ // Scratchpad inserted after memory
1473
+ expect(
1474
+ (result.content[1] as { type: "text"; text: string }).text,
1475
+ ).toContain("<NOW.md");
1476
+ // User content is last
1477
+ expect((result.content[2] as { type: "text"; text: string }).text).toBe(
1478
+ "What should I work on?",
1479
+ );
1480
+ });
1481
+
1482
+ test("preserves existing multi-block content with scratchpad before it", () => {
1458
1483
  const multiBlockMessage: Message = {
1459
1484
  role: "user",
1460
1485
  content: [
@@ -1465,15 +1490,16 @@ describe("injectNowScratchpad", () => {
1465
1490
 
1466
1491
  const result = injectNowScratchpad(multiBlockMessage, "scratchpad notes");
1467
1492
  expect(result.content.length).toBe(3);
1468
- expect((result.content[0] as { type: "text"; text: string }).text).toBe(
1493
+ // Scratchpad is first (no memory_context to skip)
1494
+ expect(
1495
+ (result.content[0] as { type: "text"; text: string }).text,
1496
+ ).toContain("<NOW.md");
1497
+ expect((result.content[1] as { type: "text"; text: string }).text).toBe(
1469
1498
  "First block",
1470
1499
  );
1471
- expect((result.content[1] as { type: "text"; text: string }).text).toBe(
1500
+ expect((result.content[2] as { type: "text"; text: string }).text).toBe(
1472
1501
  "Second block",
1473
1502
  );
1474
- expect(
1475
- (result.content[2] as { type: "text"; text: string }).text,
1476
- ).toContain("<now_scratchpad>");
1477
1503
  });
1478
1504
  });
1479
1505
 
@@ -1482,7 +1508,7 @@ describe("injectNowScratchpad", () => {
1482
1508
  // ---------------------------------------------------------------------------
1483
1509
 
1484
1510
  describe("stripNowScratchpad", () => {
1485
- test("strips now_scratchpad blocks from user messages", () => {
1511
+ test("strips NOW.md blocks from user messages", () => {
1486
1512
  const messages: Message[] = [
1487
1513
  {
1488
1514
  role: "user",
@@ -1490,7 +1516,7 @@ describe("stripNowScratchpad", () => {
1490
1516
  { type: "text", text: "Hello" },
1491
1517
  {
1492
1518
  type: "text",
1493
- text: "<now_scratchpad>\nSome notes\n</now_scratchpad>",
1519
+ text: "<NOW.md Always keep this up to date>\nSome notes\n</NOW.md>",
1494
1520
  },
1495
1521
  ],
1496
1522
  },
@@ -1511,14 +1537,14 @@ describe("stripNowScratchpad", () => {
1511
1537
  expect(result[1].content.length).toBe(1);
1512
1538
  });
1513
1539
 
1514
- test("removes user messages that only contain now_scratchpad", () => {
1540
+ test("removes user messages that only contain NOW.md", () => {
1515
1541
  const messages: Message[] = [
1516
1542
  {
1517
1543
  role: "user",
1518
1544
  content: [
1519
1545
  {
1520
1546
  type: "text",
1521
- text: "<now_scratchpad>\nSome notes\n</now_scratchpad>",
1547
+ text: "<NOW.md Always keep this up to date>\nSome notes\n</NOW.md>",
1522
1548
  },
1523
1549
  ],
1524
1550
  },
@@ -1528,7 +1554,7 @@ describe("stripNowScratchpad", () => {
1528
1554
  expect(result.length).toBe(0);
1529
1555
  });
1530
1556
 
1531
- test("leaves messages without now_scratchpad untouched", () => {
1557
+ test("leaves messages without NOW.md untouched", () => {
1532
1558
  const messages: Message[] = [
1533
1559
  {
1534
1560
  role: "user",
@@ -1543,11 +1569,11 @@ describe("stripNowScratchpad", () => {
1543
1569
  });
1544
1570
 
1545
1571
  // ---------------------------------------------------------------------------
1546
- // stripInjectedContext removes now_scratchpad blocks
1572
+ // stripInjectedContext removes NOW.md blocks
1547
1573
  // ---------------------------------------------------------------------------
1548
1574
 
1549
- describe("stripInjectedContext with now_scratchpad", () => {
1550
- test("strips now_scratchpad blocks alongside other injections", () => {
1575
+ describe("stripInjectedContext with NOW.md", () => {
1576
+ test("strips NOW.md blocks alongside other injections", () => {
1551
1577
  const messages: Message[] = [
1552
1578
  {
1553
1579
  role: "user",
@@ -1559,7 +1585,7 @@ describe("stripInjectedContext with now_scratchpad", () => {
1559
1585
  { type: "text", text: "Hello" },
1560
1586
  {
1561
1587
  type: "text",
1562
- text: "<now_scratchpad>\nCurrent focus\n</now_scratchpad>",
1588
+ text: "<NOW.md Always keep this up to date>\nCurrent focus\n</NOW.md>",
1563
1589
  },
1564
1590
  ],
1565
1591
  },
@@ -1586,32 +1612,32 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
1586
1612
  },
1587
1613
  ];
1588
1614
 
1589
- test("injects now_scratchpad block when provided", () => {
1615
+ test("injects NOW.md block when provided", () => {
1590
1616
  const result = applyRuntimeInjections(baseMessages, {
1591
1617
  nowScratchpad: "Current focus: fix the bug",
1592
1618
  });
1593
1619
 
1594
1620
  expect(result.length).toBe(1);
1595
1621
  expect(result[0].content.length).toBe(2);
1596
- const injected = result[0].content[1];
1622
+ const injected = result[0].content[0];
1597
1623
  const text = (injected as { type: "text"; text: string }).text;
1598
- expect(text).toContain("<now_scratchpad>");
1624
+ expect(text).toContain("<NOW.md");
1599
1625
  expect(text).toContain("Current focus: fix the bug");
1600
1626
  });
1601
1627
 
1602
- test("appended block appears after user's original text content", () => {
1628
+ test("scratchpad appears before user's original text content", () => {
1603
1629
  const result = applyRuntimeInjections(baseMessages, {
1604
1630
  nowScratchpad: "scratchpad notes",
1605
1631
  });
1606
1632
 
1607
- // Original text is first
1633
+ // Scratchpad comes first (before user content)
1608
1634
  expect(
1609
1635
  (result[0].content[0] as { type: "text"; text: string }).text,
1610
- ).toBe("What should I do?");
1611
- // Scratchpad is appended after
1636
+ ).toContain("<NOW.md");
1637
+ // Original text is last
1612
1638
  expect(
1613
1639
  (result[0].content[1] as { type: "text"; text: string }).text,
1614
- ).toContain("<now_scratchpad>");
1640
+ ).toBe("What should I do?");
1615
1641
  });
1616
1642
 
1617
1643
  test("does not inject when nowScratchpad is null", () => {
@@ -1641,6 +1667,6 @@ describe("applyRuntimeInjections with nowScratchpad", () => {
1641
1667
  .map((b) => b.text)
1642
1668
  .join("\n");
1643
1669
 
1644
- expect(allText).not.toContain("<now_scratchpad>");
1670
+ expect(allText).not.toContain("<NOW.md");
1645
1671
  });
1646
1672
  });
@@ -36,6 +36,7 @@ describe("resolveSlash /commands interface-aware help", () => {
36
36
  );
37
37
  expect(lines).toEqual([
38
38
  "/commands — List all available commands",
39
+ "/compact — Force context compaction immediately",
39
40
  "/models — List all available models",
40
41
  "/status — Show conversation status and context usage",
41
42
  "/btw — Ask a side question while the assistant is working",
@@ -53,6 +54,7 @@ describe("resolveSlash /commands interface-aware help", () => {
53
54
  );
54
55
  expect(lines).toEqual([
55
56
  "/commands — List all available commands",
57
+ "/compact — Force context compaction immediately",
56
58
  "/models — List all available models",
57
59
  "/status — Show conversation status and context usage",
58
60
  "/btw — Ask a side question while the assistant is working",
@@ -66,6 +68,7 @@ describe("resolveSlash /commands interface-aware help", () => {
66
68
  );
67
69
  expect(lines).toEqual([
68
70
  "/commands — List all available commands",
71
+ "/compact — Force context compaction immediately",
69
72
  "/models — List all available models",
70
73
  "/status — Show conversation status and context usage",
71
74
  "/btw — Ask a side question while the assistant is working",
@@ -76,6 +79,7 @@ describe("resolveSlash /commands interface-aware help", () => {
76
79
  const lines = await resolveCommandsLines(makeSlashContext());
77
80
  expect(lines).toEqual([
78
81
  "/commands — List all available commands",
82
+ "/compact — Force context compaction immediately",
79
83
  "/models — List all available models",
80
84
  "/pair — Generate pairing info for connecting a mobile device",
81
85
  "/status — Show conversation status and context usage",
@@ -86,6 +90,7 @@ describe("resolveSlash /commands interface-aware help", () => {
86
90
  const lines = await resolveCommandsLines();
87
91
  expect(lines).toEqual([
88
92
  "/commands — List all available commands",
93
+ "/compact — Force context compaction immediately",
89
94
  "/models — List all available models",
90
95
  "/pair — Generate pairing info for connecting a mobile device",
91
96
  ]);
@@ -17,10 +17,6 @@ mock.module("../util/logger.js", () => ({
17
17
  new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
18
18
  }));
19
19
 
20
- mock.module("../util/platform.js", () => ({
21
- getDataDir: () => "/tmp",
22
- }));
23
-
24
20
  mock.module("../memory/guardian-action-store.js", () => ({
25
21
  getGuardianActionRequest: () => null,
26
22
  resolveGuardianActionRequest: () => {},
@@ -17,10 +17,6 @@ mock.module("../util/logger.js", () => ({
17
17
  new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
18
18
  }));
19
19
 
20
- mock.module("../util/platform.js", () => ({
21
- getDataDir: () => "/tmp",
22
- }));
23
-
24
20
  mock.module("../memory/guardian-action-store.js", () => ({
25
21
  getGuardianActionRequest: () => null,
26
22
  resolveGuardianActionRequest: () => {},
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Tests for per-conversation speed override.
3
+ *
4
+ * Verifies that the Conversation constructor resolves speed from the
5
+ * per-conversation speedOverride parameter first, falling back to the
6
+ * global config speed setting.
7
+ */
8
+ import { describe, expect, mock, test } from "bun:test";
9
+
10
+ import type {
11
+ AgentEvent,
12
+ AgentLoopConfig,
13
+ CheckpointDecision,
14
+ CheckpointInfo,
15
+ } from "../agent/loop.js";
16
+ import type { ServerMessage } from "../daemon/message-protocol.js";
17
+ import type { Message, ProviderResponse } from "../providers/types.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Mocks — must precede Conversation import
21
+ // ---------------------------------------------------------------------------
22
+
23
+ function makeLoggerStub(): Record<string, unknown> {
24
+ const stub: Record<string, unknown> = {};
25
+ for (const m of [
26
+ "info",
27
+ "warn",
28
+ "error",
29
+ "debug",
30
+ "trace",
31
+ "fatal",
32
+ "silent",
33
+ "child",
34
+ ]) {
35
+ stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
36
+ }
37
+ return stub;
38
+ }
39
+
40
+ mock.module("../util/logger.js", () => ({
41
+ getLogger: () => makeLoggerStub(),
42
+ }));
43
+
44
+ mock.module("../memory/guardian-action-store.js", () => ({
45
+ getGuardianActionRequest: () => null,
46
+ resolveGuardianActionRequest: () => {},
47
+ }));
48
+
49
+ mock.module("../providers/registry.js", () => ({
50
+ getProvider: () => ({ name: "mock-provider" }),
51
+ initializeProviders: () => {},
52
+ }));
53
+
54
+ // Controllable config mock — speed and feature flag behavior are test-specific.
55
+ let mockConfigSpeed: "standard" | "fast" = "fast";
56
+
57
+ mock.module("../config/loader.js", () => ({
58
+ getConfig: () => ({
59
+ ui: {},
60
+ provider: "mock-provider",
61
+ maxTokens: 4096,
62
+ thinking: false,
63
+ speed: mockConfigSpeed,
64
+ effort: "high",
65
+ contextWindow: {
66
+ maxInputTokens: 100000,
67
+ thresholdTokens: 80000,
68
+ preserveRecentMessages: 6,
69
+ summaryModel: "mock-model",
70
+ maxSummaryTokens: 512,
71
+ },
72
+ rateLimit: { maxRequestsPerMinute: 0 },
73
+ timeouts: { permissionTimeoutSec: 1 },
74
+ skills: { entries: {}, allowBundled: true },
75
+ permissions: { mode: "workspace" },
76
+ }),
77
+ loadRawConfig: () => ({}),
78
+ saveRawConfig: () => {},
79
+ invalidateConfigCache: () => {},
80
+ }));
81
+
82
+ // Feature flag mock — fast-mode enabled for all tests in this file.
83
+ mock.module("../config/assistant-feature-flags.js", () => ({
84
+ isAssistantFeatureFlagEnabled: (key: string) => {
85
+ if (key === "fast-mode") return true;
86
+ return true;
87
+ },
88
+ }));
89
+
90
+ mock.module("../prompts/system-prompt.js", () => ({
91
+ buildSystemPrompt: () => "system prompt",
92
+ }));
93
+
94
+ mock.module("../config/skills.js", () => ({
95
+ loadSkillCatalog: () => [],
96
+ loadSkillBySelector: () => ({ skill: null }),
97
+ ensureSkillIcon: async () => null,
98
+ }));
99
+
100
+ mock.module("../config/skill-state.js", () => ({
101
+ resolveSkillStates: () => [],
102
+ }));
103
+
104
+ mock.module("../permissions/trust-store.js", () => ({
105
+ addRule: () => {},
106
+ findHighestPriorityRule: () => null,
107
+ clearCache: () => {},
108
+ }));
109
+
110
+ mock.module("../security/secret-allowlist.js", () => ({
111
+ resetAllowlist: () => {},
112
+ }));
113
+
114
+ mock.module("../memory/conversation-crud.js", () => ({
115
+ getConversationType: () => "default",
116
+ setConversationOriginChannelIfUnset: () => {},
117
+ updateConversationContextWindow: () => {},
118
+ deleteMessageById: () => {},
119
+ provenanceFromTrustContext: () => ({
120
+ source: "user",
121
+ trustContext: undefined,
122
+ }),
123
+ getConversationOriginInterface: () => null,
124
+ getConversationOriginChannel: () => null,
125
+ getMessages: () => [],
126
+ getConversation: () => ({
127
+ id: "conv-1",
128
+ contextSummary: null,
129
+ contextCompactedMessageCount: 0,
130
+ totalInputTokens: 0,
131
+ totalOutputTokens: 0,
132
+ totalEstimatedCost: 0,
133
+ }),
134
+ createConversation: () => ({ id: "conv-1" }),
135
+ addMessage: () => ({ id: `msg-${Date.now()}` }),
136
+ updateConversationUsage: () => {},
137
+ updateConversationTitle: () => {},
138
+ }));
139
+
140
+ mock.module("../memory/conversation-queries.js", () => ({
141
+ listConversations: () => [],
142
+ }));
143
+
144
+ mock.module("../memory/attachments-store.js", () => ({
145
+ uploadAttachment: () => ({ id: `att-${Date.now()}` }),
146
+ linkAttachmentToMessage: () => {},
147
+ }));
148
+
149
+ mock.module("../memory/retriever.js", () => ({
150
+ buildMemoryRecall: async () => ({
151
+ enabled: false,
152
+ degraded: false,
153
+ injectedText: "",
154
+ semanticHits: 0,
155
+ injectedTokens: 0,
156
+ latencyMs: 0,
157
+ }),
158
+ injectMemoryRecallAsUserBlock: (msgs: Message[]) => msgs,
159
+ }));
160
+
161
+ mock.module("../context/window-manager.js", () => ({
162
+ ContextWindowManager: class {
163
+ constructor() {}
164
+ shouldCompact() {
165
+ return { needed: false, estimatedTokens: 0 };
166
+ }
167
+ async maybeCompact() {
168
+ return { compacted: false };
169
+ }
170
+ },
171
+ createContextSummaryMessage: () => ({
172
+ role: "user",
173
+ content: [{ type: "text", text: "summary" }],
174
+ }),
175
+ getSummaryFromContextMessage: () => null,
176
+ }));
177
+
178
+ mock.module("../memory/llm-usage-store.js", () => ({
179
+ recordUsageEvent: () => ({ id: "mock-id", createdAt: Date.now() }),
180
+ listUsageEvents: () => [],
181
+ }));
182
+
183
+ // Capture AgentLoop constructor config for assertions.
184
+ let lastAgentLoopConfig: Partial<AgentLoopConfig> | undefined;
185
+
186
+ mock.module("../agent/loop.js", () => ({
187
+ AgentLoop: class {
188
+ constructor(
189
+ _provider: unknown,
190
+ _systemPrompt: string,
191
+ config?: Partial<AgentLoopConfig>,
192
+ ) {
193
+ lastAgentLoopConfig = config;
194
+ }
195
+ getToolTokenBudget() {
196
+ return 0;
197
+ }
198
+ async run(
199
+ _messages: Message[],
200
+ _onEvent: (event: AgentEvent) => void,
201
+ _signal?: AbortSignal,
202
+ _requestId?: string,
203
+ _onCheckpoint?: (checkpoint: CheckpointInfo) => CheckpointDecision,
204
+ ): Promise<Message[]> {
205
+ return [];
206
+ }
207
+ },
208
+ }));
209
+
210
+ mock.module("../memory/canonical-guardian-store.js", () => ({
211
+ listPendingCanonicalGuardianRequestsByDestinationConversation: () => [],
212
+ listCanonicalGuardianRequests: () => [],
213
+ listPendingRequestsByConversationScope: () => [],
214
+ createCanonicalGuardianRequest: () => ({
215
+ id: "mock-cg-id",
216
+ code: "MOCK",
217
+ status: "pending",
218
+ }),
219
+ getCanonicalGuardianRequest: () => null,
220
+ getCanonicalGuardianRequestByCode: () => null,
221
+ updateCanonicalGuardianRequest: () => {},
222
+ resolveCanonicalGuardianRequest: () => {},
223
+ createCanonicalGuardianDelivery: () => ({ id: "mock-cgd-id" }),
224
+ listCanonicalGuardianDeliveries: () => [],
225
+ listPendingCanonicalGuardianRequestsByDestinationChat: () => [],
226
+ updateCanonicalGuardianDelivery: () => {},
227
+ generateCanonicalRequestCode: () => "MOCK-CODE",
228
+ }));
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Import Conversation AFTER mocks
232
+ // ---------------------------------------------------------------------------
233
+
234
+ import { Conversation } from "../daemon/conversation.js";
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Helpers
238
+ // ---------------------------------------------------------------------------
239
+
240
+ function makeProvider() {
241
+ return {
242
+ name: "mock",
243
+ async sendMessage(): Promise<ProviderResponse> {
244
+ return {
245
+ content: [],
246
+ model: "mock",
247
+ usage: { inputTokens: 0, outputTokens: 0 },
248
+ stopReason: "end_turn",
249
+ };
250
+ },
251
+ };
252
+ }
253
+
254
+ function makeSendToClient(): (msg: ServerMessage) => void {
255
+ return () => {};
256
+ }
257
+
258
+ // ---------------------------------------------------------------------------
259
+ // Tests
260
+ // ---------------------------------------------------------------------------
261
+
262
+ describe("per-conversation speed override", () => {
263
+ test("speedOverride 'standard' prevents fast mode even when global config is 'fast'", () => {
264
+ mockConfigSpeed = "fast";
265
+ lastAgentLoopConfig = undefined;
266
+
267
+ new Conversation(
268
+ "conv-speed-override-1",
269
+ makeProvider(),
270
+ "system prompt",
271
+ 4096,
272
+ makeSendToClient(),
273
+ "/tmp",
274
+ undefined, // broadcastToAllClients
275
+ undefined, // memoryPolicy
276
+ undefined, // sharedCesClient
277
+ "standard", // speedOverride
278
+ );
279
+
280
+ expect(lastAgentLoopConfig).toBeDefined();
281
+ // When speedOverride is "standard", the AgentLoop should NOT receive speed: "fast"
282
+ expect(lastAgentLoopConfig!.speed).toBeUndefined();
283
+ });
284
+
285
+ test("no speedOverride uses global config speed", () => {
286
+ mockConfigSpeed = "fast";
287
+ lastAgentLoopConfig = undefined;
288
+
289
+ new Conversation(
290
+ "conv-speed-global-1",
291
+ makeProvider(),
292
+ "system prompt",
293
+ 4096,
294
+ makeSendToClient(),
295
+ "/tmp",
296
+ undefined, // broadcastToAllClients
297
+ undefined, // memoryPolicy
298
+ undefined, // sharedCesClient
299
+ // no speedOverride — should fall back to global config "fast"
300
+ );
301
+
302
+ expect(lastAgentLoopConfig).toBeDefined();
303
+ expect(lastAgentLoopConfig!.speed).toBe("fast");
304
+ });
305
+
306
+ test("speedOverride 'fast' enables fast mode even when global config is 'standard'", () => {
307
+ mockConfigSpeed = "standard";
308
+ lastAgentLoopConfig = undefined;
309
+
310
+ new Conversation(
311
+ "conv-speed-override-fast-1",
312
+ makeProvider(),
313
+ "system prompt",
314
+ 4096,
315
+ makeSendToClient(),
316
+ "/tmp",
317
+ undefined, // broadcastToAllClients
318
+ undefined, // memoryPolicy
319
+ undefined, // sharedCesClient
320
+ "fast", // speedOverride
321
+ );
322
+
323
+ expect(lastAgentLoopConfig).toBeDefined();
324
+ expect(lastAgentLoopConfig!.speed).toBe("fast");
325
+ });
326
+ });
@@ -1,25 +1,7 @@
1
- import { mkdtempSync, rmSync } from "node:fs";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
1
  import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
2
 
6
3
  import { v4 as uuid } from "uuid";
7
4
 
8
- const testDir = mkdtempSync(
9
- join(tmpdir(), "conversation-starter-routes-test-"),
10
- );
11
-
12
- mock.module("../util/platform.js", () => ({
13
- getDataDir: () => testDir,
14
- isMacOS: () => process.platform === "darwin",
15
- isLinux: () => process.platform === "linux",
16
- isWindows: () => process.platform === "win32",
17
- getPidPath: () => join(testDir, "test.pid"),
18
- getDbPath: () => join(testDir, "test.db"),
19
- getLogPath: () => join(testDir, "test.log"),
20
- ensureDataDir: () => {},
21
- }));
22
-
23
5
  mock.module("../util/logger.js", () => ({
24
6
  getLogger: () =>
25
7
  new Proxy({} as Record<string, unknown>, {
@@ -37,11 +19,6 @@ initializeDb();
37
19
 
38
20
  afterAll(() => {
39
21
  resetDb();
40
- try {
41
- rmSync(testDir, { recursive: true });
42
- } catch {
43
- /* best effort */
44
- }
45
22
  });
46
23
 
47
24
  const routes = conversationStarterRouteDefinitions();