@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
@@ -22,6 +22,7 @@ import {
22
22
  addMessage,
23
23
  provenanceFromTrustContext,
24
24
  } from "../memory/conversation-crud.js";
25
+ import { setMessageIdOnLogs } from "../memory/llm-request-log-store.js";
25
26
  import type { Message } from "../providers/types.js";
26
27
  import type { WatchSession } from "../tools/watch/watch-state.js";
27
28
  import {
@@ -69,37 +70,73 @@ export function registerConversationNotifiers(
69
70
  });
70
71
  });
71
72
 
72
- registerWatchCommentaryNotifier(conversationId, (_session: WatchSession) => {
73
- const commentary = lastCommentaryByConversation.get(conversationId);
74
- if (commentary) {
75
- lastCommentaryByConversation.delete(conversationId);
76
- ctx.sendToClient({
77
- type: "assistant_text_delta",
78
- text: commentary,
79
- conversationId: conversationId,
80
- });
81
- ctx.sendToClient({
82
- type: "message_complete",
83
- conversationId: conversationId,
84
- });
85
- }
86
- });
73
+ registerWatchCommentaryNotifier(
74
+ conversationId,
75
+ async (_session: WatchSession) => {
76
+ const result = lastCommentaryByConversation.get(conversationId);
77
+ if (result) {
78
+ lastCommentaryByConversation.delete(conversationId);
87
79
 
88
- registerWatchCompletionNotifier(conversationId, (_session: WatchSession) => {
89
- const summary = lastSummaryByConversation.get(conversationId);
90
- if (summary) {
91
- lastSummaryByConversation.delete(conversationId);
92
- ctx.sendToClient({
93
- type: "assistant_text_delta",
94
- text: summary,
95
- conversationId: conversationId,
96
- });
97
- ctx.sendToClient({
98
- type: "message_complete",
99
- conversationId: conversationId,
100
- });
101
- }
102
- });
80
+ const msg = await addMessage(
81
+ conversationId,
82
+ "assistant",
83
+ JSON.stringify([{ type: "text", text: result.text }]),
84
+ provenanceFromTrustContext(ctx.trustContext),
85
+ );
86
+ ctx.messages.push(createAssistantMessage(result.text));
87
+
88
+ try {
89
+ setMessageIdOnLogs(result.logIds, msg.id);
90
+ } catch {
91
+ // non-fatal — message is persisted even if log linkage fails
92
+ }
93
+
94
+ ctx.sendToClient({
95
+ type: "assistant_text_delta",
96
+ text: result.text,
97
+ conversationId: conversationId,
98
+ });
99
+ ctx.sendToClient({
100
+ type: "message_complete",
101
+ conversationId: conversationId,
102
+ });
103
+ }
104
+ },
105
+ );
106
+
107
+ registerWatchCompletionNotifier(
108
+ conversationId,
109
+ async (_session: WatchSession) => {
110
+ const result = lastSummaryByConversation.get(conversationId);
111
+ if (result) {
112
+ lastSummaryByConversation.delete(conversationId);
113
+
114
+ const msg = await addMessage(
115
+ conversationId,
116
+ "assistant",
117
+ JSON.stringify([{ type: "text", text: result.text }]),
118
+ provenanceFromTrustContext(ctx.trustContext),
119
+ );
120
+ ctx.messages.push(createAssistantMessage(result.text));
121
+
122
+ try {
123
+ setMessageIdOnLogs(result.logIds, msg.id);
124
+ } catch {
125
+ // non-fatal — message is persisted even if log linkage fails
126
+ }
127
+
128
+ ctx.sendToClient({
129
+ type: "assistant_text_delta",
130
+ text: result.text,
131
+ conversationId: conversationId,
132
+ });
133
+ ctx.sendToClient({
134
+ type: "message_complete",
135
+ conversationId: conversationId,
136
+ });
137
+ }
138
+ },
139
+ );
103
140
 
104
141
  registerCallQuestionNotifier(
105
142
  conversationId,
@@ -17,6 +17,7 @@ import type {
17
17
  } from "../channels/types.js";
18
18
  import { parseChannelId, parseInterfaceId } from "../channels/types.js";
19
19
  import { getConfig } from "../config/loader.js";
20
+ import type { ContextWindowResult } from "../context/window-manager.js";
20
21
  import { listPendingRequestsByConversationScope } from "../memory/canonical-guardian-store.js";
21
22
  import {
22
23
  addMessage,
@@ -47,6 +48,20 @@ import { resolveVerificationSessionIntent } from "./verification-session-intent.
47
48
 
48
49
  const log = getLogger("conversation-process");
49
50
 
51
+ /** Format the result of a forced compaction into a user-facing message. */
52
+ export function formatCompactResult(result: ContextWindowResult): string {
53
+ const fmt = (n: number) => n.toLocaleString("en-US");
54
+ if (!result.compacted) {
55
+ return `Context compaction skipped — ${result.reason ?? "nothing to compact"}.`;
56
+ }
57
+ const saved = result.previousEstimatedInputTokens - result.estimatedInputTokens;
58
+ return [
59
+ "Context Compacted\n",
60
+ `Tokens: ${fmt(result.previousEstimatedInputTokens)} → ${fmt(result.estimatedInputTokens)} (${fmt(saved)} saved)`,
61
+ `Messages: ${fmt(result.compactedMessages)} compacted`,
62
+ ].join("\n");
63
+ }
64
+
50
65
  /** Build a model_info event with fresh config data. */
51
66
  export async function buildModelInfoEvent(): Promise<ServerMessage> {
52
67
  return { type: "model_info", ...(await getModelInfo()) };
@@ -139,6 +154,8 @@ export interface ProcessConversationContext {
139
154
  requestId?: string,
140
155
  statusText?: string,
141
156
  ): void;
157
+ /** Force context compaction regardless of threshold/cooldown. */
158
+ forceCompact(): Promise<ContextWindowResult>;
142
159
  }
143
160
 
144
161
  function resolveQueuedTurnContext(
@@ -335,6 +352,7 @@ export async function drainQueue(
335
352
  ...(Object.keys(drainImageSourcePaths).length > 0
336
353
  ? { imageSourcePaths: drainImageSourcePaths }
337
354
  : {}),
355
+ sentAt: next.sentAt,
338
356
  };
339
357
  const cleanUserMsg = createUserMessage(next.content, next.attachments);
340
358
  const llmUserMsg = enrichMessageWithSourcePaths(
@@ -362,7 +380,7 @@ export async function drainQueue(
362
380
  conversation.conversationId,
363
381
  "assistant",
364
382
  JSON.stringify(assistantMsg.content),
365
- drainChannelMeta,
383
+ { ...drainChannelMeta, sentAt: Date.now() },
366
384
  );
367
385
  conversation.messages.push(assistantMsg);
368
386
 
@@ -423,6 +441,82 @@ export async function drainQueue(
423
441
  return;
424
442
  }
425
443
 
444
+ // /compact — force context compaction, persist exchange, continue draining.
445
+ if (slashResult.kind === "compact") {
446
+ try {
447
+ const drainProvenance = provenanceFromTrustContext(
448
+ conversation.trustContext,
449
+ );
450
+ const drainChannelMeta = {
451
+ ...drainProvenance,
452
+ ...(queuedTurnCtx
453
+ ? {
454
+ userMessageChannel: queuedTurnCtx.userMessageChannel,
455
+ assistantMessageChannel: queuedTurnCtx.assistantMessageChannel,
456
+ }
457
+ : {}),
458
+ ...(queuedInterfaceCtx
459
+ ? {
460
+ userMessageInterface: queuedInterfaceCtx.userMessageInterface,
461
+ assistantMessageInterface:
462
+ queuedInterfaceCtx.assistantMessageInterface,
463
+ }
464
+ : {}),
465
+ sentAt: next.sentAt,
466
+ };
467
+ const cleanUserMsg = createUserMessage(next.content, next.attachments);
468
+ await addMessage(
469
+ conversation.conversationId,
470
+ "user",
471
+ JSON.stringify(cleanUserMsg.content),
472
+ drainChannelMeta,
473
+ );
474
+ conversation.messages.push(cleanUserMsg);
475
+
476
+ conversation.emitActivityState(
477
+ "thinking",
478
+ "context_compacting",
479
+ "assistant_turn",
480
+ next.requestId,
481
+ );
482
+ const result = await conversation.forceCompact();
483
+ const responseText = formatCompactResult(result);
484
+
485
+ const assistantMsg = createAssistantMessage(responseText);
486
+ await addMessage(
487
+ conversation.conversationId,
488
+ "assistant",
489
+ JSON.stringify(assistantMsg.content),
490
+ { ...drainChannelMeta, sentAt: Date.now() },
491
+ );
492
+ conversation.messages.push(assistantMsg);
493
+
494
+ next.onEvent({ type: "assistant_text_delta", text: responseText });
495
+ conversation.traceEmitter.emit(
496
+ "message_complete",
497
+ "Compact slash command handled",
498
+ { requestId: next.requestId, status: "success" },
499
+ );
500
+ next.onEvent({
501
+ type: "message_complete",
502
+ conversationId: conversation.conversationId,
503
+ });
504
+ } catch (err) {
505
+ const message = err instanceof Error ? err.message : String(err);
506
+ log.error(
507
+ {
508
+ err,
509
+ conversationId: conversation.conversationId,
510
+ requestId: next.requestId,
511
+ },
512
+ "Failed to execute /compact",
513
+ );
514
+ next.onEvent({ type: "error", message });
515
+ }
516
+ await drainQueue(conversation);
517
+ return;
518
+ }
519
+
426
520
  const resolvedContent = slashResult.content;
427
521
 
428
522
  // Guardian verification intent interception for queued messages.
@@ -455,7 +549,7 @@ export async function drainQueue(
455
549
  resolvedContent,
456
550
  next.attachments,
457
551
  next.requestId,
458
- next.metadata,
552
+ { ...next.metadata, sentAt: next.sentAt },
459
553
  next.displayContent,
460
554
  );
461
555
  } catch (err) {
@@ -780,6 +874,71 @@ export async function processMessage(
780
874
  return persisted.id;
781
875
  }
782
876
 
877
+ // /compact — force context compaction, persist exchange, return message ID.
878
+ if (slashResult.kind === "compact") {
879
+ conversation.processing = true;
880
+ try {
881
+ const pmTurnCtx = conversation.getTurnChannelContext();
882
+ const pmInterfaceCtx = conversation.getTurnInterfaceContext();
883
+ const pmProvenance = provenanceFromTrustContext(conversation.trustContext);
884
+ const pmChannelMeta = {
885
+ ...pmProvenance,
886
+ ...(pmTurnCtx
887
+ ? {
888
+ userMessageChannel: pmTurnCtx.userMessageChannel,
889
+ assistantMessageChannel: pmTurnCtx.assistantMessageChannel,
890
+ }
891
+ : {}),
892
+ ...(pmInterfaceCtx
893
+ ? {
894
+ userMessageInterface: pmInterfaceCtx.userMessageInterface,
895
+ assistantMessageInterface: pmInterfaceCtx.assistantMessageInterface,
896
+ }
897
+ : {}),
898
+ };
899
+ const cleanUserMsg = createUserMessage(content, attachments);
900
+ const persisted = await addMessage(
901
+ conversation.conversationId,
902
+ "user",
903
+ JSON.stringify(cleanUserMsg.content),
904
+ pmChannelMeta,
905
+ );
906
+ conversation.messages.push(cleanUserMsg);
907
+
908
+ conversation.emitActivityState(
909
+ "thinking",
910
+ "context_compacting",
911
+ "assistant_turn",
912
+ requestId,
913
+ );
914
+ const result = await conversation.forceCompact();
915
+ const responseText = formatCompactResult(result);
916
+
917
+ const assistantMsg = createAssistantMessage(responseText);
918
+ await addMessage(
919
+ conversation.conversationId,
920
+ "assistant",
921
+ JSON.stringify(assistantMsg.content),
922
+ pmChannelMeta,
923
+ );
924
+ conversation.messages.push(assistantMsg);
925
+
926
+ onEvent({ type: "assistant_text_delta", text: responseText });
927
+ conversation.traceEmitter.emit(
928
+ "message_complete",
929
+ "Compact slash command handled",
930
+ { requestId, status: "success" },
931
+ );
932
+ onEvent({
933
+ type: "message_complete",
934
+ conversationId: conversation.conversationId,
935
+ });
936
+ return persisted.id;
937
+ } finally {
938
+ conversation.processing = false;
939
+ }
940
+ }
941
+
783
942
  const resolvedContent = slashResult.content;
784
943
 
785
944
  // Guardian verification intent interception — force direct guardian
@@ -31,6 +31,8 @@ export interface QueuedMessage {
31
31
  isInteractive?: boolean;
32
32
  /** Original user message text to persist to DB when recording intent stripping produced a different `content`. */
33
33
  displayContent?: string;
34
+ /** Wall-clock time (ms since epoch) when the message was enqueued, used as the display timestamp. */
35
+ sentAt: number;
34
36
  }
35
37
 
36
38
  /**
@@ -16,11 +16,11 @@ import {
16
16
  type TurnInterfaceContext,
17
17
  } from "../channels/types.js";
18
18
  import { getAppDirPath, listAppFiles } from "../memory/app-store.js";
19
- import { stripCommentLines } from "../prompts/system-prompt.js";
20
19
  import type { Message } from "../providers/types.js";
21
20
  import type { ActorTrustContext } from "../runtime/actor-trust-resolver.js";
22
21
  import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/acl-enforcement.js";
23
22
  import { getWorkspacePromptPath } from "../util/platform.js";
23
+ import { stripCommentLines } from "../util/strip-comment-lines.js";
24
24
 
25
25
  /**
26
26
  * Describes the capabilities of the channel through which the user is
@@ -487,28 +487,49 @@ export function readNowScratchpad(): string | null {
487
487
  }
488
488
 
489
489
  /**
490
- * Append NOW.md scratchpad content to the last user message so the model
491
- * has access to the user's ephemeral scratchpad notes at the end of context.
490
+ * Insert NOW.md scratchpad content into the user message, after any
491
+ * injected context blocks (e.g. memory_context) but before the user's
492
+ * original content. This keeps the user's actual message as the last
493
+ * thing the model reads.
492
494
  */
493
495
  export function injectNowScratchpad(
494
496
  message: Message,
495
497
  content: string,
496
498
  ): Message {
499
+ const scratchpadBlock = {
500
+ type: "text" as const,
501
+ text: `<NOW.md Always keep this up to date>\n${content}\n</NOW.md>`,
502
+ };
503
+
504
+ // Find insertion point: skip any leading injected-context text blocks
505
+ // (e.g. memory_context) so the scratchpad lands between injected context
506
+ // and the user's original content.
507
+ let insertIdx = 0;
508
+ for (let i = 0; i < message.content.length; i++) {
509
+ const block = message.content[i];
510
+ if (block.type === "text" && block.text.startsWith("<memory_context")) {
511
+ insertIdx = i + 1;
512
+ } else {
513
+ break;
514
+ }
515
+ }
516
+
497
517
  return {
498
518
  ...message,
499
519
  content: [
500
- ...message.content,
501
- {
502
- type: "text",
503
- text: `<now_scratchpad>\n${content}\n</now_scratchpad>`,
504
- },
520
+ ...message.content.slice(0, insertIdx),
521
+ scratchpadBlock,
522
+ ...message.content.slice(insertIdx),
505
523
  ],
506
524
  };
507
525
  }
508
526
 
509
- /** Strip `<now_scratchpad>` blocks injected by `injectNowScratchpad`. */
527
+ /** Strip `<NOW.md>` blocks injected by `injectNowScratchpad`. */
510
528
  export function stripNowScratchpad(messages: Message[]): Message[] {
511
- return stripUserTextBlocksByPrefix(messages, ["<now_scratchpad>"]);
529
+ return stripUserTextBlocksByPrefix(messages, [
530
+ "<NOW.md Always keep this up to date>",
531
+ "<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
532
+ ]);
512
533
  }
513
534
 
514
535
  /**
@@ -1034,7 +1055,8 @@ const RUNTIME_INJECTION_PREFIXES = [
1034
1055
  "<active_workspace>",
1035
1056
  "<active_dynamic_page>",
1036
1057
  "<non_interactive_context>",
1037
- "<now_scratchpad>",
1058
+ "<NOW.md Always keep this up to date>",
1059
+ "<now_scratchpad>", // backward-compat: strip legacy blocks from pre-rename history
1038
1060
  "<transport_hints>",
1039
1061
  ];
1040
1062
 
@@ -16,7 +16,8 @@ import type { PairingStore } from "./pairing-store.js";
16
16
 
17
17
  export type SlashResolution =
18
18
  | { kind: "passthrough"; content: string }
19
- | { kind: "unknown"; message: string; qrFilename?: string };
19
+ | { kind: "unknown"; message: string; qrFilename?: string }
20
+ | { kind: "compact" };
20
21
 
21
22
  // ── /pair command — module-level pairing context ────────────────────
22
23
 
@@ -129,6 +130,7 @@ function resolveStatusCommand(context: SlashContext): SlashResolution {
129
130
  function resolveCommandsList(context?: SlashContext): string[] {
130
131
  const fallbackLines = [
131
132
  "/commands — List all available commands",
133
+ "/compact — Force context compaction immediately",
132
134
  "/models — List all available models",
133
135
  "/pair — Generate pairing info for connecting a mobile device",
134
136
  ];
@@ -141,6 +143,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
141
143
  if (context.userMessageInterface === "macos") {
142
144
  return [
143
145
  "/commands — List all available commands",
146
+ "/compact — Force context compaction immediately",
144
147
  "/models — List all available models",
145
148
  "/status — Show conversation status and context usage",
146
149
  "/btw — Ask a side question while the assistant is working",
@@ -152,6 +155,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
152
155
  if (context.userMessageInterface === "ios") {
153
156
  return [
154
157
  "/commands — List all available commands",
158
+ "/compact — Force context compaction immediately",
155
159
  "/models — List all available models",
156
160
  "/status — Show conversation status and context usage",
157
161
  "/btw — Ask a side question while the assistant is working",
@@ -161,6 +165,7 @@ function resolveCommandsList(context?: SlashContext): string[] {
161
165
 
162
166
  return [
163
167
  "/commands — List all available commands",
168
+ "/compact — Force context compaction immediately",
164
169
  "/models — List all available models",
165
170
  "/status — Show conversation status and context usage",
166
171
  "/btw — Ask a side question while the assistant is working",
@@ -168,8 +173,9 @@ function resolveCommandsList(context?: SlashContext): string[] {
168
173
  }
169
174
 
170
175
  /**
171
- * Resolve built-in slash commands (/models, /status, /commands, /pair).
172
- * Returns `unknown` with a deterministic message, or the (possibly rewritten) content.
176
+ * Resolve built-in slash commands (/models, /status, /commands, /compact, /pair).
177
+ * Returns `unknown` with a deterministic message, `compact` for forced compaction,
178
+ * or the (possibly rewritten) content as `passthrough`.
173
179
  */
174
180
  export async function resolveSlash(
175
181
  content: string,
@@ -209,6 +215,11 @@ export async function resolveSlash(
209
215
  const pairResult = resolvePairCommand(content, context);
210
216
  if (pairResult) return pairResult;
211
217
 
218
+ // Handle /compact command
219
+ if (trimmed === "/compact") {
220
+ return { kind: "compact" };
221
+ }
222
+
212
223
  // Handle /status command
213
224
  if (trimmed === "/status") {
214
225
  if (!context) {
@@ -177,6 +177,8 @@ export function createToolExecutor(
177
177
  ctx.surfaceActionRequestIds?.has(ctx.currentRequestId ?? "") ?? false,
178
178
  requesterExternalUserId: ctx.trustContext?.requesterExternalUserId,
179
179
  requesterChatId: ctx.trustContext?.requesterChatId,
180
+ requesterIdentifier: ctx.trustContext?.requesterIdentifier,
181
+ requesterDisplayName: ctx.trustContext?.requesterDisplayName,
180
182
  channelPermissionChannelId:
181
183
  ctx.trustContext?.sourceChannel === "slack"
182
184
  ? getBindingByConversation(ctx.conversationId)?.externalChatId
@@ -75,6 +75,26 @@ function extractAnthropicCacheCreation(
75
75
  };
76
76
  }
77
77
 
78
+ /**
79
+ * Extract the speed indicator from Anthropic fast mode API responses.
80
+ * The API returns `usage.speed: "fast" | "standard"` when using the
81
+ * fast-mode beta. For multi-response arrays, returns "fast" if any
82
+ * response used fast mode.
83
+ */
84
+ function extractAnthropicSpeed(
85
+ rawResponse: unknown,
86
+ ): "fast" | "standard" | null {
87
+ const responses = Array.isArray(rawResponse) ? rawResponse : [rawResponse];
88
+ let foundStandard = false;
89
+ for (const response of responses) {
90
+ const rec = asRecord(response);
91
+ const usage = asRecord(rec?.usage);
92
+ if (usage?.speed === "fast") return "fast";
93
+ if (usage?.speed === "standard") foundStandard = true;
94
+ }
95
+ return foundStandard ? "standard" : null;
96
+ }
97
+
78
98
  function resolveStructuredPricing(
79
99
  providerName: string,
80
100
  model: string,
@@ -105,6 +125,8 @@ export function recordUsage(
105
125
  cacheCreationInputTokens = 0,
106
126
  cacheReadInputTokens = 0,
107
127
  rawResponse?: unknown,
128
+ llmCallCount = 1,
129
+ contextWindow?: { tokens: number; maxTokens: number },
108
130
  ): void {
109
131
  if (inputTokens <= 0 && outputTokens <= 0) return;
110
132
 
@@ -120,15 +142,16 @@ export function recordUsage(
120
142
  0,
121
143
  );
122
144
 
145
+ const isAnthropic = ctx.providerName === "anthropic";
123
146
  const pricingUsage: PricingUsage = {
124
147
  directInputTokens,
125
148
  outputTokens,
126
149
  cacheCreationInputTokens: normalizedCacheCreationInputTokens,
127
150
  cacheReadInputTokens: normalizedCacheReadInputTokens,
128
- anthropicCacheCreation:
129
- ctx.providerName === "anthropic"
130
- ? extractAnthropicCacheCreation(rawResponse)
131
- : null,
151
+ anthropicCacheCreation: isAnthropic
152
+ ? extractAnthropicCacheCreation(rawResponse)
153
+ : null,
154
+ speed: isAnthropic ? extractAnthropicSpeed(rawResponse) : null,
132
155
  };
133
156
  const pricing = resolveStructuredPricing(
134
157
  ctx.providerName,
@@ -158,6 +181,10 @@ export function recordUsage(
158
181
  totalOutputTokens: ctx.usageStats.outputTokens,
159
182
  estimatedCost,
160
183
  model,
184
+ ...(contextWindow && {
185
+ contextWindowTokens: contextWindow.tokens,
186
+ contextWindowMaxTokens: contextWindow.maxTokens,
187
+ }),
161
188
  });
162
189
 
163
190
  // Dual-write: persist per-turn usage event to the new ledger table
@@ -174,6 +201,7 @@ export function recordUsage(
174
201
  conversationId: ctx.conversationId,
175
202
  runId: null,
176
203
  requestId,
204
+ llmCallCount,
177
205
  },
178
206
  pricing,
179
207
  );
@@ -21,8 +21,13 @@ import type {
21
21
  TurnChannelContext,
22
22
  TurnInterfaceContext,
23
23
  } from "../channels/types.js";
24
+ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
24
25
  import { getConfig } from "../config/loader.js";
25
- import { ContextWindowManager } from "../context/window-manager.js";
26
+ import type { Speed } from "../config/schemas/inference.js";
27
+ import {
28
+ ContextWindowManager,
29
+ type ContextWindowResult,
30
+ } from "../context/window-manager.js";
26
31
  import type { CesClient } from "../credential-execution/client.js";
27
32
  import { EventBus } from "../events/bus.js";
28
33
  import type { AssistantDomainEvents } from "../events/domain-events.js";
@@ -38,6 +43,7 @@ import {
38
43
  import { registerToolTraceListener } from "../events/tool-trace-listener.js";
39
44
  import { getHookManager } from "../hooks/manager.js";
40
45
  import { resolveCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
46
+ import { updateConversationContextWindow } from "../memory/conversation-crud.js";
41
47
  import { PermissionPrompter } from "../permissions/prompter.js";
42
48
  import { SecretPrompter } from "../permissions/secret-prompter.js";
43
49
  import { patternMatchesCandidate } from "../permissions/trust-store.js";
@@ -263,6 +269,7 @@ export class Conversation {
263
269
  broadcastToAllClients?: (msg: ServerMessage) => void,
264
270
  memoryPolicy?: ConversationMemoryPolicy,
265
271
  sharedCesClient?: CesClient,
272
+ speedOverride?: Speed,
266
273
  ) {
267
274
  this.conversationId = conversationId;
268
275
  this.systemPrompt = systemPrompt;
@@ -383,6 +390,9 @@ export class Conversation {
383
390
  return resolved;
384
391
  };
385
392
 
393
+ const fastModeEnabled = isAssistantFeatureFlagEnabled("fast-mode", config);
394
+ const resolvedSpeed = speedOverride ?? config.speed;
395
+
386
396
  this.agentLoop = new AgentLoop(
387
397
  provider,
388
398
  systemPrompt,
@@ -391,6 +401,9 @@ export class Conversation {
391
401
  maxInputTokens: config.contextWindow.maxInputTokens,
392
402
  thinking: config.thinking,
393
403
  effort: config.effort,
404
+ ...(fastModeEnabled && resolvedSpeed === "fast"
405
+ ? { speed: resolvedSpeed }
406
+ : {}),
394
407
  },
395
408
  toolDefs.length > 0 ? toolDefs : undefined,
396
409
  toolDefs.length > 0 ? toolExecutor : undefined,
@@ -873,6 +886,25 @@ export class Conversation {
873
886
  this.sendToClient(msg);
874
887
  }
875
888
 
889
+ async forceCompact(): Promise<ContextWindowResult> {
890
+ const result = await this.contextWindowManager.maybeCompact(
891
+ this.messages,
892
+ this.abortController?.signal ?? undefined,
893
+ { force: true, lastCompactedAt: this.contextCompactedAt ?? undefined },
894
+ );
895
+ if (result.compacted) {
896
+ this.messages = result.messages;
897
+ this.contextCompactedMessageCount += result.compactedPersistedMessages;
898
+ this.contextCompactedAt = Date.now();
899
+ updateConversationContextWindow(
900
+ this.conversationId,
901
+ result.summaryText,
902
+ this.contextCompactedMessageCount,
903
+ );
904
+ }
905
+ return result;
906
+ }
907
+
876
908
  setChannelCapabilities(caps: ChannelCapabilities | null): void {
877
909
  this.channelCapabilities = caps ?? undefined;
878
910
  }