@vellumai/assistant 0.8.2 → 0.8.3
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 +11 -12
- package/docker-entrypoint.sh +13 -1
- package/docker-init-apt-root.sh +79 -6
- package/openapi.yaml +336 -21
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/context-token-estimator.test.ts +30 -65
- package/src/__tests__/conversation-agent-loop.test.ts +57 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-runtime-assembly.test.ts +26 -4
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +5 -0
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +255 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +218 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +6 -73
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/loop.ts +167 -18
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/notifications.ts +65 -35
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/call-site-defaults.ts +105 -0
- package/src/config/feature-flag-registry.json +21 -29
- package/src/config/llm-resolver.ts +52 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +1 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +4 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +72 -12
- package/src/context/token-estimator.ts +32 -34
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
- package/src/daemon/conversation-agent-loop.ts +29 -2
- package/src/daemon/conversation-runtime-assembly.ts +9 -0
- package/src/daemon/conversation.ts +0 -7
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +289 -0
- package/src/daemon/handlers/conversations.ts +1 -0
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/lifecycle.ts +49 -61
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/process-message.ts +3 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -0
- package/src/heartbeat/heartbeat-service.ts +34 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/feed-types.ts +14 -2
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +6 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-enqueue.ts +1 -20
- package/src/memory/memory-retrospective-job.ts +33 -6
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection.test.ts +190 -3
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection.ts +49 -20
- package/src/memory/v2/page-index.ts +38 -13
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +11 -2
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +1 -0
- package/src/notifications/home-feed-side-effect.ts +85 -6
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/plugins/defaults/injectors.ts +38 -19
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +6 -51
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/system-prompt.ts +0 -8
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/system-sections.ts +0 -9
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +9 -20
- package/src/providers/inference/auth.ts +12 -0
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +2 -0
- package/src/providers/model-catalog.ts +199 -244
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +159 -6
- package/src/providers/openrouter/client.ts +42 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/runtime/agent-wake.ts +61 -1
- package/src/runtime/auth/route-policy.ts +13 -0
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +0 -47
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +66 -4
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/conversation-query-routes.ts +70 -11
- package/src/runtime/routes/conversation-routes.ts +7 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +134 -1
- package/src/runtime/routes/integrations/a2a.ts +235 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/subagent/manager.ts +2 -0
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/registry.ts +2 -2
- package/src/tools/types.ts +37 -2
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies that `GET /v1/config` enriches each profile in `llm.profiles`
|
|
3
|
+
* with `supportsVision` resolved from the model catalog.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import { makeMockLogger } from "./helpers/mock-logger.js";
|
|
9
|
+
|
|
10
|
+
mock.module("../util/logger.js", () => ({
|
|
11
|
+
getLogger: () => makeMockLogger(),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Mocks for handleGetConfig's transitive deps
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
let rawConfig: Record<string, unknown> = {};
|
|
19
|
+
|
|
20
|
+
mock.module("../config/loader.js", () => ({
|
|
21
|
+
loadRawConfig: () => structuredClone(rawConfig),
|
|
22
|
+
saveRawConfig: () => {},
|
|
23
|
+
deepMergeOverwrite: () => {},
|
|
24
|
+
getConfig: () => rawConfig,
|
|
25
|
+
getDeploymentContextDefaults: () => ({}),
|
|
26
|
+
fillContextDefaultsForMissingKeys: () => {},
|
|
27
|
+
invalidateConfigCache: () => {},
|
|
28
|
+
setNestedValue: () => {},
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
mock.module("../providers/registry.js", () => ({
|
|
32
|
+
initializeProviders: async () => {},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
mock.module("../memory/embedding-backend.js", () => ({
|
|
36
|
+
clearEmbeddingBackendCache: () => {},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
mock.module("../security/secret-allowlist.js", () => ({
|
|
40
|
+
validateAllowlistFile: () => null,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
import { ROUTES } from "../runtime/routes/conversation-query-routes.js";
|
|
44
|
+
|
|
45
|
+
const configGetRoute = ROUTES.find((r) => r.operationId === "config_get")!;
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Tests
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
describe("GET /v1/config profile vision enrichment", () => {
|
|
52
|
+
test("profile with a non-vision model gets supportsVision: false", () => {
|
|
53
|
+
rawConfig = {
|
|
54
|
+
llm: {
|
|
55
|
+
profiles: {
|
|
56
|
+
"test-no-vision": {
|
|
57
|
+
provider: "fireworks",
|
|
58
|
+
model: "accounts/fireworks/models/kimi-k2p5",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const result = configGetRoute.handler({}) as {
|
|
65
|
+
llm?: {
|
|
66
|
+
profiles?: Record<string, { supportsVision?: boolean }>;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
expect(result?.llm?.profiles?.["test-no-vision"]?.supportsVision).toBe(
|
|
71
|
+
false,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("profile with a vision-capable model gets supportsVision: true", () => {
|
|
76
|
+
rawConfig = {
|
|
77
|
+
llm: {
|
|
78
|
+
profiles: {
|
|
79
|
+
"test-vision": {
|
|
80
|
+
provider: "anthropic",
|
|
81
|
+
model: "claude-opus-4-6",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const result = configGetRoute.handler({}) as {
|
|
88
|
+
llm?: {
|
|
89
|
+
profiles?: Record<string, { supportsVision?: boolean }>;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
expect(result?.llm?.profiles?.["test-vision"]?.supportsVision).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("profile with an unknown model defaults supportsVision to true (fail-open)", () => {
|
|
97
|
+
rawConfig = {
|
|
98
|
+
llm: {
|
|
99
|
+
profiles: {
|
|
100
|
+
"test-unknown": {
|
|
101
|
+
provider: "anthropic",
|
|
102
|
+
model: "some-unknown-model-xyz",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = configGetRoute.handler({}) as {
|
|
109
|
+
llm?: {
|
|
110
|
+
profiles?: Record<string, { supportsVision?: boolean }>;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
expect(result?.llm?.profiles?.["test-unknown"]?.supportsVision).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("profile without provider/model is left without supportsVision", () => {
|
|
118
|
+
rawConfig = {
|
|
119
|
+
llm: {
|
|
120
|
+
profiles: {
|
|
121
|
+
"test-empty": {},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const result = configGetRoute.handler({}) as {
|
|
127
|
+
llm?: {
|
|
128
|
+
profiles?: Record<string, { supportsVision?: boolean }>;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
expect(
|
|
133
|
+
result?.llm?.profiles?.["test-empty"]?.supportsVision,
|
|
134
|
+
).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
writeFileSync,
|
|
8
8
|
} from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
import { Database } from "bun:sqlite";
|
|
10
11
|
import {
|
|
11
12
|
afterAll,
|
|
12
13
|
afterEach,
|
|
@@ -17,6 +18,8 @@ import {
|
|
|
17
18
|
test,
|
|
18
19
|
} from "bun:test";
|
|
19
20
|
|
|
21
|
+
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
22
|
+
|
|
20
23
|
// ---------------------------------------------------------------------------
|
|
21
24
|
// Mocks — declared before imports that depend on platform/logger
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
@@ -73,6 +76,12 @@ import {
|
|
|
73
76
|
mergeDefaultWorkspaceConfig,
|
|
74
77
|
} from "../config/loader.js";
|
|
75
78
|
import { seedInferenceProfiles } from "../config/seed-inference-profiles.js";
|
|
79
|
+
import type { DrizzleDb } from "../memory/db-connection.js";
|
|
80
|
+
import { migrateCreateProviderConnections } from "../memory/migrations/243-provider-connections.js";
|
|
81
|
+
import { migrateProviderConnectionStatusLabel } from "../memory/migrations/244-provider-connection-status-label.js";
|
|
82
|
+
import { migrateProviderConnectionBaseUrlAndModels } from "../memory/migrations/250-provider-connection-base-url-and-models.js";
|
|
83
|
+
import * as schema from "../memory/schema.js";
|
|
84
|
+
import { getConnection } from "../providers/inference/connections.js";
|
|
76
85
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
77
86
|
|
|
78
87
|
// ---------------------------------------------------------------------------
|
|
@@ -83,15 +92,26 @@ function writeConfig(obj: unknown): void {
|
|
|
83
92
|
writeFileSync(CONFIG_PATH, JSON.stringify(obj, null, 2) + "\n");
|
|
84
93
|
}
|
|
85
94
|
|
|
86
|
-
function mergeDefaultConfigAndSeedInferenceProfiles(): void {
|
|
95
|
+
function mergeDefaultConfigAndSeedInferenceProfiles(db?: DrizzleDb): void {
|
|
87
96
|
const defaultConfigMerge = mergeDefaultWorkspaceConfig();
|
|
88
97
|
seedInferenceProfiles({
|
|
89
98
|
preserveProfileNames: defaultConfigMerge.providedLlmProfileNames,
|
|
90
99
|
preserveActiveProfile: defaultConfigMerge.providedLlmActiveProfile,
|
|
91
100
|
isHatch: defaultConfigMerge.hadOverlay,
|
|
101
|
+
db,
|
|
92
102
|
});
|
|
93
103
|
}
|
|
94
104
|
|
|
105
|
+
function createProviderConnectionsDb(): DrizzleDb {
|
|
106
|
+
const sqlite = new Database(":memory:");
|
|
107
|
+
sqlite.exec("PRAGMA journal_mode=WAL");
|
|
108
|
+
const db = drizzle(sqlite, { schema });
|
|
109
|
+
migrateCreateProviderConnections(db);
|
|
110
|
+
migrateProviderConnectionStatusLabel(db);
|
|
111
|
+
migrateProviderConnectionBaseUrlAndModels(db);
|
|
112
|
+
return db;
|
|
113
|
+
}
|
|
114
|
+
|
|
95
115
|
// ---------------------------------------------------------------------------
|
|
96
116
|
// Tests: deepMergeOverwrite (unit) — JSON-null-as-deletion semantics
|
|
97
117
|
//
|
|
@@ -523,7 +543,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
523
543
|
expect(raw.llm.profiles["custom-balanced"].provider_connection).toBe(
|
|
524
544
|
"anthropic-personal",
|
|
525
545
|
);
|
|
526
|
-
// Managed
|
|
546
|
+
// Managed balanced profile is seeded for anthropic-managed.
|
|
527
547
|
expect(raw.llm.profiles.balanced.provider).toBe("anthropic");
|
|
528
548
|
expect(raw.llm.profiles.balanced.provider_connection).toBe(
|
|
529
549
|
"anthropic-managed",
|
|
@@ -589,11 +609,8 @@ describe("loadConfig startup behavior", () => {
|
|
|
589
609
|
const overlayPath = join(WORKSPACE_DIR, "hatch-overlay.json");
|
|
590
610
|
writeFileSync(
|
|
591
611
|
overlayPath,
|
|
592
|
-
JSON.stringify(
|
|
593
|
-
|
|
594
|
-
null,
|
|
595
|
-
2,
|
|
596
|
-
) + "\n",
|
|
612
|
+
JSON.stringify({ llm: { default: { provider: "openai" } } }, null, 2) +
|
|
613
|
+
"\n",
|
|
597
614
|
);
|
|
598
615
|
process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = overlayPath;
|
|
599
616
|
|
|
@@ -617,7 +634,7 @@ describe("loadConfig startup behavior", () => {
|
|
|
617
634
|
"gpt-5.4-nano",
|
|
618
635
|
);
|
|
619
636
|
|
|
620
|
-
// Managed
|
|
637
|
+
// Managed profiles are also seeded (balanced uses Anthropic).
|
|
621
638
|
expect(raw.llm.profiles.balanced.provider).toBe("anthropic");
|
|
622
639
|
expect(raw.llm.profiles.balanced.provider_connection).toBe(
|
|
623
640
|
"anthropic-managed",
|
|
@@ -981,11 +998,8 @@ describe("seedInferenceProfiles BYOK-mode managed profile labels", () => {
|
|
|
981
998
|
const overlayPath = join(WORKSPACE_DIR, "hatch-overlay.json");
|
|
982
999
|
writeFileSync(
|
|
983
1000
|
overlayPath,
|
|
984
|
-
JSON.stringify(
|
|
985
|
-
|
|
986
|
-
null,
|
|
987
|
-
2,
|
|
988
|
-
) + "\n",
|
|
1001
|
+
JSON.stringify({ llm: { default: { provider: "anthropic" } } }, null, 2) +
|
|
1002
|
+
"\n",
|
|
989
1003
|
);
|
|
990
1004
|
process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = overlayPath;
|
|
991
1005
|
|
|
@@ -997,6 +1011,92 @@ describe("seedInferenceProfiles BYOK-mode managed profile labels", () => {
|
|
|
997
1011
|
expect(config.llm.profiles["cost-optimized"]?.status).toBe("disabled");
|
|
998
1012
|
});
|
|
999
1013
|
|
|
1014
|
+
test("off-platform managed-inference hatch keeps selected managed connection active", () => {
|
|
1015
|
+
const overlayPath = join(WORKSPACE_DIR, "hatch-overlay.json");
|
|
1016
|
+
writeFileSync(
|
|
1017
|
+
overlayPath,
|
|
1018
|
+
JSON.stringify(
|
|
1019
|
+
{
|
|
1020
|
+
llm: {
|
|
1021
|
+
default: { provider: "anthropic" },
|
|
1022
|
+
activeProfile: "balanced",
|
|
1023
|
+
},
|
|
1024
|
+
},
|
|
1025
|
+
null,
|
|
1026
|
+
2,
|
|
1027
|
+
) + "\n",
|
|
1028
|
+
);
|
|
1029
|
+
process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = overlayPath;
|
|
1030
|
+
const db = createProviderConnectionsDb();
|
|
1031
|
+
|
|
1032
|
+
mergeDefaultConfigAndSeedInferenceProfiles(db);
|
|
1033
|
+
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
1034
|
+
|
|
1035
|
+
expect(raw.llm.activeProfile).toBe("balanced");
|
|
1036
|
+
expect(raw.llm.profiles.balanced.provider_connection).toBe(
|
|
1037
|
+
"anthropic-managed",
|
|
1038
|
+
);
|
|
1039
|
+
expect("status" in raw.llm.profiles.balanced).toBe(false);
|
|
1040
|
+
expect(getConnection(db, "anthropic-managed")?.status).toBe("active");
|
|
1041
|
+
expect(getConnection(db, "openai-managed")?.status).toBe("disabled");
|
|
1042
|
+
expect(getConnection(db, "gemini-managed")?.status).toBe("disabled");
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
test("off-platform managed-inference hatch respects explicit non-managed active connection", () => {
|
|
1046
|
+
const overlayPath = join(WORKSPACE_DIR, "hatch-overlay.json");
|
|
1047
|
+
writeFileSync(
|
|
1048
|
+
overlayPath,
|
|
1049
|
+
JSON.stringify(
|
|
1050
|
+
{
|
|
1051
|
+
llm: {
|
|
1052
|
+
default: { provider: "anthropic" },
|
|
1053
|
+
profiles: {
|
|
1054
|
+
balanced: {
|
|
1055
|
+
source: "managed",
|
|
1056
|
+
provider: "anthropic",
|
|
1057
|
+
provider_connection: "anthropic-personal",
|
|
1058
|
+
model: "claude-sonnet-4-6",
|
|
1059
|
+
},
|
|
1060
|
+
},
|
|
1061
|
+
activeProfile: "balanced",
|
|
1062
|
+
},
|
|
1063
|
+
},
|
|
1064
|
+
null,
|
|
1065
|
+
2,
|
|
1066
|
+
) + "\n",
|
|
1067
|
+
);
|
|
1068
|
+
process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = overlayPath;
|
|
1069
|
+
const db = createProviderConnectionsDb();
|
|
1070
|
+
|
|
1071
|
+
mergeDefaultConfigAndSeedInferenceProfiles(db);
|
|
1072
|
+
const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
1073
|
+
|
|
1074
|
+
expect(raw.llm.activeProfile).toBe("balanced");
|
|
1075
|
+
expect(raw.llm.profiles.balanced.provider_connection).toBe(
|
|
1076
|
+
"anthropic-personal",
|
|
1077
|
+
);
|
|
1078
|
+
expect(getConnection(db, "anthropic-managed")?.status).toBe("disabled");
|
|
1079
|
+
expect(getConnection(db, "openai-managed")?.status).toBe("disabled");
|
|
1080
|
+
expect(getConnection(db, "gemini-managed")?.status).toBe("disabled");
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
test("off-platform BYOK hatch still disables managed connections", () => {
|
|
1084
|
+
const overlayPath = join(WORKSPACE_DIR, "hatch-overlay.json");
|
|
1085
|
+
writeFileSync(
|
|
1086
|
+
overlayPath,
|
|
1087
|
+
JSON.stringify({ llm: { default: { provider: "anthropic" } } }, null, 2) +
|
|
1088
|
+
"\n",
|
|
1089
|
+
);
|
|
1090
|
+
process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = overlayPath;
|
|
1091
|
+
const db = createProviderConnectionsDb();
|
|
1092
|
+
|
|
1093
|
+
mergeDefaultConfigAndSeedInferenceProfiles(db);
|
|
1094
|
+
|
|
1095
|
+
expect(getConnection(db, "anthropic-managed")?.status).toBe("disabled");
|
|
1096
|
+
expect(getConnection(db, "openai-managed")?.status).toBe("disabled");
|
|
1097
|
+
expect(getConnection(db, "gemini-managed")?.status).toBe("disabled");
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1000
1100
|
test("non-hatch off-platform boot does NOT auto-disable freshly-materialized managed profiles", () => {
|
|
1001
1101
|
// Existing installs that upgrade to a version where the managed
|
|
1002
1102
|
// profile didn't previously exist (e.g. a new template added later)
|
|
@@ -1029,11 +1129,8 @@ describe("seedInferenceProfiles BYOK-mode managed profile labels", () => {
|
|
|
1029
1129
|
const overlayPath = join(WORKSPACE_DIR, "hatch-overlay.json");
|
|
1030
1130
|
writeFileSync(
|
|
1031
1131
|
overlayPath,
|
|
1032
|
-
JSON.stringify(
|
|
1033
|
-
|
|
1034
|
-
null,
|
|
1035
|
-
2,
|
|
1036
|
-
) + "\n",
|
|
1132
|
+
JSON.stringify({ llm: { default: { provider: "anthropic" } } }, null, 2) +
|
|
1133
|
+
"\n",
|
|
1037
1134
|
);
|
|
1038
1135
|
process.env.VELLUM_DEFAULT_WORKSPACE_CONFIG_PATH = overlayPath;
|
|
1039
1136
|
|
|
@@ -219,36 +219,7 @@ describe("token estimator", () => {
|
|
|
219
219
|
expect(largeFileTokens).toBe(smallFileTokens);
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
-
|
|
223
|
-
test("scales image token estimate with base64 payload size (non-Anthropic)", () => {
|
|
224
|
-
const smallImageTokens = estimateContentBlockTokens(
|
|
225
|
-
{
|
|
226
|
-
type: "image",
|
|
227
|
-
source: {
|
|
228
|
-
type: "base64",
|
|
229
|
-
media_type: "image/png",
|
|
230
|
-
data: "a".repeat(64),
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
{ providerName: "openai" },
|
|
234
|
-
);
|
|
235
|
-
const largeImageTokens = estimateContentBlockTokens(
|
|
236
|
-
{
|
|
237
|
-
type: "image",
|
|
238
|
-
source: {
|
|
239
|
-
type: "base64",
|
|
240
|
-
media_type: "image/png",
|
|
241
|
-
data: "a".repeat(60_000),
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
{ providerName: "openai" },
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
expect(largeImageTokens).toBeGreaterThan(smallImageTokens);
|
|
248
|
-
expect(largeImageTokens - smallImageTokens).toBeGreaterThan(1000);
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
test("estimates Anthropic image tokens from dimensions, not base64 size", () => {
|
|
222
|
+
test("estimates image tokens from dimensions, not base64 size", () => {
|
|
252
223
|
// Build a minimal valid PNG header encoding 1920x1080 dimensions.
|
|
253
224
|
// PNG header: 8-byte signature + 4-byte IHDR length + 4-byte "IHDR" + 4-byte width + 4-byte height = 24 bytes minimum
|
|
254
225
|
const pngHeader = Buffer.alloc(24);
|
|
@@ -278,55 +249,49 @@ describe("token estimator", () => {
|
|
|
278
249
|
const fullPayload = Buffer.concat([pngHeader, padding]);
|
|
279
250
|
const base64Data = fullPayload.toString("base64");
|
|
280
251
|
|
|
281
|
-
const anthropicTokens = estimateContentBlockTokens(
|
|
282
|
-
{
|
|
283
|
-
type: "image",
|
|
284
|
-
source: { type: "base64", media_type: "image/png", data: base64Data },
|
|
285
|
-
},
|
|
286
|
-
{ providerName: "anthropic" },
|
|
287
|
-
);
|
|
288
|
-
|
|
289
252
|
// 1920x1080 scaled to fit 1568px bounding box: dimScale = 1568/1920 = 0.8167
|
|
290
253
|
// scaledWidth = round(1920 * 0.8167) = 1568, scaledHeight = round(1080 * 0.8167) = 882
|
|
291
254
|
// pixels = 1568 * 882 = 1,382,976 > 1,200,000 → mpScale = sqrt(1200000/1382976) = 0.9315
|
|
292
255
|
// scaledWidth = round(1568 * 0.9315) = 1461, scaledHeight = round(882 * 0.9315) = 822
|
|
293
256
|
// tokens = ceil(1461 * 822 / 750) = ceil(1601.26) = ~1,602
|
|
294
|
-
// With IMAGE_BLOCK_OVERHEAD_TOKENS and media_type overhead, still well under 5000
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
257
|
+
// With IMAGE_BLOCK_OVERHEAD_TOKENS and media_type overhead, still well under 5000.
|
|
258
|
+
// Same result for every provider — dimension-based estimate is universal.
|
|
259
|
+
for (const providerName of ["anthropic", "openai", "openrouter"]) {
|
|
260
|
+
const tokens = estimateContentBlockTokens(
|
|
261
|
+
{
|
|
262
|
+
type: "image",
|
|
263
|
+
source: { type: "base64", media_type: "image/png", data: base64Data },
|
|
264
|
+
},
|
|
265
|
+
{ providerName },
|
|
266
|
+
);
|
|
267
|
+
expect(tokens).toBeLessThan(5_000);
|
|
268
|
+
}
|
|
306
269
|
});
|
|
307
270
|
|
|
308
|
-
test("falls back to max tokens when
|
|
271
|
+
test("falls back to max tokens when image dimensions can't be parsed", () => {
|
|
309
272
|
// Corrupted base64 that won't parse as a valid image header
|
|
310
273
|
const corruptedData = Buffer.from(
|
|
311
274
|
"not-a-valid-image-header-at-all",
|
|
312
275
|
).toString("base64");
|
|
313
276
|
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
277
|
+
for (const providerName of ["anthropic", "openai", "openrouter"]) {
|
|
278
|
+
const tokens = estimateContentBlockTokens(
|
|
279
|
+
{
|
|
280
|
+
type: "image",
|
|
281
|
+
source: {
|
|
282
|
+
type: "base64",
|
|
283
|
+
media_type: "image/png",
|
|
284
|
+
data: corruptedData,
|
|
285
|
+
},
|
|
321
286
|
},
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
);
|
|
287
|
+
{ providerName },
|
|
288
|
+
);
|
|
325
289
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
290
|
+
// Falls back to the per-image cap (1,600 tokens). Total = 16 (block
|
|
291
|
+
// overhead) + ceil(9/4) (media_type) + 1600 = 1619.
|
|
292
|
+
expect(tokens).toBeGreaterThanOrEqual(1_600);
|
|
293
|
+
expect(tokens).toBeLessThan(2_000);
|
|
294
|
+
}
|
|
330
295
|
});
|
|
331
296
|
|
|
332
297
|
test("Anthropic image tokens are the same for same-dimension images regardless of payload size", () => {
|
|
@@ -386,6 +386,7 @@ mock.module("../daemon/history-repair.js", () => ({
|
|
|
386
386
|
|
|
387
387
|
const recordUsageMock = mock(() => {});
|
|
388
388
|
const recordRequestLogMock = mock(() => {});
|
|
389
|
+
const backfillMessageIdOnLogsMock = mock(() => {});
|
|
389
390
|
mock.module("../daemon/conversation-usage.js", () => ({
|
|
390
391
|
recordUsage: recordUsageMock,
|
|
391
392
|
}));
|
|
@@ -482,7 +483,7 @@ mock.module("../memory/archive-store.js", () => ({
|
|
|
482
483
|
|
|
483
484
|
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
484
485
|
recordRequestLog: recordRequestLogMock,
|
|
485
|
-
backfillMessageIdOnLogs:
|
|
486
|
+
backfillMessageIdOnLogs: backfillMessageIdOnLogsMock,
|
|
486
487
|
}));
|
|
487
488
|
|
|
488
489
|
let mockHasProactiveArtifactCompleted = true;
|
|
@@ -658,6 +659,7 @@ beforeEach(() => {
|
|
|
658
659
|
mockInjectionBlocks = {};
|
|
659
660
|
recordUsageMock.mockClear();
|
|
660
661
|
recordRequestLogMock.mockClear();
|
|
662
|
+
backfillMessageIdOnLogsMock.mockClear();
|
|
661
663
|
syncMessageToDiskMock.mockClear();
|
|
662
664
|
rebuildConversationDiskViewFromDbStateMock.mockClear();
|
|
663
665
|
updateMessageMetadataMock.mockClear();
|
|
@@ -2855,6 +2857,60 @@ describe("session-agent-loop", () => {
|
|
|
2855
2857
|
);
|
|
2856
2858
|
expect(conversationErrors.length).toBeGreaterThanOrEqual(1);
|
|
2857
2859
|
});
|
|
2860
|
+
|
|
2861
|
+
test("pipes synthetic assistant message id into provider-error log rows via backfill", async () => {
|
|
2862
|
+
// Codex P1 regression test: the provider-failure turn must not leave
|
|
2863
|
+
// its `llm_request_logs` row orphaned. Without the backfill call in
|
|
2864
|
+
// the synthetic-message branch, a later turn's `handleMessageComplete`
|
|
2865
|
+
// sweep would wrong-attach this row to the wrong assistant message.
|
|
2866
|
+
const events: ServerMessage[] = [];
|
|
2867
|
+
|
|
2868
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
2869
|
+
// 1) handleProviderError -> writes an `llm_request_logs` row with
|
|
2870
|
+
// messageId=null (the orphan we are trying to link).
|
|
2871
|
+
onEvent({
|
|
2872
|
+
type: "provider_error",
|
|
2873
|
+
error: new Error("upstream 500"),
|
|
2874
|
+
rawRequest: { model: "gpt-4.1", messages: [] },
|
|
2875
|
+
actualProvider: "openai",
|
|
2876
|
+
});
|
|
2877
|
+
// 2) handleError -> sets `state.providerErrorUserMessage`, which
|
|
2878
|
+
// activates the synthetic-message branch below the loop.
|
|
2879
|
+
onEvent({
|
|
2880
|
+
type: "error",
|
|
2881
|
+
error: new Error("upstream 500"),
|
|
2882
|
+
});
|
|
2883
|
+
// Provider returned no assistant content — same messages back.
|
|
2884
|
+
return messages;
|
|
2885
|
+
};
|
|
2886
|
+
|
|
2887
|
+
const ctx = makeCtx({ agentLoopRun });
|
|
2888
|
+
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
2889
|
+
|
|
2890
|
+
// The orphan was written with messageId=undefined.
|
|
2891
|
+
expect(recordRequestLogMock).toHaveBeenCalledTimes(1);
|
|
2892
|
+
const recordCall = recordRequestLogMock.mock.calls[0] as unknown as [
|
|
2893
|
+
string,
|
|
2894
|
+
string,
|
|
2895
|
+
string,
|
|
2896
|
+
string | undefined,
|
|
2897
|
+
string | undefined,
|
|
2898
|
+
];
|
|
2899
|
+
expect(recordCall[0]).toBe("test-conv");
|
|
2900
|
+
expect(recordCall[3]).toBeUndefined();
|
|
2901
|
+
|
|
2902
|
+
// The synthetic-message branch then piped the assigned message id
|
|
2903
|
+
// (from the mocked `addMessage` -> `{ id: "mock-msg-id" }`) into the
|
|
2904
|
+
// backfill primitive, scoped to this conversation.
|
|
2905
|
+
expect(backfillMessageIdOnLogsMock).toHaveBeenCalledTimes(1);
|
|
2906
|
+
const backfillCall =
|
|
2907
|
+
backfillMessageIdOnLogsMock.mock.calls[0] as unknown as [
|
|
2908
|
+
string,
|
|
2909
|
+
string,
|
|
2910
|
+
];
|
|
2911
|
+
expect(backfillCall[0]).toBe("test-conv");
|
|
2912
|
+
expect(backfillCall[1]).toBe("mock-msg-id");
|
|
2913
|
+
});
|
|
2858
2914
|
});
|
|
2859
2915
|
|
|
2860
2916
|
describe("pkbSystemReminderBlock metadata persistence", () => {
|
|
@@ -23,7 +23,11 @@ function makeImageBlockWithSize(
|
|
|
23
23
|
): Extract<ContentBlock, { type: "image" }> {
|
|
24
24
|
return {
|
|
25
25
|
type: "image",
|
|
26
|
-
source: {
|
|
26
|
+
source: {
|
|
27
|
+
type: "base64",
|
|
28
|
+
media_type: "image/png",
|
|
29
|
+
data: "A".repeat(dataLength),
|
|
30
|
+
},
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -103,16 +107,19 @@ describe("stripMediaPayloadsForRetry", () => {
|
|
|
103
107
|
// ---------------------------------------------------------------------------
|
|
104
108
|
|
|
105
109
|
test("budget-aware: keeps images that fit within token budget", () => {
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// Budget of
|
|
109
|
-
|
|
110
|
+
// Dimension-based estimation: when the base64 data has no parseable image
|
|
111
|
+
// header, fall back to IMAGE_MAX_TOKENS (1600) + overhead (~19 tokens) ≈
|
|
112
|
+
// 1619 tokens/image. Budget of 5000 allows 3 images (3 * 1619 = 4857
|
|
113
|
+
// <= 5000) but not 4 (4 * 1619 = 6476 > 5000).
|
|
114
|
+
const images = Array.from({ length: 5 }, () =>
|
|
115
|
+
makeImageBlockWithSize(4000),
|
|
116
|
+
);
|
|
110
117
|
const messages: Message[] = [
|
|
111
118
|
makeUserMessage({ type: "text", text: "describe these" }, ...images),
|
|
112
119
|
];
|
|
113
120
|
|
|
114
121
|
const result = stripMediaPayloadsForRetry(messages, {
|
|
115
|
-
mediaTokenBudget:
|
|
122
|
+
mediaTokenBudget: 5000,
|
|
116
123
|
providerName: "mock",
|
|
117
124
|
});
|
|
118
125
|
expect(result.modified).toBe(true);
|
|
@@ -120,7 +127,9 @@ describe("stripMediaPayloadsForRetry", () => {
|
|
|
120
127
|
const content = result.messages[0].content;
|
|
121
128
|
const keptImages = content.filter((b) => b.type === "image");
|
|
122
129
|
const stubs = content.filter(
|
|
123
|
-
(b) =>
|
|
130
|
+
(b) =>
|
|
131
|
+
b.type === "text" &&
|
|
132
|
+
(b as { text: string }).text.includes("Image omitted"),
|
|
124
133
|
);
|
|
125
134
|
expect(keptImages.length).toBe(3);
|
|
126
135
|
expect(stubs.length).toBe(2);
|
|
@@ -174,7 +183,9 @@ describe("stripMediaPayloadsForRetry", () => {
|
|
|
174
183
|
const content = result.messages[0].content;
|
|
175
184
|
const keptImages = content.filter((b) => b.type === "image");
|
|
176
185
|
const stubs = content.filter(
|
|
177
|
-
(b) =>
|
|
186
|
+
(b) =>
|
|
187
|
+
b.type === "text" &&
|
|
188
|
+
(b as { text: string }).text.includes("Image omitted"),
|
|
178
189
|
);
|
|
179
190
|
expect(keptImages.length).toBe(3);
|
|
180
191
|
expect(stubs.length).toBe(2);
|
|
@@ -930,6 +930,28 @@ describe("stripInjectionsForCompaction with NOW.md", () => {
|
|
|
930
930
|
"Hello",
|
|
931
931
|
);
|
|
932
932
|
});
|
|
933
|
+
|
|
934
|
+
test("strips <background_turn> blocks", () => {
|
|
935
|
+
const messages: Message[] = [
|
|
936
|
+
{
|
|
937
|
+
role: "user",
|
|
938
|
+
content: [
|
|
939
|
+
{
|
|
940
|
+
type: "text",
|
|
941
|
+
text: "<background_turn>\nGuardian isn't watching — notify on anything noteworthy.\n</background_turn>",
|
|
942
|
+
},
|
|
943
|
+
{ type: "text", text: "Hello" },
|
|
944
|
+
],
|
|
945
|
+
},
|
|
946
|
+
];
|
|
947
|
+
|
|
948
|
+
const result = stripInjectionsForCompaction(messages);
|
|
949
|
+
expect(result.length).toBe(1);
|
|
950
|
+
expect(result[0].content.length).toBe(1);
|
|
951
|
+
expect((result[0].content[0] as { type: "text"; text: string }).text).toBe(
|
|
952
|
+
"Hello",
|
|
953
|
+
);
|
|
954
|
+
});
|
|
933
955
|
});
|
|
934
956
|
|
|
935
957
|
// ---------------------------------------------------------------------------
|
|
@@ -1880,7 +1902,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
1880
1902
|
},
|
|
1881
1903
|
];
|
|
1882
1904
|
|
|
1883
|
-
const FLAT_REMINDER = buildPkbReminder([]
|
|
1905
|
+
const FLAT_REMINDER = buildPkbReminder([]);
|
|
1884
1906
|
|
|
1885
1907
|
// Use a platform-agnostic absolute workspace root so the tests work on
|
|
1886
1908
|
// macOS and Linux runners alike. `pkbRoot` sits under `pkbWorkingDir` to
|
|
@@ -2136,7 +2158,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2136
2158
|
role: "user",
|
|
2137
2159
|
content: [
|
|
2138
2160
|
{ type: "text", text: "hello" },
|
|
2139
|
-
{ type: "text", text: buildPkbReminder([]
|
|
2161
|
+
{ type: "text", text: buildPkbReminder([]) },
|
|
2140
2162
|
],
|
|
2141
2163
|
};
|
|
2142
2164
|
const hintedMessage: Message = {
|
|
@@ -2145,7 +2167,7 @@ describe("applyRuntimeInjections — PKB relevance hints", () => {
|
|
|
2145
2167
|
{ type: "text", text: "hello" },
|
|
2146
2168
|
{
|
|
2147
2169
|
type: "text",
|
|
2148
|
-
text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"]
|
|
2170
|
+
text: buildPkbReminder(["topics/alpha.md", "topics/beta.md"]),
|
|
2149
2171
|
},
|
|
2150
2172
|
],
|
|
2151
2173
|
};
|
|
@@ -4827,7 +4849,7 @@ describe("applyRuntimeInjections blocks.pkbSystemReminder", () => {
|
|
|
4827
4849
|
mode: "full",
|
|
4828
4850
|
});
|
|
4829
4851
|
|
|
4830
|
-
const expected = buildPkbReminder([]
|
|
4852
|
+
const expected = buildPkbReminder([]);
|
|
4831
4853
|
expect(blocks.pkbSystemReminder).toBe(expected);
|
|
4832
4854
|
});
|
|
4833
4855
|
|