@vellumai/assistant 0.8.11 → 0.8.12-staging.2

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 (244) hide show
  1. package/ARCHITECTURE.md +15 -17
  2. package/README.md +0 -6
  3. package/bun.lock +6 -122
  4. package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
  5. package/node_modules/@vellumai/gateway-client/package.json +3 -1
  6. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
  7. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
  9. package/openapi.yaml +633 -4
  10. package/package.json +1 -3
  11. package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
  12. package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
  13. package/src/__tests__/anthropic-provider.test.ts +129 -0
  14. package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
  15. package/src/__tests__/btw-routes.test.ts +7 -34
  16. package/src/__tests__/checker.test.ts +6 -12
  17. package/src/__tests__/config-loader-backfill.test.ts +4 -2
  18. package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
  19. package/src/__tests__/config-watcher.test.ts +2 -2
  20. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  21. package/src/__tests__/conversation-error.test.ts +2 -6
  22. package/src/__tests__/conversation-history-web-search.test.ts +8 -0
  23. package/src/__tests__/conversation-title-service.test.ts +2 -1
  24. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  25. package/src/__tests__/disk-pressure-tools.test.ts +1 -1
  26. package/src/__tests__/exploration-drift-hook.test.ts +692 -0
  27. package/src/__tests__/filing-service.test.ts +8 -3
  28. package/src/__tests__/guardian-action-store.test.ts +0 -167
  29. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
  30. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
  31. package/src/__tests__/heartbeat-service.test.ts +5 -2
  32. package/src/__tests__/identity-intro-cache.test.ts +12 -5
  33. package/src/__tests__/identity-routes.test.ts +16 -57
  34. package/src/__tests__/injector-chain.test.ts +8 -3
  35. package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
  36. package/src/__tests__/llm-catalog-parity.test.ts +16 -0
  37. package/src/__tests__/llm-usage-store.test.ts +11 -0
  38. package/src/__tests__/log-export-workspace.test.ts +468 -3
  39. package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
  40. package/src/__tests__/model-intents.test.ts +1 -1
  41. package/src/__tests__/oauth-cli.test.ts +19 -8
  42. package/src/__tests__/openai-provider.test.ts +34 -0
  43. package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
  44. package/src/__tests__/recurrence-engine.test.ts +45 -0
  45. package/src/__tests__/schedule-routes.test.ts +34 -0
  46. package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
  48. package/src/__tests__/secret-fixtures.ts +20 -0
  49. package/src/__tests__/skill-tool-factory.test.ts +49 -0
  50. package/src/__tests__/subagent-role-registry.test.ts +24 -1
  51. package/src/__tests__/subagent-tools.test.ts +1 -0
  52. package/src/__tests__/system-prompt.test.ts +109 -11
  53. package/src/__tests__/tool-approval-handler.test.ts +85 -0
  54. package/src/__tests__/tool-audit-listener.test.ts +86 -0
  55. package/src/__tests__/tool-error-hook.test.ts +1 -0
  56. package/src/__tests__/tool-result-spool.test.ts +337 -0
  57. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  58. package/src/__tests__/validate-input.test.ts +95 -1
  59. package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
  60. package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
  61. package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
  62. package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
  63. package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
  64. package/src/acp/__tests__/agent-process.test.ts +315 -2
  65. package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
  66. package/src/acp/agent-process.ts +163 -34
  67. package/src/acp/prepare-agent-env.ts +55 -15
  68. package/src/agent/loop.ts +81 -24
  69. package/src/api/events/usage-progress.ts +28 -0
  70. package/src/api/index.ts +6 -0
  71. package/src/background-wake/wake-intent-hooks.test.ts +2 -0
  72. package/src/bundler/app-bundler.ts +25 -42
  73. package/src/bundler/app-compiler.ts +8 -0
  74. package/src/calls/call-controller.ts +1 -1
  75. package/src/cli/commands/plugins.ts +248 -15
  76. package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
  77. package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
  78. package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
  79. package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
  80. package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
  81. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +307 -0
  82. package/src/cli/lib/inspect-plugin.ts +252 -0
  83. package/src/cli/lib/install-from-github.ts +214 -21
  84. package/src/cli/lib/list-installed-plugins.ts +17 -6
  85. package/src/cli/lib/plugin-artifact.ts +103 -0
  86. package/src/cli/lib/plugin-details.ts +18 -1
  87. package/src/cli/lib/plugin-fingerprint.ts +197 -0
  88. package/src/cli/lib/upgrade-plugin.ts +225 -0
  89. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  90. package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
  91. package/src/config/call-site-defaults.ts +13 -2
  92. package/src/config/feature-flag-registry.json +8 -16
  93. package/src/config/loader.ts +52 -59
  94. package/src/config/schema.ts +0 -2
  95. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  96. package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
  97. package/src/config/schemas/llm.ts +10 -0
  98. package/src/config/schemas/memory-v2.ts +13 -0
  99. package/src/config/schemas/memory-v3.ts +92 -0
  100. package/src/config/seed-inference-profiles.ts +4 -8
  101. package/src/context/post-turn-tool-result-truncation.ts +32 -18
  102. package/src/context/tool-result-spool.ts +104 -0
  103. package/src/credential-execution/feature-gates.ts +0 -1
  104. package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
  105. package/src/daemon/conversation-error.ts +6 -15
  106. package/src/daemon/conversation.ts +9 -0
  107. package/src/daemon/disk-pressure-policy.ts +0 -1
  108. package/src/daemon/lifecycle.ts +1 -20
  109. package/src/daemon/message-types/conversations.ts +2 -15
  110. package/src/daemon/trust-context.ts +1 -1
  111. package/src/events/tool-audit-listener.ts +40 -9
  112. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  113. package/src/home/__tests__/home-content-refresh.test.ts +114 -0
  114. package/src/home/__tests__/suggested-prompts.test.ts +86 -5
  115. package/src/home/home-content-refresh.ts +43 -31
  116. package/src/home/home-greeting-cache.ts +8 -1
  117. package/src/home/home-greeting.ts +13 -9
  118. package/src/home/suggested-prompts.ts +77 -24
  119. package/src/ipc/routes/trust-rules.test.ts +66 -72
  120. package/src/media/image-credentials.ts +2 -2
  121. package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
  122. package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
  123. package/src/memory/conversation-attention-store.ts +1 -0
  124. package/src/memory/conversation-bootstrap.ts +18 -9
  125. package/src/memory/conversation-crud.ts +12 -2
  126. package/src/memory/conversation-title-service.ts +53 -9
  127. package/src/memory/delivery-channels.ts +0 -69
  128. package/src/memory/graph/extraction-job.ts +0 -15
  129. package/src/memory/guardian-action-store.ts +1 -376
  130. package/src/memory/llm-usage-store.ts +5 -1
  131. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
  132. package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
  133. package/src/memory/v2/__tests__/injection.test.ts +70 -0
  134. package/src/memory/v2/__tests__/static-context.test.ts +12 -0
  135. package/src/memory/v2/consolidation-job.ts +93 -9
  136. package/src/memory/v2/injection.ts +53 -0
  137. package/src/memory/v2/prompts/consolidation.ts +1 -0
  138. package/src/memory/v2/static-context.ts +13 -1
  139. package/src/memory/v2/sweep-job.ts +1 -1
  140. package/src/memory/v2/types.ts +5 -0
  141. package/src/plugin-api/types.ts +7 -0
  142. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
  143. package/src/plugins/defaults/exploration-drift/package.json +15 -0
  144. package/src/plugins/defaults/index.ts +25 -0
  145. package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
  146. package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
  147. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
  148. package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
  149. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  150. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
  151. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
  152. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
  153. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
  154. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
  155. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
  156. package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
  157. package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
  158. package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
  159. package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
  160. package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
  161. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
  162. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
  163. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
  164. package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
  165. package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
  166. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
  167. package/src/prompts/cache-boundary.ts +17 -0
  168. package/src/prompts/sections.ts +50 -17
  169. package/src/prompts/system-prompt.ts +12 -4
  170. package/src/prompts/templates/system-sections.ts +22 -0
  171. package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
  172. package/src/providers/anthropic/client.ts +74 -28
  173. package/src/providers/gemini/client.ts +5 -1
  174. package/src/providers/minimax/client.ts +9 -0
  175. package/src/providers/model-catalog.ts +28 -0
  176. package/src/providers/model-intents.ts +3 -3
  177. package/src/providers/openai/chat-completions-provider.ts +4 -2
  178. package/src/providers/openai/responses-provider.ts +7 -2
  179. package/src/providers/retry.ts +8 -0
  180. package/src/providers/types.ts +11 -0
  181. package/src/providers/unparseable-tool-args.ts +56 -0
  182. package/src/runtime/AGENTS.md +6 -0
  183. package/src/runtime/__tests__/agent-wake.test.ts +2 -2
  184. package/src/runtime/agent-wake.ts +5 -5
  185. package/src/runtime/background-job-runner.ts +2 -2
  186. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
  187. package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
  188. package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
  189. package/src/runtime/migrations/vbundle-importer.ts +9 -4
  190. package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
  191. package/src/runtime/pre-first-message-gate.ts +1 -1
  192. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
  193. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
  194. package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
  195. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
  196. package/src/runtime/routes/__tests__/plugins-routes.test.ts +348 -0
  197. package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
  198. package/src/runtime/routes/btw-routes.ts +0 -14
  199. package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
  200. package/src/runtime/routes/conversation-list-routes.ts +77 -5
  201. package/src/runtime/routes/conversation-management-routes.ts +54 -0
  202. package/src/runtime/routes/conversation-query-routes.ts +79 -4
  203. package/src/runtime/routes/gateway-log-routes.ts +14 -64
  204. package/src/runtime/routes/home-feed-routes.ts +10 -0
  205. package/src/runtime/routes/identity-intro-cache.ts +1 -1
  206. package/src/runtime/routes/identity-routes.ts +76 -20
  207. package/src/runtime/routes/inbound-message-handler.ts +0 -36
  208. package/src/runtime/routes/log-export-routes.ts +143 -96
  209. package/src/runtime/routes/plugins-routes.ts +380 -0
  210. package/src/runtime/routes/redact-staged-export.ts +259 -0
  211. package/src/runtime/routes/schedule-routes.ts +19 -2
  212. package/src/runtime/routes/trust-rules-routes.ts +14 -67
  213. package/src/schedule/recurrence-engine.ts +34 -0
  214. package/src/schedule/scheduler.ts +1 -0
  215. package/src/security/redact-json.ts +61 -0
  216. package/src/skills/validate-input.ts +41 -1
  217. package/src/subagent/types.ts +26 -1
  218. package/src/telemetry/types.ts +15 -1
  219. package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
  220. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  221. package/src/tools/apps/executors.ts +1 -1
  222. package/src/tools/skills/skill-tool-factory.ts +19 -8
  223. package/src/tools/tool-approval-handler.ts +31 -0
  224. package/src/usage/types.ts +8 -1
  225. package/src/util/platform.ts +16 -0
  226. package/src/watcher/engine.ts +1 -0
  227. package/src/workspace/adaptive-thinking-repair.ts +113 -0
  228. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
  229. package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
  230. package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
  231. package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
  232. package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
  233. package/src/workspace/migrations/registry.ts +8 -0
  234. package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
  235. package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
  236. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
  237. package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
  238. package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
  239. package/src/__tests__/update-bulletin-job.test.ts +0 -292
  240. package/src/config/schemas/updates.ts +0 -14
  241. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
  242. package/src/memory/conversation-starters-cadence.ts +0 -78
  243. package/src/prompts/update-bulletin-job.ts +0 -180
  244. package/src/runtime/guardian-action-followup-executor.ts +0 -306
@@ -0,0 +1,174 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+
12
+ import { upgradeQualityProfileToFable5Migration } from "../workspace/migrations/100-upgrade-quality-profile-to-fable-5.js";
13
+
14
+ let workspaceDir: string;
15
+
16
+ function writeConfig(data: Record<string, unknown>): void {
17
+ writeFileSync(
18
+ join(workspaceDir, "config.json"),
19
+ JSON.stringify(data, null, 2) + "\n",
20
+ );
21
+ }
22
+
23
+ function readConfig(): Record<string, unknown> {
24
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
25
+ }
26
+
27
+ function readProfiles(): Record<string, Record<string, unknown>> {
28
+ const llm = readConfig().llm as Record<string, unknown>;
29
+ return llm.profiles as Record<string, Record<string, unknown>>;
30
+ }
31
+
32
+ beforeEach(() => {
33
+ workspaceDir = join(
34
+ tmpdir(),
35
+ `vellum-migration-100-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
36
+ );
37
+ mkdirSync(workspaceDir, { recursive: true });
38
+ });
39
+
40
+ afterEach(() => {
41
+ if (existsSync(workspaceDir)) {
42
+ rmSync(workspaceDir, { recursive: true, force: true });
43
+ }
44
+ });
45
+
46
+ describe("100-upgrade-quality-profile-to-fable-5 migration", () => {
47
+ test("no-op when config.json does not exist", () => {
48
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
49
+ expect(existsSync(join(workspaceDir, "config.json"))).toBe(false);
50
+ });
51
+
52
+ test("no-op when config has no llm.profiles", () => {
53
+ const original = { llm: { default: { provider: "anthropic" } } };
54
+ writeConfig(original);
55
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
56
+ expect(readConfig()).toEqual(original);
57
+ });
58
+
59
+ test("gracefully handles invalid JSON", () => {
60
+ writeFileSync(join(workspaceDir, "config.json"), "not-valid-json");
61
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
62
+ expect(readFileSync(join(workspaceDir, "config.json"), "utf-8")).toBe(
63
+ "not-valid-json",
64
+ );
65
+ });
66
+
67
+ test("upgrades quality-optimized from claude-opus-4-7", () => {
68
+ writeConfig({
69
+ llm: {
70
+ profiles: {
71
+ "quality-optimized": {
72
+ provider: "anthropic",
73
+ model: "claude-opus-4-7",
74
+ label: "Quality",
75
+ },
76
+ },
77
+ },
78
+ });
79
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
80
+ const profile = readProfiles()["quality-optimized"]!;
81
+ expect(profile.model).toBe("claude-fable-5");
82
+ expect(profile.label).toBe("Quality");
83
+ });
84
+
85
+ test("upgrades the managed profile but not the user-owned custom copy", () => {
86
+ writeConfig({
87
+ llm: {
88
+ profiles: {
89
+ "quality-optimized": {
90
+ provider: "anthropic",
91
+ model: "claude-opus-4-8",
92
+ },
93
+ "custom-quality-optimized": {
94
+ provider: "anthropic",
95
+ model: "claude-opus-4-8",
96
+ },
97
+ },
98
+ },
99
+ });
100
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
101
+ const profiles = readProfiles();
102
+ expect(profiles["quality-optimized"]!.model).toBe("claude-fable-5");
103
+ expect(profiles["custom-quality-optimized"]!.model).toBe("claude-opus-4-8");
104
+ });
105
+
106
+ test("upgrades OpenRouter-prefixed model ids", () => {
107
+ writeConfig({
108
+ llm: {
109
+ profiles: {
110
+ "quality-optimized": {
111
+ provider: "openrouter",
112
+ model: "anthropic/claude-opus-4.8",
113
+ },
114
+ },
115
+ },
116
+ });
117
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
118
+ expect(readProfiles()["quality-optimized"]!.model).toBe(
119
+ "anthropic/claude-fable-5",
120
+ );
121
+ });
122
+
123
+ test("leaves user-customized models untouched", () => {
124
+ const original = {
125
+ llm: {
126
+ profiles: {
127
+ "quality-optimized": { provider: "openai", model: "gpt-5.4" },
128
+ },
129
+ },
130
+ };
131
+ writeConfig(original);
132
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
133
+ expect(readConfig()).toEqual(original);
134
+ });
135
+
136
+ test("leaves non-quality profiles untouched", () => {
137
+ writeConfig({
138
+ llm: {
139
+ profiles: {
140
+ balanced: { provider: "anthropic", model: "claude-opus-4-8" },
141
+ "quality-optimized": {
142
+ provider: "anthropic",
143
+ model: "claude-opus-4-8",
144
+ },
145
+ },
146
+ },
147
+ });
148
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
149
+ const profiles = readProfiles();
150
+ expect(profiles["balanced"]!.model).toBe("claude-opus-4-8");
151
+ expect(profiles["quality-optimized"]!.model).toBe("claude-fable-5");
152
+ });
153
+
154
+ test("idempotency: no-op on already-upgraded config (no writes)", () => {
155
+ writeConfig({
156
+ llm: {
157
+ profiles: {
158
+ "quality-optimized": {
159
+ provider: "anthropic",
160
+ model: "claude-fable-5",
161
+ },
162
+ },
163
+ },
164
+ });
165
+ const beforeContent = readFileSync(
166
+ join(workspaceDir, "config.json"),
167
+ "utf-8",
168
+ );
169
+ upgradeQualityProfileToFable5Migration.run(workspaceDir);
170
+ expect(readFileSync(join(workspaceDir, "config.json"), "utf-8")).toBe(
171
+ beforeContent,
172
+ );
173
+ });
174
+ });
@@ -0,0 +1,162 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
11
+
12
+ import { upgradeBalancedEconomyToMinimaxM3Migration } from "../workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.js";
13
+
14
+ let workspaceDir: string;
15
+
16
+ function writeConfig(data: Record<string, unknown>): void {
17
+ writeFileSync(
18
+ join(workspaceDir, "config.json"),
19
+ JSON.stringify(data, null, 2) + "\n",
20
+ );
21
+ }
22
+
23
+ function readConfig(): Record<string, unknown> {
24
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
25
+ }
26
+
27
+ function readProfiles(): Record<string, Record<string, unknown>> {
28
+ const llm = readConfig().llm as Record<string, unknown>;
29
+ return llm.profiles as Record<string, Record<string, unknown>>;
30
+ }
31
+
32
+ beforeEach(() => {
33
+ workspaceDir = join(
34
+ tmpdir(),
35
+ `vellum-migration-101-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
36
+ );
37
+ mkdirSync(workspaceDir, { recursive: true });
38
+ });
39
+
40
+ afterEach(() => {
41
+ if (existsSync(workspaceDir)) {
42
+ rmSync(workspaceDir, { recursive: true, force: true });
43
+ }
44
+ });
45
+
46
+ describe("101-upgrade-balanced-economy-to-minimax-m3 migration", () => {
47
+ test("no-op when config.json does not exist", () => {
48
+ upgradeBalancedEconomyToMinimaxM3Migration.run(workspaceDir);
49
+ expect(existsSync(join(workspaceDir, "config.json"))).toBe(false);
50
+ });
51
+
52
+ test("no-op when config has no llm.profiles", () => {
53
+ const original = { llm: { default: { provider: "fireworks" } } };
54
+ writeConfig(original);
55
+ upgradeBalancedEconomyToMinimaxM3Migration.run(workspaceDir);
56
+ expect(readConfig()).toEqual(original);
57
+ });
58
+
59
+ test("gracefully handles invalid JSON", () => {
60
+ writeFileSync(join(workspaceDir, "config.json"), "not-valid-json");
61
+ upgradeBalancedEconomyToMinimaxM3Migration.run(workspaceDir);
62
+ expect(readFileSync(join(workspaceDir, "config.json"), "utf-8")).toBe(
63
+ "not-valid-json",
64
+ );
65
+ });
66
+
67
+ test("upgrades balanced-economy from Kimi K2.6 and drops the logit-bias preset", () => {
68
+ writeConfig({
69
+ llm: {
70
+ profiles: {
71
+ "balanced-economy": {
72
+ provider: "fireworks",
73
+ provider_connection: "fireworks-managed",
74
+ model: "accounts/fireworks/models/kimi-k2p6",
75
+ label: "Balanced Economy",
76
+ description: "Strong open model (Kimi K2.6) at a lower price point",
77
+ maxTokens: 16000,
78
+ effort: "high",
79
+ thinking: { enabled: true, streamThinking: true },
80
+ contextWindow: { maxInputTokens: 200000 },
81
+ logitBias: "suppress-cjk",
82
+ },
83
+ },
84
+ },
85
+ });
86
+ upgradeBalancedEconomyToMinimaxM3Migration.run(workspaceDir);
87
+ const profile = readProfiles()["balanced-economy"]!;
88
+ expect(profile.model).toBe("accounts/fireworks/models/minimax-m3");
89
+ expect(profile.description).toBe(
90
+ "Strong open model (MiniMax M3) at a lower price point",
91
+ );
92
+ expect(profile.maxTokens).toBe(32000);
93
+ expect(profile.contextWindow).toEqual({ maxInputTokens: 200000 });
94
+ expect("logitBias" in profile).toBe(false);
95
+ expect(profile.label).toBe("Balanced Economy");
96
+ expect(profile.effort).toBe("high");
97
+ expect(profile.thinking).toEqual({ enabled: true, streamThinking: true });
98
+ });
99
+
100
+ test("leaves user-customized models untouched", () => {
101
+ const original = {
102
+ llm: {
103
+ profiles: {
104
+ "balanced-economy": {
105
+ provider: "fireworks",
106
+ model: "accounts/fireworks/models/deepseek-v4-pro",
107
+ logitBias: "suppress-cjk",
108
+ },
109
+ },
110
+ },
111
+ };
112
+ writeConfig(original);
113
+ upgradeBalancedEconomyToMinimaxM3Migration.run(workspaceDir);
114
+ expect(readConfig()).toEqual(original);
115
+ });
116
+
117
+ test("leaves other profiles untouched", () => {
118
+ writeConfig({
119
+ llm: {
120
+ profiles: {
121
+ "my-kimi": {
122
+ provider: "fireworks",
123
+ model: "accounts/fireworks/models/kimi-k2p6",
124
+ },
125
+ "balanced-economy": {
126
+ provider: "fireworks",
127
+ model: "accounts/fireworks/models/kimi-k2p6",
128
+ },
129
+ },
130
+ },
131
+ });
132
+ upgradeBalancedEconomyToMinimaxM3Migration.run(workspaceDir);
133
+ const profiles = readProfiles();
134
+ expect(profiles["my-kimi"]!.model).toBe(
135
+ "accounts/fireworks/models/kimi-k2p6",
136
+ );
137
+ expect(profiles["balanced-economy"]!.model).toBe(
138
+ "accounts/fireworks/models/minimax-m3",
139
+ );
140
+ });
141
+
142
+ test("idempotency: no-op on already-upgraded config (no writes)", () => {
143
+ writeConfig({
144
+ llm: {
145
+ profiles: {
146
+ "balanced-economy": {
147
+ provider: "fireworks",
148
+ model: "accounts/fireworks/models/minimax-m3",
149
+ },
150
+ },
151
+ },
152
+ });
153
+ const beforeContent = readFileSync(
154
+ join(workspaceDir, "config.json"),
155
+ "utf-8",
156
+ );
157
+ upgradeBalancedEconomyToMinimaxM3Migration.run(workspaceDir);
158
+ expect(readFileSync(join(workspaceDir, "config.json"), "utf-8")).toBe(
159
+ beforeContent,
160
+ );
161
+ });
162
+ });
@@ -1,37 +1,29 @@
1
- import { readdirSync, readFileSync } from "node:fs";
1
+ import { readdirSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { describe, expect, test } from "bun:test";
4
4
 
5
- type Registry = {
6
- flags: Array<{
7
- key: string;
8
- scope: string;
9
- defaultEnabled: boolean;
10
- }>;
11
- };
12
-
13
- const LEGACY_FLAGGED_RELEASE_NOTE_ALLOWLIST = new Map<string, string>([
14
- [
15
- "045-release-notes-meet-avatar.ts",
16
- "Historical bulletin that already shipped before this guard existed.",
17
- ],
18
- ]);
19
-
20
- const FLAGGED_FEATURE_LANGUAGE_PATTERNS: Array<{
21
- label: string;
22
- pattern: RegExp;
23
- }> = [
24
- { label: "feature flag", pattern: /\bfeature[- ]flag(?:ged)?\b/i },
25
- { label: "rollout flag", pattern: /\brollout flag\b/i },
26
- { label: "behind ... flag", pattern: /\bbehind\b[^\n.]{0,120}\bflag\b/i },
27
- { label: "gated on", pattern: /\bgated on\b/i },
28
- { label: "when enabled", pattern: /\bwhen enabled\b/i },
5
+ /**
6
+ * The historical release-note workspace migrations. These appended bulletins
7
+ * to `<workspace>/UPDATES.md`, which a background job processed at daemon
8
+ * startup. That job has been removed — nothing consumes UPDATES.md release
9
+ * notes anymore — so the set below is frozen. Migration files themselves are
10
+ * append-only and must never be deleted (see the migrations AGENTS.md), but
11
+ * no new ones of this shape may be added.
12
+ */
13
+ const FROZEN_RELEASE_NOTE_MIGRATIONS = [
14
+ "043-release-notes-latex-rendering.ts",
15
+ "045-release-notes-meet-avatar.ts",
16
+ "049-release-notes-default-sonnet.ts",
17
+ "053-release-notes-acp-codex.ts",
18
+ "055-release-notes-agentic-recall.ts",
19
+ "056-release-notes-inference-profile-reordering.ts",
20
+ "058-release-notes-acp-sessions-ui.ts",
21
+ "063-release-notes-dynamic-model-context.ts",
22
+ "067-release-notes-safe-storage-limits.ts",
23
+ "068-release-notes-local-timezone.ts",
24
+ "078-release-notes-tavily-web-search.ts",
29
25
  ];
30
26
 
31
- function getRepoRoot(): string {
32
- return join(process.cwd(), "..");
33
- }
34
-
35
27
  function getReleaseNoteMigrationFiles(): string[] {
36
28
  const migrationsDir = join(process.cwd(), "src", "workspace", "migrations");
37
29
  return readdirSync(migrationsDir)
@@ -39,77 +31,35 @@ function getReleaseNoteMigrationFiles(): string[] {
39
31
  .sort();
40
32
  }
41
33
 
42
- function loadDefaultDisabledAssistantFlagKeys(): string[] {
43
- const registryPath = join(
44
- getRepoRoot(),
45
- "meta",
46
- "feature-flags",
47
- "feature-flag-registry.json",
48
- );
49
- const registry = JSON.parse(readFileSync(registryPath, "utf-8")) as Registry;
50
- return registry.flags
51
- .filter((flag) => flag.scope === "assistant" && !flag.defaultEnabled)
52
- .map((flag) => flag.key)
53
- .sort();
54
- }
55
-
56
- function escapeRegExp(value: string): string {
57
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
58
- }
59
-
60
- function featureFlagKeyPattern(key: string): RegExp {
61
- const escaped = escapeRegExp(key);
62
- if (key.includes("-")) {
63
- return new RegExp(`(?:^|[^a-z0-9-])${escaped}(?:$|[^a-z0-9-])`, "i");
64
- }
65
-
66
- return new RegExp("[`'\"]" + escaped + "[`'\"]", "i");
67
- }
34
+ describe("workspace release-note migrations guard", () => {
35
+ test("no new release-note migrations are added", () => {
36
+ const found = getReleaseNoteMigrationFiles();
37
+ const newMigrations = found.filter(
38
+ (fileName) => !FROZEN_RELEASE_NOTE_MIGRATIONS.includes(fileName),
39
+ );
68
40
 
69
- describe("workspace release-note migrations feature flag guard", () => {
70
- test("new release-note migrations do not announce default-disabled feature-flagged work", () => {
71
- const migrationsDir = join(process.cwd(), "src", "workspace", "migrations");
72
- const defaultDisabledFlagKeys = loadDefaultDisabledAssistantFlagKeys();
73
- const violations: string[] = [];
74
-
75
- for (const fileName of getReleaseNoteMigrationFiles()) {
76
- if (LEGACY_FLAGGED_RELEASE_NOTE_ALLOWLIST.has(fileName)) {
77
- continue;
78
- }
79
-
80
- const content = readFileSync(join(migrationsDir, fileName), "utf-8");
81
-
82
- for (const { label, pattern } of FLAGGED_FEATURE_LANGUAGE_PATTERNS) {
83
- if (pattern.test(content)) {
84
- violations.push(
85
- `${fileName}: contains flagged-feature language "${label}"`,
86
- );
87
- }
88
- }
89
-
90
- for (const key of defaultDisabledFlagKeys) {
91
- if (featureFlagKeyPattern(key).test(content)) {
92
- violations.push(
93
- `${fileName}: references default-disabled assistant feature flag "${key}"`,
94
- );
95
- }
96
- }
97
- }
98
-
99
- if (violations.length > 0) {
41
+ if (newMigrations.length > 0) {
100
42
  const message = [
101
- "Release-note migrations write to UPDATES.md, which is processed without checking feature flags.",
102
- "Do not announce features that are still behind default-disabled assistant flags or rollout flags.",
103
- "Wait until GA and add a new append-only release-note migration with a new marker.",
104
- "",
105
- "Violations:",
106
- ...violations.map((violation) => ` - ${violation}`),
43
+ "Release-note migrations append to <workspace>/UPDATES.md, but the",
44
+ "background job that processed that file has been removed — nothing",
45
+ "consumes release notes written there. Do not add new release-note",
46
+ "migrations until a replacement surfacing mechanism is designed.",
107
47
  "",
108
- "If this is an already-shipped historical bulletin, add a narrow entry to",
109
- "LEGACY_FLAGGED_RELEASE_NOTE_ALLOWLIST in workspace-release-notes-feature-flag-guard.test.ts.",
48
+ "New release-note migrations found:",
49
+ ...newMigrations.map((fileName) => ` - ${fileName}`),
110
50
  ].join("\n");
111
51
 
112
- expect(violations, message).toEqual([]);
52
+ expect(newMigrations, message).toEqual([]);
53
+ }
54
+ });
55
+
56
+ test("frozen release-note migrations are never deleted", () => {
57
+ // Migrations are append-only: even though their bulletins are no longer
58
+ // processed, deleting an entry breaks the sequential migration chain on
59
+ // existing installs.
60
+ const found = getReleaseNoteMigrationFiles();
61
+ for (const fileName of FROZEN_RELEASE_NOTE_MIGRATIONS) {
62
+ expect(found).toContain(fileName);
113
63
  }
114
64
  });
115
65
  });