@vellumai/assistant 0.8.9-staging.2 → 0.8.9-staging.4

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 (196) hide show
  1. package/docs/activation-funnel-telemetry.md +310 -0
  2. package/openapi.yaml +16 -115
  3. package/package.json +1 -1
  4. package/src/__tests__/activation-early-marking.test.ts +120 -0
  5. package/src/__tests__/agent-loop-output-hooks.test.ts +13 -13
  6. package/src/__tests__/anthropic-provider.test.ts +23 -8
  7. package/src/__tests__/approval-cascade.test.ts +1 -1
  8. package/src/__tests__/compaction-direct.test.ts +32 -18
  9. package/src/__tests__/compaction-events.test.ts +2 -2
  10. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  11. package/src/__tests__/context-overflow-reducer.test.ts +5 -5
  12. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  13. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  14. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  15. package/src/__tests__/conversation-error.test.ts +15 -1
  16. package/src/__tests__/conversation-history-web-search.test.ts +5 -0
  17. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  18. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +40 -0
  19. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  20. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  21. package/src/__tests__/conversation-queue.test.ts +1 -1
  22. package/src/__tests__/conversation-runtime-assembly.test.ts +71 -0
  23. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  24. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  25. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  26. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  27. package/src/__tests__/conversation-surfaces-app-control.test.ts +44 -0
  28. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  29. package/src/__tests__/conversation-undo.test.ts +2 -2
  30. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  31. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  32. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  33. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  34. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  35. package/src/__tests__/history-repair-hook.test.ts +2 -0
  36. package/src/__tests__/llm-resolver.test.ts +73 -0
  37. package/src/__tests__/memory-retrieval-hook.test.ts +1 -2
  38. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  39. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  40. package/src/__tests__/plugin-external-api.test.ts +68 -0
  41. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  42. package/src/__tests__/published-app-updater.test.ts +138 -0
  43. package/src/__tests__/skill-feature-flags-integration.test.ts +5 -7
  44. package/src/__tests__/title-generate-hook.test.ts +2 -0
  45. package/src/__tests__/web-fetch.test.ts +45 -0
  46. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  47. package/src/acp/resolve-agent.test.ts +0 -56
  48. package/src/acp/resolve-agent.ts +10 -38
  49. package/src/agent/loop.ts +13 -27
  50. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  51. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  52. package/src/cli/commands/memory-v3.ts +57 -199
  53. package/src/cli/lib/__tests__/install-from-github.test.ts +232 -29
  54. package/src/cli/lib/__tests__/plugin-details.test.ts +28 -19
  55. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +57 -7
  56. package/src/cli/lib/__tests__/search-plugins.test.ts +17 -10
  57. package/src/cli/lib/install-from-github.ts +258 -41
  58. package/src/cli/lib/plugin-details.ts +20 -13
  59. package/src/cli/lib/plugin-marketplace.ts +23 -5
  60. package/src/cli/lib/search-plugins.ts +14 -8
  61. package/src/config/acp-defaults.ts +3 -3
  62. package/src/config/acp-schema.ts +1 -7
  63. package/src/config/bundled-skills/acp/SKILL.md +4 -17
  64. package/src/config/bundled-skills/acp/TOOLS.json +2 -2
  65. package/src/config/call-site-defaults.ts +0 -1
  66. package/src/config/feature-flag-registry.json +3 -18
  67. package/src/config/llm-resolver.ts +39 -7
  68. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  69. package/src/config/schemas/call-site-catalog.ts +0 -7
  70. package/src/config/schemas/llm.ts +17 -1
  71. package/src/config/schemas/memory-v3.ts +58 -8
  72. package/src/config/seed-inference-profiles.ts +18 -0
  73. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  74. package/src/daemon/conversation-agent-loop-handlers.ts +8 -1
  75. package/src/daemon/conversation-agent-loop.ts +87 -95
  76. package/src/daemon/conversation-error.ts +31 -4
  77. package/src/daemon/conversation-history.ts +1 -1
  78. package/src/daemon/conversation-media-retry.ts +19 -6
  79. package/src/daemon/conversation-messaging.ts +17 -0
  80. package/src/daemon/conversation-process.ts +14 -5
  81. package/src/daemon/conversation-queue-manager.ts +8 -0
  82. package/src/daemon/conversation-runtime-assembly.ts +37 -1
  83. package/src/daemon/conversation-surfaces.ts +141 -3
  84. package/src/daemon/conversation.ts +48 -13
  85. package/src/daemon/external-plugins-bootstrap.ts +8 -3
  86. package/src/daemon/persist-unsendable-image.ts +62 -25
  87. package/src/daemon/process-message.ts +1 -1
  88. package/src/daemon/tool-side-effects.ts +15 -0
  89. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  90. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  91. package/src/memory/activation-session-store.ts +43 -0
  92. package/src/memory/db-init.ts +4 -0
  93. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  94. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  95. package/src/memory/migrations/index.ts +2 -0
  96. package/src/memory/onboarding-events-store.ts +66 -18
  97. package/src/memory/schema/infrastructure.ts +13 -0
  98. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  99. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  100. package/src/memory/v2/consolidation-job.ts +2 -72
  101. package/src/memory/v2/types.ts +5 -0
  102. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  103. package/src/notifications/adapters/telegram.ts +7 -1
  104. package/src/plugin-api/constants.ts +2 -2
  105. package/src/plugin-api/index.ts +2 -2
  106. package/src/plugin-api/types.ts +19 -5
  107. package/src/plugins/defaults/compaction/compact.ts +24 -15
  108. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +4 -4
  109. package/src/plugins/defaults/compaction/manager-store.ts +1 -1
  110. package/src/{context → plugins/defaults/compaction}/window-manager.ts +68 -12
  111. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +12 -18
  112. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +19 -66
  113. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  114. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  115. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +139 -129
  116. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +332 -164
  117. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +516 -293
  118. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  119. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +51 -21
  120. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  121. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  122. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  123. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +66 -11
  124. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  125. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +271 -110
  126. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +0 -22
  127. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +26 -62
  128. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  129. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  130. package/src/plugins/defaults/memory-v3-shadow/injector.ts +3 -2
  131. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +415 -181
  132. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +196 -56
  133. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +39 -2
  134. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  135. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +15 -7
  136. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  137. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  138. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  139. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +75 -3
  140. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +111 -78
  141. package/src/plugins/defaults/memory-v3-shadow/types.ts +52 -19
  142. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +4 -1
  143. package/src/plugins/external-api.ts +114 -0
  144. package/src/prompts/system-prompt.ts +61 -10
  145. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +37 -2
  146. package/src/providers/anthropic/client.ts +9 -10
  147. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  148. package/src/providers/inference/logit-bias.ts +55 -0
  149. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  150. package/src/providers/openai/chat-completions-provider.ts +44 -1
  151. package/src/providers/retry.ts +22 -0
  152. package/src/providers/types.ts +6 -0
  153. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  154. package/src/runtime/routes/acp-routes.test.ts +3 -20
  155. package/src/runtime/routes/app-management-routes.ts +3 -0
  156. package/src/runtime/routes/conversation-routes.ts +2 -0
  157. package/src/runtime/routes/memory-v3-routes.ts +64 -314
  158. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  159. package/src/runtime/routes/stt-routes.ts +45 -12
  160. package/src/runtime/routes/workspace-routes.ts +50 -15
  161. package/src/services/published-app-updater.ts +30 -8
  162. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  163. package/src/telemetry/activation-funnel.ts +167 -0
  164. package/src/telemetry/types.ts +13 -0
  165. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  166. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  167. package/src/tools/acp/list-agents.test.ts +2 -18
  168. package/src/tools/acp/list-agents.ts +3 -15
  169. package/src/tools/acp/spawn.test.ts +0 -10
  170. package/src/tools/browser/browser-execution.ts +12 -2
  171. package/src/tools/network/web-fetch.ts +65 -24
  172. package/src/tools/skills/load.ts +1 -1
  173. package/src/tools/ui-surface/definitions.ts +7 -0
  174. package/src/acp/feature-gate.test.ts +0 -48
  175. package/src/acp/feature-gate.ts +0 -34
  176. package/src/plugins/defaults/memory-v3-shadow/__tests__/assign.test.ts +0 -242
  177. package/src/plugins/defaults/memory-v3-shadow/__tests__/core.test.ts +0 -39
  178. package/src/plugins/defaults/memory-v3-shadow/__tests__/health.test.ts +0 -219
  179. package/src/plugins/defaults/memory-v3-shadow/__tests__/needle.test.ts +0 -107
  180. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +0 -13
  181. package/src/plugins/defaults/memory-v3-shadow/__tests__/reconcile.test.ts +0 -274
  182. package/src/plugins/defaults/memory-v3-shadow/__tests__/router.test.ts +0 -337
  183. package/src/plugins/defaults/memory-v3-shadow/__tests__/selector.test.ts +0 -470
  184. package/src/plugins/defaults/memory-v3-shadow/__tests__/snapshot.test.ts +0 -168
  185. package/src/plugins/defaults/memory-v3-shadow/__tests__/tree.test.ts +0 -192
  186. package/src/plugins/defaults/memory-v3-shadow/assign.ts +0 -272
  187. package/src/plugins/defaults/memory-v3-shadow/core.ts +0 -26
  188. package/src/plugins/defaults/memory-v3-shadow/health.ts +0 -0
  189. package/src/plugins/defaults/memory-v3-shadow/needle.ts +0 -115
  190. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +0 -26
  191. package/src/plugins/defaults/memory-v3-shadow/reconcile.ts +0 -527
  192. package/src/plugins/defaults/memory-v3-shadow/router.ts +0 -190
  193. package/src/plugins/defaults/memory-v3-shadow/selector.ts +0 -226
  194. package/src/plugins/defaults/memory-v3-shadow/snapshot.ts +0 -209
  195. package/src/plugins/defaults/memory-v3-shadow/tree.ts +0 -174
  196. package/src/util/map-limit.ts +0 -27
@@ -1,337 +0,0 @@
1
- /**
2
- * Tests for `assistant/src/memory/v3/router.ts`.
3
- *
4
- * Coverage matrix:
5
- * - Returned IDs map to the right leaves by 1-based index, in model order.
6
- * - Omitted `ids` → no leaves (the router must name leaves explicitly,
7
- * never open the whole tree).
8
- * - Explicit `ids: []` → no leaves (deliberate abstention).
9
- * - Out-of-range / duplicate IDs ignored, no throw.
10
- * - No provider / missing tool_use / schema mismatch / throw → no leaves
11
- * (degrade to the deterministic lanes), the last three after a re-prompt
12
- * retry; a malformed response that recovers on retry returns its IDs.
13
- * - The rendered leaf block is byte-identical across two calls with
14
- * different queries (the cache invariant).
15
- * - The system prompt mentions "register" (locks the routing commitment).
16
- *
17
- * The provider is stubbed so no network calls fire.
18
- */
19
-
20
- import { beforeEach, describe, expect, mock, test } from "bun:test";
21
-
22
- import type {
23
- Message,
24
- Provider,
25
- ProviderResponse,
26
- SendMessageOptions,
27
- ToolUseContent,
28
- } from "../../../../providers/types.js";
29
- import type {
30
- LeafNode,
31
- LeafPath,
32
- LeafTree,
33
- MemoryRoutingTurn,
34
- } from "../types.js";
35
-
36
- // ---------------------------------------------------------------------------
37
- // Mocks installed BEFORE the router import so the module observes them at
38
- // load time.
39
- // ---------------------------------------------------------------------------
40
-
41
- let providerStub: Provider | null = null;
42
-
43
- interface ProviderCall {
44
- messages: Message[];
45
- options: SendMessageOptions | undefined;
46
- }
47
- const providerCalls: ProviderCall[] = [];
48
-
49
- mock.module("../../../../providers/provider-send-message.js", () => ({
50
- getConfiguredProvider: async () => providerStub,
51
- extractToolUse: (response: ProviderResponse) =>
52
- response.content.find((b): b is ToolUseContent => b.type === "tool_use"),
53
- }));
54
-
55
- mock.module("../../../../util/logger.js", () => ({
56
- getLogger: () =>
57
- new Proxy({} as Record<string, unknown>, {
58
- get: (_t, prop) => (prop === "child" ? () => ({}) : () => {}),
59
- }),
60
- }));
61
-
62
- const { routeL1, renderLeafBlock } = await import("../router.js");
63
-
64
- // ---------------------------------------------------------------------------
65
- // Helpers.
66
- // ---------------------------------------------------------------------------
67
-
68
- function makeProvider(response: ProviderResponse): Provider {
69
- return {
70
- name: "stub",
71
- sendMessage: async (messages, options) => {
72
- providerCalls.push({ messages, options });
73
- return response;
74
- },
75
- };
76
- }
77
-
78
- function toolUseResponse(input: Record<string, unknown>): ProviderResponse {
79
- return {
80
- model: "stub-model",
81
- stopReason: "tool_use",
82
- usage: { inputTokens: 0, outputTokens: 0 },
83
- content: [{ type: "tool_use", id: "tu-1", name: "open_leaves", input }],
84
- };
85
- }
86
-
87
- /** A 200 response that carries no tool_use — the malformed-but-successful case
88
- * the re-prompt retry exists to recover from. */
89
- function noToolResponse(): ProviderResponse {
90
- return {
91
- model: "stub-model",
92
- stopReason: "end_turn",
93
- usage: { inputTokens: 0, outputTokens: 0 },
94
- content: [{ type: "text", text: "no tool call" }],
95
- };
96
- }
97
-
98
- /** Provider returning a different response per call (the i-th call returns
99
- * responses[i], or the last entry once exhausted), recording each call so a
100
- * test can assert how many attempts were made. */
101
- function makeSequenceProvider(responses: ProviderResponse[]): Provider {
102
- let i = 0;
103
- return {
104
- name: "sequence",
105
- sendMessage: async (messages, options) => {
106
- providerCalls.push({ messages, options });
107
- const response = responses[Math.min(i, responses.length - 1)];
108
- i += 1;
109
- return response;
110
- },
111
- };
112
- }
113
-
114
- /** Provider that records each call and then throws — for the throw-after-retries
115
- * path (the provider's own RetryProvider has already exhausted its backoff). */
116
- function makeThrowingProvider(): Provider {
117
- return {
118
- name: "throwing",
119
- sendMessage: async (messages, options) => {
120
- providerCalls.push({ messages, options });
121
- throw new Error("boom");
122
- },
123
- };
124
- }
125
-
126
- function makeLeaf(path: LeafPath, description: string): LeafNode {
127
- return {
128
- path,
129
- frontmatter: { path, in_core: false },
130
- description,
131
- members: [],
132
- domain: path.split("/")[0],
133
- };
134
- }
135
-
136
- /** Tree whose leaves are intentionally inserted out of sorted order so the
137
- * tests verify the router sorts by path (not insertion order) before
138
- * assigning 1-based IDs. Sorted → [people/alice, people/bob, projects/atlas]. */
139
- function makeTree(): LeafTree {
140
- const entries: LeafNode[] = [
141
- makeLeaf("projects/atlas", "The Atlas project roadmap and status."),
142
- makeLeaf("people/bob", "Bob — colleague, prefers async."),
143
- makeLeaf("people/alice", "Alice — manager, weekly 1:1s."),
144
- ];
145
- return {
146
- leaves: new Map(entries.map((n) => [n.path, n])),
147
- byPage: new Map(),
148
- };
149
- }
150
-
151
- function makeTurn(currentMessage: string): MemoryRoutingTurn {
152
- return {
153
- conversationId: "conv-xyz",
154
- turnNumber: 1,
155
- currentMessage,
156
- recentContext: "earlier we talked about the timeline",
157
- };
158
- }
159
-
160
- beforeEach(() => {
161
- providerStub = null;
162
- providerCalls.length = 0;
163
- });
164
-
165
- // ---------------------------------------------------------------------------
166
- // Tests.
167
- // ---------------------------------------------------------------------------
168
-
169
- describe("routeL1 — id mapping", () => {
170
- test("returned IDs map to leaves by 1-based index, in model order", async () => {
171
- providerStub = makeProvider(toolUseResponse({ ids: [3, 1] }));
172
- const result = await routeL1(makeTurn("how's atlas?"), makeTree());
173
- expect(result).toEqual(["projects/atlas", "people/alice"]);
174
- });
175
-
176
- test("omitted ids opens no leaves (must name leaves explicitly)", async () => {
177
- providerStub = makeProvider(toolUseResponse({}));
178
- const result = await routeL1(makeTurn("anything"), makeTree());
179
- expect(result).toEqual([]);
180
- });
181
-
182
- test("explicit empty ids opens no leaves (abstention)", async () => {
183
- providerStub = makeProvider(toolUseResponse({ ids: [] }));
184
- const result = await routeL1(makeTurn("nothing relevant"), makeTree());
185
- expect(result).toEqual([]);
186
- });
187
-
188
- test("out-of-range and duplicate IDs are ignored without throwing", async () => {
189
- providerStub = makeProvider(toolUseResponse({ ids: [2, 99, 0, -1, 2] }));
190
- const result = await routeL1(makeTurn("bob?"), makeTree());
191
- expect(result).toEqual(["people/bob"]);
192
- });
193
-
194
- test("empty tree returns no leaves and never calls the provider", async () => {
195
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
196
- const result = await routeL1(makeTurn("hi"), {
197
- leaves: new Map(),
198
- byPage: new Map(),
199
- });
200
- expect(result).toEqual([]);
201
- expect(providerCalls).toHaveLength(0);
202
- });
203
- });
204
-
205
- describe("routeL1 — degradation on failure", () => {
206
- test("no provider → no leaves, without calling the provider", async () => {
207
- providerStub = null;
208
- const result = await routeL1(makeTurn("x"), makeTree());
209
- expect(result).toEqual([]);
210
- expect(providerCalls).toHaveLength(0);
211
- });
212
-
213
- test("missing tool_use → no leaves after retrying", async () => {
214
- providerStub = makeProvider(noToolResponse());
215
- const result = await routeL1(makeTurn("x"), makeTree());
216
- expect(result).toEqual([]);
217
- expect(providerCalls).toHaveLength(3);
218
- });
219
-
220
- test("schema mismatch → no leaves after retrying", async () => {
221
- providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
222
- const result = await routeL1(makeTurn("x"), makeTree());
223
- expect(result).toEqual([]);
224
- expect(providerCalls).toHaveLength(3);
225
- });
226
-
227
- test("provider throw → no leaves after retrying", async () => {
228
- providerStub = makeThrowingProvider();
229
- const result = await routeL1(makeTurn("x"), makeTree());
230
- expect(result).toEqual([]);
231
- expect(providerCalls).toHaveLength(3);
232
- });
233
-
234
- test("a malformed response that recovers on retry returns its IDs", async () => {
235
- providerStub = makeSequenceProvider([
236
- noToolResponse(),
237
- toolUseResponse({ ids: [2] }),
238
- ]);
239
- const result = await routeL1(makeTurn("bob?"), makeTree());
240
- expect(result).toEqual(["people/bob"]);
241
- expect(providerCalls).toHaveLength(2);
242
- });
243
- });
244
-
245
- describe("routeL1 — request shape", () => {
246
- test("forces tool_choice to open_leaves with the v3 call site", async () => {
247
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
248
- await routeL1(makeTurn("alice?"), makeTree());
249
-
250
- expect(providerCalls).toHaveLength(1);
251
- const [call] = providerCalls;
252
- const cfg = call.options?.config as Record<string, unknown>;
253
- expect(cfg?.callSite).toBe("memoryV3RouteL1");
254
- expect(cfg?.tool_choice).toEqual({ type: "tool", name: "open_leaves" });
255
- expect(call.options?.tools?.[0]?.name).toBe("open_leaves");
256
- });
257
-
258
- test("leaf block is the first content block with an ephemeral cache breakpoint", async () => {
259
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
260
- await routeL1(makeTurn("alice?"), makeTree());
261
-
262
- const [blockA, blockB] = providerCalls[0].messages[0].content as Array<{
263
- type: string;
264
- text: string;
265
- cache_control?: { type: string; ttl?: string };
266
- }>;
267
- expect(blockA.type).toBe("text");
268
- expect(blockA.text).toContain("<leaves>");
269
- expect(blockA.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
270
-
271
- expect(blockB.type).toBe("text");
272
- expect(blockB.text).toContain("<current_message>alice?</current_message>");
273
- expect(blockB.text).toContain("<recent_context>");
274
- expect(blockB.cache_control).toBeUndefined();
275
- });
276
-
277
- test("situational context renders in the per-turn block when present", async () => {
278
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
279
- await routeL1(
280
- {
281
- ...makeTurn("x"),
282
- situationalContext: "Today is Saturday. Alice's anniversary is today.",
283
- },
284
- makeTree(),
285
- );
286
- const blockB = providerCalls[0].messages[0].content[1] as { text: string };
287
- expect(blockB.text).toContain(
288
- "<situation>Today is Saturday. Alice's anniversary is today.</situation>",
289
- );
290
- });
291
-
292
- test("situational context is omitted when the turn has none", async () => {
293
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
294
- await routeL1(makeTurn("x"), makeTree());
295
- const blockB = providerCalls[0].messages[0].content[1] as { text: string };
296
- expect(blockB.text).not.toContain("<situation>");
297
- });
298
-
299
- test("system prompt mentions situation (locks the routing commitment)", async () => {
300
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
301
- await routeL1(makeTurn("x"), makeTree());
302
- expect(providerCalls[0].options?.systemPrompt).toMatch(/[Ss]ituation/);
303
- });
304
-
305
- test("system prompt mentions register (locks the routing commitment)", async () => {
306
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
307
- await routeL1(makeTurn("x"), makeTree());
308
- expect(providerCalls[0].options?.systemPrompt).toMatch(/register/);
309
- });
310
- });
311
-
312
- describe("renderLeafBlock — cache invariant", () => {
313
- test("is byte-identical across two calls with different queries", async () => {
314
- const tree = makeTree();
315
- providerStub = makeProvider(toolUseResponse({ ids: [1] }));
316
-
317
- await routeL1(makeTurn("first query about alice"), tree);
318
- await routeL1(makeTurn("totally different query about atlas"), tree);
319
-
320
- const prefixA = (
321
- providerCalls[0].messages[0].content[0] as { text: string }
322
- ).text;
323
- const prefixB = (
324
- providerCalls[1].messages[0].content[0] as { text: string }
325
- ).text;
326
- expect(prefixA).toBe(prefixB);
327
- // And it equals the pure renderer's output.
328
- expect(prefixA).toBe(renderLeafBlock(tree));
329
- });
330
-
331
- test("renders leaves sorted by path with 1-based numbering", () => {
332
- const block = renderLeafBlock(makeTree());
333
- expect(block).toContain("[1] people/alice — ");
334
- expect(block).toContain("[2] people/bob — ");
335
- expect(block).toContain("[3] projects/atlas — ");
336
- });
337
- });