@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,167 @@
1
+ /**
2
+ * When loadConfig()/loadRawConfig() quarantines a corrupt config.json, it
3
+ * writes a small JSON sentinel to <workspace>/data/config-quarantine-notice.json
4
+ * recording the event. The per-turn `config-quarantine-notice` injector reads
5
+ * that sentinel and surfaces the reset to the agent — it's agent-visible
6
+ * context, not a push notification.
7
+ *
8
+ * The sentinel is overwritten on each quarantine (idempotent per event) and
9
+ * resolves from a workspace-derived path so it is safe to write during the
10
+ * very early config load, before the SQLite DB or getConfig().dataDir exist.
11
+ */
12
+
13
+ import {
14
+ existsSync,
15
+ mkdirSync,
16
+ readdirSync,
17
+ readFileSync,
18
+ rmSync,
19
+ writeFileSync,
20
+ } from "node:fs";
21
+ import { join } from "node:path";
22
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
23
+
24
+ import {
25
+ _writeQuarantineNotice,
26
+ invalidateConfigCache,
27
+ loadConfig,
28
+ } from "../config/loader.js";
29
+ import { getConfigQuarantineNoticePath } from "../util/platform.js";
30
+ import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Helpers
34
+ // ---------------------------------------------------------------------------
35
+
36
+ const WORKSPACE_DIR = process.env.VELLUM_WORKSPACE_DIR!;
37
+ const CONFIG_PATH = join(WORKSPACE_DIR, "config.json");
38
+ const NOTICE_PATH = getConfigQuarantineNoticePath();
39
+
40
+ function ensureTestDir(): void {
41
+ const dirs = [
42
+ WORKSPACE_DIR,
43
+ join(WORKSPACE_DIR, "data"),
44
+ join(WORKSPACE_DIR, "data", "memory"),
45
+ join(WORKSPACE_DIR, "data", "memory", "knowledge"),
46
+ join(WORKSPACE_DIR, "data", "logs"),
47
+ ];
48
+ for (const dir of dirs) {
49
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
50
+ }
51
+ }
52
+
53
+ function resetWorkspace(): void {
54
+ for (const name of readdirSync(WORKSPACE_DIR)) {
55
+ rmSync(join(WORKSPACE_DIR, name), { recursive: true, force: true });
56
+ }
57
+ ensureTestDir();
58
+ }
59
+
60
+ function listQuarantinedFiles(): string[] {
61
+ return readdirSync(WORKSPACE_DIR).filter((name) =>
62
+ /^config\.json\.corrupt-.+\.json$/.test(name),
63
+ );
64
+ }
65
+
66
+ function readNotice(): {
67
+ quarantinedAt: string;
68
+ quarantinePath: string;
69
+ originalPath: string;
70
+ } {
71
+ return JSON.parse(readFileSync(NOTICE_PATH, "utf-8"));
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Tests
76
+ // ---------------------------------------------------------------------------
77
+
78
+ describe("config-quarantine notice sentinel", () => {
79
+ beforeEach(() => {
80
+ resetWorkspace();
81
+ setStorePathForTesting(join(WORKSPACE_DIR, "keys.enc"));
82
+ invalidateConfigCache();
83
+ });
84
+
85
+ afterEach(() => {
86
+ setStorePathForTesting(null);
87
+ invalidateConfigCache();
88
+ });
89
+
90
+ test("writes a sentinel recording the quarantine when config.json is corrupt", () => {
91
+ writeFileSync(CONFIG_PATH, '{"provider": "anthropic", "mo');
92
+
93
+ loadConfig();
94
+
95
+ const [quarantinedName] = listQuarantinedFiles();
96
+ expect(quarantinedName).toBeDefined();
97
+ const quarantinedPath = join(WORKSPACE_DIR, quarantinedName);
98
+
99
+ expect(existsSync(NOTICE_PATH)).toBe(true);
100
+ const notice = readNotice();
101
+ expect(notice.quarantinePath).toBe(quarantinedPath);
102
+ expect(notice.originalPath).toBe(CONFIG_PATH);
103
+ // quarantinedAt is a parseable ISO timestamp.
104
+ expect(Number.isNaN(Date.parse(notice.quarantinedAt))).toBe(false);
105
+ });
106
+
107
+ test("a second quarantine overwrites the sentinel with the latest event", () => {
108
+ // First corruption round.
109
+ writeFileSync(CONFIG_PATH, '{"partial": ');
110
+ loadConfig();
111
+ invalidateConfigCache();
112
+
113
+ const firstNotice = readNotice();
114
+ expect(listQuarantinedFiles()).toHaveLength(1);
115
+
116
+ // Loader wrote a fresh default config.json after quarantine; corrupt it
117
+ // again. Spin briefly so the second quarantine gets a distinct
118
+ // millisecond-precision filename (and a later timestamp).
119
+ const untilDifferentMs = Date.now() + 5;
120
+ while (Date.now() < untilDifferentMs) {
121
+ /* spin */
122
+ }
123
+ writeFileSync(CONFIG_PATH, "still not json");
124
+ loadConfig();
125
+
126
+ const quarantined = listQuarantinedFiles().sort();
127
+ expect(quarantined).toHaveLength(2);
128
+
129
+ // Single sentinel, pointing at the most recent quarantine.
130
+ const secondNotice = readNotice();
131
+ expect(secondNotice.quarantinePath).not.toBe(firstNotice.quarantinePath);
132
+ expect(secondNotice.quarantinePath).toBe(
133
+ join(WORKSPACE_DIR, quarantined[quarantined.length - 1]),
134
+ );
135
+ });
136
+
137
+ test("valid config.json does not create a sentinel (regression guard)", () => {
138
+ writeFileSync(
139
+ CONFIG_PATH,
140
+ JSON.stringify({ provider: "anthropic", model: "claude-opus-4-7" }),
141
+ );
142
+
143
+ loadConfig();
144
+
145
+ expect(listQuarantinedFiles()).toHaveLength(0);
146
+ expect(existsSync(NOTICE_PATH)).toBe(false);
147
+ });
148
+
149
+ test("sentinel write failure is swallowed (startup never blocks)", () => {
150
+ // Make the notice directory unwritable by replacing it with a file so the
151
+ // write/mkdir path throws. The helper must not propagate the error.
152
+ const noticeDir = join(WORKSPACE_DIR, "data");
153
+ rmSync(noticeDir, { recursive: true, force: true });
154
+ // A regular file where the `data` directory is expected makes mkdir/write
155
+ // fail with ENOTDIR/EEXIST.
156
+ writeFileSync(noticeDir, "not a directory", "utf-8");
157
+
158
+ const quarantinePath = join(
159
+ WORKSPACE_DIR,
160
+ "config.json.corrupt-2026-04-20T12-00-00.000Z.json",
161
+ );
162
+
163
+ expect(() =>
164
+ _writeQuarantineNotice(CONFIG_PATH, quarantinePath),
165
+ ).not.toThrow();
166
+ });
167
+ });
@@ -266,12 +266,12 @@ describe("ConfigWatcher workspace file handlers", () => {
266
266
  expect(identityCallCount).toBe(1);
267
267
  });
268
268
 
269
- test("UPDATES.md is not subscribed (only the registered handler set is)", () => {
269
+ test("unregistered workspace files are not subscribed (only the registered handler set is)", () => {
270
270
  watcher.start(onConversationEvict);
271
271
  // Per-file watching only registers config.json, SOUL.md, IDENTITY.md.
272
272
  // The whole workspace dir must not be watched either — that was the
273
273
  // ENXIO-on-Unix-sockets bug.
274
- expect(findFileWatch(join(WORKSPACE_DIR, "UPDATES.md"))).toBeUndefined();
274
+ expect(findFileWatch(join(WORKSPACE_DIR, "OTHER.md"))).toBeUndefined();
275
275
  expect(findWatcher(WORKSPACE_DIR)).toBeUndefined();
276
276
  });
277
277
 
@@ -3,7 +3,7 @@
3
3
  * `inferenceProfile` column at turn start and threads it through to
4
4
  * `AgentLoop.run()` as the per-turn `overrideProfile`. Background
5
5
  * conversations intentionally skip the column so background fan-out
6
- * (subagents, scheduled tasks, update bulletins) runs on the workspace
6
+ * (subagents, scheduled tasks) runs on the workspace
7
7
  * defaults rather than inheriting an interactive override.
8
8
  *
9
9
  * This is the "conversation-agent-loop integration" half of the
@@ -461,7 +461,7 @@ describe("classifyConversationError", () => {
461
461
  const result = classifyConversationError(err, baseCtx);
462
462
  expect(result.code).toBe("PROVIDER_NOT_CONFIGURED");
463
463
  expect(result.userMessage).toBe(
464
- "No API key configured for inference. Add one in Settings to start chatting.",
464
+ "No API key configured for inference. Add one in Settings → Models & Services to start chatting.",
465
465
  );
466
466
  expect(result.retryable).toBe(true);
467
467
  expect(result.errorCategory).toBe("provider_not_configured");
@@ -636,11 +636,7 @@ describe("classifyConversationError", () => {
636
636
  });
637
637
 
638
638
  it("classifies ProviderError 403 with 'invalid api key' message as PROVIDER_INVALID_KEY", () => {
639
- const err = new ProviderError(
640
- "OpenAI: Invalid API key",
641
- "openai",
642
- 403,
643
- );
639
+ const err = new ProviderError("OpenAI: Invalid API key", "openai", 403);
644
640
  const result = classifyConversationError(err, baseCtx);
645
641
  expect(result.code).toBe("PROVIDER_INVALID_KEY");
646
642
  expect(result.errorCategory).toBe("provider_invalid_key");
@@ -755,6 +755,7 @@ describe("web_search_tool_result structural guard", () => {
755
755
  // (array of web_search_result objects) and is not truncated this way.
756
756
  "context/tool-result-truncation.ts",
757
757
  "context/post-turn-tool-result-truncation.ts",
758
+ "context/tool-result-spool.ts",
758
759
 
759
760
  // Anthropic provider type guards define API-specific discriminants.
760
761
  // It has a separate isWebSearchToolResultBlock for the other type.
@@ -788,6 +789,13 @@ describe("web_search_tool_result structural guard", () => {
788
789
  // tool_result is relevant. Same reasoning as agent/loop.ts above.
789
790
  "plugins/defaults/tool-error/hooks/post-tool-use.ts",
790
791
 
792
+ // Counts unbroken runs of locally-executed exploration tools (bash,
793
+ // file_read, file_list) to detect inline drift. Server-side web search
794
+ // results (web_search_tool_result) are not produced by the tool executor;
795
+ // any non-exploration block simply bounds the trailing run, which is the
796
+ // conservative direction (fewer nudges). Same reasoning as agent/loop.ts.
797
+ "plugins/defaults/exploration-drift/hooks/post-tool-use.ts",
798
+
791
799
  // Reconciles synthesized cancellation tool_results for locally-executed
792
800
  // tools only. Same reasoning as agent/loop.ts above.
793
801
  "daemon/conversation-agent-loop.ts",
@@ -59,6 +59,7 @@ mock.module("../runtime/sync/resource-sync-events.js", () => ({
59
59
  }));
60
60
 
61
61
  import {
62
+ AUTO_TITLE_DETERMINISTIC,
62
63
  generateAndPersistConversationTitle,
63
64
  queueGenerateConversationTitle,
64
65
  regenerateConversationTitle,
@@ -260,7 +261,7 @@ describe("conversation-title-service", () => {
260
261
  expect(mockUpdateConversationTitle).toHaveBeenCalledWith(
261
262
  "conv-1",
262
263
  "Untitled Conversation",
263
- 1,
264
+ AUTO_TITLE_DETERMINISTIC,
264
265
  );
265
266
  });
266
267
 
@@ -216,7 +216,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
216
216
  "media/image-credentials.ts", // shared image-gen credential resolver (provider API key lookup)
217
217
  "memory/embedding-backend.ts", // embedding backend API key lookup
218
218
  "memory/llm-request-log-source-clickhouse.ts", // ClickHouse read source — lazy lookup of clickhouse:url + clickhouse:password + vellum:platform_assistant_id for self-scoped mirror reads
219
- "memory/compaction-log-writer-clickhouse.ts", // ClickHouse compaction log writer — lazy lookup of clickhouse:url + clickhouse:password + vellum:platform_assistant_id for self-scoped event writes
219
+ "memory/compaction-log-store-clickhouse.ts", // ClickHouse compaction log writer — lazy lookup of clickhouse:url + clickhouse:password + vellum:platform_assistant_id for self-scoped event writes
220
220
  "daemon/providers-setup.ts", // provider initialization API key lookup
221
221
  "workspace/migrations/006-services-config.ts", // services config migration reads provider API keys
222
222
  "workspace/migrations/018-rekey-compound-credential-keys.ts", // re-key compound credential storage keys
@@ -41,7 +41,7 @@ mock.module("../config/loader.js", () => ({
41
41
  setNestedValue: () => undefined,
42
42
  mergeDefaultWorkspaceConfig: (config: unknown) => config,
43
43
  API_KEY_PROVIDERS: [] as const,
44
- _appendQuarantineBulletin: () => undefined,
44
+ _writeQuarantineNotice: () => undefined,
45
45
  }));
46
46
 
47
47
  mock.module("../daemon/conversation-skill-tools.js", () => ({