@vellumai/assistant 0.4.52 → 0.4.53
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 +2 -2
- package/docs/architecture/keychain-broker.md +6 -20
- package/docs/architecture/memory.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +3 -1
- package/src/__tests__/approval-routes-http.test.ts +0 -1
- package/src/__tests__/asset-materialize-tool.test.ts +0 -1
- package/src/__tests__/asset-search-tool.test.ts +0 -1
- package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
- package/src/__tests__/attachments-store.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +6 -1
- package/src/__tests__/browser-fill-credential.test.ts +3 -0
- package/src/__tests__/btw-routes.test.ts +39 -0
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +1 -0
- package/src/__tests__/call-routes-http.test.ts +1 -2
- package/src/__tests__/canonical-guardian-store.test.ts +33 -2
- package/src/__tests__/channel-readiness-service.test.ts +1 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +6 -2
- package/src/__tests__/claude-code-tool-profiles.test.ts +7 -2
- package/src/__tests__/config-loader-backfill.test.ts +1 -2
- package/src/__tests__/config-schema.test.ts +6 -37
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
- package/src/__tests__/credential-broker-server-use.test.ts +16 -16
- package/src/__tests__/credential-security-invariants.test.ts +14 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -2
- package/src/__tests__/host-shell-tool.test.ts +0 -1
- package/src/__tests__/http-user-message-parity.test.ts +19 -0
- package/src/__tests__/list-messages-attachments.test.ts +0 -1
- package/src/__tests__/log-export-workspace.test.ts +233 -0
- package/src/__tests__/managed-proxy-context.test.ts +1 -1
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/media-generate-image.test.ts +7 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -1
- package/src/__tests__/memory-regressions.test.ts +0 -1
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
- package/src/__tests__/migration-export-http.test.ts +0 -1
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
- package/src/__tests__/migration-validate-http.test.ts +0 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
- package/src/__tests__/oauth-cli.test.ts +1 -10
- package/src/__tests__/oauth-store.test.ts +3 -5
- package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
- package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -2
- package/src/__tests__/pricing.test.ts +0 -11
- package/src/__tests__/provider-commit-message-generator.test.ts +21 -14
- package/src/__tests__/provider-fail-open-selection.test.ts +9 -8
- package/src/__tests__/provider-managed-proxy-integration.test.ts +27 -24
- package/src/__tests__/provider-registry-ollama.test.ts +8 -2
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +0 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
- package/src/__tests__/runtime-events-sse.test.ts +0 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -1
- package/src/__tests__/secret-scanner-executor.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/session-abort-tool-results.test.ts +3 -1
- package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
- package/src/__tests__/session-agent-loop.test.ts +2 -2
- package/src/__tests__/session-confirmation-signals.test.ts +3 -1
- package/src/__tests__/session-error.test.ts +5 -4
- package/src/__tests__/session-history-web-search.test.ts +34 -9
- package/src/__tests__/session-pre-run-repair.test.ts +3 -1
- package/src/__tests__/session-provider-retry-repair.test.ts +31 -26
- package/src/__tests__/session-queue.test.ts +3 -1
- package/src/__tests__/session-runtime-assembly.test.ts +118 -0
- package/src/__tests__/session-slash-known.test.ts +31 -13
- package/src/__tests__/session-slash-queue.test.ts +3 -1
- package/src/__tests__/session-slash-unknown.test.ts +3 -1
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
- package/src/__tests__/session-workspace-injection.test.ts +3 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/skillssh-registry.test.ts +21 -0
- package/src/__tests__/slack-share-routes.test.ts +1 -1
- package/src/__tests__/swarm-recursion.test.ts +5 -1
- package/src/__tests__/swarm-session-integration.test.ts +25 -14
- package/src/__tests__/swarm-tool.test.ts +5 -2
- package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/trust-store.test.ts +5 -1
- package/src/__tests__/twilio-routes.test.ts +2 -2
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-quality.test.ts +2 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/agent/loop.ts +17 -1
- package/src/bundler/app-bundler.ts +40 -24
- package/src/calls/call-controller.ts +16 -0
- package/src/calls/relay-server.ts +29 -13
- package/src/calls/voice-control-protocol.ts +1 -0
- package/src/calls/voice-quality.ts +1 -1
- package/src/calls/voice-session-bridge.ts +9 -3
- package/src/channels/types.ts +16 -0
- package/src/cli/commands/bash.ts +173 -0
- package/src/cli/commands/doctor.ts +5 -23
- package/src/cli/commands/oauth/connections.ts +4 -2
- package/src/cli/commands/oauth/providers.ts +1 -13
- package/src/cli/program.ts +2 -0
- package/src/cli/reference.ts +1 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
- package/src/config/feature-flag-registry.json +8 -0
- package/src/config/loader.ts +7 -135
- package/src/config/schema.ts +0 -6
- package/src/config/schemas/channels.ts +1 -0
- package/src/config/schemas/elevenlabs.ts +2 -2
- package/src/contacts/contact-store.ts +21 -25
- package/src/contacts/contacts-write.ts +6 -6
- package/src/contacts/types.ts +2 -0
- package/src/context/token-estimator.ts +35 -2
- package/src/context/window-manager.ts +16 -2
- package/src/daemon/config-watcher.ts +24 -6
- package/src/daemon/context-overflow-reducer.ts +13 -2
- package/src/daemon/handlers/config-ingress.ts +25 -8
- package/src/daemon/handlers/config-model.ts +21 -15
- package/src/daemon/handlers/config-telegram.ts +18 -6
- package/src/daemon/handlers/dictation.ts +0 -429
- package/src/daemon/handlers/skills.ts +1 -200
- package/src/daemon/lifecycle.ts +8 -5
- package/src/daemon/message-types/contacts.ts +2 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/sessions.ts +2 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
- package/src/daemon/server.ts +23 -2
- package/src/daemon/session-agent-loop-handlers.ts +1 -1
- package/src/daemon/session-agent-loop.ts +27 -79
- package/src/daemon/session-error.ts +5 -4
- package/src/daemon/session-process.ts +17 -10
- package/src/daemon/session-runtime-assembly.ts +50 -0
- package/src/daemon/session-slash.ts +32 -20
- package/src/daemon/session.ts +1 -0
- package/src/events/domain-events.ts +1 -0
- package/src/media/app-icon-generator.ts +2 -1
- package/src/media/avatar-router.ts +3 -2
- package/src/memory/canonical-guardian-store.ts +25 -3
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.ts +25 -16
- package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
- package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
- package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/retriever.test.ts +19 -12
- package/src/memory/schema/contacts.ts +2 -2
- package/src/memory/schema/oauth.ts +0 -1
- package/src/oauth/connect-orchestrator.ts +5 -3
- package/src/oauth/connect-types.ts +9 -2
- package/src/oauth/manual-token-connection.ts +9 -7
- package/src/oauth/oauth-store.ts +2 -8
- package/src/oauth/provider-behaviors.ts +10 -0
- package/src/oauth/seed-providers.ts +13 -5
- package/src/permissions/checker.ts +20 -1
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
- package/src/prompts/system-prompt.ts +2 -11
- package/src/prompts/templates/BOOTSTRAP.md +1 -3
- package/src/providers/anthropic/client.ts +16 -8
- package/src/providers/managed-proxy/constants.ts +1 -1
- package/src/providers/registry.ts +21 -15
- package/src/providers/types.ts +1 -1
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +12 -6
- package/src/runtime/channel-retry-sweep.ts +6 -0
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/middleware/error-handler.ts +1 -2
- package/src/runtime/routes/app-management-routes.ts +1 -0
- package/src/runtime/routes/btw-routes.ts +20 -1
- package/src/runtime/routes/conversation-routes.ts +32 -13
- package/src/runtime/routes/inbound-message-handler.ts +10 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
- package/src/runtime/routes/integrations/slack/share.ts +5 -5
- package/src/runtime/routes/log-export-routes.ts +122 -10
- package/src/runtime/routes/session-query-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +53 -0
- package/src/runtime/routes/workspace-routes.ts +3 -0
- package/src/runtime/verification-templates.ts +1 -1
- package/src/security/oauth2.ts +4 -4
- package/src/security/secure-keys.ts +4 -4
- package/src/signals/bash.ts +157 -0
- package/src/skills/skillssh-registry.ts +6 -1
- package/src/swarm/backend-claude-code.ts +6 -6
- package/src/swarm/worker-backend.ts +1 -1
- package/src/swarm/worker-runner.ts +1 -1
- package/src/telegram/bot-username.ts +11 -0
- package/src/tools/claude-code/claude-code.ts +4 -4
- package/src/tools/credentials/broker.ts +7 -5
- package/src/tools/credentials/vault.ts +3 -2
- package/src/tools/network/__tests__/web-search.test.ts +18 -86
- package/src/tools/network/web-search.ts +9 -15
- package/src/util/platform.ts +7 -1
- package/src/util/pricing.ts +0 -1
- package/src/workspace/provider-commit-message-generator.ts +10 -6
|
@@ -237,6 +237,20 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
237
237
|
"cli/commands/oauth/connections.ts", // CLI OAuth connection delete (legacy credential cleanup)
|
|
238
238
|
"oauth/manual-token-connection.ts", // manual-token provider backfill (keychain credential existence check)
|
|
239
239
|
"cli/commands/doctor.ts", // CLI diagnostic API key verification via secure storage
|
|
240
|
+
"swarm/backend-claude-code.ts", // Claude Code swarm backend API key lookup
|
|
241
|
+
"workspace/provider-commit-message-generator.ts", // commit message generation provider key lookup
|
|
242
|
+
"config/bundled-skills/transcribe/tools/transcribe-media.ts", // transcription tool API key lookup
|
|
243
|
+
"config/bundled-skills/image-studio/tools/media-generate-image.ts", // image generation tool API key lookup
|
|
244
|
+
"config/bundled-skills/media-processing/tools/analyze-keyframes.ts", // keyframe analysis tool API key lookup
|
|
245
|
+
"config/bundled-skills/media-processing/tools/extract-keyframes.ts", // keyframe extraction tool API key lookup
|
|
246
|
+
"providers/registry.ts", // provider registry API key lookup for initialization
|
|
247
|
+
"media/app-icon-generator.ts", // app icon generation API key lookup
|
|
248
|
+
"media/avatar-router.ts", // avatar generation API key lookup
|
|
249
|
+
"memory/embedding-backend.ts", // embedding backend API key lookup
|
|
250
|
+
"daemon/handlers/config-model.ts", // model config handler API key lookup
|
|
251
|
+
"daemon/session-slash.ts", // session slash command API key lookup
|
|
252
|
+
"daemon/session-process.ts", // session process API key lookup
|
|
253
|
+
"tools/claude-code/claude-code.ts", // Claude Code tool API key lookup
|
|
240
254
|
]);
|
|
241
255
|
|
|
242
256
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -1113,7 +1113,7 @@ describe("CredentialBroker — serverUseById edge cases", () => {
|
|
|
1113
1113
|
_resetBackend();
|
|
1114
1114
|
});
|
|
1115
1115
|
|
|
1116
|
-
test("serverUseById with multiple injection templates returns all", () => {
|
|
1116
|
+
test("serverUseById with multiple injection templates returns all", async () => {
|
|
1117
1117
|
const meta = upsertCredentialMetadata("multi", "api_key", {
|
|
1118
1118
|
allowedTools: ["proxy"],
|
|
1119
1119
|
injectionTemplates: [
|
|
@@ -1132,7 +1132,7 @@ describe("CredentialBroker — serverUseById edge cases", () => {
|
|
|
1132
1132
|
});
|
|
1133
1133
|
setSecureKey(credentialKey("multi", "api_key"), "multi-secret");
|
|
1134
1134
|
|
|
1135
|
-
const result = broker.serverUseById({
|
|
1135
|
+
const result = await broker.serverUseById({
|
|
1136
1136
|
credentialId: meta.credentialId,
|
|
1137
1137
|
requestingTool: "proxy",
|
|
1138
1138
|
});
|
|
@@ -1146,13 +1146,13 @@ describe("CredentialBroker — serverUseById edge cases", () => {
|
|
|
1146
1146
|
expect(JSON.stringify(result)).not.toContain("multi-secret");
|
|
1147
1147
|
});
|
|
1148
1148
|
|
|
1149
|
-
test("serverUseById verifies secret exists in storage (fail-closed)", () => {
|
|
1149
|
+
test("serverUseById verifies secret exists in storage (fail-closed)", async () => {
|
|
1150
1150
|
const meta = upsertCredentialMetadata("fal", "api_key", {
|
|
1151
1151
|
allowedTools: ["proxy"],
|
|
1152
1152
|
});
|
|
1153
1153
|
// No setSecureKey — metadata exists but value doesn't
|
|
1154
1154
|
|
|
1155
|
-
const result = broker.serverUseById({
|
|
1155
|
+
const result = await broker.serverUseById({
|
|
1156
1156
|
credentialId: meta.credentialId,
|
|
1157
1157
|
requestingTool: "proxy",
|
|
1158
1158
|
});
|
|
@@ -15,11 +15,10 @@ describe("withErrorHandling – friendly error messages", () => {
|
|
|
15
15
|
};
|
|
16
16
|
expect(body.error.code).toBe("UNPROCESSABLE_ENTITY");
|
|
17
17
|
expect(body.error.message).toContain("No API key configured");
|
|
18
|
-
expect(body.error.message).toContain("
|
|
19
|
-
expect(body.error.message).toContain("vellum hatch");
|
|
18
|
+
expect(body.error.message).toContain("keys set anthropic");
|
|
20
19
|
});
|
|
21
20
|
|
|
22
|
-
test("ProviderNotConfiguredError tailors
|
|
21
|
+
test("ProviderNotConfiguredError tailors keys set command to requested provider", async () => {
|
|
23
22
|
const response = await withErrorHandling("test", async () => {
|
|
24
23
|
throw new ProviderNotConfiguredError("openai", []);
|
|
25
24
|
});
|
|
@@ -28,8 +27,8 @@ describe("withErrorHandling – friendly error messages", () => {
|
|
|
28
27
|
const body = (await response.json()) as {
|
|
29
28
|
error: { code: string; message: string };
|
|
30
29
|
};
|
|
31
|
-
expect(body.error.message).toContain("
|
|
32
|
-
expect(body.error.message).not.toContain("
|
|
30
|
+
expect(body.error.message).toContain("keys set openai");
|
|
31
|
+
expect(body.error.message).not.toContain("keys set anthropic");
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
test("generic ConfigError still returns its own message", async () => {
|
|
@@ -57,7 +57,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
57
57
|
loadConfig: () => ({
|
|
58
58
|
model: "test",
|
|
59
59
|
provider: "test",
|
|
60
|
-
apiKeys: {},
|
|
61
60
|
memory: { enabled: false },
|
|
62
61
|
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
63
62
|
secretDetection: { enabled: false },
|
|
@@ -80,7 +79,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
80
79
|
getConfig: () => ({
|
|
81
80
|
model: "test",
|
|
82
81
|
provider: "test",
|
|
83
|
-
apiKeys: {},
|
|
84
82
|
memory: { enabled: false },
|
|
85
83
|
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
86
84
|
secretDetection: { enabled: false },
|
|
@@ -349,6 +349,25 @@ describe("HTTP POST /v1/messages does not intercept recording intents (by design
|
|
|
349
349
|
expect(runAgentLoop).toHaveBeenCalledTimes(1);
|
|
350
350
|
});
|
|
351
351
|
|
|
352
|
+
test("structured commandIntent recording actions are ignored by HTTP path", async () => {
|
|
353
|
+
// Even when the request includes a structured commandIntent for recording,
|
|
354
|
+
// the HTTP path does not parse or act on it — handleSendMessage only reads
|
|
355
|
+
// content, conversationKey, attachmentIds, sourceChannel, and interface.
|
|
356
|
+
// This ensures a future regression that starts parsing commandIntent would
|
|
357
|
+
// be caught.
|
|
358
|
+
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
359
|
+
const runAgentLoop = mock(async () => undefined);
|
|
360
|
+
const session = makeSession({ persistUserMessage, runAgentLoop });
|
|
361
|
+
|
|
362
|
+
const res = await sendMessage("start screen recording", session, {
|
|
363
|
+
commandIntent: { type: "start_recording", payload: "screen" },
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
expect(res.status).toBe(202);
|
|
367
|
+
expect(persistUserMessage).toHaveBeenCalledTimes(1);
|
|
368
|
+
expect(runAgentLoop).toHaveBeenCalledTimes(1);
|
|
369
|
+
});
|
|
370
|
+
|
|
352
371
|
test("stop recording commands pass through to the agent loop", async () => {
|
|
353
372
|
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
354
373
|
const runAgentLoop = mock(async () => undefined);
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for workspace file collection in the log export route handler.
|
|
3
|
+
*
|
|
4
|
+
* Validates that `POST /v1/export` includes workspace files with correct
|
|
5
|
+
* filtering: text files included, SQLite DB files dumped as SQL, excluded
|
|
6
|
+
* directories (embedding-models/, data/qdrant/) absent, binary files skipped.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
mkdirSync,
|
|
11
|
+
mkdtempSync,
|
|
12
|
+
realpathSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
symlinkSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { afterAll, describe, expect, mock, test } from "bun:test";
|
|
20
|
+
|
|
21
|
+
// Set up temp directories before mocking
|
|
22
|
+
const testDir = realpathSync(
|
|
23
|
+
mkdtempSync(join(tmpdir(), "log-export-workspace-test-")),
|
|
24
|
+
);
|
|
25
|
+
const testWorkspaceDir = join(testDir, "workspace");
|
|
26
|
+
const testDbDir = join(testDir, "db");
|
|
27
|
+
const testDbPath = join(testDbDir, "assistant.db");
|
|
28
|
+
|
|
29
|
+
mkdirSync(testWorkspaceDir, { recursive: true });
|
|
30
|
+
mkdirSync(testDbDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
mock.module("../util/platform.js", () => ({
|
|
33
|
+
getRootDir: () => testDir,
|
|
34
|
+
getDataDir: () => testDir,
|
|
35
|
+
getWorkspaceDir: () => testWorkspaceDir,
|
|
36
|
+
getWorkspaceConfigPath: () => join(testWorkspaceDir, "config.json"),
|
|
37
|
+
isMacOS: () => process.platform === "darwin",
|
|
38
|
+
isLinux: () => process.platform === "linux",
|
|
39
|
+
isWindows: () => process.platform === "win32",
|
|
40
|
+
getPidPath: () => join(testDir, "test.pid"),
|
|
41
|
+
getDbPath: () => testDbPath,
|
|
42
|
+
getLogPath: () => join(testDir, "test.log"),
|
|
43
|
+
ensureDataDir: () => {},
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
mock.module("../util/logger.js", () => ({
|
|
47
|
+
getLogger: () =>
|
|
48
|
+
new Proxy({} as Record<string, unknown>, {
|
|
49
|
+
get: () => () => {},
|
|
50
|
+
}),
|
|
51
|
+
}));
|
|
52
|
+
|
|
53
|
+
// Mock getSecureKeyAsync to avoid keychain access during tests
|
|
54
|
+
mock.module("../util/secure-keys.js", () => ({
|
|
55
|
+
getSecureKeyAsync: async () => undefined,
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
import { initializeDb, resetDb } from "../memory/db.js";
|
|
59
|
+
import { logExportRouteDefinitions } from "../runtime/routes/log-export-routes.js";
|
|
60
|
+
|
|
61
|
+
initializeDb();
|
|
62
|
+
|
|
63
|
+
afterAll(() => {
|
|
64
|
+
resetDb();
|
|
65
|
+
try {
|
|
66
|
+
rmSync(testDir, { recursive: true });
|
|
67
|
+
} catch {
|
|
68
|
+
/* best effort */
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Helpers
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
const routes = logExportRouteDefinitions();
|
|
77
|
+
const exportRoute = routes.find((r) => r.endpoint === "export")!;
|
|
78
|
+
|
|
79
|
+
async function callExport(): Promise<Response> {
|
|
80
|
+
const req = new Request("http://localhost/v1/export", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify({}),
|
|
84
|
+
});
|
|
85
|
+
const url = new URL(req.url);
|
|
86
|
+
return exportRoute.handler({
|
|
87
|
+
req,
|
|
88
|
+
url,
|
|
89
|
+
server: null as never,
|
|
90
|
+
authContext: {} as never,
|
|
91
|
+
params: {},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// Seed workspace files
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
// Text files — should be included
|
|
100
|
+
writeFileSync(join(testWorkspaceDir, "IDENTITY.md"), "# My Identity\nHello");
|
|
101
|
+
mkdirSync(join(testWorkspaceDir, "notes"), { recursive: true });
|
|
102
|
+
writeFileSync(join(testWorkspaceDir, "notes", "daily.txt"), "Some daily notes");
|
|
103
|
+
|
|
104
|
+
// SQLite DB file — should be dumped as .sql
|
|
105
|
+
mkdirSync(join(testWorkspaceDir, "data", "db"), { recursive: true });
|
|
106
|
+
// Create a real sqlite db with a table
|
|
107
|
+
import { Database } from "bun:sqlite";
|
|
108
|
+
const wsDbPath = join(testWorkspaceDir, "data", "db", "assistant.db");
|
|
109
|
+
const wsDb = new Database(wsDbPath);
|
|
110
|
+
wsDb.run("CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT)");
|
|
111
|
+
wsDb.run("INSERT INTO test_table (name) VALUES ('hello')");
|
|
112
|
+
wsDb.close();
|
|
113
|
+
|
|
114
|
+
// Excluded directory: embedding-models/
|
|
115
|
+
mkdirSync(join(testWorkspaceDir, "embedding-models"), { recursive: true });
|
|
116
|
+
writeFileSync(
|
|
117
|
+
join(testWorkspaceDir, "embedding-models", "model.bin"),
|
|
118
|
+
"large binary model data",
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Excluded directory: data/qdrant/
|
|
122
|
+
mkdirSync(join(testWorkspaceDir, "data", "qdrant"), { recursive: true });
|
|
123
|
+
writeFileSync(
|
|
124
|
+
join(testWorkspaceDir, "data", "qdrant", "index.bin"),
|
|
125
|
+
"vector index data",
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// Binary file — should be skipped
|
|
129
|
+
writeFileSync(
|
|
130
|
+
join(testWorkspaceDir, "binary-file.dat"),
|
|
131
|
+
Buffer.from([0x48, 0x65, 0x6c, 0x00, 0x6f]), // contains null byte
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// config.json at workspace root — should be skipped (already in configSnapshot)
|
|
135
|
+
writeFileSync(
|
|
136
|
+
join(testWorkspaceDir, "config.json"),
|
|
137
|
+
JSON.stringify({ provider: "anthropic" }),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// Symlink pointing outside workspace — should be skipped
|
|
141
|
+
const outsideFile = join(testDir, "outside-secret.txt");
|
|
142
|
+
writeFileSync(outsideFile, "sensitive data outside workspace");
|
|
143
|
+
try {
|
|
144
|
+
symlinkSync(outsideFile, join(testWorkspaceDir, "sneaky-link.txt"));
|
|
145
|
+
} catch {
|
|
146
|
+
// Symlink creation may fail on some platforms; tests will still pass
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Tests
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
describe("POST /v1/export — workspace files", () => {
|
|
154
|
+
test("includes text files in workspaceFiles", async () => {
|
|
155
|
+
const res = await callExport();
|
|
156
|
+
expect(res.status).toBe(200);
|
|
157
|
+
const body = (await res.json()) as {
|
|
158
|
+
workspaceFiles: Record<string, string>;
|
|
159
|
+
};
|
|
160
|
+
expect(body.workspaceFiles["IDENTITY.md"]).toBe("# My Identity\nHello");
|
|
161
|
+
expect(body.workspaceFiles["notes/daily.txt"]).toBe("Some daily notes");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("dumps SQLite DB files as .sql text", async () => {
|
|
165
|
+
const res = await callExport();
|
|
166
|
+
const body = (await res.json()) as {
|
|
167
|
+
workspaceFiles: Record<string, string>;
|
|
168
|
+
};
|
|
169
|
+
const sqlKey = "data/db/assistant.db.sql";
|
|
170
|
+
expect(body.workspaceFiles[sqlKey]).toBeDefined();
|
|
171
|
+
expect(body.workspaceFiles[sqlKey]).toContain("CREATE TABLE");
|
|
172
|
+
expect(body.workspaceFiles[sqlKey]).toContain("test_table");
|
|
173
|
+
// The raw .db file should NOT be present
|
|
174
|
+
expect(body.workspaceFiles["data/db/assistant.db"]).toBeUndefined();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("excludes embedding-models/ directory", async () => {
|
|
178
|
+
const res = await callExport();
|
|
179
|
+
const body = (await res.json()) as {
|
|
180
|
+
workspaceFiles: Record<string, string>;
|
|
181
|
+
};
|
|
182
|
+
const embeddingKeys = Object.keys(body.workspaceFiles).filter((k) =>
|
|
183
|
+
k.startsWith("embedding-models/"),
|
|
184
|
+
);
|
|
185
|
+
expect(embeddingKeys).toHaveLength(0);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("excludes data/qdrant/ directory", async () => {
|
|
189
|
+
const res = await callExport();
|
|
190
|
+
const body = (await res.json()) as {
|
|
191
|
+
workspaceFiles: Record<string, string>;
|
|
192
|
+
};
|
|
193
|
+
const qdrantKeys = Object.keys(body.workspaceFiles).filter((k) =>
|
|
194
|
+
k.startsWith("data/qdrant/"),
|
|
195
|
+
);
|
|
196
|
+
expect(qdrantKeys).toHaveLength(0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("skips binary files containing null bytes", async () => {
|
|
200
|
+
const res = await callExport();
|
|
201
|
+
const body = (await res.json()) as {
|
|
202
|
+
workspaceFiles: Record<string, string>;
|
|
203
|
+
};
|
|
204
|
+
expect(body.workspaceFiles["binary-file.dat"]).toBeUndefined();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("excludes config.json at workspace root", async () => {
|
|
208
|
+
const res = await callExport();
|
|
209
|
+
const body = (await res.json()) as {
|
|
210
|
+
workspaceFiles: Record<string, string>;
|
|
211
|
+
};
|
|
212
|
+
expect(body.workspaceFiles["config.json"]).toBeUndefined();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("skips symlinks", async () => {
|
|
216
|
+
const res = await callExport();
|
|
217
|
+
const body = (await res.json()) as {
|
|
218
|
+
workspaceFiles: Record<string, string>;
|
|
219
|
+
};
|
|
220
|
+
expect(body.workspaceFiles["sneaky-link.txt"]).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("response includes workspaceFiles field", async () => {
|
|
224
|
+
const res = await callExport();
|
|
225
|
+
const body = (await res.json()) as {
|
|
226
|
+
success: boolean;
|
|
227
|
+
workspaceFiles: Record<string, string>;
|
|
228
|
+
};
|
|
229
|
+
expect(body.success).toBe(true);
|
|
230
|
+
expect(body.workspaceFiles).toBeDefined();
|
|
231
|
+
expect(typeof body.workspaceFiles).toBe("object");
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -110,7 +110,7 @@ describe("buildManagedBaseUrl", () => {
|
|
|
110
110
|
"https://platform.example.com/v1/runtime-proxy/openai",
|
|
111
111
|
);
|
|
112
112
|
expect(buildManagedBaseUrl("anthropic")).toBe(
|
|
113
|
-
"https://platform.example.com/v1/runtime-proxy/
|
|
113
|
+
"https://platform.example.com/v1/runtime-proxy/anthropic",
|
|
114
114
|
);
|
|
115
115
|
expect(buildManagedBaseUrl("gemini")).toBe(
|
|
116
116
|
"https://platform.example.com/v1/runtime-proxy/vertex",
|
|
@@ -22,11 +22,16 @@ let lastGenerateCredentials: unknown = null;
|
|
|
22
22
|
mock.module("../config/loader.js", () => ({
|
|
23
23
|
getConfig: () => ({
|
|
24
24
|
ui: {},
|
|
25
|
-
|
|
26
|
-
apiKeys: { gemini: mockApiKey },
|
|
27
25
|
}),
|
|
28
26
|
}));
|
|
29
27
|
|
|
28
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
29
|
+
getSecureKeyAsync: async (account: string) => {
|
|
30
|
+
if (account === "gemini") return mockApiKey;
|
|
31
|
+
return undefined;
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
30
35
|
mock.module("../media/gemini-image-service.js", () => ({
|
|
31
36
|
generateImage: async (
|
|
32
37
|
credentials: unknown,
|
|
@@ -52,7 +52,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
52
52
|
|
|
53
53
|
model: "test",
|
|
54
54
|
provider: "test",
|
|
55
|
-
apiKeys: {},
|
|
56
55
|
memory: { enabled: false },
|
|
57
56
|
rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
|
|
58
57
|
timeouts: { shellDefaultTimeoutSec: 30, shellMaxTimeoutSec: 60 },
|