@vellumai/assistant 0.8.11 → 0.8.12-staging.1
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 +126 -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-usage-store.test.ts +11 -0
- 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__/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-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-release-notes-feature-flag-guard.test.ts +45 -95
- 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/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 +301 -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 +219 -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/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/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/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-intents.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +2 -1
- package/src/providers/openai/responses-provider.ts +5 -1
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -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__/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 +1 -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/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/plugins-routes.ts +21 -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/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/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/registry.ts +4 -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
|
@@ -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"),
|
|
@@ -206,6 +206,8 @@ mock.module("../../../util/platform.js", () => {
|
|
|
206
206
|
getPlatformName: () => "linux",
|
|
207
207
|
normalizeAssistantId: (id: string) => id,
|
|
208
208
|
getDataDir: stub,
|
|
209
|
+
getConfigQuarantineNoticePath: () =>
|
|
210
|
+
"/mock/workspace/data/config-quarantine-notice.json",
|
|
209
211
|
getEmbeddingModelsDir: stub,
|
|
210
212
|
getSandboxRootDir: stub,
|
|
211
213
|
getSandboxWorkingDir: stub,
|
|
@@ -414,9 +416,7 @@ describe("task_delete route", () => {
|
|
|
414
416
|
|
|
415
417
|
test("throws Zod validation error for empty task_ids array", async () => {
|
|
416
418
|
const route = findRoute("task_delete");
|
|
417
|
-
await expect(
|
|
418
|
-
route.handler({ body: { task_ids: [] } }),
|
|
419
|
-
).rejects.toThrow();
|
|
419
|
+
await expect(route.handler({ body: { task_ids: [] } })).rejects.toThrow();
|
|
420
420
|
|
|
421
421
|
expect(mockTaskDeleteCalls).toHaveLength(0);
|
|
422
422
|
});
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
18
17
|
import { getConfig } from "../../config/loader.js";
|
|
19
18
|
import { getOrCreateConversation } from "../../daemon/conversation-store.js";
|
|
20
19
|
import { readNowScratchpad } from "../../daemon/now-scratchpad.js";
|
|
@@ -34,9 +33,6 @@ const IDENTITY_INTRO_KEY = "identity-intro";
|
|
|
34
33
|
|
|
35
34
|
/** Conversation key used by the client for empty-state greeting generation. */
|
|
36
35
|
const GREETING_KEY = "greeting";
|
|
37
|
-
const EMPTY_STATE_DYNAMIC_GREETINGS_FLAG =
|
|
38
|
-
"empty-state-dynamic-greetings" as const;
|
|
39
|
-
const STATIC_EMPTY_STATE_GREETING = "What are we working on?";
|
|
40
36
|
|
|
41
37
|
// ---------------------------------------------------------------------------
|
|
42
38
|
// SSE helpers
|
|
@@ -68,16 +64,6 @@ async function handleBtw({
|
|
|
68
64
|
|
|
69
65
|
const trimmedContent = content.trim();
|
|
70
66
|
|
|
71
|
-
if (
|
|
72
|
-
conversationKey === GREETING_KEY &&
|
|
73
|
-
!isAssistantFeatureFlagEnabled(
|
|
74
|
-
EMPTY_STATE_DYNAMIC_GREETINGS_FLAG,
|
|
75
|
-
getConfig(),
|
|
76
|
-
)
|
|
77
|
-
) {
|
|
78
|
-
return streamText(STATIC_EMPTY_STATE_GREETING);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
67
|
// ----- Cached identity intro fast-path -----
|
|
82
68
|
if (conversationKey === IDENTITY_INTRO_KEY) {
|
|
83
69
|
const fastText = getCachedIntro()?.greetings[0];
|
|
@@ -34,30 +34,37 @@
|
|
|
34
34
|
* which has no compaction immediately before it but had several
|
|
35
35
|
* earlier in the same turn. The turn is the right unit of analysis.
|
|
36
36
|
*
|
|
37
|
-
* # Data
|
|
37
|
+
* # Data sources
|
|
38
38
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* table. Until then, missing fields surface as `null` and the Compaction
|
|
46
|
-
* tab renders `"Unavailable"` for them.
|
|
39
|
+
* When `compactionLogs.destination = "clickhouse"` is configured, the
|
|
40
|
+
* trail is served from the first-class compaction log: the agent loop's
|
|
41
|
+
* start/end event pairs written by
|
|
42
|
+
* `memory/compaction-log-store-clickhouse.ts`. Those rows carry real
|
|
43
|
+
* durations, summary-model token totals, and trigger reasons — none of
|
|
44
|
+
* which exist on `llm_request_logs`.
|
|
47
45
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* The legacy projection over `llm_request_logs` rows with
|
|
47
|
+
* `call_site = "compactionAgent"` remains as the fallback: it serves
|
|
48
|
+
* turns that predate the compaction log (the table is append-only from
|
|
49
|
+
* the moment the destination is configured), assistants that never opt
|
|
50
|
+
* in, and reads where the ClickHouse query fails. On the legacy path
|
|
51
|
+
* `durationMs` is always `null` — the column doesn't exist on
|
|
52
|
+
* `llm_request_logs`.
|
|
51
53
|
*/
|
|
52
54
|
|
|
53
55
|
import { z } from "zod";
|
|
54
56
|
|
|
57
|
+
import {
|
|
58
|
+
type CompactionLogEvent,
|
|
59
|
+
getCompactionLogStore,
|
|
60
|
+
} from "../../memory/compaction-log-store-clickhouse.js";
|
|
55
61
|
import {
|
|
56
62
|
getConversation,
|
|
57
63
|
getTurnTimeBounds,
|
|
58
64
|
} from "../../memory/conversation-crud.js";
|
|
59
65
|
import { getLlmRequestLogSource } from "../../memory/llm-request-log-source.js";
|
|
60
66
|
import type { LogRow } from "../../memory/llm-request-log-store.js";
|
|
67
|
+
import { getLogger } from "../../util/logger.js";
|
|
61
68
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
62
69
|
import { BadRequestError, NotFoundError } from "./errors.js";
|
|
63
70
|
import {
|
|
@@ -66,6 +73,8 @@ import {
|
|
|
66
73
|
} from "./llm-context-normalization.js";
|
|
67
74
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
68
75
|
|
|
76
|
+
const log = getLogger("conversation-compaction-routes");
|
|
77
|
+
|
|
69
78
|
/**
|
|
70
79
|
* Wire shape for a single compaction event. Mirrors the React Query
|
|
71
80
|
* response type at
|
|
@@ -81,12 +90,9 @@ export interface CompactionTrailEvent {
|
|
|
81
90
|
inputTokens: number | null;
|
|
82
91
|
outputTokens: number | null;
|
|
83
92
|
/**
|
|
84
|
-
* Per-
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* having latency here, that's a concrete signal to extend the row
|
|
88
|
-
* (either as a column on `llm_request_logs` or on a dedicated
|
|
89
|
-
* `compaction_logs` table per the open data-model decision).
|
|
93
|
+
* Per-attempt wall-clock latency, in milliseconds. Populated from the
|
|
94
|
+
* compaction log when configured; always `null` on the legacy
|
|
95
|
+
* `llm_request_logs` path, which has no duration column.
|
|
90
96
|
*/
|
|
91
97
|
durationMs: number | null;
|
|
92
98
|
responsePreview: string | null;
|
|
@@ -172,6 +178,33 @@ export function projectLogRowToCompactionTrailEvent(
|
|
|
172
178
|
};
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Project a paired compaction-log event into the trail wire shape. Token
|
|
183
|
+
* counts are the summarizer's own usage (what the compaction itself cost),
|
|
184
|
+
* matching what the legacy path derived from the compaction agent's
|
|
185
|
+
* request/response payloads.
|
|
186
|
+
*
|
|
187
|
+
* Exported only for unit tests; the route handler is the sole production
|
|
188
|
+
* caller.
|
|
189
|
+
*/
|
|
190
|
+
export function projectCompactionLogEventToTrailEvent(
|
|
191
|
+
event: CompactionLogEvent,
|
|
192
|
+
): CompactionTrailEvent {
|
|
193
|
+
return {
|
|
194
|
+
id: event.compactionId,
|
|
195
|
+
createdAt: event.startedAt,
|
|
196
|
+
model: event.summaryModel,
|
|
197
|
+
provider: null,
|
|
198
|
+
inputTokens: event.summaryInputTokens,
|
|
199
|
+
outputTokens: event.summaryOutputTokens,
|
|
200
|
+
durationMs: event.durationMs,
|
|
201
|
+
responsePreview: event.summaryText,
|
|
202
|
+
requestMessageCount: event.preMessageCount,
|
|
203
|
+
stopReason: event.reason,
|
|
204
|
+
estimatedCostUsd: null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
175
208
|
// ---------------------------------------------------------------------------
|
|
176
209
|
// Handler
|
|
177
210
|
// ---------------------------------------------------------------------------
|
|
@@ -232,6 +265,40 @@ async function handleGetCompactionTrail({
|
|
|
232
265
|
turnBounds !== null ? turnBounds.startTime - 1 : null;
|
|
233
266
|
const beforeCreatedAt: number =
|
|
234
267
|
turnBounds !== null ? turnBounds.endTime + 1 : selectedCall.createdAt;
|
|
268
|
+
// Prefer the first-class compaction log when the assistant has opted
|
|
269
|
+
// in. Zero rows means the turn predates the log (it is append-only from
|
|
270
|
+
// the moment the destination is configured), so fall through to the
|
|
271
|
+
// legacy projection rather than returning an empty trail; same for a
|
|
272
|
+
// failed ClickHouse read. Writes are best-effort and the start/end rows
|
|
273
|
+
// land independently, so an event without its end row (no `finishedAt`)
|
|
274
|
+
// means the end write failed or is lagging — in that case the legacy
|
|
275
|
+
// projection may still have the summarizer call details, so fall back
|
|
276
|
+
// rather than serve a trail with null model/tokens/duration.
|
|
277
|
+
const compactionStore = getCompactionLogStore();
|
|
278
|
+
if (compactionStore) {
|
|
279
|
+
try {
|
|
280
|
+
const events = await compactionStore.getEventsBetween(
|
|
281
|
+
conversationId,
|
|
282
|
+
afterCreatedAt,
|
|
283
|
+
beforeCreatedAt,
|
|
284
|
+
);
|
|
285
|
+
if (
|
|
286
|
+
events.length > 0 &&
|
|
287
|
+
events.every((event) => event.finishedAt !== null)
|
|
288
|
+
) {
|
|
289
|
+
return {
|
|
290
|
+
conversationId,
|
|
291
|
+
events: events.map(projectCompactionLogEventToTrailEvent),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
} catch (err) {
|
|
295
|
+
log.warn(
|
|
296
|
+
{ err, conversationId, callId },
|
|
297
|
+
"Compaction log read failed; falling back to llm_request_logs projection",
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
235
302
|
const logs = await source.getCompactionLogsBetween(
|
|
236
303
|
conversationId,
|
|
237
304
|
afterCreatedAt,
|
|
@@ -259,7 +326,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
259
326
|
},
|
|
260
327
|
summary: "Get the compaction trail leading up to an LLM call",
|
|
261
328
|
description:
|
|
262
|
-
|
|
329
|
+
'Return the chronological list of compaction events that ran in the same agent turn as the call identified by `callId`. Turn bounds are walked from the `messages` table (real user messages — tool-result user messages are not boundaries). Served from the first-class compaction log when `compactionLogs.destination = "clickhouse"` is configured, falling back to the legacy projection over `llm_request_logs` rows where `call_site = "compactionAgent"`. When the conversation has no other messages around the selected call, every compaction strictly before the call\'s `createdAt` is in scope. Drives the Inspector\'s Compaction tab.',
|
|
263
330
|
tags: ["conversations"],
|
|
264
331
|
pathParams: [
|
|
265
332
|
{
|