@vellumai/assistant 0.6.4 → 0.6.5

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 (717) hide show
  1. package/.prettierignore +5 -0
  2. package/ARCHITECTURE.md +32 -36
  3. package/Dockerfile +12 -0
  4. package/README.md +3 -4
  5. package/bun.lock +8 -3
  6. package/docs/architecture/integrations.md +1 -20
  7. package/docs/architecture/security.md +16 -16
  8. package/docs/error-handling.md +111 -0
  9. package/docs/skills.md +10 -10
  10. package/docs/stt-provider-onboarding.md +2 -1
  11. package/knip.json +9 -2
  12. package/node_modules/@vellumai/ces-contracts/package.json +2 -1
  13. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
  14. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
  15. package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
  16. package/node_modules/@vellumai/credential-storage/package.json +2 -2
  17. package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
  18. package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
  19. package/node_modules/@vellumai/egress-proxy/package.json +2 -2
  20. package/openapi.yaml +123 -11
  21. package/package.json +6 -3
  22. package/scripts/generate-openapi.ts +50 -11
  23. package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
  24. package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
  25. package/src/__tests__/agent-loop.test.ts +112 -1
  26. package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
  27. package/src/__tests__/anthropic-provider.test.ts +171 -2
  28. package/src/__tests__/approval-cascade.test.ts +31 -10
  29. package/src/__tests__/approval-routes-http.test.ts +134 -10
  30. package/src/__tests__/assistant-attachments.test.ts +44 -0
  31. package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
  32. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  33. package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
  34. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
  35. package/src/__tests__/browser-skill-endstate.test.ts +51 -182
  36. package/src/__tests__/btw-routes.test.ts +47 -1
  37. package/src/__tests__/call-controller.test.ts +1 -2
  38. package/src/__tests__/call-site-routing-provider.test.ts +214 -0
  39. package/src/__tests__/catalog-cache.test.ts +27 -4
  40. package/src/__tests__/channel-approval-routes.test.ts +4 -4
  41. package/src/__tests__/channel-reply-delivery.test.ts +300 -2
  42. package/src/__tests__/checker.test.ts +428 -501
  43. package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
  44. package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
  45. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  46. package/src/__tests__/config-analysis.test.ts +11 -28
  47. package/src/__tests__/config-loader-backfill.test.ts +174 -0
  48. package/src/__tests__/config-loader-corrupt.test.ts +183 -0
  49. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
  50. package/src/__tests__/config-schema-cmd.test.ts +11 -5
  51. package/src/__tests__/config-schema.test.ts +427 -114
  52. package/src/__tests__/config-watcher.test.ts +2 -2
  53. package/src/__tests__/contact-store-user-file.test.ts +72 -73
  54. package/src/__tests__/contacts-write.test.ts +4 -4
  55. package/src/__tests__/context-token-estimator.test.ts +191 -1
  56. package/src/__tests__/context-window-manager.test.ts +530 -2
  57. package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
  58. package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
  59. package/src/__tests__/conversation-agent-loop.test.ts +412 -82
  60. package/src/__tests__/conversation-attachments.test.ts +1 -1
  61. package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
  62. package/src/__tests__/conversation-error.test.ts +37 -6
  63. package/src/__tests__/conversation-history-web-search.test.ts +6 -0
  64. package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
  65. package/src/__tests__/conversation-lifecycle.test.ts +336 -0
  66. package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
  67. package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
  68. package/src/__tests__/conversation-process-callsite.test.ts +306 -0
  69. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
  70. package/src/__tests__/conversation-queue.test.ts +41 -26
  71. package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
  75. package/src/__tests__/conversation-skill-tools.test.ts +12 -146
  76. package/src/__tests__/conversation-slash-queue.test.ts +34 -19
  77. package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
  78. package/src/__tests__/conversation-speed-override.test.ts +30 -11
  79. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
  80. package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
  81. package/src/__tests__/conversation-title-service.test.ts +2 -2
  82. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
  83. package/src/__tests__/conversation-unread-route.test.ts +2 -2
  84. package/src/__tests__/conversation-usage.test.ts +3 -1
  85. package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
  86. package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
  87. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
  88. package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
  89. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  90. package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
  91. package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
  92. package/src/__tests__/credential-vault-unit.test.ts +135 -19
  93. package/src/__tests__/credentials-cli.test.ts +1 -9
  94. package/src/__tests__/cross-provider-web-search.test.ts +84 -0
  95. package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
  96. package/src/__tests__/delete-propagation.test.ts +437 -0
  97. package/src/__tests__/dm-backfill.test.ts +417 -0
  98. package/src/__tests__/dm-persistence.test.ts +227 -0
  99. package/src/__tests__/edit-propagation.test.ts +280 -0
  100. package/src/__tests__/ephemeral-permissions.test.ts +93 -3
  101. package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
  102. package/src/__tests__/estimator-calibration.test.ts +213 -0
  103. package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
  104. package/src/__tests__/file-write-tool.test.ts +151 -1
  105. package/src/__tests__/filing-service.test.ts +255 -0
  106. package/src/__tests__/gemini-provider.test.ts +0 -3
  107. package/src/__tests__/guardian-grant-minting.test.ts +8 -0
  108. package/src/__tests__/headless-browser-interactions.test.ts +1 -1
  109. package/src/__tests__/heartbeat-service.test.ts +96 -15
  110. package/src/__tests__/host-shell-tool.test.ts +124 -18
  111. package/src/__tests__/http-user-message-parity.test.ts +29 -1
  112. package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
  113. package/src/__tests__/intent-routing.test.ts +1 -40
  114. package/src/__tests__/llm-catalog-parity.test.ts +174 -0
  115. package/src/__tests__/llm-context-normalization.test.ts +121 -0
  116. package/src/__tests__/llm-resolver.test.ts +214 -0
  117. package/src/__tests__/llm-schema.test.ts +223 -0
  118. package/src/__tests__/managed-proxy-context.test.ts +6 -2
  119. package/src/__tests__/messaging-skill-split.test.ts +3 -34
  120. package/src/__tests__/migration-import-from-url.test.ts +684 -0
  121. package/src/__tests__/model-intents.test.ts +9 -83
  122. package/src/__tests__/notification-decision-fallback.test.ts +0 -10
  123. package/src/__tests__/notification-decision-identity.test.ts +0 -9
  124. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
  125. package/src/__tests__/oauth-store.test.ts +10 -7
  126. package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
  127. package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
  128. package/src/__tests__/openai-provider.test.ts +7 -0
  129. package/src/__tests__/openai-responses-provider.test.ts +396 -0
  130. package/src/__tests__/openrouter-provider-only.test.ts +135 -0
  131. package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
  132. package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
  133. package/src/__tests__/permission-mode.test.ts +16 -0
  134. package/src/__tests__/permission-types.test.ts +0 -1
  135. package/src/__tests__/persona-resolver.test.ts +13 -13
  136. package/src/__tests__/pkb-autoinject.test.ts +37 -1
  137. package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
  138. package/src/__tests__/pricing.test.ts +50 -3
  139. package/src/__tests__/profiler-routes.test.ts +1 -1
  140. package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
  141. package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +135 -6
  143. package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
  144. package/src/__tests__/provider-registry-ollama.test.ts +1 -2
  145. package/src/__tests__/proxy-approval-callback.test.ts +0 -1
  146. package/src/__tests__/reaction-persistence.test.ts +560 -0
  147. package/src/__tests__/relay-server.test.ts +1 -1
  148. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  149. package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
  150. package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
  151. package/src/__tests__/risk-classifier-parity.test.ts +230 -0
  152. package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
  153. package/src/__tests__/secret-ingress-http.test.ts +28 -0
  154. package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
  155. package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
  156. package/src/__tests__/secret-scanner-executor.test.ts +1 -1
  157. package/src/__tests__/send-endpoint-busy.test.ts +29 -1
  158. package/src/__tests__/server-history-render.test.ts +31 -0
  159. package/src/__tests__/shell-parser-property.test.ts +13 -13
  160. package/src/__tests__/skill-cache-store.test.ts +182 -0
  161. package/src/__tests__/skills.test.ts +19 -33
  162. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  163. package/src/__tests__/slack-skill.test.ts +3 -8
  164. package/src/__tests__/starter-bundle.test.ts +35 -0
  165. package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
  166. package/src/__tests__/suggestion-routes.test.ts +160 -3
  167. package/src/__tests__/system-prompt.test.ts +22 -35
  168. package/src/__tests__/task-runner.test.ts +3 -1
  169. package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
  170. package/src/__tests__/terminal-tools.test.ts +8 -0
  171. package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
  172. package/src/__tests__/thread-backfill.test.ts +941 -0
  173. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
  174. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
  175. package/src/__tests__/tool-executor.test.ts +60 -94
  176. package/src/__tests__/trust-store.test.ts +442 -109
  177. package/src/__tests__/update-bulletin-job.test.ts +389 -0
  178. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
  179. package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
  180. package/src/__tests__/voice-session-bridge.test.ts +39 -0
  181. package/src/__tests__/volume-security-guard.test.ts +3 -2
  182. package/src/__tests__/web-search-history.test.ts +337 -0
  183. package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
  184. package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
  185. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
  186. package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
  187. package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
  188. package/src/__tests__/workspace-policy.test.ts +1 -13
  189. package/src/acp/client-handler.ts +1 -2
  190. package/src/agent/loop.ts +209 -17
  191. package/src/avatar/resvg-lazy.test.ts +136 -0
  192. package/src/avatar/resvg-lazy.ts +82 -9
  193. package/src/avatar/traits-png-sync.ts +21 -1
  194. package/src/browser/__tests__/operations.test.ts +163 -0
  195. package/src/browser/identifiers.ts +51 -0
  196. package/src/browser/operations.ts +660 -0
  197. package/src/browser/types.ts +81 -0
  198. package/src/calls/guardian-question-copy.ts +2 -2
  199. package/src/calls/telephony-stt-routing.ts +1 -1
  200. package/src/calls/voice-session-bridge.ts +1 -0
  201. package/src/cli/AGENTS.md +1 -1
  202. package/src/cli/commands/__tests__/attachment.test.ts +438 -0
  203. package/src/cli/commands/__tests__/browser.test.ts +554 -0
  204. package/src/cli/commands/__tests__/cache.test.ts +623 -0
  205. package/src/cli/commands/__tests__/email-list.test.ts +6 -0
  206. package/src/cli/commands/__tests__/email-send.test.ts +93 -1
  207. package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
  208. package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
  209. package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
  210. package/src/cli/commands/__tests__/task.test.ts +913 -0
  211. package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
  212. package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
  213. package/src/cli/commands/__tests__/ui.test.ts +1215 -0
  214. package/src/cli/commands/__tests__/watchers.test.ts +716 -0
  215. package/src/cli/commands/attachment.ts +182 -0
  216. package/src/cli/commands/browser.ts +350 -0
  217. package/src/cli/commands/cache.ts +341 -0
  218. package/src/cli/commands/completions.ts +0 -3
  219. package/src/cli/commands/config.ts +6 -6
  220. package/src/cli/commands/conversations-import.ts +347 -0
  221. package/src/cli/commands/conversations.ts +14 -1
  222. package/src/cli/commands/email.ts +234 -194
  223. package/src/cli/commands/image-generation.ts +300 -0
  224. package/src/cli/commands/inference.ts +200 -0
  225. package/src/cli/commands/memory.ts +127 -17
  226. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
  227. package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
  228. package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
  229. package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
  230. package/src/cli/commands/stt.ts +339 -0
  231. package/src/cli/commands/task.ts +795 -0
  232. package/src/cli/commands/trust.ts +50 -19
  233. package/src/cli/commands/tts.ts +273 -0
  234. package/src/cli/commands/ui.ts +670 -0
  235. package/src/cli/commands/watchers.ts +509 -0
  236. package/src/cli/lib/daemon-credential-client.ts +0 -19
  237. package/src/cli/program.ts +23 -4
  238. package/src/cli.ts +0 -37
  239. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
  240. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  241. package/src/config/bundled-skills/messaging/SKILL.md +2 -2
  242. package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
  243. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
  244. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
  245. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
  246. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
  247. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
  248. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  249. package/src/config/bundled-tool-registry.ts +0 -175
  250. package/src/config/env.ts +7 -2
  251. package/src/config/feature-flag-registry.json +25 -9
  252. package/src/config/llm-resolver.ts +128 -0
  253. package/src/config/loader.ts +194 -10
  254. package/src/config/raw-config-utils.ts +30 -2
  255. package/src/config/sanitize-for-transfer.ts +35 -0
  256. package/src/config/schema.ts +30 -41
  257. package/src/config/schemas/analysis.ts +3 -22
  258. package/src/config/schemas/calls.ts +0 -4
  259. package/src/config/schemas/filing.ts +2 -7
  260. package/src/config/schemas/heartbeat.ts +0 -5
  261. package/src/config/schemas/inference.ts +3 -23
  262. package/src/config/schemas/llm.ts +318 -0
  263. package/src/config/schemas/memory-processing.ts +1 -9
  264. package/src/config/schemas/notifications.ts +4 -11
  265. package/src/config/schemas/platform.ts +3 -9
  266. package/src/config/schemas/security.ts +33 -0
  267. package/src/config/schemas/services.ts +9 -4
  268. package/src/config/schemas/stt.ts +1 -0
  269. package/src/config/schemas/tts.ts +53 -0
  270. package/src/config/schemas/updates.ts +1 -1
  271. package/src/config/schemas/workspace-git.ts +3 -40
  272. package/src/config/skills.ts +2 -2
  273. package/src/context/__tests__/compact-prompt.test.ts +45 -0
  274. package/src/context/__tests__/microcompact.test.ts +805 -0
  275. package/src/context/estimator-calibration.ts +136 -0
  276. package/src/context/microcompact.ts +443 -0
  277. package/src/context/prompts/compact.md +12 -0
  278. package/src/context/token-estimator.ts +61 -3
  279. package/src/context/window-manager.ts +229 -25
  280. package/src/credential-execution/approval-bridge.ts +0 -1
  281. package/src/credential-execution/executable-discovery.ts +19 -8
  282. package/src/credential-execution/process-manager.test.ts +109 -0
  283. package/src/credential-execution/process-manager.ts +65 -2
  284. package/src/daemon/approval-generators.ts +29 -4
  285. package/src/daemon/assistant-attachments.ts +24 -13
  286. package/src/daemon/classifier.ts +2 -2
  287. package/src/daemon/config-watcher.ts +0 -1
  288. package/src/daemon/context-overflow-reducer.ts +4 -1
  289. package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
  290. package/src/daemon/conversation-agent-loop.ts +462 -80
  291. package/src/daemon/conversation-attachments.ts +2 -6
  292. package/src/daemon/conversation-error.ts +36 -1
  293. package/src/daemon/conversation-lifecycle.ts +30 -6
  294. package/src/daemon/conversation-messaging.ts +73 -4
  295. package/src/daemon/conversation-process.ts +10 -4
  296. package/src/daemon/conversation-queue-manager.ts +3 -0
  297. package/src/daemon/conversation-runtime-assembly.ts +760 -29
  298. package/src/daemon/conversation-slash.ts +2 -2
  299. package/src/daemon/conversation-surfaces.ts +389 -1
  300. package/src/daemon/conversation-tool-setup.ts +10 -5
  301. package/src/daemon/conversation-usage.ts +1 -1
  302. package/src/daemon/conversation.ts +118 -30
  303. package/src/daemon/external-skills-bootstrap.ts +41 -0
  304. package/src/daemon/guardian-action-generators.ts +34 -14
  305. package/src/daemon/handlers/config-model.test.ts +86 -0
  306. package/src/daemon/handlers/config-model.ts +54 -12
  307. package/src/daemon/handlers/conversations.ts +9 -2
  308. package/src/daemon/handlers/shared.ts +39 -11
  309. package/src/daemon/handlers/skills.ts +2 -2
  310. package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
  311. package/src/daemon/lifecycle.ts +76 -14
  312. package/src/daemon/message-types/conversations.ts +14 -0
  313. package/src/daemon/message-types/messages.ts +9 -1
  314. package/src/daemon/message-types/trust.ts +0 -2
  315. package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
  316. package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
  317. package/src/daemon/pkb-context-tracker.test.ts +169 -0
  318. package/src/daemon/pkb-context-tracker.ts +125 -0
  319. package/src/daemon/pkb-reminder-builder.test.ts +70 -0
  320. package/src/daemon/pkb-reminder-builder.ts +31 -0
  321. package/src/daemon/providers-setup.ts +6 -0
  322. package/src/daemon/server.ts +117 -9
  323. package/src/daemon/tool-side-effects.ts +0 -9
  324. package/src/daemon/watch-handler.ts +4 -4
  325. package/src/daemon/web-search-history.ts +126 -0
  326. package/src/events/domain-events.ts +0 -1
  327. package/src/filing/filing-service.ts +9 -10
  328. package/src/heartbeat/heartbeat-service.ts +76 -28
  329. package/src/home/__tests__/feed-scheduler.test.ts +39 -11
  330. package/src/home/__tests__/rollup-producer.test.ts +44 -0
  331. package/src/home/assistant-feed-authoring.ts +4 -0
  332. package/src/home/emit-feed-event.ts +4 -0
  333. package/src/home/feed-scheduler.ts +20 -4
  334. package/src/home/feed-types.ts +56 -2
  335. package/src/home/relationship-state-writer.ts +2 -2
  336. package/src/home/rollup-producer.ts +34 -5
  337. package/src/home/suggested-prompts.ts +101 -0
  338. package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
  339. package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
  340. package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
  341. package/src/ipc/__tests__/socket-path.test.ts +73 -0
  342. package/src/ipc/__tests__/task-ipc.test.ts +577 -0
  343. package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
  344. package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
  345. package/src/ipc/cli-client.ts +2 -1
  346. package/src/ipc/cli-server.ts +26 -8
  347. package/src/ipc/gateway-client.ts +4 -4
  348. package/src/ipc/routes/attachment.ts +114 -0
  349. package/src/ipc/routes/browser-context.ts +61 -0
  350. package/src/ipc/routes/browser.ts +96 -0
  351. package/src/ipc/routes/cache.ts +96 -0
  352. package/src/ipc/routes/index.ts +17 -1
  353. package/src/ipc/routes/task-queue.ts +226 -0
  354. package/src/ipc/routes/task.ts +173 -0
  355. package/src/ipc/routes/ui-request.ts +50 -0
  356. package/src/ipc/routes/watcher.ts +203 -0
  357. package/src/ipc/socket-path.ts +100 -0
  358. package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
  359. package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
  360. package/src/memory/admin.ts +18 -0
  361. package/src/memory/conversation-analyze-job.ts +14 -13
  362. package/src/memory/conversation-attention-store.ts +13 -6
  363. package/src/memory/conversation-crud.ts +103 -3
  364. package/src/memory/conversation-group-migration.ts +38 -6
  365. package/src/memory/conversation-title-service.ts +7 -4
  366. package/src/memory/db-init.ts +2 -0
  367. package/src/memory/embedding-backend.ts +1 -1
  368. package/src/memory/graph/compaction.ts +299 -0
  369. package/src/memory/graph/consolidation.ts +4 -4
  370. package/src/memory/graph/conversation-graph-memory.ts +89 -29
  371. package/src/memory/graph/extraction.test.ts +272 -2
  372. package/src/memory/graph/extraction.ts +173 -51
  373. package/src/memory/graph/graph-search.test.ts +92 -0
  374. package/src/memory/graph/graph-search.ts +4 -1
  375. package/src/memory/graph/narrative.ts +2 -2
  376. package/src/memory/graph/pattern-scan.ts +2 -2
  377. package/src/memory/graph/retriever.test.ts +459 -0
  378. package/src/memory/graph/retriever.ts +230 -48
  379. package/src/memory/graph/store.ts +41 -0
  380. package/src/memory/graph/tool-handlers.ts +27 -0
  381. package/src/memory/graph/tools.ts +6 -1
  382. package/src/memory/indexer.ts +5 -5
  383. package/src/memory/job-handlers/conversation-starters.ts +23 -20
  384. package/src/memory/job-handlers/summarization.ts +2 -2
  385. package/src/memory/job-utils.ts +7 -1
  386. package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
  387. package/src/memory/jobs/embed-pkb-file.ts +54 -0
  388. package/src/memory/jobs-store.ts +44 -3
  389. package/src/memory/jobs-worker.ts +4 -0
  390. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
  391. package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
  392. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
  393. package/src/memory/migrations/index.ts +1 -0
  394. package/src/memory/pkb/pkb-index.test.ts +368 -0
  395. package/src/memory/pkb/pkb-index.ts +255 -0
  396. package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
  397. package/src/memory/pkb/pkb-reconcile.ts +148 -0
  398. package/src/memory/pkb/pkb-search.test.ts +438 -0
  399. package/src/memory/pkb/pkb-search.ts +137 -0
  400. package/src/memory/pkb/types.ts +53 -0
  401. package/src/memory/qdrant-client.ts +122 -1
  402. package/src/memory/slack-thread-store.ts +37 -0
  403. package/src/messaging/providers/gmail/adapter.ts +6 -16
  404. package/src/messaging/providers/gmail/client.ts +22 -0
  405. package/src/messaging/providers/gmail/types.ts +7 -0
  406. package/src/messaging/providers/slack/adapter.ts +14 -2
  407. package/src/messaging/providers/slack/backfill.test.ts +257 -0
  408. package/src/messaging/providers/slack/backfill.ts +101 -0
  409. package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
  410. package/src/messaging/providers/slack/message-metadata.ts +123 -0
  411. package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
  412. package/src/messaging/providers/slack/render-transcript.ts +443 -0
  413. package/src/messaging/style-analyzer.ts +5 -2
  414. package/src/notifications/README.md +9 -5
  415. package/src/notifications/decision-engine.ts +3 -9
  416. package/src/notifications/preference-extractor.ts +2 -6
  417. package/src/oauth/oauth-store.ts +1 -0
  418. package/src/oauth/platform-connection.test.ts +47 -0
  419. package/src/oauth/platform-connection.ts +15 -5
  420. package/src/oauth/seed-providers.ts +4 -2
  421. package/src/permissions/approval-policy.test.ts +948 -0
  422. package/src/permissions/approval-policy.ts +257 -0
  423. package/src/permissions/bash-risk-classifier.test.ts +1208 -0
  424. package/src/permissions/bash-risk-classifier.ts +707 -0
  425. package/src/permissions/checker.ts +217 -708
  426. package/src/permissions/command-registry.test.ts +535 -0
  427. package/src/permissions/command-registry.ts +825 -0
  428. package/src/permissions/defaults.ts +26 -78
  429. package/src/permissions/file-risk-classifier.test.ts +535 -0
  430. package/src/permissions/file-risk-classifier.ts +274 -0
  431. package/src/permissions/risk-types.ts +205 -0
  432. package/src/permissions/secret-prompter.ts +53 -2
  433. package/src/permissions/skill-risk-classifier.test.ts +311 -0
  434. package/src/permissions/skill-risk-classifier.ts +214 -0
  435. package/src/permissions/trust-client.ts +52 -25
  436. package/src/permissions/trust-store-interface.ts +1 -6
  437. package/src/permissions/trust-store.ts +161 -62
  438. package/src/permissions/types.ts +23 -14
  439. package/src/permissions/web-risk-classifier.test.ts +170 -0
  440. package/src/permissions/web-risk-classifier.ts +89 -0
  441. package/src/permissions/workspace-policy.ts +1 -16
  442. package/src/platform/client.ts +19 -1
  443. package/src/prompts/persona-resolver.ts +3 -3
  444. package/src/prompts/system-prompt.ts +19 -20
  445. package/src/prompts/templates/SOUL.md +2 -2
  446. package/src/prompts/update-bulletin-job.ts +190 -0
  447. package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
  448. package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
  449. package/src/providers/__tests__/retry-callsite.test.ts +424 -0
  450. package/src/providers/anthropic/client.ts +183 -14
  451. package/src/providers/call-site-routing.ts +71 -0
  452. package/src/providers/gemini/client.ts +65 -2
  453. package/src/providers/managed-proxy/constants.ts +2 -1
  454. package/src/providers/model-catalog.ts +501 -33
  455. package/src/providers/model-intents.ts +4 -4
  456. package/src/providers/openai/chat-completions-provider.ts +57 -1
  457. package/src/providers/openai/responses-provider.ts +86 -9
  458. package/src/providers/openrouter/client.ts +76 -9
  459. package/src/providers/provider-env-vars.ts +56 -0
  460. package/src/providers/provider-send-message.ts +22 -5
  461. package/src/providers/ratelimit.ts +4 -0
  462. package/src/providers/registry.ts +19 -8
  463. package/src/providers/retry.ts +174 -39
  464. package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
  465. package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
  466. package/src/providers/speech-to-text/provider-catalog.ts +17 -0
  467. package/src/providers/speech-to-text/resolve.ts +7 -0
  468. package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
  469. package/src/providers/speech-to-text/xai-realtime.ts +796 -0
  470. package/src/providers/speech-to-text/xai.test.ts +155 -0
  471. package/src/providers/speech-to-text/xai.ts +97 -0
  472. package/src/providers/types.ts +93 -3
  473. package/src/runtime/AGENTS.md +2 -2
  474. package/src/runtime/__tests__/agent-wake.test.ts +43 -2
  475. package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
  476. package/src/runtime/agent-wake.ts +63 -22
  477. package/src/runtime/auth/route-policy.ts +4 -0
  478. package/src/runtime/btw-sidechain.ts +13 -3
  479. package/src/runtime/channel-reply-delivery.ts +106 -2
  480. package/src/runtime/decision-token.ts +116 -0
  481. package/src/runtime/gateway-client.ts +2 -2
  482. package/src/runtime/http-router.ts +32 -0
  483. package/src/runtime/http-server.ts +52 -1
  484. package/src/runtime/http-types.ts +23 -1
  485. package/src/runtime/interactive-ui.ts +362 -0
  486. package/src/runtime/invite-instruction-generator.ts +2 -2
  487. package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
  488. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
  489. package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
  490. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
  491. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
  492. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
  493. package/src/runtime/migrations/gcs-signed-url.ts +162 -0
  494. package/src/runtime/migrations/vbundle-importer.ts +154 -9
  495. package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
  496. package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
  497. package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
  498. package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
  499. package/src/runtime/migrations/vbundle-validator.ts +15 -6
  500. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
  501. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
  502. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
  503. package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
  504. package/src/runtime/routes/approval-routes.ts +12 -17
  505. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
  506. package/src/runtime/routes/avatar-routes.ts +20 -4
  507. package/src/runtime/routes/btw-routes.ts +1 -4
  508. package/src/runtime/routes/conversation-management-routes.ts +20 -2
  509. package/src/runtime/routes/conversation-routes.ts +133 -27
  510. package/src/runtime/routes/debug-routes.ts +1 -1
  511. package/src/runtime/routes/diagnostics-routes.ts +6 -4
  512. package/src/runtime/routes/events-routes.ts +16 -0
  513. package/src/runtime/routes/guardian-approval-interception.ts +33 -3
  514. package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
  515. package/src/runtime/routes/home-feed-routes.ts +120 -2
  516. package/src/runtime/routes/inbound-message-handler.ts +912 -2
  517. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
  518. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
  519. package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
  520. package/src/runtime/routes/integrations/slack/channel.ts +25 -3
  521. package/src/runtime/routes/llm-context-normalization.ts +23 -1
  522. package/src/runtime/routes/migration-routes.ts +720 -124
  523. package/src/runtime/routes/settings-routes.ts +4 -2
  524. package/src/runtime/routes/trust-rules-routes.ts +30 -14
  525. package/src/runtime/routes/work-items-routes.test.ts +1 -1
  526. package/src/runtime/routes/work-items-routes.ts +3 -2
  527. package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
  528. package/src/runtime/services/analyze-conversation.ts +12 -16
  529. package/src/runtime/skill-route-registry.ts +28 -6
  530. package/src/schedule/scheduler.ts +8 -0
  531. package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
  532. package/src/security/__tests__/untrusted-content.test.ts +109 -0
  533. package/src/security/oauth2.ts +98 -35
  534. package/src/security/secure-keys.ts +7 -8
  535. package/src/security/token-manager.ts +27 -13
  536. package/src/security/untrusted-content.ts +102 -0
  537. package/src/skills/catalog-cache.ts +26 -7
  538. package/src/skills/catalog-install.ts +31 -3
  539. package/src/skills/skill-cache-store.ts +97 -0
  540. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
  541. package/src/stt/daemon-batch-transcriber.ts +33 -0
  542. package/src/stt/stt-stream-session.ts +8 -1
  543. package/src/stt/types.ts +5 -1
  544. package/src/subagent/manager.ts +41 -13
  545. package/src/tasks/ephemeral-permissions.ts +9 -4
  546. package/src/telemetry/usage-telemetry-reporter.ts +27 -5
  547. package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
  548. package/src/tools/browser/browser-execution.ts +65 -38
  549. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
  550. package/src/tools/credentials/tool-policy.ts +39 -5
  551. package/src/tools/credentials/vault.ts +9 -4
  552. package/src/tools/executor.ts +4 -0
  553. package/src/tools/filesystem/write.ts +52 -0
  554. package/src/tools/host-terminal/host-shell.ts +45 -5
  555. package/src/tools/memory/register.test.ts +185 -0
  556. package/src/tools/memory/register.ts +3 -1
  557. package/src/tools/network/web-fetch.ts +20 -10
  558. package/src/tools/network/web-search.ts +19 -4
  559. package/src/tools/permission-checker.ts +36 -15
  560. package/src/tools/policy-context.ts +25 -8
  561. package/src/tools/registry.ts +55 -3
  562. package/src/tools/side-effects.ts +0 -11
  563. package/src/tools/skills/execute.ts +2 -2
  564. package/src/tools/skills/sandbox-runner.ts +5 -2
  565. package/src/tools/terminal/backends/native.ts +51 -2
  566. package/src/tools/terminal/safe-env.ts +3 -2
  567. package/src/tools/terminal/shell.ts +1 -0
  568. package/src/tools/tool-manifest.ts +6 -21
  569. package/src/tools/types.ts +12 -3
  570. package/src/tools/verification-control-plane-policy.ts +1 -1
  571. package/src/tts/__tests__/provider-adapters.test.ts +240 -13
  572. package/src/tts/provider-catalog.ts +18 -0
  573. package/src/tts/providers/index.ts +2 -0
  574. package/src/tts/providers/xai-provider.ts +224 -0
  575. package/src/tts/types.ts +46 -0
  576. package/src/types/tar-stream.d.ts +66 -0
  577. package/src/util/json.ts +17 -0
  578. package/src/util/platform.ts +2 -2
  579. package/src/util/pricing.ts +15 -5
  580. package/src/watcher/engine.ts +1 -1
  581. package/src/watcher/providers/google-calendar.ts +134 -8
  582. package/src/watcher/providers/outlook-calendar.ts +42 -2
  583. package/src/workspace/git-service.ts +23 -4
  584. package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
  585. package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
  586. package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
  587. package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
  588. package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
  589. package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
  590. package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
  591. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
  592. package/src/workspace/migrations/AGENTS.md +1 -1
  593. package/src/workspace/migrations/registry.ts +16 -0
  594. package/src/workspace/provider-commit-message-generator.ts +19 -38
  595. package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
  596. package/src/__tests__/gmail-archive-gate.test.ts +0 -246
  597. package/src/__tests__/gmail-preferences.test.ts +0 -117
  598. package/src/__tests__/outlook-attachments.test.ts +0 -301
  599. package/src/__tests__/outlook-automation-tools.test.ts +0 -425
  600. package/src/__tests__/outlook-categories.test.ts +0 -212
  601. package/src/__tests__/outlook-compose-tools.test.ts +0 -325
  602. package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
  603. package/src/__tests__/outlook-follow-up.test.ts +0 -196
  604. package/src/__tests__/outlook-trash.test.ts +0 -77
  605. package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
  606. package/src/__tests__/update-bulletin-format.test.ts +0 -181
  607. package/src/__tests__/update-bulletin-state.test.ts +0 -135
  608. package/src/__tests__/update-bulletin.test.ts +0 -478
  609. package/src/__tests__/update-template-contract.test.ts +0 -29
  610. package/src/cli/commands/doctor.ts +0 -341
  611. package/src/config/bundled-skills/browser/SKILL.md +0 -88
  612. package/src/config/bundled-skills/browser/TOOLS.json +0 -516
  613. package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
  614. package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
  615. package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
  616. package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
  617. package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
  618. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
  619. package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
  620. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
  621. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
  622. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
  623. package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
  624. package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
  625. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
  626. package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
  627. package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
  628. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
  629. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
  630. package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
  631. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
  632. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
  633. package/src/config/bundled-skills/gmail/SKILL.md +0 -221
  634. package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
  635. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
  636. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
  637. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
  638. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
  639. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
  640. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
  641. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
  642. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
  643. package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
  644. package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
  645. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
  646. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
  647. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
  648. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
  649. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
  650. package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
  651. package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
  652. package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
  653. package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
  654. package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
  655. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
  656. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
  657. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
  658. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
  659. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
  660. package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
  661. package/src/config/bundled-skills/google-calendar/types.ts +0 -97
  662. package/src/config/bundled-skills/outlook/SKILL.md +0 -196
  663. package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
  664. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
  665. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
  666. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
  667. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
  668. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
  669. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
  670. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
  671. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
  672. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
  673. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
  674. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
  675. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
  676. package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
  677. package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
  678. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
  679. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
  680. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
  681. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
  682. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
  683. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
  684. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
  685. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
  686. package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
  687. package/src/config/bundled-skills/slack/SKILL.md +0 -108
  688. package/src/config/bundled-skills/tasks/SKILL.md +0 -37
  689. package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
  690. package/src/config/bundled-skills/tasks/icon.svg +0 -34
  691. package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
  692. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
  693. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
  694. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
  695. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
  696. package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
  697. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
  698. package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
  699. package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
  700. package/src/config/bundled-skills/watcher/SKILL.md +0 -31
  701. package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
  702. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
  703. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
  704. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
  705. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
  706. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
  707. package/src/prompts/templates/UPDATES.md +0 -50
  708. package/src/prompts/update-bulletin-format.ts +0 -85
  709. package/src/prompts/update-bulletin-state.ts +0 -58
  710. package/src/prompts/update-bulletin-template-path.ts +0 -13
  711. package/src/prompts/update-bulletin.ts +0 -139
  712. package/src/shared/provider-env-vars.ts +0 -19
  713. package/src/tools/watcher/create.ts +0 -86
  714. package/src/tools/watcher/delete.ts +0 -36
  715. package/src/tools/watcher/digest.ts +0 -54
  716. package/src/tools/watcher/list.ts +0 -83
  717. package/src/tools/watcher/update.ts +0 -71
@@ -113,7 +113,7 @@ export async function handleWatchObservation(
113
113
 
114
114
  async function generateCommentary(session: WatchSession): Promise<void> {
115
115
  try {
116
- const provider = await getConfiguredProvider();
116
+ const provider = await getConfiguredProvider("watchCommentary");
117
117
  if (!provider) {
118
118
  log.warn(
119
119
  { watchId: session.watchId },
@@ -164,7 +164,7 @@ async function generateCommentary(session: WatchSession): Promise<void> {
164
164
  systemPrompt,
165
165
  {
166
166
  config: {
167
- modelIntent: "latency-optimized",
167
+ callSite: "watchCommentary",
168
168
  max_tokens: 200,
169
169
  },
170
170
  },
@@ -225,7 +225,7 @@ export async function generateSummary(session: WatchSession): Promise<void> {
225
225
  },
226
226
  "generateSummary starting — calling LLM",
227
227
  );
228
- const provider = await getConfiguredProvider();
228
+ const provider = await getConfiguredProvider("watchSummary");
229
229
  if (!provider) {
230
230
  log.warn(
231
231
  { watchId: session.watchId },
@@ -329,7 +329,7 @@ export async function generateSummary(session: WatchSession): Promise<void> {
329
329
  systemPrompt,
330
330
  {
331
331
  config: {
332
- modelIntent: "quality-optimized",
332
+ callSite: "watchSummary",
333
333
  max_tokens: 2000,
334
334
  },
335
335
  },
@@ -0,0 +1,126 @@
1
+ import type {
2
+ ContentBlock,
3
+ Message,
4
+ ServerToolUseContent,
5
+ TextContent,
6
+ WebSearchToolResultContent,
7
+ } from "../providers/types.js";
8
+
9
+ export interface StripStats {
10
+ blocksStripped: number;
11
+ serverToolUsesDropped: number;
12
+ messagesModified: number;
13
+ }
14
+
15
+ export interface StripResult {
16
+ messages: Message[];
17
+ stats: StripStats;
18
+ }
19
+
20
+ /**
21
+ * Replaces every `web_search_tool_result` block in the message list with a
22
+ * plain `text` summary of its results, and drops the paired `server_tool_use`
23
+ * that produced it.
24
+ *
25
+ * Anthropic's `encrypted_content` tokens attached to each `web_search_result`
26
+ * are opaque server tokens with bounded validity (they expire and/or are
27
+ * route-scoped). Replaying a stale token produces
28
+ * `messages.N.content.M: Invalid encrypted_content in search_result block`.
29
+ * For historical turns the model does not need the opaque token to re-read
30
+ * the body — a title+url summary is sufficient to preserve context.
31
+ *
32
+ * Intended to run on `runMessages` immediately before the agent loop starts a
33
+ * new turn, at which point every `web_search_tool_result` in the list is by
34
+ * definition from a prior turn.
35
+ */
36
+ export function stripHistoricalWebSearchResults(
37
+ messages: Message[],
38
+ ): StripResult {
39
+ const stats: StripStats = {
40
+ blocksStripped: 0,
41
+ serverToolUsesDropped: 0,
42
+ messagesModified: 0,
43
+ };
44
+
45
+ const next: Message[] = messages.map((msg) => {
46
+ const droppedServerToolUseIds = new Set<string>();
47
+ const transformed: ContentBlock[] = [];
48
+
49
+ for (const block of msg.content) {
50
+ if (block.type !== "web_search_tool_result") continue;
51
+ const wsr = block as WebSearchToolResultContent;
52
+ const query = findQueryForToolUseId(msg.content, wsr.tool_use_id);
53
+ transformed.push(formatAsText(wsr, query));
54
+ droppedServerToolUseIds.add(wsr.tool_use_id);
55
+ stats.blocksStripped++;
56
+ }
57
+
58
+ if (droppedServerToolUseIds.size === 0) return msg;
59
+
60
+ const rewritten: ContentBlock[] = [];
61
+ let wsrIndex = 0;
62
+ for (const block of msg.content) {
63
+ if (block.type === "server_tool_use") {
64
+ const stu = block as ServerToolUseContent;
65
+ if (droppedServerToolUseIds.has(stu.id)) {
66
+ stats.serverToolUsesDropped++;
67
+ continue;
68
+ }
69
+ rewritten.push(block);
70
+ } else if (block.type === "web_search_tool_result") {
71
+ rewritten.push(transformed[wsrIndex++]);
72
+ } else {
73
+ rewritten.push(block);
74
+ }
75
+ }
76
+
77
+ stats.messagesModified++;
78
+ return { ...msg, content: rewritten };
79
+ });
80
+
81
+ return { messages: next, stats };
82
+ }
83
+
84
+ function findQueryForToolUseId(
85
+ blocks: ContentBlock[],
86
+ toolUseId: string,
87
+ ): string | null {
88
+ for (const b of blocks) {
89
+ if (b.type !== "server_tool_use") continue;
90
+ const stu = b as ServerToolUseContent;
91
+ if (stu.id !== toolUseId) continue;
92
+ const q = stu.input?.query;
93
+ return typeof q === "string" ? q : null;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ function formatAsText(
99
+ block: WebSearchToolResultContent,
100
+ query: string | null,
101
+ ): TextContent {
102
+ const header = query
103
+ ? `[Prior web_search results for "${query}":`
104
+ : "[Prior web_search results:";
105
+
106
+ const content = block.content;
107
+ if (!Array.isArray(content)) {
108
+ return { type: "text", text: `${header} (results unavailable)]` };
109
+ }
110
+
111
+ const entries = content
112
+ .filter(
113
+ (r): r is { type: string; title?: unknown; url?: unknown } =>
114
+ typeof r === "object" &&
115
+ r != null &&
116
+ (r as { type?: string }).type === "web_search_result",
117
+ )
118
+ .map((r, i) => {
119
+ const title = typeof r.title === "string" ? r.title : "(untitled)";
120
+ const url = typeof r.url === "string" ? r.url : "";
121
+ return url ? `${i + 1}. ${title}\n ${url}` : `${i + 1}. ${title}`;
122
+ });
123
+
124
+ const body = entries.length > 0 ? entries.join("\n") : "(no results)";
125
+ return { type: "text", text: `${header}\n${body}]` };
126
+ }
@@ -22,7 +22,6 @@ export interface ToolDomainEvents {
22
22
  | "allow_10m"
23
23
  | "allow_conversation"
24
24
  | "always_allow"
25
- | "always_allow_high_risk"
26
25
  | "deny"
27
26
  | "always_deny"
28
27
  | "temporary_override";
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  import { getConfig } from "../config/loader.js";
5
- import type { Speed } from "../config/schemas/inference.js";
5
+ import type { LLMCallSite } from "../config/schemas/llm.js";
6
6
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
7
7
  import { getLogger } from "../util/logger.js";
8
8
  import { getWorkspaceDir } from "../util/platform.js";
@@ -17,23 +17,24 @@ const FILING_PROMPT_TEMPLATE = `You are running a periodic knowledge base filing
17
17
  ## Part 1: File the buffer
18
18
 
19
19
  Read \`pkb/buffer.md\`. For each item in the buffer:
20
- 1. Determine which topic file it belongs in. Check \`pkb/INDEX.md\` to see what topic files exist.
21
- 2. Read the target topic file, then append or integrate the new fact.
20
+ 1. Determine which topic file(s) it belongs in. Check \`pkb/INDEX.md\` to see what topic files exist.
21
+ 2. Read the target topic file(s), then integrate the new fact.
22
22
  3. If the fact is important enough to always be in context, add it to \`pkb/essentials.md\` instead.
23
23
  4. If the fact is a commitment, follow-up, or active project, add it to \`pkb/threads.md\`.
24
24
  5. If no existing topic file fits, create a new one and update \`pkb/INDEX.md\`.
25
+ 6. If the topic file is getting too long (>1500 tokens), compress it or split it into multiple topic files.
25
26
 
26
27
  After all items are filed, clear the processed items from \`pkb/buffer.md\` (leave the file empty, don't delete it).
27
28
 
28
- ## Part 2: Nest
29
+ ## Part 2: Review
29
30
 
30
- Pick 1-2 topic files from your knowledge base and review them:
31
+ Pick 3 random topic files from your knowledge base and review them:
31
32
  - Is the information still accurate and up to date?
32
33
  - Are there duplicates that should be consolidated?
33
34
  - Is anything important enough to promote to \`pkb/essentials.md\`?
34
35
  - Is anything in \`pkb/essentials.md\` that's no longer essential? Demote it to a topic file.
35
36
  - Are any threads in \`pkb/threads.md\` completed or stale? Remove them.
36
- - Is any file getting too long? Consider splitting it.
37
+ - Is any file getting too long (>1500 tokens)? Strongly consider compressing it or splitting it into multiple topic files.
37
38
  - Should any topic file be restructured for clarity?
38
39
 
39
40
  Make improvements as you see fit. This is your knowledge base — keep it sharp.`;
@@ -42,7 +43,7 @@ export interface FilingDeps {
42
43
  processMessage: (
43
44
  conversationId: string,
44
45
  content: string,
45
- options?: { speed?: Speed },
46
+ options?: { callSite?: LLMCallSite },
46
47
  ) => Promise<{ messageId: string }>;
47
48
  onConversationCreated?: (info: {
48
49
  conversationId: string;
@@ -190,8 +191,6 @@ export class FilingService {
190
191
  log.info("Running filing job");
191
192
 
192
193
  try {
193
- const config = getConfig().filing;
194
-
195
194
  const conversation = bootstrapConversation({
196
195
  conversationType: "background",
197
196
  source: "filing",
@@ -206,7 +205,7 @@ export class FilingService {
206
205
  });
207
206
 
208
207
  await this.deps.processMessage(conversation.id, FILING_PROMPT_TEMPLATE, {
209
- speed: config.speed,
208
+ callSite: "filingAgent",
210
209
  });
211
210
 
212
211
  log.info({ conversationId: conversation.id }, "Filing job completed");
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
4
  import { getConfig } from "../config/loader.js";
5
- import type { Speed } from "../config/schemas/inference.js";
5
+ import type { LLMCallSite } from "../config/schemas/llm.js";
6
6
  import type { HeartbeatAlert } from "../daemon/message-protocol.js";
7
7
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
8
8
  import {
@@ -39,7 +39,8 @@ export function isShallowProfile(): boolean {
39
39
  try {
40
40
  const identityPath = getWorkspacePromptPath("IDENTITY.md");
41
41
  const rawIdentity = readTextFileSync(identityPath);
42
- const identity = rawIdentity != null ? stripCommentLines(rawIdentity) : null;
42
+ const identity =
43
+ rawIdentity != null ? stripCommentLines(rawIdentity) : null;
43
44
  // `resolveGuardianPersona` returns already-stripped, trimmed content
44
45
  // (or null for missing/empty files).
45
46
  const user = resolveGuardianPersona();
@@ -81,7 +82,7 @@ export interface HeartbeatDeps {
81
82
  processMessage: (
82
83
  conversationId: string,
83
84
  content: string,
84
- options?: { speed?: Speed },
85
+ options?: { callSite?: LLMCallSite },
85
86
  ) => Promise<{ messageId: string }>;
86
87
  alerter: (alert: HeartbeatAlert) => void;
87
88
  onConversationCreated?: (info: {
@@ -222,11 +223,13 @@ export class HeartbeatService {
222
223
  // permanently blocked. The .finally() handler still serves as the
223
224
  // normal-completion cleanup path and uses an identity guard to avoid
224
225
  // clearing a different run's activeRun.
225
- run.finally(() => {
226
- if (this.activeRun === run) {
227
- this.activeRun = null;
228
- }
229
- }).catch(() => {}); // Suppress unhandled rejection if executeRun rejects
226
+ run
227
+ .finally(() => {
228
+ if (this.activeRun === run) {
229
+ this.activeRun = null;
230
+ }
231
+ })
232
+ .catch(() => {}); // Suppress unhandled rejection if executeRun rejects
230
233
 
231
234
  let timerId: ReturnType<typeof setTimeout> | undefined;
232
235
  try {
@@ -255,18 +258,47 @@ export class HeartbeatService {
255
258
  this._nextRunAt = Date.now() + intervalMs;
256
259
  }
257
260
 
258
- private async runCredentialHealthCheck(): Promise<void> {
261
+ /**
262
+ * Run credential health checks and notify about unhealthy credentials.
263
+ * Returns a list of unhealthy provider names so callers can gate tool usage.
264
+ */
265
+ private async runCredentialHealthCheck(): Promise<string[]> {
259
266
  try {
260
- const { checkAllCredentials } = await import(
261
- "../credential-health/credential-health-service.js"
262
- );
267
+ const { checkAllCredentials } =
268
+ await import("../credential-health/credential-health-service.js");
263
269
  const report = await checkAllCredentials();
264
270
  if (report.unhealthy.length > 0) {
265
271
  await this.notifyUnhealthyCredentials(report.unhealthy);
272
+ // Only block providers for hard-failure statuses — expiring and ping_failed
273
+ // are transient/still-usable and should not disable provider tools.
274
+ // missing_scopes is a hard failure because required scopes are absent and
275
+ // provider tools will predictably fail.
276
+ const hardFailureStatuses = new Set([
277
+ "revoked",
278
+ "missing_token",
279
+ "expired",
280
+ "missing_scopes",
281
+ ]);
282
+ const hardFailures = report.unhealthy.filter((r) =>
283
+ hardFailureStatuses.has(r.status),
284
+ );
285
+ return [...new Set(hardFailures.map((r) => r.provider))];
266
286
  }
267
287
  } catch (err) {
268
- log.warn({ err }, "Credential health check failed (non-fatal)");
288
+ log.error({ err }, "Credential health check failed");
289
+ try {
290
+ this.deps.alerter({
291
+ type: "heartbeat_alert",
292
+ title: "Credential Health Check Failed",
293
+ body:
294
+ "Could not verify OAuth credential health. " +
295
+ (err instanceof Error ? err.message : String(err)),
296
+ });
297
+ } catch {
298
+ // Last resort — alerter itself failed. Already logged above.
299
+ }
269
300
  }
301
+ return [];
270
302
  }
271
303
 
272
304
  private async notifyUnhealthyCredentials(
@@ -281,11 +313,13 @@ export class HeartbeatService {
281
313
  ): Promise<void> {
282
314
  let emitNotificationSignal: typeof import("../notifications/emit-signal.js").emitNotificationSignal;
283
315
  try {
284
- ({ emitNotificationSignal } = await import(
285
- "../notifications/emit-signal.js"
286
- ));
287
- } catch {
288
- log.warn("Failed to import notification signal emitter");
316
+ ({ emitNotificationSignal } =
317
+ await import("../notifications/emit-signal.js"));
318
+ } catch (importErr) {
319
+ log.error(
320
+ { err: importErr },
321
+ "Failed to import notification signal emitter",
322
+ );
289
323
  return;
290
324
  }
291
325
 
@@ -317,7 +351,7 @@ export class HeartbeatService {
317
351
  routingIntent: "single_channel",
318
352
  });
319
353
  } catch (err) {
320
- log.warn(
354
+ log.error(
321
355
  { err, provider: result.provider, connectionId: result.connectionId },
322
356
  "Failed to emit credential health notification",
323
357
  );
@@ -329,13 +363,16 @@ export class HeartbeatService {
329
363
  log.info("Running heartbeat");
330
364
 
331
365
  // Credential health check — surface broken credentials proactively
332
- // before the LLM heartbeat prompt runs.
333
- await this.runCredentialHealthCheck();
366
+ // before the LLM heartbeat prompt runs. Returns unhealthy provider
367
+ // names so the prompt can instruct the LLM to skip those providers.
368
+ const unhealthyProviders = await this.runCredentialHealthCheck();
334
369
 
335
370
  try {
336
- const config = getConfig().heartbeat;
337
371
  const checklist = this.readChecklist();
338
- const { prompt, includedReengagement } = this.buildPrompt(checklist);
372
+ const { prompt, includedReengagement } = this.buildPrompt(
373
+ checklist,
374
+ unhealthyProviders,
375
+ );
339
376
 
340
377
  const conversation = bootstrapConversation({
341
378
  conversationType: "background",
@@ -351,7 +388,7 @@ export class HeartbeatService {
351
388
  });
352
389
 
353
390
  await this.deps.processMessage(conversation.id, prompt, {
354
- speed: config.speed,
391
+ callSite: "heartbeatAgent",
355
392
  });
356
393
 
357
394
  if (includedReengagement) {
@@ -368,7 +405,7 @@ export class HeartbeatService {
368
405
  body: err instanceof Error ? err.message : String(err),
369
406
  });
370
407
  } catch (alertErr) {
371
- log.warn({ alertErr }, "Failed to broadcast heartbeat alert");
408
+ log.error({ alertErr }, "Failed to broadcast heartbeat alert");
372
409
  }
373
410
  }
374
411
  }
@@ -381,14 +418,25 @@ export class HeartbeatService {
381
418
  }
382
419
 
383
420
  /** @internal Exposed for testing. */
384
- buildPrompt(checklist: string): { prompt: string; includedReengagement: boolean } {
421
+ buildPrompt(
422
+ checklist: string,
423
+ unhealthyProviders: string[] = [],
424
+ ): { prompt: string; includedReengagement: boolean } {
385
425
  let prompt = `You are running a periodic heartbeat check. Review the following checklist and take any necessary actions.
386
426
 
387
427
  <heartbeat-checklist>
388
428
  ${checklist}
389
- </heartbeat-checklist>
429
+ </heartbeat-checklist>`;
430
+
431
+ if (unhealthyProviders.length > 0) {
432
+ const providers = unhealthyProviders.join(", ");
433
+ prompt += `\n\n<credential-status>
434
+ The following providers have broken or expired OAuth credentials: ${providers}.
435
+ Do NOT attempt to use tools for these providers — they will fail. Skip any checklist items that depend on them and note the outage in your summary.
436
+ </credential-status>`;
437
+ }
390
438
 
391
- <heartbeat-disposition>
439
+ prompt += `\n\n<heartbeat-disposition>
392
440
  After completing your review, end your response with one of:
393
441
  - HEARTBEAT_OK — if everything looks good, no action needed
394
442
  - HEARTBEAT_ALERT — if you found issues that need attention (describe them before this marker)
@@ -70,28 +70,36 @@ describe("startFeedScheduler", () => {
70
70
  expect(summary2.gmailDigestRan).toBe(true);
71
71
  });
72
72
 
73
- test("rollup only re-runs every 30 minutes", async () => {
73
+ test("rollup only re-runs every 2 hours as the safety-net cadence", async () => {
74
+ // The scheduler is the safety net; the primary trigger is the
75
+ // on-visit refresh in home-feed-routes.ts. Long cadence is
76
+ // intentional so the scheduler doesn't fight the route.
74
77
  handle = startFeedScheduler(defaultOptions());
75
78
 
76
79
  const t0 = new Date("2026-04-14T12:00:00.000Z");
77
80
  await handle.runOnce(t0);
78
81
 
79
- // 5 min later — below the 30-min reflection gate.
80
- const t1 = new Date("2026-04-14T12:05:00.000Z");
82
+ // 30 min later — below the 2-hour gate.
83
+ const t1 = new Date("2026-04-14T12:30:00.000Z");
81
84
  const summary1 = await handle.runOnce(t1);
82
85
  expect(summary1.rollupRan).toBe(false);
83
86
 
84
- // 31 min later — past the 30-min gate, should re-run.
85
- const t2 = new Date("2026-04-14T12:31:00.000Z");
87
+ // 1h later — still below the 2-hour gate.
88
+ const t2 = new Date("2026-04-14T13:00:00.000Z");
86
89
  const summary2 = await handle.runOnce(t2);
87
- expect(summary2.rollupRan).toBe(true);
90
+ expect(summary2.rollupRan).toBe(false);
91
+
92
+ // 2h 1m later — past the gate, should re-run.
93
+ const t3 = new Date("2026-04-14T14:01:00.000Z");
94
+ const summary3 = await handle.runOnce(t3);
95
+ expect(summary3.rollupRan).toBe(true);
88
96
  });
89
97
 
90
98
  test("rollup cooldown is NOT advanced on no_provider so the next tick retries", async () => {
91
99
  // Mimic the daemon startup ordering: the scheduler boots before
92
100
  // the provider registry is ready. The first tick gets no_provider;
93
101
  // the next tick (even one second later) must still run the rollup
94
- // instead of waiting 30 minutes.
102
+ // instead of waiting 2 hours.
95
103
  rollupRunner.mockImplementationOnce(async () => ({
96
104
  wroteCount: 0,
97
105
  skippedReason: "no_provider",
@@ -110,10 +118,30 @@ describe("startFeedScheduler", () => {
110
118
  expect(rollupRunner).toHaveBeenCalledTimes(2);
111
119
  });
112
120
 
121
+ test("rollup cooldown is NOT advanced on in_flight so the next tick retries", async () => {
122
+ // in_flight means another caller (on-visit refresh, usually) is
123
+ // already running the producer. Advancing the gate here would
124
+ // force the NEXT tick to wait out the full cadence window even
125
+ // though nothing broken happened — the other caller's result is
126
+ // effectively this tick's run.
127
+ rollupRunner.mockImplementationOnce(async () => ({
128
+ wroteCount: 0,
129
+ skippedReason: "in_flight",
130
+ }));
131
+
132
+ handle = startFeedScheduler(defaultOptions());
133
+ await handle.runOnce(new Date("2026-04-14T12:00:00.000Z"));
134
+ expect(rollupRunner).toHaveBeenCalledTimes(1);
135
+
136
+ const summary = await handle.runOnce(new Date("2026-04-14T12:00:01.000Z"));
137
+ expect(summary.rollupRan).toBe(true);
138
+ expect(rollupRunner).toHaveBeenCalledTimes(2);
139
+ });
140
+
113
141
  test("rollup cooldown is NOT advanced on no_actions so the next tick retries", async () => {
114
142
  // no_actions means the activity log was empty — no LLM call was
115
143
  // made. A subsequent tick should retry as soon as new actions
116
- // land, not wait the full 30-minute window.
144
+ // land, not wait the full 2-hour window.
117
145
  rollupRunner.mockImplementationOnce(async () => ({
118
146
  wroteCount: 0,
119
147
  skippedReason: "no_actions",
@@ -131,7 +159,7 @@ describe("startFeedScheduler", () => {
131
159
 
132
160
  test("rollup cooldown IS advanced on other skip reasons to preserve backoff", async () => {
133
161
  // empty_items / malformed_output / provider_error are real LLM
134
- // attempts — the next tick should be gated by the full 30-minute
162
+ // attempts — the next tick should be gated by the full 2-hour
135
163
  // window so a broken producer doesn't get hammered every tick.
136
164
  rollupRunner.mockImplementationOnce(async () => ({
137
165
  wroteCount: 0,
@@ -142,8 +170,8 @@ describe("startFeedScheduler", () => {
142
170
  await handle.runOnce(new Date("2026-04-14T12:00:00.000Z"));
143
171
  expect(rollupRunner).toHaveBeenCalledTimes(1);
144
172
 
145
- // Ten minutes later — below the 30-min gate, should NOT re-run.
146
- const summary = await handle.runOnce(new Date("2026-04-14T12:10:00.000Z"));
173
+ // Thirty minutes later — below the 2-hour gate, should NOT re-run.
174
+ const summary = await handle.runOnce(new Date("2026-04-14T12:30:00.000Z"));
147
175
  expect(summary.rollupRan).toBe(false);
148
176
  expect(rollupRunner).toHaveBeenCalledTimes(1);
149
177
  });
@@ -364,6 +364,50 @@ describe("runRollupProducer", () => {
364
364
  expect(result.wroteCount).toBe(0);
365
365
  });
366
366
 
367
+ test("concurrent calls short-circuit the second with in_flight", async () => {
368
+ // Gate the provider behind a manually-controlled deferred so we
369
+ // can observe state while the first call is still inside the
370
+ // producer body. Without this we'd race the runtime's microtask
371
+ // scheduler to check in-flightness.
372
+ let release: ((value: ContentBlock[]) => void) | null = null;
373
+ const gated = new Promise<ContentBlock[]>((resolve) => {
374
+ release = resolve;
375
+ });
376
+ const provider = makeProvider(async () => {
377
+ const content = await gated;
378
+ return {
379
+ content,
380
+ model: "mock-model",
381
+ usage: { inputTokens: 0, outputTokens: 0 },
382
+ stopReason: "tool_use",
383
+ };
384
+ });
385
+
386
+ const first = runRollupProducer(new Date(), {
387
+ writeItem,
388
+ loadRelationshipState: stubRelationshipState,
389
+ loadRecentActions: stubLoadRecentActions(oneAction),
390
+ resolveProvider: () => provider,
391
+ });
392
+
393
+ // Second call lands while `first` is blocked awaiting the gated
394
+ // provider response — the in-flight guard must short-circuit it.
395
+ const second = await runRollupProducer(new Date(), {
396
+ writeItem,
397
+ loadRelationshipState: stubRelationshipState,
398
+ loadRecentActions: stubLoadRecentActions(oneAction),
399
+ resolveProvider: () => provider,
400
+ });
401
+
402
+ expect(second.skippedReason).toBe("in_flight");
403
+ expect(second.wroteCount).toBe(0);
404
+
405
+ // Release the first call and let it finish.
406
+ release!([toolUseContent({ items: [] })]);
407
+ const firstResult = await first;
408
+ expect(firstResult.skippedReason).toBe("empty_items");
409
+ });
410
+
367
411
  test("clamps priority to the valid [0, 100] window", async () => {
368
412
  const provider = scriptedProvider([
369
413
  toolUseContent({
@@ -41,6 +41,7 @@ import {
41
41
  feedItemSchema,
42
42
  type FeedItemSource,
43
43
  type FeedItemType,
44
+ type FeedItemUrgency,
44
45
  } from "./feed-types.js";
45
46
  import { appendFeedItem } from "./feed-writer.js";
46
47
 
@@ -80,6 +81,8 @@ export interface WriteAssistantFeedItemParams {
80
81
  minTimeAway?: number;
81
82
  /** Absolute ISO-8601 expiry timestamp. */
82
83
  expiresAt?: string;
84
+ /** Visual urgency treatment — controls badge color independently of sort priority. */
85
+ urgency?: FeedItemUrgency;
83
86
  }
84
87
 
85
88
  /**
@@ -110,6 +113,7 @@ export async function writeAssistantFeedItem(
110
113
  timestamp: now,
111
114
  createdAt: now,
112
115
  actions: params.actions,
116
+ urgency: params.urgency,
113
117
  minTimeAway: params.minTimeAway,
114
118
  expiresAt: params.expiresAt,
115
119
  };
@@ -43,6 +43,7 @@ import {
43
43
  type FeedItem,
44
44
  feedItemSchema,
45
45
  type FeedItemSource,
46
+ type FeedItemUrgency,
46
47
  } from "./feed-types.js";
47
48
  import { appendFeedItem } from "./feed-writer.js";
48
49
 
@@ -93,6 +94,8 @@ export interface EmitFeedEventParams {
93
94
  * until the user dismisses it (default for activity-log actions).
94
95
  */
95
96
  expiresAt?: string;
97
+ /** Visual urgency treatment — controls badge color independently of sort priority. */
98
+ urgency?: FeedItemUrgency;
96
99
  }
97
100
 
98
101
  /**
@@ -144,6 +147,7 @@ export async function emitFeedEvent(
144
147
  timestamp: now,
145
148
  createdAt: now,
146
149
  actions: params.actions,
150
+ urgency: params.urgency,
147
151
  minTimeAway: params.minTimeAway,
148
152
  expiresAt: params.expiresAt,
149
153
  };
@@ -43,7 +43,15 @@ const TICK_INTERVAL_MS = 5 * 60 * 1000;
43
43
 
44
44
  /** Per-producer minimum gap between runs. */
45
45
  const GMAIL_DIGEST_INTERVAL_MS = 5 * 60 * 1000;
46
- const ROLLUP_INTERVAL_MS = 30 * 60 * 1000;
46
+ /**
47
+ * Roll-up cadence is deliberately long — 120 minutes — because the
48
+ * scheduler is the *safety net*, not the primary trigger. Opening the
49
+ * Home page fires a debounced on-visit refresh in the HTTP route (see
50
+ * `runtime/routes/home-feed-routes.ts`), which is the path most users
51
+ * actually hit. The scheduler exists so the feed still stays fresh
52
+ * for long idle stretches where nobody opens the Home page.
53
+ */
54
+ const ROLLUP_INTERVAL_MS = 2 * 60 * 60 * 1000;
47
55
 
48
56
  export interface FeedSchedulerHandle {
49
57
  /** Stops the interval. Safe to call multiple times. */
@@ -146,8 +154,9 @@ export function startFeedScheduler(
146
154
  "Rollup producer ran",
147
155
  );
148
156
  // Only advance the cooldown gate when the producer actually
149
- // had a chance to run the LLM. Two skip reasons short-circuit
150
- // before any provider call and should NOT burn the window:
157
+ // had a chance to run the LLM. Three skip reasons short-
158
+ // circuit before any provider call and should NOT burn the
159
+ // window:
151
160
  // - `no_provider`: the provider registry wasn't ready yet
152
161
  // (happens on the startup tick because the feed scheduler
153
162
  // boots before the provider init pass in
@@ -155,12 +164,19 @@ export function startFeedScheduler(
155
164
  // - `no_actions`: there was nothing to roll up. A subsequent
156
165
  // tick should retry as soon as new actions land, not wait
157
166
  // the full window.
167
+ // - `in_flight`: another caller (usually the on-visit
168
+ // refresh trigger in `home-feed-routes.ts`) is already
169
+ // running the rollup. That caller's result effectively
170
+ // counts as this scheduler tick's real run; bumping the
171
+ // gate here would force the NEXT tick to also wait out
172
+ // the full window even though nothing broken happened.
158
173
  // Every other outcome — success, empty items, malformed
159
174
  // output, provider error — is a real LLM attempt and does
160
175
  // advance the gate so a broken producer doesn't hammer us.
161
176
  if (
162
177
  result.skippedReason !== "no_provider" &&
163
- result.skippedReason !== "no_actions"
178
+ result.skippedReason !== "no_actions" &&
179
+ result.skippedReason !== "in_flight"
164
180
  ) {
165
181
  lastRollupAt = nowMs;
166
182
  }