@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
@@ -65,6 +65,7 @@ afterAll(() => {
65
65
 
66
66
  import {
67
67
  deepMergeMissing,
68
+ deepMergeOverwrite,
68
69
  invalidateConfigCache,
69
70
  loadConfig,
70
71
  } from "../config/loader.js";
@@ -143,6 +144,179 @@ describe("deepMergeMissing", () => {
143
144
  });
144
145
  });
145
146
 
147
+ // ---------------------------------------------------------------------------
148
+ // Tests: deepMergeOverwrite (unit) — JSON-null-as-deletion semantics
149
+ // ---------------------------------------------------------------------------
150
+
151
+ describe("deepMergeOverwrite", () => {
152
+ test("overwrites top-level scalars", () => {
153
+ const target: Record<string, unknown> = { a: 1, b: "old" };
154
+ deepMergeOverwrite(target, { a: 2, c: "new" });
155
+ expect(target).toEqual({ a: 2, b: "old", c: "new" });
156
+ });
157
+
158
+ test("recursively merges nested objects, overwriting leaves", () => {
159
+ const target: Record<string, unknown> = {
160
+ nested: { keep: "yes", change: "before" },
161
+ };
162
+ deepMergeOverwrite(target, {
163
+ nested: { change: "after", added: 42 },
164
+ });
165
+ expect(target).toEqual({
166
+ nested: { keep: "yes", change: "after", added: 42 },
167
+ });
168
+ });
169
+
170
+ test("replaces arrays wholesale rather than merging", () => {
171
+ const target: Record<string, unknown> = { items: [1, 2, 3] };
172
+ deepMergeOverwrite(target, { items: [9] });
173
+ expect(target).toEqual({ items: [9] });
174
+ });
175
+
176
+ test("assigns null to scalar fields (preserves nullable config values)", () => {
177
+ const target: Record<string, unknown> = { a: 1, b: 2 };
178
+ deepMergeOverwrite(target, { a: null });
179
+ expect(target).toEqual({ a: null, b: 2 });
180
+ expect("a" in target).toBe(true);
181
+ });
182
+
183
+ test("assigns null to nested scalar fields, preserving siblings", () => {
184
+ const target: Record<string, unknown> = {
185
+ a: { b: 1, c: 2, d: 3 },
186
+ };
187
+ deepMergeOverwrite(target, { a: { b: null } });
188
+ expect(target).toEqual({ a: { b: null, c: 2, d: 3 } });
189
+ expect("b" in (target.a as Record<string, unknown>)).toBe(true);
190
+ });
191
+
192
+ test("assigns null to existing null fields (no-op for already-null)", () => {
193
+ const target: Record<string, unknown> = {
194
+ heartbeat: { activeHoursStart: null, intervalMs: 6000 },
195
+ };
196
+ deepMergeOverwrite(target, {
197
+ heartbeat: { activeHoursStart: null },
198
+ });
199
+ expect(target).toEqual({
200
+ heartbeat: { activeHoursStart: null, intervalMs: 6000 },
201
+ });
202
+ });
203
+
204
+ test("deletion of a nested key whose value is itself an object", () => {
205
+ // Models the macOS clear-call-site-override case:
206
+ // PATCH { llm: { callSites: { commitMessage: null } } } removes the
207
+ // commitMessage entry entirely while keeping other call-site entries
208
+ // and unrelated llm fields intact.
209
+ const target: Record<string, unknown> = {
210
+ llm: {
211
+ provider: "anthropic",
212
+ callSites: {
213
+ commitMessage: { provider: "openai", model: "gpt-4" },
214
+ memoryRetrieval: { profile: "fast" },
215
+ },
216
+ },
217
+ };
218
+ deepMergeOverwrite(target, {
219
+ llm: { callSites: { commitMessage: null } },
220
+ });
221
+ expect(target).toEqual({
222
+ llm: {
223
+ provider: "anthropic",
224
+ callSites: {
225
+ memoryRetrieval: { profile: "fast" },
226
+ },
227
+ },
228
+ });
229
+ });
230
+
231
+ test("deletion is a no-op when the key is already absent", () => {
232
+ const target: Record<string, unknown> = { a: 1 };
233
+ deepMergeOverwrite(target, { missing: null });
234
+ expect(target).toEqual({ a: 1 });
235
+ expect("missing" in target).toBe(false);
236
+ });
237
+
238
+ test("strips null leaves when assigning a whole subtree to a missing key", () => {
239
+ // Models a PATCH that introduces a new call-site entry while clearing
240
+ // some of its sub-fields in the same payload — the nulls must not
241
+ // be persisted.
242
+ const target: Record<string, unknown> = { llm: { provider: "anthropic" } };
243
+ deepMergeOverwrite(target, {
244
+ llm: {
245
+ callSites: {
246
+ commitMessage: { provider: null, model: "gpt-4", profile: null },
247
+ },
248
+ },
249
+ });
250
+ expect(target).toEqual({
251
+ llm: {
252
+ provider: "anthropic",
253
+ callSites: {
254
+ commitMessage: { model: "gpt-4" },
255
+ },
256
+ },
257
+ });
258
+ });
259
+
260
+ test("strips null leaves when overwriting a scalar with an object subtree", () => {
261
+ const target: Record<string, unknown> = { a: 1 };
262
+ deepMergeOverwrite(target, { a: { b: null, c: 5, d: { e: null, f: 6 } } });
263
+ expect(target).toEqual({ a: { c: 5, d: { f: 6 } } });
264
+ });
265
+
266
+ test("nullable config fields: null replaces scalar default, not deleted", () => {
267
+ // Models PATCH { heartbeat: { activeHoursStart: null, activeHoursEnd: null } }
268
+ // on a config where the defaults (8, 22) are in place. The nullable fields
269
+ // must store null (meaning "disabled") — NOT be deleted (which would
270
+ // re-apply schema defaults on next load).
271
+ const target: Record<string, unknown> = {
272
+ heartbeat: { intervalMs: 6000, activeHoursStart: 8, activeHoursEnd: 22 },
273
+ };
274
+ deepMergeOverwrite(target, {
275
+ heartbeat: { activeHoursStart: null, activeHoursEnd: null },
276
+ });
277
+ expect(target).toEqual({
278
+ heartbeat: { intervalMs: 6000, activeHoursStart: null, activeHoursEnd: null },
279
+ });
280
+ });
281
+
282
+ test("mixed: deletes object entries, assigns null to scalars in same merge", () => {
283
+ // Verifies both behaviors coexist in a single merge: object entries are
284
+ // deleted (call-site clearing) while scalar nulls are assigned (nullable fields).
285
+ const target: Record<string, unknown> = {
286
+ llm: {
287
+ callSites: {
288
+ commitMessage: { provider: "openai" },
289
+ },
290
+ },
291
+ heartbeat: { activeHoursStart: 8 },
292
+ };
293
+ deepMergeOverwrite(target, {
294
+ llm: { callSites: { commitMessage: null } },
295
+ heartbeat: { activeHoursStart: null },
296
+ });
297
+ expect(target).toEqual({
298
+ llm: { callSites: {} },
299
+ heartbeat: { activeHoursStart: null },
300
+ });
301
+ });
302
+
303
+ test("preserves explicit boolean false and zero (not treated as null)", () => {
304
+ const target: Record<string, unknown> = { a: true, b: 1 };
305
+ deepMergeOverwrite(target, { a: false, b: 0 });
306
+ expect(target).toEqual({ a: false, b: 0 });
307
+ });
308
+
309
+ test("undefined override values are passed through, not treated as deletion", () => {
310
+ // JSON.parse never produces undefined, but guard the in-process call path:
311
+ // an explicit undefined assignment should follow the same "scalar overwrite"
312
+ // path as before, not the null-deletion path.
313
+ const target: Record<string, unknown> = { a: 1 };
314
+ deepMergeOverwrite(target, { a: undefined });
315
+ expect("a" in target).toBe(true);
316
+ expect(target.a).toBeUndefined();
317
+ });
318
+ });
319
+
146
320
  // ---------------------------------------------------------------------------
147
321
  // Tests: startup backfill integration
148
322
  // ---------------------------------------------------------------------------
@@ -0,0 +1,183 @@
1
+ /**
2
+ * A corrupt config.json (truncated during a power-loss mid-write, or
3
+ * hand-edited to invalid JSON) is quarantined by the loader, which
4
+ * logs an error with a remediation hint and falls through to the
5
+ * default-config path so startup proceeds. These tests verify that
6
+ * loadConfig() / loadRawConfig() never throw on corrupt input, and
7
+ * that the corrupt file is preserved for debugging.
8
+ */
9
+
10
+ import {
11
+ existsSync,
12
+ mkdirSync,
13
+ readdirSync,
14
+ readFileSync,
15
+ rmSync,
16
+ writeFileSync,
17
+ } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
20
+
21
+ import {
22
+ invalidateConfigCache,
23
+ loadConfig,
24
+ loadRawConfig,
25
+ } from "../config/loader.js";
26
+ import { _setStorePath } from "../security/encrypted-store.js";
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Helpers
30
+ // ---------------------------------------------------------------------------
31
+
32
+ const WORKSPACE_DIR = process.env.VELLUM_WORKSPACE_DIR!;
33
+ const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
34
+
35
+ function ensureTestDir(): void {
36
+ const dirs = [
37
+ WORKSPACE_DIR,
38
+ join(WORKSPACE_DIR, "data"),
39
+ join(WORKSPACE_DIR, "data", "memory"),
40
+ join(WORKSPACE_DIR, "data", "memory", "knowledge"),
41
+ join(WORKSPACE_DIR, "data", "logs"),
42
+ ];
43
+ for (const dir of dirs) {
44
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
45
+ }
46
+ }
47
+
48
+ function resetWorkspace(): void {
49
+ for (const name of readdirSync(WORKSPACE_DIR)) {
50
+ rmSync(join(WORKSPACE_DIR, name), { recursive: true, force: true });
51
+ }
52
+ ensureTestDir();
53
+ }
54
+
55
+ function listQuarantinedFiles(): string[] {
56
+ return readdirSync(WORKSPACE_DIR).filter((name) =>
57
+ /^config\.json\.corrupt-.+\.json$/.test(name),
58
+ );
59
+ }
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Tests
63
+ // ---------------------------------------------------------------------------
64
+
65
+ describe("loadConfig corrupt-file recovery", () => {
66
+ beforeEach(() => {
67
+ resetWorkspace();
68
+ _setStorePath(join(WORKSPACE_DIR, "keys.enc"));
69
+ invalidateConfigCache();
70
+ });
71
+
72
+ afterEach(() => {
73
+ _setStorePath(null);
74
+ invalidateConfigCache();
75
+ });
76
+
77
+ test("quarantines corrupt config.json and returns defaults", () => {
78
+ // Simulate a truncated mid-write: valid JSON prefix, abrupt end.
79
+ writeFileSync(CONFIG_PATH, '{"provider": "anthropic", "mo');
80
+
81
+ // Must not throw — the daemon must never block startup on corrupt config.
82
+ const config = loadConfig();
83
+
84
+ // Defaults loaded — config is populated through the Zod schema.
85
+ expect(config).toBeDefined();
86
+ expect(config.memory).toBeDefined();
87
+
88
+ // Corrupt file renamed, not deleted — content preserved for debug.
89
+ const quarantined = listQuarantinedFiles();
90
+ expect(quarantined).toHaveLength(1);
91
+ const quarantinedPath = join(WORKSPACE_DIR, quarantined[0]);
92
+ expect(readFileSync(quarantinedPath, "utf-8")).toBe(
93
+ '{"provider": "anthropic", "mo',
94
+ );
95
+
96
+ // After quarantine the daemon wrote a fresh default config.json.
97
+ expect(existsSync(CONFIG_PATH)).toBe(true);
98
+ });
99
+
100
+ test("loads a valid config without renaming (regression guard)", () => {
101
+ writeFileSync(
102
+ CONFIG_PATH,
103
+ JSON.stringify({ provider: "anthropic", model: "claude-opus-4-7" }),
104
+ );
105
+
106
+ const config = loadConfig();
107
+ expect(config).toBeDefined();
108
+
109
+ // No quarantine file for valid JSON.
110
+ expect(listQuarantinedFiles()).toHaveLength(0);
111
+ // The original file is still present.
112
+ expect(existsSync(CONFIG_PATH)).toBe(true);
113
+ });
114
+
115
+ test("does not re-quarantine existing quarantine files on subsequent startups", () => {
116
+ // First startup: corrupt config is quarantined.
117
+ writeFileSync(CONFIG_PATH, "}{not json");
118
+ loadConfig();
119
+
120
+ const firstBatch = listQuarantinedFiles();
121
+ expect(firstBatch).toHaveLength(1);
122
+
123
+ // Daemon restart with no new config.json (the loader wrote defaults after
124
+ // the quarantine on the previous load, so there IS now a valid config.json
125
+ // on disk — no second quarantine should occur).
126
+ invalidateConfigCache();
127
+ loadConfig();
128
+
129
+ // Still exactly one quarantined file — the loader did not re-rename a
130
+ // pristine or already-quarantined file.
131
+ expect(listQuarantinedFiles()).toHaveLength(1);
132
+ expect(listQuarantinedFiles()[0]).toBe(firstBatch[0]);
133
+ });
134
+
135
+ test("quarantine filenames are filesystem-safe (no colons)", () => {
136
+ writeFileSync(CONFIG_PATH, "not-valid-json");
137
+ loadConfig();
138
+
139
+ const quarantined = listQuarantinedFiles();
140
+ expect(quarantined).toHaveLength(1);
141
+ // ISO-8601 colons must have been replaced — filenames with `:` are
142
+ // invalid on Windows and awkward on macOS Finder.
143
+ expect(quarantined[0]).not.toContain(":");
144
+ // Should match the documented shape: config.json.corrupt-<ISO>.json
145
+ expect(quarantined[0]).toMatch(
146
+ /^config\.json\.corrupt-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.\d{3}Z\.json$/,
147
+ );
148
+ });
149
+ });
150
+
151
+ describe("loadRawConfig corrupt-file recovery", () => {
152
+ beforeEach(() => {
153
+ resetWorkspace();
154
+ _setStorePath(join(WORKSPACE_DIR, "keys.enc"));
155
+ invalidateConfigCache();
156
+ });
157
+
158
+ afterEach(() => {
159
+ _setStorePath(null);
160
+ invalidateConfigCache();
161
+ });
162
+
163
+ test("returns {} and quarantines corrupt file instead of throwing", () => {
164
+ writeFileSync(CONFIG_PATH, "this is not json at all");
165
+
166
+ // Must not throw — the /v1/config handler depends on this.
167
+ const raw = loadRawConfig();
168
+
169
+ expect(raw).toEqual({});
170
+ expect(listQuarantinedFiles()).toHaveLength(1);
171
+ });
172
+
173
+ test("returns parsed object when config is valid", () => {
174
+ writeFileSync(
175
+ CONFIG_PATH,
176
+ JSON.stringify({ foo: "bar", nested: { k: 1 } }),
177
+ );
178
+
179
+ const raw = loadRawConfig();
180
+ expect(raw).toEqual({ foo: "bar", nested: { k: 1 } });
181
+ expect(listQuarantinedFiles()).toHaveLength(0);
182
+ });
183
+ });
@@ -0,0 +1,202 @@
1
+ /**
2
+ * When loadConfig()/loadRawConfig() quarantines a corrupt config.json, it
3
+ * appends a bulletin to <workspace>/UPDATES.md so the background update-
4
+ * bulletin job picks up the event inside a background-only conversation.
5
+ * The agent decides whether to surface the quarantine to the user — it's
6
+ * agent-visible context, not a push notification.
7
+ *
8
+ * The bulletin is keyed on the quarantine filename via an HTML marker so
9
+ * repeated appends for the same quarantine are idempotent — per the
10
+ * Release-Update-Hygiene rule in the root AGENTS.md, idempotency at both the
11
+ * runner level AND an in-file marker is required to close the crash-mid-append
12
+ * window.
13
+ */
14
+
15
+ import {
16
+ existsSync,
17
+ mkdirSync,
18
+ readdirSync,
19
+ readFileSync,
20
+ rmSync,
21
+ writeFileSync,
22
+ } from "node:fs";
23
+ import { basename, join } from "node:path";
24
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
25
+
26
+ import {
27
+ _appendQuarantineBulletin,
28
+ invalidateConfigCache,
29
+ loadConfig,
30
+ } from "../config/loader.js";
31
+ import { _setStorePath } from "../security/encrypted-store.js";
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Helpers
35
+ // ---------------------------------------------------------------------------
36
+
37
+ const WORKSPACE_DIR = process.env.VELLUM_WORKSPACE_DIR!;
38
+ const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
39
+ const UPDATES_PATH = join(WORKSPACE_DIR, "UPDATES.md");
40
+
41
+ function ensureTestDir(): void {
42
+ const dirs = [
43
+ WORKSPACE_DIR,
44
+ join(WORKSPACE_DIR, "data"),
45
+ join(WORKSPACE_DIR, "data", "memory"),
46
+ join(WORKSPACE_DIR, "data", "memory", "knowledge"),
47
+ join(WORKSPACE_DIR, "data", "logs"),
48
+ ];
49
+ for (const dir of dirs) {
50
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
51
+ }
52
+ }
53
+
54
+ function resetWorkspace(): void {
55
+ for (const name of readdirSync(WORKSPACE_DIR)) {
56
+ rmSync(join(WORKSPACE_DIR, name), { recursive: true, force: true });
57
+ }
58
+ ensureTestDir();
59
+ }
60
+
61
+ function listQuarantinedFiles(): string[] {
62
+ return readdirSync(WORKSPACE_DIR).filter((name) =>
63
+ /^config\.json\.corrupt-.+\.json$/.test(name),
64
+ );
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Tests
69
+ // ---------------------------------------------------------------------------
70
+
71
+ describe("config-quarantine UPDATES.md bulletin", () => {
72
+ beforeEach(() => {
73
+ resetWorkspace();
74
+ _setStorePath(join(WORKSPACE_DIR, "keys.enc"));
75
+ invalidateConfigCache();
76
+ });
77
+
78
+ afterEach(() => {
79
+ _setStorePath(null);
80
+ invalidateConfigCache();
81
+ });
82
+
83
+ test("writes a bulletin with the quarantine marker when config.json is corrupt", () => {
84
+ writeFileSync(CONFIG_PATH, '{"provider": "anthropic", "mo');
85
+
86
+ loadConfig();
87
+
88
+ const [quarantinedName] = listQuarantinedFiles();
89
+ expect(quarantinedName).toBeDefined();
90
+ const quarantinedPath = join(WORKSPACE_DIR, quarantinedName);
91
+
92
+ expect(existsSync(UPDATES_PATH)).toBe(true);
93
+ const body = readFileSync(UPDATES_PATH, "utf-8");
94
+
95
+ // Heading must match the wording the background update-bulletin job
96
+ // relays to the user.
97
+ expect(body).toContain("## Config was reset to defaults");
98
+ // Quarantine file path is what the user cats to recover.
99
+ expect(body).toContain(quarantinedPath);
100
+ // Idempotency marker is shape-exact, basename-keyed (not full path).
101
+ expect(body).toContain(`<!-- config-quarantine:${quarantinedName} -->`);
102
+ expect(body).toMatch(
103
+ /<!-- config-quarantine:config\.json\.corrupt-.+\.json -->/,
104
+ );
105
+ });
106
+
107
+ test("two successive quarantines append two distinct bulletins (not overwrite)", () => {
108
+ // First corruption round.
109
+ writeFileSync(CONFIG_PATH, '{"partial": ');
110
+ loadConfig();
111
+ invalidateConfigCache();
112
+
113
+ const firstQuarantined = listQuarantinedFiles();
114
+ expect(firstQuarantined).toHaveLength(1);
115
+ const firstBody = readFileSync(UPDATES_PATH, "utf-8");
116
+ const firstMarker = `<!-- config-quarantine:${firstQuarantined[0]} -->`;
117
+ expect(firstBody).toContain(firstMarker);
118
+
119
+ // Loader wrote a fresh default config.json after quarantine; corrupt it
120
+ // again. Sleep briefly to guarantee a different ISO-timestamp millisecond
121
+ // component and therefore a distinct quarantine filename.
122
+ const untilDifferentMs = Date.now() + 5;
123
+ while (Date.now() < untilDifferentMs) {
124
+ /* spin */
125
+ }
126
+ writeFileSync(CONFIG_PATH, "still not json");
127
+ loadConfig();
128
+
129
+ const quarantined = listQuarantinedFiles().sort();
130
+ expect(quarantined).toHaveLength(2);
131
+
132
+ const body = readFileSync(UPDATES_PATH, "utf-8");
133
+ for (const name of quarantined) {
134
+ expect(body).toContain(`<!-- config-quarantine:${name} -->`);
135
+ }
136
+ // Two "Config was reset to defaults" sections — appended, not overwritten.
137
+ const headingMatches = body.match(/## Config was reset to defaults/g) ?? [];
138
+ expect(headingMatches).toHaveLength(2);
139
+ // The earlier marker is still present (append semantics).
140
+ expect(body).toContain(firstMarker);
141
+ });
142
+
143
+ test("idempotent: pre-existing marker for the same quarantine filename skips the append", () => {
144
+ // Simulate a crash-mid-append: UPDATES.md already contains a marker for
145
+ // a specific quarantine filename. A follow-up call referencing the same
146
+ // filename must leave the file untouched.
147
+ const quarantineName = "config.json.corrupt-2026-04-20T12-00-00.000Z.json";
148
+ const quarantinePath = join(WORKSPACE_DIR, quarantineName);
149
+ const preexisting =
150
+ `## Config was reset to defaults\n\n` +
151
+ `Pre-existing bulletin for ${quarantinePath}.\n\n` +
152
+ `<!-- config-quarantine:${quarantineName} -->\n`;
153
+ writeFileSync(UPDATES_PATH, preexisting, "utf-8");
154
+ // Also create the quarantine file on disk so the helper sees an
155
+ // environment consistent with a prior successful rename.
156
+ writeFileSync(quarantinePath, "{ not json", "utf-8");
157
+
158
+ _appendQuarantineBulletin(CONFIG_PATH, quarantinePath);
159
+
160
+ const after = readFileSync(UPDATES_PATH, "utf-8");
161
+ expect(after).toBe(preexisting);
162
+ // Exactly one marker present — no duplicate was appended.
163
+ expect(
164
+ after.match(
165
+ new RegExp(`<!-- config-quarantine:${quarantineName} -->`, "g"),
166
+ )?.length,
167
+ ).toBe(1);
168
+ expect(basename(quarantinePath)).toBe(quarantineName);
169
+ });
170
+
171
+ test("valid config.json does not create UPDATES.md (regression guard)", () => {
172
+ writeFileSync(
173
+ CONFIG_PATH,
174
+ JSON.stringify({ provider: "anthropic", model: "claude-opus-4-7" }),
175
+ );
176
+
177
+ loadConfig();
178
+
179
+ expect(listQuarantinedFiles()).toHaveLength(0);
180
+ expect(existsSync(UPDATES_PATH)).toBe(false);
181
+ });
182
+
183
+ test("appends (does not overwrite) when UPDATES.md already has unrelated content", () => {
184
+ const priorContent =
185
+ `## Some earlier bulletin\n\n` +
186
+ `Unrelated prior content from a previous migration.\n\n` +
187
+ `<!-- release-note-id:unrelated-note -->\n`;
188
+ writeFileSync(UPDATES_PATH, priorContent, "utf-8");
189
+
190
+ writeFileSync(CONFIG_PATH, "{oops");
191
+ loadConfig();
192
+
193
+ const body = readFileSync(UPDATES_PATH, "utf-8");
194
+ // Prior content preserved verbatim at the start.
195
+ expect(body.startsWith(priorContent)).toBe(true);
196
+ // New bulletin appended.
197
+ expect(body).toContain("## Config was reset to defaults");
198
+ expect(body).toMatch(
199
+ /<!-- config-quarantine:config\.json\.corrupt-.+\.json -->/,
200
+ );
201
+ });
202
+ });
@@ -82,8 +82,11 @@ import { getSchemaAtPath } from "../config/schema-utils.js";
82
82
  // ---------------------------------------------------------------------------
83
83
 
84
84
  describe("getSchemaAtPath", () => {
85
- test("returns full schema for a top-level key (maxTokens → number schema)", () => {
86
- const result = getSchemaAtPath(AssistantConfigSchema, "maxTokens");
85
+ test("returns full schema for a leaf key (llm.default.maxTokens → number schema)", () => {
86
+ const result = getSchemaAtPath(
87
+ AssistantConfigSchema,
88
+ "llm.default.maxTokens",
89
+ );
87
90
  expect(result).not.toBeNull();
88
91
  // maxTokens has a default, so it should be parseable
89
92
  const parsed = (result as z.ZodType).parse(undefined);
@@ -184,7 +187,7 @@ describe("z.toJSONSchema integration", () => {
184
187
  expect(properties).toBeDefined();
185
188
  // Check that top-level keys are present
186
189
  expect(properties.services).toBeDefined();
187
- expect(properties.maxTokens).toBeDefined();
190
+ expect(properties.llm).toBeDefined();
188
191
  expect(properties.calls).toBeDefined();
189
192
  expect(properties.memory).toBeDefined();
190
193
  expect(properties.timeouts).toBeDefined();
@@ -222,8 +225,11 @@ describe("z.toJSONSchema integration", () => {
222
225
  expect(properties!.safety).toBeDefined();
223
226
  });
224
227
 
225
- test("sub-schema at a leaf like maxTokens produces integer schema", () => {
226
- const maxTokensSchema = getSchemaAtPath(AssistantConfigSchema, "maxTokens");
228
+ test("sub-schema at a leaf like llm.default.maxTokens produces integer schema", () => {
229
+ const maxTokensSchema = getSchemaAtPath(
230
+ AssistantConfigSchema,
231
+ "llm.default.maxTokens",
232
+ );
227
233
  expect(maxTokensSchema).not.toBeNull();
228
234
  const jsonSchema = z.toJSONSchema(maxTokensSchema!, {
229
235
  unrepresentable: "any",