@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.
- package/ARCHITECTURE.md +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +633 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-catalog-parity.test.ts +16 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/log-export-workspace.test.ts +468 -3
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/secret-fixtures.ts +20 -0
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-approval-handler.test.ts +85 -0
- package/src/__tests__/tool-audit-listener.test.ts +86 -0
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
- package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/acp/__tests__/agent-process.test.ts +315 -2
- package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
- package/src/acp/agent-process.ts +163 -34
- package/src/acp/prepare-agent-env.ts +55 -15
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/bundler/app-compiler.ts +8 -0
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +307 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +225 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/config/seed-inference-profiles.ts +4 -8
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +40 -9
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-catalog.ts +28 -0
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/chat-completions-provider.ts +4 -2
- package/src/providers/openai/responses-provider.ts +7 -2
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/providers/unparseable-tool-args.ts +56 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +348 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/conversation-query-routes.ts +79 -4
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/log-export-routes.ts +143 -96
- package/src/runtime/routes/plugins-routes.ts +380 -0
- package/src/runtime/routes/redact-staged-export.ts +259 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/security/redact-json.ts +61 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/tools/tool-approval-handler.ts +31 -0
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
- package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
|
@@ -175,6 +175,26 @@ function seedRequestLog(
|
|
|
175
175
|
.run();
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
function seedRequestLogWithSections(messageId: string, id: string): void {
|
|
179
|
+
getDb()
|
|
180
|
+
.insert(llmRequestLogs)
|
|
181
|
+
.values({
|
|
182
|
+
id,
|
|
183
|
+
conversationId: "conv-1",
|
|
184
|
+
messageId,
|
|
185
|
+
provider: "openai",
|
|
186
|
+
requestPayload: JSON.stringify({
|
|
187
|
+
model: "gpt-4.1",
|
|
188
|
+
messages: [{ role: "user", content: "hello" }],
|
|
189
|
+
}),
|
|
190
|
+
responsePayload: JSON.stringify({
|
|
191
|
+
choices: [{ message: { content: "hi" } }],
|
|
192
|
+
}),
|
|
193
|
+
createdAt: 1_700_000_000_000,
|
|
194
|
+
})
|
|
195
|
+
.run();
|
|
196
|
+
}
|
|
197
|
+
|
|
178
198
|
function seedConversationKey(
|
|
179
199
|
conversationKey: string,
|
|
180
200
|
conversationId: string,
|
|
@@ -274,6 +294,118 @@ describe("GET /v1/conversations/llm-context", () => {
|
|
|
274
294
|
});
|
|
275
295
|
});
|
|
276
296
|
|
|
297
|
+
describe("llm-context view=summary", () => {
|
|
298
|
+
beforeEach(() => {
|
|
299
|
+
clearTables();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
function seedConversationWithLog(): void {
|
|
303
|
+
seedConversationAndMessage({
|
|
304
|
+
conversationId: "conv-1",
|
|
305
|
+
messageId: "msg-1",
|
|
306
|
+
source: "user",
|
|
307
|
+
conversationType: "standard",
|
|
308
|
+
});
|
|
309
|
+
seedConversationKey("conv-key", "conv-1");
|
|
310
|
+
seedRequestLogWithSections("msg-1", "log-a");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
test("conversation endpoint omits sections in summary view but keeps summary fields", async () => {
|
|
314
|
+
seedConversationWithLog();
|
|
315
|
+
|
|
316
|
+
const body = (await dispatchConversationLlmContext({
|
|
317
|
+
conversationKey: "conv-key",
|
|
318
|
+
view: "summary",
|
|
319
|
+
})) as {
|
|
320
|
+
logs: Array<Record<string, unknown>>;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
expect(body.logs).toHaveLength(1);
|
|
324
|
+
const log = body.logs[0]!;
|
|
325
|
+
expect(log.requestSections).toBeUndefined();
|
|
326
|
+
expect(log.responseSections).toBeUndefined();
|
|
327
|
+
expect(log.id).toBe("log-a");
|
|
328
|
+
expect(log.summary).toBeDefined();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("conversation endpoint includes sections by default", async () => {
|
|
332
|
+
seedConversationWithLog();
|
|
333
|
+
|
|
334
|
+
const body = (await dispatchConversationLlmContext({
|
|
335
|
+
conversationKey: "conv-key",
|
|
336
|
+
})) as {
|
|
337
|
+
logs: Array<Record<string, unknown>>;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
expect(Array.isArray(body.logs[0]!.requestSections)).toBe(true);
|
|
341
|
+
expect(Array.isArray(body.logs[0]!.responseSections)).toBe(true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("message endpoint omits sections in summary view", async () => {
|
|
345
|
+
seedConversationWithLog();
|
|
346
|
+
|
|
347
|
+
const body = (await llmContextRoute.handler({
|
|
348
|
+
pathParams: { id: "msg-1" },
|
|
349
|
+
queryParams: { view: "summary" },
|
|
350
|
+
})) as {
|
|
351
|
+
logs: Array<Record<string, unknown>>;
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
expect(body.logs).toHaveLength(1);
|
|
355
|
+
expect(body.logs[0]!.requestSections).toBeUndefined();
|
|
356
|
+
expect(body.logs[0]!.responseSections).toBeUndefined();
|
|
357
|
+
expect(body.logs[0]!.summary).toBeDefined();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("rejects unknown view values", async () => {
|
|
361
|
+
seedConversationWithLog();
|
|
362
|
+
|
|
363
|
+
expect(
|
|
364
|
+
dispatchConversationLlmContext({
|
|
365
|
+
conversationKey: "conv-key",
|
|
366
|
+
view: "compact",
|
|
367
|
+
}),
|
|
368
|
+
).rejects.toThrow("Invalid view parameter");
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe("GET /v1/llm-request-logs/:id/context", () => {
|
|
373
|
+
const logContextRoute = ROUTES.find(
|
|
374
|
+
(r) => r.operationId === "llm_request_logs_context_get",
|
|
375
|
+
)!;
|
|
376
|
+
|
|
377
|
+
beforeEach(() => {
|
|
378
|
+
clearTables();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("returns the normalized entry with sections for a single log", async () => {
|
|
382
|
+
seedConversationAndMessage({
|
|
383
|
+
conversationId: "conv-1",
|
|
384
|
+
messageId: "msg-1",
|
|
385
|
+
source: "user",
|
|
386
|
+
conversationType: "standard",
|
|
387
|
+
});
|
|
388
|
+
seedRequestLogWithSections("msg-1", "log-detail");
|
|
389
|
+
|
|
390
|
+
const body = (await logContextRoute.handler({
|
|
391
|
+
pathParams: { id: "log-detail" },
|
|
392
|
+
})) as Record<string, unknown>;
|
|
393
|
+
|
|
394
|
+
expect(body.id).toBe("log-detail");
|
|
395
|
+
expect(body.requestPayload).toBeNull();
|
|
396
|
+
expect(body.responsePayload).toBeNull();
|
|
397
|
+
expect(body.summary).toBeDefined();
|
|
398
|
+
expect(Array.isArray(body.requestSections)).toBe(true);
|
|
399
|
+
expect(Array.isArray(body.responseSections)).toBe(true);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("throws NotFound for a missing log id", async () => {
|
|
403
|
+
expect(
|
|
404
|
+
logContextRoute.handler({ pathParams: { id: "log-missing" } }),
|
|
405
|
+
).rejects.toThrow("log not found");
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
277
409
|
describe("GET /v1/messages/:id/llm-context — memoryV2Activation", () => {
|
|
278
410
|
beforeEach(() => {
|
|
279
411
|
clearTables();
|
|
@@ -1,69 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unit tests for the gateway_logs_tail
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* - Happy path: all params → correct URL with querystring
|
|
6
|
-
* - All params absent → URL with no querystring
|
|
7
|
-
* - Only n provided → querystring has only n
|
|
8
|
-
* - Gateway returns 500 with error body → Error thrown with error message
|
|
9
|
-
* - level: "INVALID" → ZodError (no gateway call)
|
|
10
|
-
* - n: 0 → ZodError (min 1 violation)
|
|
11
|
-
* - n: 1001 → ZodError (max 1000 violation)
|
|
12
|
-
* - module: "" → accepted (empty string passes zod string validation)
|
|
2
|
+
* Unit tests for the gateway_logs_tail route handler.
|
|
13
3
|
*/
|
|
14
4
|
|
|
15
5
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
16
6
|
|
|
17
7
|
import { ZodError } from "zod";
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
9
|
+
type IpcCall = {
|
|
10
|
+
method: string;
|
|
11
|
+
params?: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let ipcCalls: IpcCall[] = [];
|
|
15
|
+
let ipcResult: unknown = { lines: [], truncated: false };
|
|
16
|
+
let ipcError: Error | undefined;
|
|
17
|
+
|
|
18
|
+
const ipcCallPersistentMock = mock(
|
|
19
|
+
async (method: string, params?: Record<string, unknown>) => {
|
|
20
|
+
ipcCalls.push({ method, params });
|
|
21
|
+
if (ipcError) throw ipcError;
|
|
22
|
+
return ipcResult;
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
mock.module("../../../ipc/gateway-client.js", () => ({
|
|
27
|
+
ipcCallPersistent: ipcCallPersistentMock,
|
|
32
28
|
}));
|
|
33
29
|
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Import the module under test AFTER mocks are set up
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
30
|
import { ROUTES } from "../gateway-log-routes.js";
|
|
39
31
|
|
|
40
32
|
const gatewayLogsTailRoute = ROUTES.find(
|
|
41
33
|
(r) => r.operationId === "gateway_logs_tail",
|
|
42
34
|
)!;
|
|
43
35
|
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// Helpers
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
function makeFetchMock(
|
|
49
|
-
status: number,
|
|
50
|
-
body: unknown,
|
|
51
|
-
): ReturnType<typeof mock> {
|
|
52
|
-
return mock(async () => ({
|
|
53
|
-
ok: status >= 200 && status < 300,
|
|
54
|
-
status,
|
|
55
|
-
json: async () => body,
|
|
56
|
-
}));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Tests
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
36
|
describe("gateway_logs_tail route", () => {
|
|
64
37
|
beforeEach(() => {
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
ipcCalls = [];
|
|
39
|
+
ipcResult = { lines: [], truncated: false };
|
|
40
|
+
ipcError = undefined;
|
|
41
|
+
ipcCallPersistentMock.mockClear();
|
|
67
42
|
});
|
|
68
43
|
|
|
69
44
|
test("route is registered with correct operationId, method, and endpoint", () => {
|
|
@@ -73,170 +48,107 @@ describe("gateway_logs_tail route", () => {
|
|
|
73
48
|
expect(gatewayLogsTailRoute.endpoint).toBe("gateway/logs/tail");
|
|
74
49
|
});
|
|
75
50
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
80
|
-
|
|
81
|
-
await gatewayLogsTailRoute.handler({
|
|
82
|
-
body: { n: 5, level: "warn", module: "mcp" },
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
86
|
-
const calledUrl = (mockFetch.mock.calls[0] as [string])[0];
|
|
87
|
-
expect(calledUrl).toBe(
|
|
88
|
-
"http://localhost:9999/v1/logs/tail?n=5&level=warn&module=mcp",
|
|
89
|
-
);
|
|
51
|
+
test("calls gateway IPC with all typed params from body", async () => {
|
|
52
|
+
await gatewayLogsTailRoute.handler({
|
|
53
|
+
body: { n: 5, level: "warn", module: "mcp" },
|
|
90
54
|
});
|
|
91
55
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
expect(result).toEqual(responseBody);
|
|
101
|
-
});
|
|
56
|
+
expect(ipcCalls).toEqual([
|
|
57
|
+
{
|
|
58
|
+
method: "gateway_logs_tail",
|
|
59
|
+
params: { n: 5, level: "warn", module: "mcp" },
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
102
62
|
});
|
|
103
63
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
64
|
+
test("returns the parsed IPC response body", async () => {
|
|
65
|
+
ipcResult = {
|
|
66
|
+
lines: [{ msg: "hello", level: 40 }],
|
|
67
|
+
truncated: false,
|
|
68
|
+
};
|
|
108
69
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const calledUrl = (mockFetch.mock.calls[0] as [string])[0];
|
|
112
|
-
expect(calledUrl).toBe("http://localhost:9999/v1/logs/tail");
|
|
70
|
+
const result = await gatewayLogsTailRoute.handler({
|
|
71
|
+
body: { n: 5, level: "warn", module: "mcp" },
|
|
113
72
|
});
|
|
114
73
|
|
|
115
|
-
|
|
116
|
-
const mockFetch = makeFetchMock(200, { entries: [] });
|
|
117
|
-
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
118
|
-
|
|
119
|
-
await gatewayLogsTailRoute.handler({});
|
|
120
|
-
|
|
121
|
-
const calledUrl = (mockFetch.mock.calls[0] as [string])[0];
|
|
122
|
-
expect(calledUrl).toBe("http://localhost:9999/v1/logs/tail");
|
|
123
|
-
});
|
|
74
|
+
expect(result).toEqual(ipcResult);
|
|
124
75
|
});
|
|
125
76
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const mockFetch = makeFetchMock(200, { entries: [] });
|
|
129
|
-
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
77
|
+
test("calls gateway IPC with empty params when no filters are provided", async () => {
|
|
78
|
+
await gatewayLogsTailRoute.handler({});
|
|
130
79
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const calledUrl = (mockFetch.mock.calls[0] as [string])[0];
|
|
134
|
-
expect(calledUrl).toBe("http://localhost:9999/v1/logs/tail?n=5");
|
|
135
|
-
expect(calledUrl).not.toContain("level");
|
|
136
|
-
expect(calledUrl).not.toContain("module");
|
|
137
|
-
});
|
|
80
|
+
expect(ipcCalls).toEqual([{ method: "gateway_logs_tail", params: {} }]);
|
|
138
81
|
});
|
|
139
82
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
globalThis.fetch = makeFetchMock(500, {
|
|
143
|
-
error: "disk error",
|
|
144
|
-
}) as unknown as typeof fetch;
|
|
83
|
+
test("sends only n when only n is provided", async () => {
|
|
84
|
+
await gatewayLogsTailRoute.handler({ body: { n: 5 } });
|
|
145
85
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("gateway 500 with non-string error falls back to generic message", async () => {
|
|
152
|
-
globalThis.fetch = makeFetchMock(500, {
|
|
153
|
-
error: { nested: true },
|
|
154
|
-
}) as unknown as typeof fetch;
|
|
155
|
-
|
|
156
|
-
await expect(
|
|
157
|
-
gatewayLogsTailRoute.handler({ body: {} }),
|
|
158
|
-
).rejects.toThrow("Gateway request failed (500)");
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("gateway 500 with unparseable JSON falls back to generic message", async () => {
|
|
162
|
-
globalThis.fetch = mock(async () => ({
|
|
163
|
-
ok: false,
|
|
164
|
-
status: 500,
|
|
165
|
-
json: async () => {
|
|
166
|
-
throw new SyntaxError("Unexpected token");
|
|
167
|
-
},
|
|
168
|
-
})) as unknown as typeof fetch;
|
|
169
|
-
|
|
170
|
-
await expect(
|
|
171
|
-
gatewayLogsTailRoute.handler({ body: {} }),
|
|
172
|
-
).rejects.toThrow("Gateway request failed (500)");
|
|
173
|
-
});
|
|
86
|
+
expect(ipcCalls).toEqual([
|
|
87
|
+
{ method: "gateway_logs_tail", params: { n: 5 } },
|
|
88
|
+
]);
|
|
174
89
|
});
|
|
175
90
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const mockFetch = makeFetchMock(200, {});
|
|
179
|
-
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
91
|
+
test("propagates gateway IPC errors", async () => {
|
|
92
|
+
ipcError = new Error("Gateway IPC socket disconnected");
|
|
180
93
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
).rejects.toBeInstanceOf(ZodError);
|
|
94
|
+
await expect(gatewayLogsTailRoute.handler({ body: {} })).rejects.toThrow(
|
|
95
|
+
"Gateway IPC socket disconnected",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
186
98
|
|
|
187
|
-
|
|
188
|
-
}
|
|
99
|
+
test("rejects malformed gateway IPC responses", async () => {
|
|
100
|
+
ipcResult = { entries: [] };
|
|
189
101
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
102
|
+
await expect(
|
|
103
|
+
gatewayLogsTailRoute.handler({ body: {} }),
|
|
104
|
+
).rejects.toBeInstanceOf(ZodError);
|
|
105
|
+
});
|
|
193
106
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
107
|
+
test("level: 'INVALID' is rejected before calling gateway IPC", async () => {
|
|
108
|
+
await expect(
|
|
109
|
+
gatewayLogsTailRoute.handler({
|
|
110
|
+
body: { level: "INVALID" },
|
|
111
|
+
}),
|
|
112
|
+
).rejects.toBeInstanceOf(ZodError);
|
|
197
113
|
|
|
198
|
-
|
|
199
|
-
|
|
114
|
+
expect(ipcCalls).toEqual([]);
|
|
115
|
+
});
|
|
200
116
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
117
|
+
test("n: 0 is rejected before calling gateway IPC", async () => {
|
|
118
|
+
await expect(
|
|
119
|
+
gatewayLogsTailRoute.handler({ body: { n: 0 } }),
|
|
120
|
+
).rejects.toBeInstanceOf(ZodError);
|
|
204
121
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
).rejects.toBeInstanceOf(ZodError);
|
|
122
|
+
expect(ipcCalls).toEqual([]);
|
|
123
|
+
});
|
|
208
124
|
|
|
209
|
-
|
|
210
|
-
|
|
125
|
+
test("n: 1001 is rejected before calling gateway IPC", async () => {
|
|
126
|
+
await expect(
|
|
127
|
+
gatewayLogsTailRoute.handler({ body: { n: 1001 } }),
|
|
128
|
+
).rejects.toBeInstanceOf(ZodError);
|
|
211
129
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
globalThis.fetch = mockFetch as unknown as typeof fetch;
|
|
130
|
+
expect(ipcCalls).toEqual([]);
|
|
131
|
+
});
|
|
215
132
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
).resolves.toBeDefined();
|
|
133
|
+
test("module: '' is accepted", async () => {
|
|
134
|
+
await gatewayLogsTailRoute.handler({ body: { module: "" } });
|
|
219
135
|
|
|
220
|
-
|
|
221
|
-
|
|
136
|
+
expect(ipcCalls).toEqual([
|
|
137
|
+
{ method: "gateway_logs_tail", params: { module: "" } },
|
|
138
|
+
]);
|
|
222
139
|
});
|
|
223
140
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// When queryParams has values, those take precedence over body.
|
|
230
|
-
// Only string-valued params (level, module) are valid from the HTTP layer;
|
|
231
|
-
// n is numeric and must come via IPC body.
|
|
232
|
-
await gatewayLogsTailRoute.handler({
|
|
233
|
-
queryParams: { level: "info", module: "cors" },
|
|
234
|
-
body: {},
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
const calledUrl = (mockFetch.mock.calls[0] as [string])[0];
|
|
238
|
-
expect(calledUrl).toContain("level=info");
|
|
239
|
-
expect(calledUrl).toContain("module=cors");
|
|
141
|
+
test("uses queryParams when provided and coerces n", async () => {
|
|
142
|
+
await gatewayLogsTailRoute.handler({
|
|
143
|
+
queryParams: { n: "7", level: "info", module: "cors" },
|
|
144
|
+
body: { n: 1, level: "error" },
|
|
240
145
|
});
|
|
146
|
+
|
|
147
|
+
expect(ipcCalls).toEqual([
|
|
148
|
+
{
|
|
149
|
+
method: "gateway_logs_tail",
|
|
150
|
+
params: { n: 7, level: "info", module: "cors" },
|
|
151
|
+
},
|
|
152
|
+
]);
|
|
241
153
|
});
|
|
242
154
|
});
|
|
@@ -65,6 +65,14 @@ mock.module("../../../memory/conversation-crud.js", () => ({
|
|
|
65
65
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
66
66
|
}));
|
|
67
67
|
|
|
68
|
+
// Stub the home-content revalidation coordinator: the GET handler fires
|
|
69
|
+
// it fire-and-forget, and the real module would reach into the LLM
|
|
70
|
+
// provider layer and checkpoint store.
|
|
71
|
+
const revalidateSpy = mock<() => void>(() => {});
|
|
72
|
+
mock.module("../../../home/home-content-refresh.js", () => ({
|
|
73
|
+
revalidateHomeContentInBackground: revalidateSpy,
|
|
74
|
+
}));
|
|
75
|
+
|
|
68
76
|
// Dynamic imports so module mocks are wired before evaluation.
|
|
69
77
|
const {
|
|
70
78
|
computeGreeting,
|
|
@@ -333,6 +341,15 @@ describe("handleGetHomeFeed", () => {
|
|
|
333
341
|
expect(res.status).toBe(400);
|
|
334
342
|
});
|
|
335
343
|
|
|
344
|
+
test("triggers fire-and-forget home content revalidation", async () => {
|
|
345
|
+
revalidateSpy.mockClear();
|
|
346
|
+
const res = await handleGetHomeFeed(
|
|
347
|
+
new Request("http://localhost/v1/home/feed?timeAwaySeconds=0"),
|
|
348
|
+
);
|
|
349
|
+
expect(res.status).toBe(200);
|
|
350
|
+
expect(revalidateSpy).toHaveBeenCalledTimes(1);
|
|
351
|
+
});
|
|
352
|
+
|
|
336
353
|
test("empty feed returns empty items and zero newCount", async () => {
|
|
337
354
|
const res = await handleGetHomeFeed(
|
|
338
355
|
new Request("http://localhost/v1/home/feed?timeAwaySeconds=0"),
|