@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/assistant",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.12-staging.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
"@vellumai/skill-host-contracts": "file:../packages/skill-host-contracts",
|
|
51
51
|
"@vellumai/slack-text": "file:../packages/slack-text",
|
|
52
52
|
"@vellumai/twilio-client": "file:../packages/twilio-client",
|
|
53
|
-
"archiver": "7.0.1",
|
|
54
53
|
"commander": "13.1.0",
|
|
55
54
|
"croner": "10.0.1",
|
|
56
55
|
"dotenv": "17.3.1",
|
|
@@ -97,7 +96,6 @@
|
|
|
97
96
|
},
|
|
98
97
|
"devDependencies": {
|
|
99
98
|
"@microsoft/api-extractor": "7.58.8",
|
|
100
|
-
"@types/archiver": "7.0.0",
|
|
101
99
|
"@types/bun": "1.3.10",
|
|
102
100
|
"@types/node": "25.5.0",
|
|
103
101
|
"@types/semver": "7.5.8",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import { repairAdaptiveThinkingOnManagedProfiles } from "../workspace/adaptive-thinking-repair.js";
|
|
13
|
+
import { enableAdaptiveThinkingManagedProfilesMigration } from "../workspace/migrations/097-enable-adaptive-thinking-managed-profiles.js";
|
|
14
|
+
|
|
15
|
+
const ADAPTIVE = { enabled: true, streamThinking: true };
|
|
16
|
+
|
|
17
|
+
let workspaceDir: string;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
workspaceDir = join(
|
|
21
|
+
tmpdir(),
|
|
22
|
+
`vellum-adaptive-thinking-repair-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
23
|
+
);
|
|
24
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
if (existsSync(workspaceDir)) {
|
|
29
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function writeConfig(data: Record<string, unknown>): void {
|
|
34
|
+
writeFileSync(
|
|
35
|
+
join(workspaceDir, "config.json"),
|
|
36
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readConfig(): Record<string, unknown> {
|
|
41
|
+
return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function profile(name: string): Record<string, unknown> {
|
|
45
|
+
const config = readConfig();
|
|
46
|
+
const llm = config.llm as Record<string, unknown>;
|
|
47
|
+
const profiles = llm.profiles as Record<string, unknown>;
|
|
48
|
+
return profiles[name] as Record<string, unknown>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The startup repair and migration 097 keep frozen copies of the same logic
|
|
52
|
+
// (migration modules must stay self-contained). Run every scenario against
|
|
53
|
+
// both so the copies can't drift.
|
|
54
|
+
const implementations: Array<[string, (dir: string) => void]> = [
|
|
55
|
+
["startup repair", repairAdaptiveThinkingOnManagedProfiles],
|
|
56
|
+
[
|
|
57
|
+
"migration 097",
|
|
58
|
+
(dir) => enableAdaptiveThinkingManagedProfilesMigration.run(dir),
|
|
59
|
+
],
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
describe.each(implementations)("%s", (_label, run) => {
|
|
63
|
+
test("patches managed Anthropic profiles missing thinking", () => {
|
|
64
|
+
writeConfig({
|
|
65
|
+
llm: {
|
|
66
|
+
default: { provider: "anthropic", model: "claude-sonnet-4-6" },
|
|
67
|
+
profiles: {
|
|
68
|
+
balanced: { source: "managed", provider: "anthropic" },
|
|
69
|
+
"quality-optimized": {
|
|
70
|
+
source: "managed",
|
|
71
|
+
provider: "anthropic",
|
|
72
|
+
thinking: { enabled: false },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
run(workspaceDir);
|
|
79
|
+
|
|
80
|
+
expect(profile("balanced").thinking).toEqual(ADAPTIVE);
|
|
81
|
+
expect(profile("quality-optimized").thinking).toEqual(ADAPTIVE);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("skips source-less empty shells when llm.default.provider is non-Anthropic", () => {
|
|
85
|
+
// Migration 052 seeds empty {} shells (no source, no provider) on legacy
|
|
86
|
+
// non-Anthropic workspaces. They inherit the non-Anthropic provider from
|
|
87
|
+
// llm.default and must stay off the Anthropic-specific repair.
|
|
88
|
+
writeConfig({
|
|
89
|
+
llm: {
|
|
90
|
+
default: { provider: "gemini", model: "gemini-2.5-pro" },
|
|
91
|
+
profiles: {
|
|
92
|
+
balanced: {},
|
|
93
|
+
"quality-optimized": {},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
run(workspaceDir);
|
|
99
|
+
|
|
100
|
+
expect(profile("balanced")).toEqual({});
|
|
101
|
+
expect(profile("quality-optimized")).toEqual({});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("treats absent llm.default.provider as Anthropic for source-less profiles", () => {
|
|
105
|
+
writeConfig({
|
|
106
|
+
llm: {
|
|
107
|
+
default: { model: "claude-sonnet-4-6" },
|
|
108
|
+
profiles: {
|
|
109
|
+
balanced: {},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
run(workspaceDir);
|
|
115
|
+
|
|
116
|
+
expect(profile("balanced").thinking).toEqual(ADAPTIVE);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("explicit Anthropic provider on the profile wins over a non-Anthropic default", () => {
|
|
120
|
+
writeConfig({
|
|
121
|
+
llm: {
|
|
122
|
+
default: { provider: "gemini" },
|
|
123
|
+
profiles: {
|
|
124
|
+
balanced: { provider: "anthropic" },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
run(workspaceDir);
|
|
130
|
+
|
|
131
|
+
expect(profile("balanced").thinking).toEqual(ADAPTIVE);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("skips profiles with an explicit non-Anthropic provider", () => {
|
|
135
|
+
writeConfig({
|
|
136
|
+
llm: {
|
|
137
|
+
default: { provider: "anthropic" },
|
|
138
|
+
profiles: {
|
|
139
|
+
balanced: { provider: "gemini" },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
run(workspaceDir);
|
|
145
|
+
|
|
146
|
+
expect(profile("balanced").thinking).toBeUndefined();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("skips source: user profiles", () => {
|
|
150
|
+
writeConfig({
|
|
151
|
+
llm: {
|
|
152
|
+
default: { provider: "anthropic" },
|
|
153
|
+
profiles: {
|
|
154
|
+
balanced: { source: "user", provider: "anthropic" },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
run(workspaceDir);
|
|
160
|
+
|
|
161
|
+
expect(profile("balanced").thinking).toBeUndefined();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("is idempotent when thinking is already enabled", () => {
|
|
165
|
+
const config = {
|
|
166
|
+
llm: {
|
|
167
|
+
default: { provider: "anthropic" },
|
|
168
|
+
profiles: {
|
|
169
|
+
balanced: {
|
|
170
|
+
source: "managed",
|
|
171
|
+
thinking: { enabled: true, streamThinking: false },
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
writeConfig(config);
|
|
177
|
+
|
|
178
|
+
run(workspaceDir);
|
|
179
|
+
|
|
180
|
+
expect(profile("balanced").thinking).toEqual({
|
|
181
|
+
enabled: true,
|
|
182
|
+
streamThinking: false,
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* The compaction pipeline between `context_compacting` (start) and
|
|
5
5
|
* `compaction_completed` (end) is plugin-owned, so consumers reconstruct a
|
|
6
6
|
* compaction attempt purely from the pair — correlated by `compactionId`.
|
|
7
|
-
* These tests pin the pairing contract: shared id, trigger, timestamps,
|
|
8
|
-
* pre-compaction state on the start event
|
|
7
|
+
* These tests pin the pairing contract: shared id, trigger, timestamps,
|
|
8
|
+
* pre-compaction state on the start event, and the unnested
|
|
9
|
+
* `ContextWindowResult` fields on the end event.
|
|
9
10
|
*/
|
|
10
11
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
12
|
|
|
@@ -185,11 +186,11 @@ describe("AgentLoop compaction start/end event pair", () => {
|
|
|
185
186
|
expect(end!.finishedAt).toBeGreaterThanOrEqual(end!.startedAt);
|
|
186
187
|
expect(end!.finishedAt).toBeLessThanOrEqual(after);
|
|
187
188
|
|
|
188
|
-
// Start carries the pre-compaction history; end carries the
|
|
189
|
-
//
|
|
189
|
+
// Start carries the pre-compaction history; end carries the
|
|
190
|
+
// ContextWindowResult fields unnested on the event.
|
|
190
191
|
expect(start!.messages.length).toBeGreaterThan(0);
|
|
191
|
-
expect(end!.
|
|
192
|
-
expect(end!.
|
|
192
|
+
expect(end!.compacted).toBe(true);
|
|
193
|
+
expect(end!.exhausted).toBe(false);
|
|
193
194
|
|
|
194
195
|
// Start precedes end in the event stream.
|
|
195
196
|
expect(events.indexOf(start!)).toBeLessThan(events.indexOf(end!));
|
|
@@ -106,6 +106,7 @@ mock.module("@anthropic-ai/sdk", () => ({
|
|
|
106
106
|
}));
|
|
107
107
|
|
|
108
108
|
// Import after mocking
|
|
109
|
+
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../prompts/cache-boundary.js";
|
|
109
110
|
import { AnthropicProvider } from "../providers/anthropic/client.js";
|
|
110
111
|
import {
|
|
111
112
|
isPlaceholderSentinelText,
|
|
@@ -308,6 +309,91 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
308
309
|
});
|
|
309
310
|
});
|
|
310
311
|
|
|
312
|
+
test("splits a boundary-carrying system prompt into two cached blocks and drops the tool breakpoint", async () => {
|
|
313
|
+
/**
|
|
314
|
+
* Tests that the SYSTEM_PROMPT_CACHE_BOUNDARY marker yields two
|
|
315
|
+
* independently cached system blocks, and that the explicit
|
|
316
|
+
* last-tool breakpoint is suppressed (the first system breakpoint
|
|
317
|
+
* already covers tools) so the total stays within Anthropic's
|
|
318
|
+
* 4-breakpoint budget.
|
|
319
|
+
*/
|
|
320
|
+
// GIVEN a system prompt carrying a cache boundary, plus tools and a
|
|
321
|
+
// tool-use loop (so turn-start and tail breakpoints also apply)
|
|
322
|
+
const prompt = `stable sections${SYSTEM_PROMPT_CACHE_BOUNDARY}volatile sections`;
|
|
323
|
+
const messages: Message[] = [
|
|
324
|
+
userMsg("Do something"),
|
|
325
|
+
toolUseMsg("tu_1", "bash"),
|
|
326
|
+
toolResultMsg("tu_1", "output"),
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
// WHEN the message is sent
|
|
330
|
+
await provider.sendMessage(messages, {
|
|
331
|
+
tools: sampleTools,
|
|
332
|
+
systemPrompt: prompt,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// THEN the system prompt is split into two blocks, each 1h-cached
|
|
336
|
+
const system = lastStreamParams!.system as Array<{
|
|
337
|
+
type: string;
|
|
338
|
+
text: string;
|
|
339
|
+
cache_control?: { type: string; ttl?: string };
|
|
340
|
+
}>;
|
|
341
|
+
expect(system).toHaveLength(2);
|
|
342
|
+
expect(system[0].text).toBe("stable sections");
|
|
343
|
+
expect(system[1].text).toBe("volatile sections");
|
|
344
|
+
expect(system[0].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
345
|
+
expect(system[1].cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
346
|
+
|
|
347
|
+
// AND no tool carries a breakpoint (system block 1 covers them)
|
|
348
|
+
const tools = lastStreamParams!.tools as Array<{
|
|
349
|
+
cache_control?: { type: string; ttl?: string };
|
|
350
|
+
}>;
|
|
351
|
+
for (const tool of tools) {
|
|
352
|
+
expect(tool.cache_control).toBeUndefined();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// AND the turn-start and tail message breakpoints are unchanged,
|
|
356
|
+
// keeping the total at the 4-breakpoint cap
|
|
357
|
+
const sent = lastStreamParams!.messages as Array<{
|
|
358
|
+
role: string;
|
|
359
|
+
content: Array<{
|
|
360
|
+
type: string;
|
|
361
|
+
cache_control?: { type: string; ttl?: string };
|
|
362
|
+
}>;
|
|
363
|
+
}>;
|
|
364
|
+
const turnStart = sent[0];
|
|
365
|
+
expect(
|
|
366
|
+
turnStart.content[turnStart.content.length - 1].cache_control,
|
|
367
|
+
).toEqual({ type: "ephemeral", ttl: "1h" });
|
|
368
|
+
const lastMsg = sent[sent.length - 1];
|
|
369
|
+
expect(lastMsg.content[lastMsg.content.length - 1].cache_control).toEqual({
|
|
370
|
+
type: "ephemeral",
|
|
371
|
+
ttl: "5m",
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("keeps the last-tool breakpoint when the system prompt has no boundary", async () => {
|
|
376
|
+
/**
|
|
377
|
+
* Tests that a boundary-free system prompt preserves the explicit
|
|
378
|
+
* tool breakpoint (single system block leaves budget for it).
|
|
379
|
+
*/
|
|
380
|
+
// GIVEN a system prompt without a cache boundary and tools
|
|
381
|
+
// WHEN the message is sent
|
|
382
|
+
await provider.sendMessage([userMsg("Hi")], {
|
|
383
|
+
tools: sampleTools,
|
|
384
|
+
systemPrompt: "You are helpful.",
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// THEN the last tool keeps its breakpoint
|
|
388
|
+
const tools = lastStreamParams!.tools as Array<{
|
|
389
|
+
cache_control?: { type: string; ttl?: string };
|
|
390
|
+
}>;
|
|
391
|
+
expect(tools[tools.length - 1].cache_control).toEqual({
|
|
392
|
+
type: "ephemeral",
|
|
393
|
+
ttl: "1h",
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
311
397
|
// -----------------------------------------------------------------------
|
|
312
398
|
// Tool cache control
|
|
313
399
|
// -----------------------------------------------------------------------
|
|
@@ -389,6 +475,49 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
389
475
|
).not.toContain("extended-cache-ttl-2025-04-11");
|
|
390
476
|
});
|
|
391
477
|
|
|
478
|
+
test("disableCache sends no cache_control anywhere and strips caller-stamped block markers", async () => {
|
|
479
|
+
// A one-shot call site that opted out of prompt caching: no breakpoint
|
|
480
|
+
// on system, tools, turn-start, or tail — and a caller-stamped block
|
|
481
|
+
// marker is removed rather than forwarded.
|
|
482
|
+
const messages: Message[] = [
|
|
483
|
+
{
|
|
484
|
+
role: "user",
|
|
485
|
+
content: [
|
|
486
|
+
cachedPrefixBlock("stable pages block"),
|
|
487
|
+
{ type: "text", text: "Do something" },
|
|
488
|
+
],
|
|
489
|
+
},
|
|
490
|
+
toolUseMsg("tu_1", "bash"),
|
|
491
|
+
toolResultMsg("tu_1", "output"),
|
|
492
|
+
];
|
|
493
|
+
await provider.sendMessage(messages, {
|
|
494
|
+
tools: sampleTools,
|
|
495
|
+
systemPrompt: "You are a helpful assistant.",
|
|
496
|
+
config: { disableCache: true },
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const system = lastStreamParams!.system as Array<{
|
|
500
|
+
cache_control?: unknown;
|
|
501
|
+
}>;
|
|
502
|
+
expect(system[0].cache_control).toBeUndefined();
|
|
503
|
+
|
|
504
|
+
const tools = lastStreamParams!.tools as Array<{
|
|
505
|
+
cache_control?: unknown;
|
|
506
|
+
}>;
|
|
507
|
+
for (const tool of tools) {
|
|
508
|
+
expect(tool.cache_control).toBeUndefined();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const sent = lastStreamParams!.messages as Array<{
|
|
512
|
+
content: Array<{ cache_control?: unknown }>;
|
|
513
|
+
}>;
|
|
514
|
+
for (const message of sent) {
|
|
515
|
+
for (const block of message.content) {
|
|
516
|
+
expect(block.cache_control).toBeUndefined();
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
392
521
|
test("v3-shape call (system + tools + cached prefix block) stays within the 4-breakpoint cap", async () => {
|
|
393
522
|
// Mirrors a v3 L2 selector call: a system prompt, a forced tool, and a user
|
|
394
523
|
// message whose stable <pages> block carries a cache_control breakpoint
|
|
@@ -52,7 +52,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
52
52
|
getNestedValue: () => undefined,
|
|
53
53
|
setNestedValue: () => {},
|
|
54
54
|
API_KEY_PROVIDERS: [],
|
|
55
|
-
|
|
55
|
+
_writeQuarantineNotice: () => {},
|
|
56
56
|
invalidateConfigCache: () => {},
|
|
57
57
|
}));
|
|
58
58
|
|
|
@@ -158,6 +158,9 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
158
158
|
|
|
159
159
|
mock.module("../memory/conversation-title-service.js", () => ({
|
|
160
160
|
GENERATING_TITLE: "Generating title...",
|
|
161
|
+
AUTO_TITLE_DETERMINISTIC: 2,
|
|
162
|
+
deriveDeterministicTitle: (context: { systemHint?: string }) =>
|
|
163
|
+
context.systemHint ?? "Untitled Conversation",
|
|
161
164
|
isReplaceableTitle: () => true,
|
|
162
165
|
queueGenerateConversationTitle: () => {},
|
|
163
166
|
queueRegenerateConversationTitle: () => {},
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { rmSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
-
import {
|
|
11
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Mocks — must be defined before importing the module under test
|
|
@@ -22,7 +22,11 @@ mock.module("../util/logger.js", () => ({
|
|
|
22
22
|
}));
|
|
23
23
|
|
|
24
24
|
const mockGetConversationByKey = mock(
|
|
25
|
-
(
|
|
25
|
+
(
|
|
26
|
+
_key: string,
|
|
27
|
+
): {
|
|
28
|
+
conversationId: string;
|
|
29
|
+
} | null => ({
|
|
26
30
|
conversationId: "conv-test-123",
|
|
27
31
|
}),
|
|
28
32
|
);
|
|
@@ -80,13 +84,6 @@ mock.module("../runtime/routes/identity-intro-cache.js", () => ({
|
|
|
80
84
|
setCachedIntro: () => {},
|
|
81
85
|
}));
|
|
82
86
|
|
|
83
|
-
const assistantFeatureFlags: Record<string, boolean> = {};
|
|
84
|
-
|
|
85
|
-
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
86
|
-
isAssistantFeatureFlagEnabled: (key: string) =>
|
|
87
|
-
assistantFeatureFlags[key] ?? false,
|
|
88
|
-
}));
|
|
89
|
-
|
|
90
87
|
// Mock getOrCreateConversation from conversation-store so the handler
|
|
91
88
|
// never touches DaemonServer.
|
|
92
89
|
const mockGetOrCreateConversation = mock(async (_id: string) =>
|
|
@@ -185,12 +182,6 @@ function makeMockSession(
|
|
|
185
182
|
const route = ROUTES.find((r) => r.endpoint === "btw" && r.method === "POST");
|
|
186
183
|
if (!route) throw new Error("btw route not found in ROUTES");
|
|
187
184
|
|
|
188
|
-
beforeEach(() => {
|
|
189
|
-
for (const key of Object.keys(assistantFeatureFlags)) {
|
|
190
|
-
delete assistantFeatureFlags[key];
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
185
|
async function callHandler(
|
|
195
186
|
body: Record<string, unknown>,
|
|
196
187
|
): Promise<{ result: unknown; error?: unknown }> {
|
|
@@ -338,25 +329,7 @@ describe("POST /v1/btw", () => {
|
|
|
338
329
|
expect(options!.config!.modelIntent).toBeUndefined();
|
|
339
330
|
});
|
|
340
331
|
|
|
341
|
-
test("greeting requests
|
|
342
|
-
mockGetOrCreateConversation.mockClear();
|
|
343
|
-
|
|
344
|
-
const { result } = await callHandler({
|
|
345
|
-
conversationKey: "greeting",
|
|
346
|
-
content: "Generate a greeting",
|
|
347
|
-
});
|
|
348
|
-
const text = await readStream(result as ReadableStream<Uint8Array>);
|
|
349
|
-
|
|
350
|
-
expect(text).toContain(
|
|
351
|
-
`event: btw_text_delta\ndata: {"text":"What are we working on?"}`,
|
|
352
|
-
);
|
|
353
|
-
expect(text).toContain("event: btw_complete\ndata: {}");
|
|
354
|
-
expect(mockGetOrCreateConversation).not.toHaveBeenCalled();
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
test("greeting requests pass callSite: 'emptyStateGreeting' when the dynamic greetings flag is on", async () => {
|
|
358
|
-
assistantFeatureFlags["empty-state-dynamic-greetings"] = true;
|
|
359
|
-
|
|
332
|
+
test("greeting requests pass callSite: 'emptyStateGreeting'", async () => {
|
|
360
333
|
const provider = makeMockProvider();
|
|
361
334
|
const session = makeMockSession(provider);
|
|
362
335
|
mockGetOrCreateConversation.mockImplementationOnce(async () => session);
|
|
@@ -428,21 +428,15 @@ describe("Permission Checker", () => {
|
|
|
428
428
|
expect(result.decision).toBe("allow");
|
|
429
429
|
});
|
|
430
430
|
|
|
431
|
-
test("file_read of workspace
|
|
432
|
-
const
|
|
433
|
-
const result = await check("file_read", { path:
|
|
434
|
-
expect(result.decision).toBe("allow");
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test("file_write of workspace UPDATES.md is auto-allowed", async () => {
|
|
438
|
-
const updatesPath = join(checkerTestDir, "UPDATES.md");
|
|
439
|
-
const result = await check("file_write", { path: updatesPath }, "/tmp");
|
|
431
|
+
test("file_read of workspace BOOTSTRAP.md is auto-allowed", async () => {
|
|
432
|
+
const bootstrapPath = join(checkerTestDir, "BOOTSTRAP.md");
|
|
433
|
+
const result = await check("file_read", { path: bootstrapPath }, "/tmp");
|
|
440
434
|
expect(result.decision).toBe("allow");
|
|
441
435
|
});
|
|
442
436
|
|
|
443
|
-
test("file_edit of workspace
|
|
444
|
-
const
|
|
445
|
-
const result = await check("file_edit", { path:
|
|
437
|
+
test("file_edit of workspace BOOTSTRAP.md is auto-allowed", async () => {
|
|
438
|
+
const bootstrapPath = join(checkerTestDir, "BOOTSTRAP.md");
|
|
439
|
+
const result = await check("file_edit", { path: bootstrapPath }, "/tmp");
|
|
446
440
|
expect(result.decision).toBe("allow");
|
|
447
441
|
});
|
|
448
442
|
|
|
@@ -82,6 +82,7 @@ import { migrateProviderConnectionStatusLabel } from "../memory/migrations/244-p
|
|
|
82
82
|
import { migrateProviderConnectionBaseUrlAndModels } from "../memory/migrations/250-provider-connection-base-url-and-models.js";
|
|
83
83
|
import * as schema from "../memory/schema.js";
|
|
84
84
|
import { getConnection } from "../providers/inference/connections.js";
|
|
85
|
+
import { getConfigQuarantineNoticePath } from "../util/platform.js";
|
|
85
86
|
import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
|
|
86
87
|
|
|
87
88
|
// ---------------------------------------------------------------------------
|
|
@@ -325,8 +326,9 @@ describe("loadConfig startup behavior", () => {
|
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
}
|
|
328
|
-
|
|
329
|
-
|
|
329
|
+
// Clear any leftover config-quarantine notice sentinel from prior runs.
|
|
330
|
+
const noticePath = getConfigQuarantineNoticePath();
|
|
331
|
+
if (existsSync(noticePath)) rmSync(noticePath, { force: true });
|
|
330
332
|
ensureTestDir();
|
|
331
333
|
setStorePathForTesting(join(WORKSPACE_DIR, "keys.enc"));
|
|
332
334
|
delete process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH;
|