@vellumai/assistant 0.5.16 → 0.6.0

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 (407) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +0 -3
  3. package/knip.json +2 -1
  4. package/openapi.yaml +660 -80
  5. package/package.json +1 -1
  6. package/src/__tests__/actor-token-service.test.ts +68 -0
  7. package/src/__tests__/agent-loop.test.ts +0 -32
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  9. package/src/__tests__/anthropic-provider.test.ts +57 -3
  10. package/src/__tests__/app-compiler.test.ts +120 -0
  11. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  12. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  13. package/src/__tests__/call-domain.test.ts +2 -6
  14. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  15. package/src/__tests__/call-recovery.test.ts +2 -6
  16. package/src/__tests__/call-routes-http.test.ts +2 -6
  17. package/src/__tests__/call-store.test.ts +2 -6
  18. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  19. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  20. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  21. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  22. package/src/__tests__/checker.test.ts +25 -3
  23. package/src/__tests__/clawhub.test.ts +54 -24
  24. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  25. package/src/__tests__/cli-memory.test.ts +74 -69
  26. package/src/__tests__/config-schema.test.ts +1 -1
  27. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  28. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  29. package/src/__tests__/contacts-tools.test.ts +31 -0
  30. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  31. package/src/__tests__/context-token-estimator.test.ts +175 -10
  32. package/src/__tests__/conversation-agent-loop-overflow.test.ts +9 -0
  33. package/src/__tests__/conversation-agent-loop.test.ts +9 -0
  34. package/src/__tests__/conversation-attachments.test.ts +2 -6
  35. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  36. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  37. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  38. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  39. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  40. package/src/__tests__/conversation-error.test.ts +33 -2
  41. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  42. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  43. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  44. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  45. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  46. package/src/__tests__/conversation-store.test.ts +2 -6
  47. package/src/__tests__/conversation-usage.test.ts +2 -6
  48. package/src/__tests__/conversation-wipe.test.ts +11 -408
  49. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  50. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  51. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  52. package/src/__tests__/followup-tools.test.ts +2 -6
  53. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  54. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  55. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  56. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  57. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  58. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  59. package/src/__tests__/guardian-action-store.test.ts +2 -6
  60. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  61. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  62. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  63. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  64. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  65. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  66. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  67. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  68. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  69. package/src/__tests__/injection-block.test.ts +154 -0
  70. package/src/__tests__/install-meta.test.ts +506 -0
  71. package/src/__tests__/install-skill-routing.test.ts +292 -0
  72. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  73. package/src/__tests__/invite-routes-http.test.ts +2 -6
  74. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -14
  75. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  76. package/src/__tests__/llm-context-route-provider.test.ts +2 -6
  77. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -6
  78. package/src/__tests__/llm-usage-store.test.ts +2 -6
  79. package/src/__tests__/log-export-workspace.test.ts +2 -6
  80. package/src/__tests__/managed-store.test.ts +38 -11
  81. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  82. package/src/__tests__/memory-recall-log-store.test.ts +2 -6
  83. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  84. package/src/__tests__/non-member-access-request.test.ts +2 -6
  85. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  86. package/src/__tests__/oauth-cli.test.ts +364 -2
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  88. package/src/__tests__/outlook-attachments.test.ts +301 -0
  89. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  90. package/src/__tests__/outlook-categories.test.ts +212 -0
  91. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  92. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  93. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  94. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  95. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  96. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  97. package/src/__tests__/outlook-trash.test.ts +77 -0
  98. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  99. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  100. package/src/__tests__/playbook-execution.test.ts +76 -80
  101. package/src/__tests__/playbook-tools.test.ts +5 -7
  102. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  103. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  104. package/src/__tests__/registry.test.ts +2 -2
  105. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  106. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  107. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  108. package/src/__tests__/schedule-store.test.ts +2 -6
  109. package/src/__tests__/schedule-tools.test.ts +2 -6
  110. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  111. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  112. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  113. package/src/__tests__/search-skills-unified.test.ts +421 -0
  114. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  115. package/src/__tests__/send-endpoint-busy.test.ts +2 -6
  116. package/src/__tests__/sequence-store.test.ts +2 -6
  117. package/src/__tests__/server-history-render.test.ts +2 -6
  118. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  119. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  120. package/src/__tests__/skill-load-feature-flag.test.ts +11 -11
  121. package/src/__tests__/skill-memory.test.ts +140 -98
  122. package/src/__tests__/skills-uninstall.test.ts +2 -2
  123. package/src/__tests__/skills.test.ts +1 -1
  124. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  125. package/src/__tests__/task-compiler.test.ts +2 -6
  126. package/src/__tests__/task-management-tools.test.ts +2 -6
  127. package/src/__tests__/task-memory-cleanup.test.ts +173 -229
  128. package/src/__tests__/task-runner.test.ts +2 -6
  129. package/src/__tests__/task-scheduler.test.ts +2 -6
  130. package/src/__tests__/test-preload.ts +3 -0
  131. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  132. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  133. package/src/__tests__/tool-side-effects-slack-dm.test.ts +276 -0
  134. package/src/__tests__/trust-store.test.ts +1 -1
  135. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  136. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -6
  137. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  138. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  139. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  140. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  141. package/src/__tests__/usage-routes.test.ts +2 -6
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  143. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  145. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  146. package/src/__tests__/volume-security-guard.test.ts +2 -0
  147. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  148. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  149. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  150. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  151. package/src/__tests__/workspace-policy.test.ts +1 -1
  152. package/src/agent/attachments.ts +7 -2
  153. package/src/agent/image-optimize.ts +165 -0
  154. package/src/agent/loop.ts +1 -15
  155. package/src/bundler/app-compiler.ts +179 -2
  156. package/src/bundler/package-resolver.ts +3 -5
  157. package/src/cli/__tests__/notifications.test.ts +1 -2
  158. package/src/cli/cli-memory.ts +67 -64
  159. package/src/cli/commands/avatar.ts +3 -3
  160. package/src/cli/commands/config.ts +26 -13
  161. package/src/cli/commands/doctor.ts +2 -2
  162. package/src/cli/commands/memory.ts +41 -55
  163. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  164. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  165. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  166. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  167. package/src/cli/commands/oauth/connect.ts +11 -6
  168. package/src/cli/commands/oauth/mode.ts +7 -0
  169. package/src/cli/commands/oauth/shared.ts +39 -3
  170. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  171. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  172. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  173. package/src/cli/commands/platform/index.ts +16 -16
  174. package/src/cli/commands/skills.ts +88 -16
  175. package/src/cli/commands/trust.ts +2 -2
  176. package/src/cli/lib/daemon-credential-client.ts +2 -3
  177. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  178. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  179. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  180. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  181. package/src/config/bundled-skills/gmail/SKILL.md +2 -10
  182. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  183. package/src/config/bundled-skills/messaging/SKILL.md +10 -18
  184. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  185. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  186. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  187. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  188. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  189. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  190. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  191. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  192. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  193. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  194. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  195. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  196. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  197. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  198. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  199. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  200. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  201. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  202. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  203. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  204. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  205. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  206. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  207. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  208. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  209. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  210. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  211. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  212. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  213. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  214. package/src/config/bundled-skills/slack/SKILL.md +1 -7
  215. package/src/config/bundled-tool-registry.ts +56 -4
  216. package/src/config/env-registry.ts +15 -8
  217. package/src/config/feature-flag-registry.json +21 -124
  218. package/src/config/schemas/platform.ts +8 -0
  219. package/src/config/schemas/timeouts.ts +1 -1
  220. package/src/config/skills.ts +18 -7
  221. package/src/context/token-estimator.ts +25 -18
  222. package/src/context/window-manager.ts +6 -2
  223. package/src/credential-execution/process-manager.ts +3 -1
  224. package/src/daemon/context-overflow-reducer.ts +46 -2
  225. package/src/daemon/conversation-agent-loop-handlers.ts +123 -82
  226. package/src/daemon/conversation-agent-loop.ts +96 -61
  227. package/src/daemon/conversation-error.ts +31 -8
  228. package/src/daemon/conversation-lifecycle.ts +33 -0
  229. package/src/daemon/conversation-media-retry.ts +85 -7
  230. package/src/daemon/conversation-notifiers.ts +4 -1
  231. package/src/daemon/conversation-runtime-assembly.ts +5 -0
  232. package/src/daemon/conversation.ts +41 -2
  233. package/src/daemon/daemon-control.ts +8 -2
  234. package/src/daemon/handlers/shared.ts +22 -12
  235. package/src/daemon/handlers/skills.ts +416 -202
  236. package/src/daemon/lifecycle.ts +40 -1
  237. package/src/daemon/main.ts +5 -1
  238. package/src/daemon/message-types/conversations.ts +4 -1
  239. package/src/daemon/message-types/messages.ts +3 -1
  240. package/src/daemon/message-types/skills.ts +97 -36
  241. package/src/daemon/providers-setup.ts +5 -0
  242. package/src/daemon/server.ts +11 -2
  243. package/src/daemon/tool-side-effects.ts +27 -5
  244. package/src/heartbeat/heartbeat-service.ts +1 -0
  245. package/src/hooks/cli.ts +2 -2
  246. package/src/hooks/runner.ts +15 -38
  247. package/src/inbound/platform-callback-registration.ts +14 -14
  248. package/src/memory/admin.ts +11 -45
  249. package/src/memory/conversation-bootstrap.ts +2 -0
  250. package/src/memory/conversation-crud.ts +242 -348
  251. package/src/memory/conversation-group-migration.ts +157 -0
  252. package/src/memory/conversation-queries.ts +4 -2
  253. package/src/memory/db-init.ts +30 -3
  254. package/src/memory/embed.ts +73 -0
  255. package/src/memory/embedding-backend.ts +8 -14
  256. package/src/memory/embedding-runtime-manager.ts +12 -114
  257. package/src/memory/fingerprint.ts +2 -2
  258. package/src/memory/graph/bootstrap.ts +512 -0
  259. package/src/memory/graph/capability-seed.ts +297 -0
  260. package/src/memory/graph/consolidation.ts +691 -0
  261. package/src/memory/graph/conversation-graph-memory.ts +630 -0
  262. package/src/memory/graph/decay.test.ts +208 -0
  263. package/src/memory/graph/decay.ts +195 -0
  264. package/src/memory/graph/extraction-job.ts +69 -0
  265. package/src/memory/graph/extraction.test.ts +936 -0
  266. package/src/memory/graph/extraction.ts +1254 -0
  267. package/src/memory/graph/graph-search.ts +266 -0
  268. package/src/memory/graph/image-ref-utils.ts +29 -0
  269. package/src/memory/graph/injection.test.ts +513 -0
  270. package/src/memory/graph/injection.ts +439 -0
  271. package/src/memory/graph/inspect.ts +534 -0
  272. package/src/memory/graph/narrative.ts +267 -0
  273. package/src/memory/graph/pattern-scan.ts +269 -0
  274. package/src/memory/graph/retriever.ts +1008 -0
  275. package/src/memory/graph/scoring.test.ts +548 -0
  276. package/src/memory/graph/scoring.ts +232 -0
  277. package/src/memory/graph/serendipity.ts +65 -0
  278. package/src/memory/graph/store.test.ts +1050 -0
  279. package/src/memory/graph/store.ts +699 -0
  280. package/src/memory/graph/tool-handlers.ts +426 -0
  281. package/src/memory/graph/tools.ts +141 -0
  282. package/src/memory/graph/triggers.test.ts +487 -0
  283. package/src/memory/graph/triggers.ts +223 -0
  284. package/src/memory/graph/types.ts +271 -0
  285. package/src/memory/group-crud.ts +191 -0
  286. package/src/memory/indexer.ts +37 -19
  287. package/src/memory/job-handlers/cleanup.ts +0 -53
  288. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  289. package/src/memory/job-handlers/embedding.ts +5 -31
  290. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  291. package/src/memory/job-handlers/summarization.ts +32 -17
  292. package/src/memory/job-utils.ts +1 -1
  293. package/src/memory/jobs-store.ts +50 -70
  294. package/src/memory/jobs-worker.ts +147 -112
  295. package/src/memory/message-content.ts +1 -0
  296. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  297. package/src/memory/migrations/203-drop-memory-items-tables.ts +23 -0
  298. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  299. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  300. package/src/memory/migrations/index.ts +4 -0
  301. package/src/memory/migrations/registry.ts +8 -0
  302. package/src/memory/qdrant-client.ts +44 -17
  303. package/src/memory/schema/index.ts +1 -0
  304. package/src/memory/schema/memory-graph.ts +139 -0
  305. package/src/memory/search/semantic.ts +47 -91
  306. package/src/memory/task-memory-cleanup.ts +28 -50
  307. package/src/messaging/providers/outlook/adapter.ts +8 -1
  308. package/src/messaging/providers/outlook/client.ts +299 -0
  309. package/src/messaging/providers/outlook/types.ts +118 -0
  310. package/src/notifications/adapters/macos.ts +1 -0
  311. package/src/notifications/copy-composer.ts +9 -0
  312. package/src/notifications/signal.ts +16 -0
  313. package/src/oauth/seed-providers.ts +2 -1
  314. package/src/permissions/checker.ts +24 -3
  315. package/src/permissions/defaults.ts +4 -4
  316. package/src/permissions/workspace-policy.ts +1 -1
  317. package/src/playbooks/playbook-compiler.ts +19 -18
  318. package/src/playbooks/types.ts +4 -3
  319. package/src/prompts/system-prompt.ts +3 -29
  320. package/src/providers/anthropic/client.ts +47 -19
  321. package/src/providers/gemini/client.ts +1 -1
  322. package/src/providers/openai/client.ts +1 -1
  323. package/src/providers/registry.ts +1 -1
  324. package/src/providers/retry.ts +19 -3
  325. package/src/runtime/actor-trust-resolver.ts +5 -1
  326. package/src/runtime/auth/route-policy.ts +7 -0
  327. package/src/runtime/guardian-reply-router.ts +5 -1
  328. package/src/runtime/http-server.ts +23 -3
  329. package/src/runtime/middleware/auth.ts +20 -0
  330. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  331. package/src/runtime/routes/attachment-routes.ts +106 -16
  332. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  333. package/src/runtime/routes/btw-routes.ts +8 -0
  334. package/src/runtime/routes/conversation-management-routes.ts +2 -0
  335. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  336. package/src/runtime/routes/debug-routes.ts +1 -1
  337. package/src/runtime/routes/global-search-routes.ts +21 -19
  338. package/src/runtime/routes/group-routes.ts +207 -0
  339. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  340. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  341. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  342. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  343. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +2 -14
  345. package/src/runtime/routes/memory-item-routes.ts +341 -388
  346. package/src/runtime/routes/schedule-routes.ts +2 -0
  347. package/src/runtime/routes/skills-routes.ts +103 -37
  348. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  349. package/src/schedule/scheduler.ts +8 -1
  350. package/src/security/oauth2.ts +1 -1
  351. package/src/security/secure-keys.ts +4 -8
  352. package/src/shared/provider-env-vars.ts +19 -0
  353. package/src/skills/catalog-cache.ts +5 -0
  354. package/src/skills/catalog-install.ts +15 -14
  355. package/src/skills/clawhub.ts +134 -154
  356. package/src/skills/install-meta.ts +208 -0
  357. package/src/skills/managed-store.ts +27 -16
  358. package/src/skills/skill-memory.ts +152 -77
  359. package/src/skills/skillssh-registry.ts +19 -17
  360. package/src/tasks/task-runner.ts +3 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  362. package/src/tools/browser/runtime-check.ts +3 -1
  363. package/src/tools/memory/register.ts +63 -46
  364. package/src/tools/permission-checker.ts +7 -1
  365. package/src/tools/shared/filesystem/image-read.ts +22 -85
  366. package/src/tools/terminal/safe-env.ts +1 -0
  367. package/src/tools/tool-manifest.ts +3 -3
  368. package/src/util/browser.ts +25 -10
  369. package/src/util/bun-runtime.ts +172 -0
  370. package/src/watcher/providers/outlook-calendar.ts +343 -0
  371. package/src/watcher/providers/outlook.ts +198 -0
  372. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  373. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  374. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  375. package/src/workspace/migrations/registry.ts +6 -0
  376. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  377. package/src/__tests__/journal-context.test.ts +0 -268
  378. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  379. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  380. package/src/__tests__/memory-query-builder.test.ts +0 -59
  381. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  382. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  383. package/src/__tests__/memory-regressions.test.ts +0 -3696
  384. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  385. package/src/daemon/conversation-memory.ts +0 -207
  386. package/src/memory/conversation-starters-cadence.ts +0 -74
  387. package/src/memory/items-extractor.ts +0 -860
  388. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  389. package/src/memory/job-handlers/extraction.ts +0 -40
  390. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  391. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  392. package/src/memory/journal-memory.ts +0 -224
  393. package/src/memory/query-builder.ts +0 -47
  394. package/src/memory/query-expansion.ts +0 -83
  395. package/src/memory/retriever.test.ts +0 -1592
  396. package/src/memory/retriever.ts +0 -1331
  397. package/src/memory/search/formatting.test.ts +0 -140
  398. package/src/memory/search/formatting.ts +0 -262
  399. package/src/memory/search/mmr.ts +0 -139
  400. package/src/memory/search/ranking.ts +0 -15
  401. package/src/memory/search/staleness.ts +0 -40
  402. package/src/memory/search/tier-classifier.ts +0 -18
  403. package/src/memory/search/types.ts +0 -121
  404. package/src/prompts/journal-context.ts +0 -154
  405. package/src/tools/memory/definitions.ts +0 -69
  406. package/src/tools/memory/handlers.test.ts +0 -562
  407. package/src/tools/memory/handlers.ts +0 -434
@@ -28,6 +28,12 @@ export interface OutlookItemBody {
28
28
  content: string;
29
29
  }
30
30
 
31
+ /** Internet message header (e.g. List-Unsubscribe, X-Mailer) */
32
+ export interface OutlookInternetMessageHeader {
33
+ name: string;
34
+ value: string;
35
+ }
36
+
31
37
  /** Full Outlook message from Microsoft Graph */
32
38
  export interface OutlookMessage {
33
39
  id: string;
@@ -46,6 +52,7 @@ export interface OutlookMessage {
46
52
  flag: {
47
53
  flagStatus: "notFlagged" | "flagged" | "complete";
48
54
  };
55
+ internetMessageHeaders?: OutlookInternetMessageHeader[];
49
56
  }
50
57
 
51
58
  /** Outlook mail folder */
@@ -75,9 +82,120 @@ export interface OutlookSendMessagePayload {
75
82
  saveToSentItems?: boolean;
76
83
  }
77
84
 
85
+ /** Draft message for creating in Drafts folder */
86
+ export interface OutlookDraftMessage {
87
+ subject: string;
88
+ body: OutlookItemBody;
89
+ toRecipients?: OutlookRecipient[];
90
+ ccRecipients?: OutlookRecipient[];
91
+ bccRecipients?: OutlookRecipient[];
92
+ }
93
+
94
+ /** Forward message payload (Graph API's /createForward returns a draft) */
95
+ export interface OutlookForwardMessage {
96
+ toRecipients: OutlookRecipient[];
97
+ comment?: string;
98
+ }
99
+
78
100
  /** Microsoft Graph user profile */
79
101
  export interface OutlookUserProfile {
80
102
  displayName: string;
81
103
  mail: string;
82
104
  userPrincipalName: string;
83
105
  }
106
+
107
+ /** Outlook attachment metadata */
108
+ export interface OutlookAttachment {
109
+ id: string;
110
+ name: string;
111
+ contentType: string;
112
+ size: number;
113
+ isInline: boolean;
114
+ "@odata.type"?: string;
115
+ }
116
+
117
+ /** Outlook file attachment with base64-encoded content */
118
+ export interface OutlookFileAttachment extends OutlookAttachment {
119
+ contentBytes: string;
120
+ }
121
+
122
+ /** Outlook attachment list response (paginated) */
123
+ export interface OutlookAttachmentListResponse {
124
+ value?: OutlookAttachment[];
125
+ "@odata.nextLink"?: string;
126
+ }
127
+
128
+ /** Outlook message flag with optional due/start dates */
129
+ export interface OutlookMessageFlag {
130
+ flagStatus: "notFlagged" | "flagged" | "complete";
131
+ dueDateTime?: { dateTime: string; timeZone: string };
132
+ startDateTime?: { dateTime: string; timeZone: string };
133
+ }
134
+
135
+ /** Outlook master category */
136
+ export interface OutlookMasterCategory {
137
+ displayName: string;
138
+ color: string;
139
+ }
140
+
141
+ /** Outlook master category list response */
142
+ export interface OutlookMasterCategoryListResponse {
143
+ value?: OutlookMasterCategory[];
144
+ }
145
+
146
+ /** Predicates for matching messages in a mail rule */
147
+ export interface OutlookMessageRulePredicates {
148
+ senderContains?: string[];
149
+ subjectContains?: string[];
150
+ bodyContains?: string[];
151
+ fromAddresses?: OutlookRecipient[];
152
+ hasAttachments?: boolean;
153
+ importance?: "low" | "normal" | "high";
154
+ }
155
+
156
+ /** Actions to take when a mail rule matches */
157
+ export interface OutlookMessageRuleActions {
158
+ moveToFolder?: string;
159
+ delete?: boolean;
160
+ stopProcessingRules?: boolean;
161
+ markAsRead?: boolean;
162
+ forwardTo?: OutlookRecipient[];
163
+ markImportance?: "low" | "normal" | "high";
164
+ }
165
+
166
+ /** A mail rule for inbox message processing */
167
+ export interface OutlookMessageRule {
168
+ id?: string;
169
+ displayName: string;
170
+ sequence: number;
171
+ isEnabled: boolean;
172
+ conditions?: OutlookMessageRulePredicates;
173
+ actions?: OutlookMessageRuleActions;
174
+ }
175
+
176
+ /** Response from listing mail rules */
177
+ export interface OutlookMessageRuleListResponse {
178
+ value?: OutlookMessageRule[];
179
+ }
180
+
181
+ /** Automatic reply (out-of-office) settings */
182
+ export interface OutlookAutoReplySettings {
183
+ status: "disabled" | "alwaysEnabled" | "scheduled";
184
+ externalAudience: "none" | "contactsOnly" | "all";
185
+ internalReplyMessage?: string;
186
+ externalReplyMessage?: string;
187
+ scheduledStartDateTime?: { dateTime: string; timeZone: string };
188
+ scheduledEndDateTime?: { dateTime: string; timeZone: string };
189
+ }
190
+
191
+ /** Mailbox settings containing automatic replies configuration */
192
+ export interface OutlookMailboxSettings {
193
+ automaticRepliesSetting?: OutlookAutoReplySettings;
194
+ }
195
+
196
+ /** Delta query response with pagination support */
197
+ export interface OutlookDeltaResponse<T> {
198
+ value?: T[];
199
+ "@odata.deltaLink"?: string;
200
+ "@odata.nextLink"?: string;
201
+ }
@@ -37,6 +37,7 @@ const GUARDIAN_SENSITIVE_EVENT_PREFIXES = [
37
37
  "guardian.question",
38
38
  "ingress.escalation",
39
39
  "ingress.access_request",
40
+ "guardian.channel_activation",
40
41
  ] as const;
41
42
 
42
43
  export function isGuardianSensitiveEvent(sourceEventName: string): boolean {
@@ -353,6 +353,15 @@ const TEMPLATES: Partial<Record<NotificationSourceEventName, CopyTemplate>> = {
353
353
  };
354
354
  },
355
355
 
356
+ "guardian.channel_activation": (payload) => {
357
+ const code = str(payload.verificationCode, "------");
358
+ const channel = str(payload.sourceChannel, "a channel");
359
+ return {
360
+ title: "Guardian Verification Code",
361
+ body: `Your ${channel} verification code is: ${code}\n\nEnter this code in your ${channel} chat to verify your identity as guardian.`,
362
+ };
363
+ },
364
+
356
365
  "ingress.access_request": (payload) => ({
357
366
  title: "Access Request",
358
367
  body: buildAccessRequestContractText(payload),
@@ -46,6 +46,11 @@ export const NOTIFICATION_SOURCE_EVENT_NAMES = [
46
46
  id: "guardian.question",
47
47
  description: "Guardian approval question requiring response",
48
48
  },
49
+ {
50
+ id: "guardian.channel_activation",
51
+ description:
52
+ "Guardian channel activation code delivered for /start verification",
53
+ },
49
54
  { id: "ingress.access_request", description: "Non-member requesting access" },
50
55
  {
51
56
  id: "ingress.access_request.callback_handoff",
@@ -155,9 +160,20 @@ export interface AccessRequestContextPayload {
155
160
  messagePreview: string | null;
156
161
  }
157
162
 
163
+ export interface GuardianChannelActivationPayload {
164
+ verificationCode: string;
165
+ sourceChannel: string;
166
+ actorExternalId: string;
167
+ actorDisplayName: string | null;
168
+ actorUsername: string | null;
169
+ sessionId: string;
170
+ expiresAt: number;
171
+ }
172
+
158
173
  export interface NotificationEventContextPayloadMap {
159
174
  "guardian.question": GuardianQuestionPayload;
160
175
  "ingress.access_request": AccessRequestContextPayload;
176
+ "guardian.channel_activation": GuardianChannelActivationPayload;
161
177
  }
162
178
 
163
179
  export type NotificationContextPayload<TEventName extends string = string> =
@@ -90,6 +90,7 @@ const PROVIDER_SEED_DATA: Record<
90
90
  forbiddenScopes: [],
91
91
  },
92
92
  extraParams: { access_type: "offline", prompt: "consent" },
93
+ loopbackPort: 17321,
93
94
  managedServiceConfigKey: "google-oauth",
94
95
  injectionTemplates: [
95
96
  {
@@ -619,6 +620,7 @@ const PROVIDER_SEED_DATA: Record<
619
620
  "Mail.Send",
620
621
  "Calendars.Read",
621
622
  "Calendars.ReadWrite",
623
+ "MailboxSettings.ReadWrite",
622
624
  ],
623
625
  scopePolicy: {
624
626
  allowAdditionalScopes: true,
@@ -640,7 +642,6 @@ const PROVIDER_SEED_DATA: Record<
640
642
  appType: "App registration",
641
643
  identityUrl: "https://graph.microsoft.com/v1.0/me",
642
644
  identityResponsePaths: ["mail", "userPrincipalName"],
643
- featureFlag: "outlook-oauth-integration",
644
645
  },
645
646
 
646
647
  // Manual-token providers: these don't use OAuth2 flows but need provider
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { homedir } from "node:os";
3
- import { dirname, resolve } from "node:path";
3
+ import { dirname, join, resolve } from "node:path";
4
4
 
5
5
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
6
6
  import { getConfig } from "../config/loader.js";
@@ -19,7 +19,7 @@ import {
19
19
  looksLikePathOnlyInput,
20
20
  } from "../tools/network/url-safety.js";
21
21
  import { getTool } from "../tools/registry.js";
22
- import { getWorkspaceHooksDir } from "../util/platform.js";
22
+ import { getDeprecatedDir, getWorkspaceHooksDir } from "../util/platform.js";
23
23
  import {
24
24
  buildShellAllowlistOptions,
25
25
  buildShellCommandCandidates,
@@ -703,7 +703,13 @@ async function classifyRiskUncached(
703
703
  preParsed?: ParsedCommand,
704
704
  manifestOverride?: ManifestOverride,
705
705
  ): Promise<RiskLevel> {
706
- if (toolName === "file_read") return RiskLevel.Low;
706
+ if (toolName === "file_read") {
707
+ const filePath = getStringField(input, "path", "file_path");
708
+ if (isActorTokenSigningKeyPath(filePath, workingDir)) {
709
+ return RiskLevel.High;
710
+ }
711
+ return RiskLevel.Low;
712
+ }
707
713
  if (toolName === "file_write" || toolName === "file_edit") {
708
714
  const filePath = getStringField(input, "path", "file_path");
709
715
  if (
@@ -937,6 +943,21 @@ async function classifyRiskUncached(
937
943
  return RiskLevel.Medium;
938
944
  }
939
945
 
946
+ function isActorTokenSigningKeyPath(
947
+ filePath: string | undefined,
948
+ workingDir?: string,
949
+ ): boolean {
950
+ if (!filePath) return false;
951
+ const cwd = workingDir ?? process.cwd();
952
+ const resolvedPath = resolve(cwd, filePath);
953
+ const signingKeyPaths = [
954
+ join(homedir(), ".vellum", "protected", "actor-token-signing-key"),
955
+ join(getDeprecatedDir(), "actor-token-signing-key"),
956
+ resolve(cwd, "deprecated", "actor-token-signing-key"),
957
+ ];
958
+ return signingKeyPaths.includes(resolvedPath);
959
+ }
960
+
940
961
  export async function check(
941
962
  toolName: string,
942
963
  input: Record<string, unknown>,
@@ -287,11 +287,11 @@ export function getDefaultRuleTemplates(): DefaultRuleTemplate[] {
287
287
  }),
288
288
  );
289
289
 
290
- // memory_recall is a read-only tool — always allow without prompting.
290
+ // recall is a read-only tool — always allow without prompting.
291
291
  const memoryRecallRule: DefaultRuleTemplate = {
292
- id: "default:allow-memory_recall-global",
293
- tool: "memory_recall",
294
- pattern: "memory_recall:*",
292
+ id: "default:allow-recall-global",
293
+ tool: "recall",
294
+ pattern: "recall:*",
295
295
  scope: "everywhere",
296
296
  decision: "allow",
297
297
  priority: 100,
@@ -79,7 +79,7 @@ const HOST_TOOLS = new Set([
79
79
  /** Safe local-only tools that are always workspace-scoped. */
80
80
  const ALWAYS_SCOPED_TOOLS = new Set([
81
81
  "skill_load",
82
- "memory_recall",
82
+ "recall",
83
83
  "ui_update",
84
84
  "ui_dismiss",
85
85
  ]);
@@ -1,20 +1,20 @@
1
1
  /**
2
- * Compile all active playbook memory items into a triage context block
2
+ * Compile all active playbook graph nodes into a triage context block
3
3
  * that can be injected into the system prompt alongside the contact
4
4
  * graph.
5
5
  */
6
6
 
7
- import { and, desc, eq, isNull } from "drizzle-orm";
7
+ import { and, eq, sql } from "drizzle-orm";
8
8
 
9
9
  import { getDb } from "../memory/db.js";
10
- import { memoryItems } from "../memory/schema.js";
10
+ import { memoryGraphNodes } from "../memory/schema.js";
11
11
  import type { Playbook } from "./types.js";
12
12
  import { parsePlaybookStatement } from "./types.js";
13
13
 
14
14
  export interface CompiledPlaybooks {
15
15
  /** Formatted text block ready for system prompt injection. */
16
16
  text: string;
17
- /** Total number of active playbook items found. */
17
+ /** Total number of active playbook nodes found. */
18
18
  totalCount: number;
19
19
  /** Number of playbooks successfully parsed and included. */
20
20
  includedCount: number;
@@ -26,8 +26,7 @@ export interface CompilePlaybooksOptions {
26
26
 
27
27
  interface PlaybookRow {
28
28
  id: string;
29
- subject: string;
30
- statement: string;
29
+ content: string;
31
30
  }
32
31
 
33
32
  export function compilePlaybooks(
@@ -38,31 +37,33 @@ export function compilePlaybooks(
38
37
 
39
38
  const rows: PlaybookRow[] = db
40
39
  .select({
41
- id: memoryItems.id,
42
- subject: memoryItems.subject,
43
- statement: memoryItems.statement,
40
+ id: memoryGraphNodes.id,
41
+ content: memoryGraphNodes.content,
44
42
  })
45
- .from(memoryItems)
43
+ .from(memoryGraphNodes)
46
44
  .where(
47
45
  and(
48
- eq(memoryItems.kind, "playbook"),
49
- eq(memoryItems.status, "active"),
50
- eq(memoryItems.scopeId, scopeId),
51
- isNull(memoryItems.invalidAt),
46
+ eq(memoryGraphNodes.scopeId, scopeId),
47
+ sql`${memoryGraphNodes.sourceConversations} LIKE '%playbook:%'`,
48
+ sql`${memoryGraphNodes.fidelity} != 'gone'`,
52
49
  ),
53
50
  )
54
- .orderBy(desc(memoryItems.importance))
51
+ .orderBy(sql`${memoryGraphNodes.significance} DESC`)
55
52
  .all();
56
53
 
57
54
  if (rows.length === 0) {
58
55
  return { text: "", totalCount: 0, includedCount: 0 };
59
56
  }
60
57
 
61
- const parsed: Array<{ id: string; subject: string; playbook: Playbook }> = [];
58
+ const parsed: Array<{ id: string; playbook: Playbook }> = [];
62
59
  for (const row of rows) {
63
- const playbook = parsePlaybookStatement(row.statement);
60
+ // Content format: "Playbook: <trigger>\n<json statement>"
61
+ const newlineIdx = row.content.indexOf("\n");
62
+ if (newlineIdx === -1) continue;
63
+ const statement = row.content.slice(newlineIdx + 1);
64
+ const playbook = parsePlaybookStatement(statement);
64
65
  if (playbook) {
65
- parsed.push({ id: row.id, subject: row.subject, playbook });
66
+ parsed.push({ id: row.id, playbook });
66
67
  }
67
68
  }
68
69
 
@@ -2,9 +2,10 @@
2
2
  * Action Playbook — a structured trigger→action rule that tells the
3
3
  * triage engine how to handle incoming messages matching a pattern.
4
4
  *
5
- * Playbooks are stored as memory items with kind='playbook'. The
6
- * structured fields below are serialized into the statement column as
7
- * JSON, while the subject column holds a human-readable label.
5
+ * Playbooks are stored as memory_graph_nodes with
6
+ * sourceConversations containing a "playbook:{nodeId}" entry. The
7
+ * content column holds "Playbook: <trigger>\n<json>" where the JSON
8
+ * encodes the structured fields below.
8
9
  */
9
10
 
10
11
  export interface Playbook {
@@ -9,7 +9,6 @@ import {
9
9
  import { join } from "node:path";
10
10
 
11
11
  import { getIsContainerized } from "../config/env-registry.js";
12
- import { getConfig } from "../config/loader.js";
13
12
  import { listConnections } from "../oauth/oauth-store.js";
14
13
  import { resolveBundledDir } from "../util/bundled-asset.js";
15
14
  import { getLogger } from "../util/logger.js";
@@ -21,7 +20,6 @@ import {
21
20
  } from "../util/platform.js";
22
21
  import { stripCommentLines } from "../util/strip-comment-lines.js";
23
22
  import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "./cache-boundary.js";
24
- import { buildJournalContext } from "./journal-context.js";
25
23
 
26
24
  export { SYSTEM_PROMPT_CACHE_BOUNDARY };
27
25
 
@@ -130,27 +128,7 @@ export function ensurePromptFiles(): void {
130
128
  }
131
129
  }
132
130
 
133
- // Seed NOW.md scratchpad always created if missing, regardless of whether
134
- // this is a fresh install or not. Kept out of PROMPT_FILES because NOW.md is
135
- // ephemeral state, not identity context.
136
- const nowDest = getWorkspacePromptPath("NOW.md");
137
- if (!existsSync(nowDest)) {
138
- const nowSrc = join(templatesDir, "NOW.md");
139
- try {
140
- if (existsSync(nowSrc)) {
141
- copyFileSync(nowSrc, nowDest);
142
- log.info(
143
- { file: "NOW.md", dest: nowDest },
144
- "Created NOW.md scratchpad from template",
145
- );
146
- }
147
- } catch (err) {
148
- log.warn(
149
- { err, file: "NOW.md" },
150
- "Failed to create NOW.md from template",
151
- );
152
- }
153
- }
131
+ // The `remember` tool handles scratchpad-style memory writes directly to the graph.
154
132
 
155
133
  // Seed users/default.md persona template
156
134
  try {
@@ -296,11 +274,8 @@ export function buildSystemPrompt(options?: BuildSystemPromptOptions): string {
296
274
  const integrationSection = buildIntegrationSection();
297
275
  if (integrationSection) dynamicParts.push(integrationSection);
298
276
 
299
- const journalContext = buildJournalContext(
300
- getConfig().journal?.contextWindowSize ?? 10,
301
- options?.userSlug,
302
- );
303
- if (journalContext) dynamicParts.push(journalContext);
277
+ // Journal entries are extracted into graph nodes by the memory pipeline.
278
+ // Journal files remain writable on disk.
304
279
 
305
280
  const dynamic = dynamicParts.join("\n\n");
306
281
 
@@ -489,4 +464,3 @@ export function buildCoreIdentityContext(opts?: {
489
464
  if (opts?.userPersona) parts.push(opts.userPersona);
490
465
  return parts.length > 0 ? parts.join("\n\n") : null;
491
466
  }
492
-
@@ -101,6 +101,14 @@ export const PLACEHOLDER_EMPTY_TURN =
101
101
  export const PLACEHOLDER_BLOCKS_OMITTED =
102
102
  "\x00__PLACEHOLDER__[internal blocks omitted]";
103
103
 
104
+ /**
105
+ * Synthetic placeholder injected as user-message content when Anthropic API
106
+ * alternation requires a user turn but no real user content exists. Uses the
107
+ * `__injected` XML tag convention so the LLM treats it as system metadata
108
+ * rather than user speech.
109
+ */
110
+ const SYNTHETIC_CONTINUATION_TEXT = "<synthetic_continuation __injected />";
111
+
104
112
  /** Type-guard for tool_use blocks in Anthropic-formatted content. */
105
113
  function isToolUseBlock(block: unknown): block is Anthropic.ToolUseBlockParam {
106
114
  return (
@@ -310,14 +318,24 @@ function expandCollapsedAssistantTurns(
310
318
  if (nextIsUser) {
311
319
  const remainingResults = Array.from(toolResultMap.values());
312
320
  const rebuiltUserContent = [...remainingResults, ...nonToolResultContent];
313
- // Replace the original user message with the rebuilt one
314
- result.push({
315
- role: "user" as const,
316
- content:
317
- rebuiltUserContent.length > 0
318
- ? rebuiltUserContent
319
- : [{ type: "text" as const, text: "(continue)" }],
320
- });
321
+ // Replace the original user message with the rebuilt one. When all
322
+ // tool_results were distributed to intermediate segments (empty rebuilt
323
+ // content), skip the synthetic placeholder if the next message is already
324
+ // a user turn — ensureToolPairing will pair the last assistant segment
325
+ // with that next user message naturally.
326
+ if (rebuiltUserContent.length > 0) {
327
+ result.push({ role: "user" as const, content: rebuiltUserContent });
328
+ } else {
329
+ const nextAfterUser = messages[mi + 2];
330
+ if (!nextAfterUser || nextAfterUser.role !== "user") {
331
+ result.push({
332
+ role: "user" as const,
333
+ content: [
334
+ { type: "text" as const, text: SYNTHETIC_CONTINUATION_TEXT },
335
+ ],
336
+ });
337
+ }
338
+ }
321
339
  mi++; // skip the original user message
322
340
  }
323
341
  }
@@ -574,16 +592,26 @@ function ensureToolPairing(
574
592
  role: "assistant" as const,
575
593
  content: carryoverContent,
576
594
  });
577
- // Always emit a trailing user message to maintain alternation, even if the
578
- // original user turn had only tool_result blocks. Use a synthetic placeholder
579
- // when remainingContent is empty.
580
- result.push({
581
- role: "user" as const,
582
- content:
583
- normalized.remainingContent.length > 0
584
- ? normalized.remainingContent
585
- : [{ type: "text" as const, text: "(continue)" }],
586
- });
595
+ // Emit a trailing user message when there is remaining content, or when
596
+ // alternation requires it (next message is assistant or end of array).
597
+ // Skip the synthetic placeholder if the next message is already a user
598
+ // turn — it will naturally maintain alternation.
599
+ if (normalized.remainingContent.length > 0) {
600
+ result.push({
601
+ role: "user" as const,
602
+ content: normalized.remainingContent,
603
+ });
604
+ } else {
605
+ const nextAfterPair = messages[i + 2];
606
+ if (!nextAfterPair || nextAfterPair.role !== "user") {
607
+ result.push({
608
+ role: "user" as const,
609
+ content: [
610
+ { type: "text" as const, text: SYNTHETIC_CONTINUATION_TEXT },
611
+ ],
612
+ });
613
+ }
614
+ }
587
615
  } else {
588
616
  // No carryover assistant text to restore, so preserve existing behavior
589
617
  // and keep additional user blocks in the same message.
@@ -678,7 +706,7 @@ export class AnthropicProvider implements Provider {
678
706
  this.client = new Anthropic({ apiKey, baseURL: options.baseURL });
679
707
  this.model = model;
680
708
  this.useNativeWebSearch = options.useNativeWebSearch ?? false;
681
- this.streamTimeoutMs = options.streamTimeoutMs ?? 300_000;
709
+ this.streamTimeoutMs = options.streamTimeoutMs ?? 1_800_000;
682
710
  }
683
711
 
684
712
  async sendMessage(
@@ -89,7 +89,7 @@ export class GeminiProvider implements Provider {
89
89
  })
90
90
  : new GoogleGenAI({ apiKey });
91
91
  this.model = model;
92
- this.streamTimeoutMs = options.streamTimeoutMs ?? 300_000;
92
+ this.streamTimeoutMs = options.streamTimeoutMs ?? 1_800_000;
93
93
  }
94
94
 
95
95
  async sendMessage(
@@ -112,7 +112,7 @@ export class OpenAIProvider implements Provider {
112
112
  baseURL: options.baseURL,
113
113
  });
114
114
  this.model = model;
115
- this.streamTimeoutMs = options.streamTimeoutMs ?? 300_000;
115
+ this.streamTimeoutMs = options.streamTimeoutMs ?? 1_800_000;
116
116
  this.extraCreateParams = options.extraCreateParams ?? {};
117
117
  }
118
118
 
@@ -140,7 +140,7 @@ export async function initializeProviders(
140
140
  routingSources.clear();
141
141
 
142
142
  const streamTimeoutMs =
143
- (config.timeouts?.providerStreamTimeoutSec ?? 300) * 1000;
143
+ (config.timeouts?.providerStreamTimeoutSec ?? 1800) * 1000;
144
144
  const inferenceMode = config.services.inference.mode;
145
145
  const useNativeWebSearch =
146
146
  config.services["web-search"].provider === "inference-provider-native";
@@ -34,16 +34,30 @@ const RETRYABLE_STREAM_PATTERNS = [
34
34
  "stream has ended, this shouldn't happen",
35
35
  ];
36
36
 
37
+ /**
38
+ * Patterns that indicate a transient provider error even when no HTTP status
39
+ * code is available (e.g. overloaded errors delivered as SSE events mid-stream
40
+ * where the initial HTTP response was 200).
41
+ */
42
+ const RETRYABLE_PROVIDER_MESSAGE_PATTERNS = [/overloaded/i];
43
+
37
44
  function isRetryableStreamError(error: unknown): boolean {
38
45
  if (!(error instanceof ProviderError)) return false;
39
46
  if (error.statusCode !== undefined) return false; // has a real HTTP status — not a stream error
40
47
  return RETRYABLE_STREAM_PATTERNS.some((p) => error.message.includes(p));
41
48
  }
42
49
 
50
+ function isRetryableProviderMessage(error: unknown): boolean {
51
+ if (!(error instanceof ProviderError)) return false;
52
+ if (error.statusCode !== undefined) return false; // has a real HTTP status — handled by status check
53
+ return RETRYABLE_PROVIDER_MESSAGE_PATTERNS.some((p) => p.test(error.message));
54
+ }
55
+
43
56
  function isRetryableError(error: unknown): boolean {
44
57
  if (error instanceof ProviderError && error.statusCode !== undefined) {
45
58
  if (error.statusCode === 429 || error.statusCode >= 500) return true;
46
59
  }
60
+ if (isRetryableProviderMessage(error)) return true;
47
61
  if (isRetryableStreamError(error)) return true;
48
62
  return isRetryableNetworkError(error);
49
63
  }
@@ -161,9 +175,11 @@ export class RetryProvider implements Provider {
161
175
  error.statusCode !== undefined &&
162
176
  error.statusCode >= 500
163
177
  ? `server_error_${error.statusCode}`
164
- : isRetryableStreamError(error)
165
- ? "stream_corruption"
166
- : "network_error";
178
+ : isRetryableProviderMessage(error)
179
+ ? "provider_overloaded"
180
+ : isRetryableStreamError(error)
181
+ ? "stream_corruption"
182
+ : "network_error";
167
183
  log.warn(
168
184
  {
169
185
  attempt: attempt + 1,
@@ -48,7 +48,11 @@ export type TrustClass = "guardian" | "trusted_contact" | "unknown";
48
48
  export function isUntrustedTrustClass(
49
49
  trustClass: TrustClass | undefined,
50
50
  ): boolean {
51
- return trustClass === "trusted_contact" || trustClass === "unknown";
51
+ return (
52
+ trustClass === "trusted_contact" ||
53
+ trustClass === "unknown" ||
54
+ trustClass === undefined
55
+ );
52
56
  }
53
57
 
54
58
  /**
@@ -358,6 +358,13 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
358
358
  { endpoint: "conversations/wipe", scopes: ["chat.write"] },
359
359
  { endpoint: "conversations/reorder", scopes: ["chat.write"] },
360
360
 
361
+ // Conversation groups
362
+ { endpoint: "groups:GET", scopes: ["chat.read"] },
363
+ { endpoint: "groups:POST", scopes: ["chat.write"] },
364
+ { endpoint: "groups:PATCH", scopes: ["chat.write"] },
365
+ { endpoint: "groups:DELETE", scopes: ["chat.write"] },
366
+ { endpoint: "groups/reorder", scopes: ["chat.write"] },
367
+
361
368
  // Conversation search
362
369
  { endpoint: "conversations/search", scopes: ["chat.read"] },
363
370