@vellumai/assistant 0.5.6 → 0.5.7
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/.env.example +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +1 -1
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +8 -14
- package/src/config/feature-flag-registry.json +48 -8
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -13
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +227 -51
- package/src/daemon/message-types/conversations.ts +3 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +12 -0
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/jobs-store.ts +30 -5
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +15 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +27 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.ts +24 -6
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +3 -50
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Mock the Agent SDK — prevents real subprocess spawning
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
const queryMock = mock(() => {
|
|
7
|
-
// Returns an async iterable that yields a success result
|
|
8
|
-
return {
|
|
9
|
-
async *[Symbol.asyncIterator]() {
|
|
10
|
-
yield {
|
|
11
|
-
type: "result" as const,
|
|
12
|
-
session_id: "test-session",
|
|
13
|
-
subtype: "success" as const,
|
|
14
|
-
result: "Done.",
|
|
15
|
-
};
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
|
|
21
|
-
query: queryMock,
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
// Mock logger
|
|
25
|
-
mock.module("../util/logger.js", () => ({
|
|
26
|
-
getLogger: () =>
|
|
27
|
-
new Proxy({} as Record<string, unknown>, {
|
|
28
|
-
get: () => () => {},
|
|
29
|
-
}),
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
// Mock config
|
|
33
|
-
mock.module("../config/loader.js", () => ({
|
|
34
|
-
getConfig: () => ({
|
|
35
|
-
ui: {},
|
|
36
|
-
}),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
|
-
// Mock secure-keys — provide a fake Anthropic API key
|
|
40
|
-
mock.module("../security/secure-keys.js", () => ({
|
|
41
|
-
getSecureKeyAsync: async (name: string) =>
|
|
42
|
-
name === "anthropic" ? "fake-anthropic-key" : null,
|
|
43
|
-
getProviderKeyAsync: async (provider: string) =>
|
|
44
|
-
provider === "anthropic" ? "fake-anthropic-key" : undefined,
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
import { claudeCodeTool } from "../tools/claude-code/claude-code.js";
|
|
48
|
-
import type { ToolContext } from "../tools/types.js";
|
|
49
|
-
|
|
50
|
-
function makeContext(overrides?: Partial<ToolContext>): ToolContext {
|
|
51
|
-
return {
|
|
52
|
-
conversationId: "test-session",
|
|
53
|
-
workingDir: "/tmp/test",
|
|
54
|
-
trustClass: "guardian",
|
|
55
|
-
onOutput: () => {},
|
|
56
|
-
...overrides,
|
|
57
|
-
} as ToolContext;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
describe("claude_code tool profile support", () => {
|
|
61
|
-
beforeEach(() => {
|
|
62
|
-
queryMock.mockClear();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
test("getDefinition includes profile parameter", () => {
|
|
66
|
-
const def = claudeCodeTool.getDefinition();
|
|
67
|
-
const props = (def.input_schema as Record<string, unknown>)
|
|
68
|
-
.properties as Record<string, unknown>;
|
|
69
|
-
expect(props.profile).toBeDefined();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("rejects invalid profile", async () => {
|
|
73
|
-
const result = await claudeCodeTool.execute(
|
|
74
|
-
{ prompt: "test", profile: "hacker" },
|
|
75
|
-
makeContext(),
|
|
76
|
-
);
|
|
77
|
-
expect(result.isError).toBe(true);
|
|
78
|
-
expect(result.content).toContain("Invalid profile");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("accepts valid profiles without error", async () => {
|
|
82
|
-
for (const profile of ["general", "researcher", "coder", "reviewer"]) {
|
|
83
|
-
queryMock.mockClear();
|
|
84
|
-
const result = await claudeCodeTool.execute(
|
|
85
|
-
{ prompt: "test", profile },
|
|
86
|
-
makeContext(),
|
|
87
|
-
);
|
|
88
|
-
expect(result.isError).toBeFalsy();
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("omitted profile defaults to general", async () => {
|
|
93
|
-
const result = await claudeCodeTool.execute(
|
|
94
|
-
{ prompt: "test" },
|
|
95
|
-
makeContext(),
|
|
96
|
-
);
|
|
97
|
-
expect(result.isError).toBeFalsy();
|
|
98
|
-
});
|
|
99
|
-
});
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the diagnostics export route handler.
|
|
3
|
-
*
|
|
4
|
-
* Validates anchor message resolution, including the fallback chain
|
|
5
|
-
* (specific ID → most recent assistant → any message → empty conversation).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
-
|
|
13
|
-
import JSZip from "jszip";
|
|
14
|
-
|
|
15
|
-
const testDir = mkdtempSync(join(tmpdir(), "diagnostics-export-test-"));
|
|
16
|
-
|
|
17
|
-
mock.module("../util/platform.js", () => ({
|
|
18
|
-
getDataDir: () => testDir,
|
|
19
|
-
isMacOS: () => process.platform === "darwin",
|
|
20
|
-
isLinux: () => process.platform === "linux",
|
|
21
|
-
isWindows: () => process.platform === "win32",
|
|
22
|
-
getPidPath: () => join(testDir, "test.pid"),
|
|
23
|
-
getDbPath: () => join(testDir, "test.db"),
|
|
24
|
-
getLogPath: () => join(testDir, "test.log"),
|
|
25
|
-
ensureDataDir: () => {},
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
mock.module("../util/logger.js", () => ({
|
|
29
|
-
getLogger: () =>
|
|
30
|
-
new Proxy({} as Record<string, unknown>, {
|
|
31
|
-
get: () => () => {},
|
|
32
|
-
}),
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
|
-
import { getSqlite, initializeDb, resetDb } from "../memory/db.js";
|
|
36
|
-
import { diagnosticsRouteDefinitions } from "../runtime/routes/diagnostics-routes.js";
|
|
37
|
-
|
|
38
|
-
initializeDb();
|
|
39
|
-
|
|
40
|
-
afterAll(() => {
|
|
41
|
-
resetDb();
|
|
42
|
-
try {
|
|
43
|
-
rmSync(testDir, { recursive: true });
|
|
44
|
-
} catch {
|
|
45
|
-
/* best effort */
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Helpers
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
|
|
53
|
-
const routes = diagnosticsRouteDefinitions();
|
|
54
|
-
const exportRoute = routes.find((r) => r.endpoint === "diagnostics/export")!;
|
|
55
|
-
|
|
56
|
-
async function callExport(body: Record<string, unknown>): Promise<Response> {
|
|
57
|
-
const req = new Request("http://localhost/v1/diagnostics/export", {
|
|
58
|
-
method: "POST",
|
|
59
|
-
headers: { "Content-Type": "application/json" },
|
|
60
|
-
body: JSON.stringify(body),
|
|
61
|
-
});
|
|
62
|
-
const url = new URL(req.url);
|
|
63
|
-
return exportRoute.handler({
|
|
64
|
-
req,
|
|
65
|
-
url,
|
|
66
|
-
server: null as never,
|
|
67
|
-
authContext: {} as never,
|
|
68
|
-
params: {},
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const db = () => getSqlite();
|
|
73
|
-
|
|
74
|
-
function seedConversation(id: string): void {
|
|
75
|
-
const now = Date.now();
|
|
76
|
-
db().run(
|
|
77
|
-
"INSERT INTO conversations (id, title, created_at, updated_at) VALUES (?, ?, ?, ?)",
|
|
78
|
-
[id, "Test", now, now],
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function seedMessage(
|
|
83
|
-
id: string,
|
|
84
|
-
conversationId: string,
|
|
85
|
-
role: string,
|
|
86
|
-
content: string,
|
|
87
|
-
createdAt: number,
|
|
88
|
-
): void {
|
|
89
|
-
db().run(
|
|
90
|
-
"INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES (?, ?, ?, ?, ?)",
|
|
91
|
-
[id, conversationId, role, content, createdAt],
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function seedLlmRequestLog(
|
|
96
|
-
id: string,
|
|
97
|
-
conversationId: string,
|
|
98
|
-
provider: string | null,
|
|
99
|
-
requestPayload: unknown,
|
|
100
|
-
responsePayload: unknown,
|
|
101
|
-
createdAt: number,
|
|
102
|
-
): void {
|
|
103
|
-
db().run(
|
|
104
|
-
"INSERT INTO llm_request_logs (id, conversation_id, provider, request_payload, response_payload, created_at) VALUES (?, ?, ?, ?, ?, ?)",
|
|
105
|
-
[
|
|
106
|
-
id,
|
|
107
|
-
conversationId,
|
|
108
|
-
provider,
|
|
109
|
-
JSON.stringify(requestPayload),
|
|
110
|
-
JSON.stringify(responsePayload),
|
|
111
|
-
createdAt,
|
|
112
|
-
],
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function seedConversationKey(
|
|
117
|
-
conversationKey: string,
|
|
118
|
-
conversationId: string,
|
|
119
|
-
): void {
|
|
120
|
-
db().run(
|
|
121
|
-
"INSERT INTO conversation_keys (id, conversation_key, conversation_id, created_at) VALUES (?, ?, ?, ?)",
|
|
122
|
-
[crypto.randomUUID(), conversationKey, conversationId, Date.now()],
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function cleanDb(): void {
|
|
127
|
-
db().run("DELETE FROM messages");
|
|
128
|
-
db().run("DELETE FROM llm_request_logs");
|
|
129
|
-
db().run("DELETE FROM conversation_keys");
|
|
130
|
-
db().run("DELETE FROM conversations");
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
beforeEach(() => {
|
|
134
|
-
cleanDb();
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
// Tests
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
|
|
141
|
-
describe("diagnostics export", () => {
|
|
142
|
-
test("returns 400 when conversationId is missing", async () => {
|
|
143
|
-
const res = await callExport({});
|
|
144
|
-
expect(res.status).toBe(400);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("succeeds with specific anchorMessageId", async () => {
|
|
148
|
-
const convId = "conv-1";
|
|
149
|
-
const msgId = "msg-assistant-1";
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
|
|
152
|
-
seedConversation(convId);
|
|
153
|
-
seedMessage("msg-user-1", convId, "user", "hello", now - 1000);
|
|
154
|
-
seedMessage(msgId, convId, "assistant", "world", now);
|
|
155
|
-
|
|
156
|
-
const res = await callExport({
|
|
157
|
-
conversationId: convId,
|
|
158
|
-
anchorMessageId: msgId,
|
|
159
|
-
});
|
|
160
|
-
expect(res.status).toBe(200);
|
|
161
|
-
const json = (await res.json()) as { success: boolean; filePath: string };
|
|
162
|
-
expect(json.success).toBe(true);
|
|
163
|
-
expect(json.filePath).toContain("diagnostics-");
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("falls back to most recent assistant message when anchorMessageId is omitted", async () => {
|
|
167
|
-
const convId = "conv-2";
|
|
168
|
-
const now = Date.now();
|
|
169
|
-
|
|
170
|
-
seedConversation(convId);
|
|
171
|
-
seedMessage("msg-user-1", convId, "user", "hello", now - 1000);
|
|
172
|
-
seedMessage("msg-assistant-1", convId, "assistant", "world", now);
|
|
173
|
-
|
|
174
|
-
const res = await callExport({ conversationId: convId });
|
|
175
|
-
expect(res.status).toBe(200);
|
|
176
|
-
const json = (await res.json()) as { success: boolean };
|
|
177
|
-
expect(json.success).toBe(true);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test("falls back to any message when no assistant messages exist (race condition fix)", async () => {
|
|
181
|
-
const convId = "conv-3";
|
|
182
|
-
const now = Date.now();
|
|
183
|
-
|
|
184
|
-
seedConversation(convId);
|
|
185
|
-
// Only a user message — assistant response hasn't been persisted yet
|
|
186
|
-
seedMessage("msg-user-1", convId, "user", "hello", now);
|
|
187
|
-
|
|
188
|
-
const res = await callExport({ conversationId: convId });
|
|
189
|
-
expect(res.status).toBe(200);
|
|
190
|
-
const json = (await res.json()) as { success: boolean; filePath: string };
|
|
191
|
-
expect(json.success).toBe(true);
|
|
192
|
-
expect(json.filePath).toContain("diagnostics-");
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test("succeeds with empty conversation (no messages at all)", async () => {
|
|
196
|
-
const convId = "conv-4";
|
|
197
|
-
|
|
198
|
-
seedConversation(convId);
|
|
199
|
-
// No messages at all — conversation was just created
|
|
200
|
-
|
|
201
|
-
const res = await callExport({ conversationId: convId });
|
|
202
|
-
expect(res.status).toBe(200);
|
|
203
|
-
const json = (await res.json()) as { success: boolean; filePath: string };
|
|
204
|
-
expect(json.success).toBe(true);
|
|
205
|
-
expect(json.filePath).toContain("diagnostics-");
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("resolves conversation key to daemon conversation ID", async () => {
|
|
209
|
-
const daemonConvId = "conv-6-daemon";
|
|
210
|
-
const clientKey = "AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE";
|
|
211
|
-
const now = Date.now();
|
|
212
|
-
|
|
213
|
-
seedConversation(daemonConvId);
|
|
214
|
-
seedConversationKey(clientKey, daemonConvId);
|
|
215
|
-
seedMessage("msg-user-6", daemonConvId, "user", "hello", now - 1000);
|
|
216
|
-
seedMessage("msg-assistant-6", daemonConvId, "assistant", "world", now);
|
|
217
|
-
|
|
218
|
-
// Client sends the conversation key (client-side UUID), not the daemon ID
|
|
219
|
-
const res = await callExport({ conversationId: clientKey });
|
|
220
|
-
expect(res.status).toBe(200);
|
|
221
|
-
const json = (await res.json()) as { success: boolean; filePath: string };
|
|
222
|
-
expect(json.success).toBe(true);
|
|
223
|
-
// The export should find the messages via the resolved daemon ID
|
|
224
|
-
expect(json.filePath).toContain("diagnostics-");
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
test("falls back to any-message when anchorMessageId is provided but not found", async () => {
|
|
228
|
-
const convId = "conv-5";
|
|
229
|
-
const now = Date.now();
|
|
230
|
-
|
|
231
|
-
seedConversation(convId);
|
|
232
|
-
seedMessage("msg-user-1", convId, "user", "hello", now);
|
|
233
|
-
|
|
234
|
-
// anchorMessageId doesn't exist — should fall through all the way
|
|
235
|
-
const res = await callExport({
|
|
236
|
-
conversationId: convId,
|
|
237
|
-
anchorMessageId: "nonexistent-msg-id",
|
|
238
|
-
});
|
|
239
|
-
expect(res.status).toBe(200);
|
|
240
|
-
const json = (await res.json()) as { success: boolean };
|
|
241
|
-
expect(json.success).toBe(true);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test("preserves llm request provider identity in the exported JSONL", async () => {
|
|
245
|
-
const convId = "conv-7";
|
|
246
|
-
const now = Date.now();
|
|
247
|
-
|
|
248
|
-
seedConversation(convId);
|
|
249
|
-
seedMessage("msg-user-7", convId, "user", "hello", now - 1000);
|
|
250
|
-
seedMessage("msg-assistant-7", convId, "assistant", "world", now);
|
|
251
|
-
seedLlmRequestLog(
|
|
252
|
-
"log-7",
|
|
253
|
-
convId,
|
|
254
|
-
"openrouter",
|
|
255
|
-
{ model: "openai/gpt-4.1-mini", input: "hello" },
|
|
256
|
-
{ output: "world" },
|
|
257
|
-
now,
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
const res = await callExport({ conversationId: convId });
|
|
261
|
-
expect(res.status).toBe(200);
|
|
262
|
-
const json = (await res.json()) as { success: boolean; filePath: string };
|
|
263
|
-
expect(json.success).toBe(true);
|
|
264
|
-
|
|
265
|
-
const zip = await JSZip.loadAsync(readFileSync(json.filePath));
|
|
266
|
-
const llmRequests = zip.file("llm_requests.jsonl");
|
|
267
|
-
expect(llmRequests).not.toBeNull();
|
|
268
|
-
|
|
269
|
-
const lines = (await llmRequests!.async("string")).trim().split("\n");
|
|
270
|
-
expect(lines).toHaveLength(1);
|
|
271
|
-
|
|
272
|
-
const row = JSON.parse(lines[0]) as {
|
|
273
|
-
id: string;
|
|
274
|
-
conversationId: string;
|
|
275
|
-
provider?: string | null;
|
|
276
|
-
request: unknown;
|
|
277
|
-
response: unknown;
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
expect(row).toMatchObject({
|
|
281
|
-
id: "log-7",
|
|
282
|
-
conversationId: convId,
|
|
283
|
-
provider: "openrouter",
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
rmSync(json.filePath, { force: true });
|
|
287
|
-
});
|
|
288
|
-
});
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
let mockGatewayInternalBaseUrl = "http://127.0.0.1:7830";
|
|
4
|
-
let mockIsContainerized = false;
|
|
5
|
-
let mockLockfile: Record<string, unknown> | null = null;
|
|
6
|
-
let mockBaseDataDir: string | undefined;
|
|
7
|
-
|
|
8
|
-
mock.module("../config/env.js", () => ({
|
|
9
|
-
getGatewayInternalBaseUrl: () => mockGatewayInternalBaseUrl,
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
mock.module("../config/env-registry.js", () => ({
|
|
13
|
-
getIsContainerized: () => mockIsContainerized,
|
|
14
|
-
getBaseDataDir: () => mockBaseDataDir,
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
|
-
mock.module("../util/platform.js", () => ({
|
|
18
|
-
readLockfile: () => mockLockfile,
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
ensureLocalGatewayReady,
|
|
23
|
-
probeLocalGatewayHealth,
|
|
24
|
-
} from "../runtime/local-gateway-health.js";
|
|
25
|
-
|
|
26
|
-
describe("local gateway health", () => {
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
mockGatewayInternalBaseUrl = "http://127.0.0.1:7830";
|
|
29
|
-
mockIsContainerized = false;
|
|
30
|
-
mockBaseDataDir = undefined;
|
|
31
|
-
mockLockfile = {
|
|
32
|
-
assistants: [
|
|
33
|
-
{
|
|
34
|
-
assistantId: "local-dev",
|
|
35
|
-
cloud: "local",
|
|
36
|
-
hatchedAt: "2026-03-07T00:00:00.000Z",
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
};
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("probeLocalGatewayHealth returns healthy result when /healthz succeeds", async () => {
|
|
43
|
-
const requests: string[] = [];
|
|
44
|
-
const fetchImpl: typeof fetch = (async (
|
|
45
|
-
input: string | URL | Request,
|
|
46
|
-
): Promise<Response> => {
|
|
47
|
-
requests.push(
|
|
48
|
-
typeof input === "string"
|
|
49
|
-
? input
|
|
50
|
-
: input instanceof URL
|
|
51
|
-
? input.toString()
|
|
52
|
-
: input.url,
|
|
53
|
-
);
|
|
54
|
-
return new Response("ok", { status: 200 });
|
|
55
|
-
}) as unknown as typeof fetch;
|
|
56
|
-
|
|
57
|
-
const result = await probeLocalGatewayHealth({
|
|
58
|
-
fetchImpl,
|
|
59
|
-
timeoutMs: 50,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
expect(requests).toEqual(["http://127.0.0.1:7830/healthz"]);
|
|
63
|
-
expect(result).toEqual({
|
|
64
|
-
target: "http://127.0.0.1:7830",
|
|
65
|
-
healthy: true,
|
|
66
|
-
localDeployment: true,
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("probeLocalGatewayHealth returns an unhealthy result when /healthz fails", async () => {
|
|
71
|
-
const fetchImpl: typeof fetch = (async (): Promise<Response> => {
|
|
72
|
-
return new Response("unavailable", { status: 503 });
|
|
73
|
-
}) as unknown as typeof fetch;
|
|
74
|
-
|
|
75
|
-
const result = await probeLocalGatewayHealth({
|
|
76
|
-
fetchImpl,
|
|
77
|
-
timeoutMs: 50,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
expect(result).toEqual({
|
|
81
|
-
target: "http://127.0.0.1:7830",
|
|
82
|
-
healthy: false,
|
|
83
|
-
localDeployment: true,
|
|
84
|
-
error: "Gateway health check returned HTTP 503",
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("ensureLocalGatewayReady runs recovery for local assistants until the gateway becomes healthy", async () => {
|
|
89
|
-
const healthStatuses = [503, 503, 200];
|
|
90
|
-
const fetchImpl: typeof fetch = (async (): Promise<Response> => {
|
|
91
|
-
const status = healthStatuses.shift() ?? 200;
|
|
92
|
-
return new Response(status === 200 ? "ok" : "unavailable", { status });
|
|
93
|
-
}) as unknown as typeof fetch;
|
|
94
|
-
|
|
95
|
-
const wakeCalls: number[] = [];
|
|
96
|
-
const result = await ensureLocalGatewayReady({
|
|
97
|
-
fetchImpl,
|
|
98
|
-
timeoutMs: 50,
|
|
99
|
-
pollTimeoutMs: 100,
|
|
100
|
-
pollIntervalMs: 0,
|
|
101
|
-
sleepImpl: async () => {},
|
|
102
|
-
runWakeCommand: async () => {
|
|
103
|
-
wakeCalls.push(Date.now());
|
|
104
|
-
return { exitCode: 0, stdout: "Wake complete.", stderr: "" };
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
expect(wakeCalls).toHaveLength(1);
|
|
109
|
-
expect(result).toEqual({
|
|
110
|
-
target: "http://127.0.0.1:7830",
|
|
111
|
-
healthy: true,
|
|
112
|
-
localDeployment: true,
|
|
113
|
-
recovered: true,
|
|
114
|
-
recoveryAttempted: true,
|
|
115
|
-
recoverySkipped: false,
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("ensureLocalGatewayReady skips recovery for non-local assistants", async () => {
|
|
120
|
-
mockLockfile = {
|
|
121
|
-
assistants: [
|
|
122
|
-
{
|
|
123
|
-
assistantId: "remote-prod",
|
|
124
|
-
cloud: "gcp",
|
|
125
|
-
hatchedAt: "2026-03-07T00:00:00.000Z",
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const fetchImpl: typeof fetch = (async (): Promise<Response> => {
|
|
131
|
-
return new Response("unavailable", { status: 503 });
|
|
132
|
-
}) as unknown as typeof fetch;
|
|
133
|
-
|
|
134
|
-
let wakeCallCount = 0;
|
|
135
|
-
const result = await ensureLocalGatewayReady({
|
|
136
|
-
fetchImpl,
|
|
137
|
-
timeoutMs: 50,
|
|
138
|
-
runWakeCommand: async () => {
|
|
139
|
-
wakeCallCount++;
|
|
140
|
-
return { exitCode: 0, stdout: "", stderr: "" };
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(wakeCallCount).toBe(0);
|
|
145
|
-
expect(result).toEqual({
|
|
146
|
-
target: "http://127.0.0.1:7830",
|
|
147
|
-
healthy: false,
|
|
148
|
-
localDeployment: false,
|
|
149
|
-
error: "Gateway health check returned HTTP 503",
|
|
150
|
-
recovered: false,
|
|
151
|
-
recoveryAttempted: false,
|
|
152
|
-
recoverySkipped: true,
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("ensureLocalGatewayReady derives assistant name from BASE_DATA_DIR when lockfile lacks assistants", async () => {
|
|
157
|
-
mockBaseDataDir = "/home/user/.local/share/vellum/assistants/alice";
|
|
158
|
-
mockLockfile = null;
|
|
159
|
-
|
|
160
|
-
const healthStatuses = [503, 200];
|
|
161
|
-
const fetchImpl: typeof fetch = (async (): Promise<Response> => {
|
|
162
|
-
const status = healthStatuses.shift() ?? 200;
|
|
163
|
-
return new Response(status === 200 ? "ok" : "unavailable", { status });
|
|
164
|
-
}) as unknown as typeof fetch;
|
|
165
|
-
|
|
166
|
-
let wakeCalled = false;
|
|
167
|
-
const result = await ensureLocalGatewayReady({
|
|
168
|
-
fetchImpl,
|
|
169
|
-
timeoutMs: 50,
|
|
170
|
-
pollTimeoutMs: 100,
|
|
171
|
-
pollIntervalMs: 0,
|
|
172
|
-
sleepImpl: async () => {},
|
|
173
|
-
runWakeCommand: async () => {
|
|
174
|
-
wakeCalled = true;
|
|
175
|
-
return { exitCode: 0, stdout: "Wake complete.", stderr: "" };
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
expect(wakeCalled).toBe(true);
|
|
180
|
-
expect(result.recovered).toBe(true);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
test("ensureLocalGatewayReady handles BASE_DATA_DIR with trailing slash", async () => {
|
|
184
|
-
mockBaseDataDir = "/home/user/.local/share/vellum/assistants/alice/";
|
|
185
|
-
mockLockfile = null;
|
|
186
|
-
|
|
187
|
-
const healthStatuses = [503, 200];
|
|
188
|
-
const fetchImpl: typeof fetch = (async (): Promise<Response> => {
|
|
189
|
-
const status = healthStatuses.shift() ?? 200;
|
|
190
|
-
return new Response(status === 200 ? "ok" : "unavailable", { status });
|
|
191
|
-
}) as unknown as typeof fetch;
|
|
192
|
-
|
|
193
|
-
let wakeCalled = false;
|
|
194
|
-
const result = await ensureLocalGatewayReady({
|
|
195
|
-
fetchImpl,
|
|
196
|
-
timeoutMs: 50,
|
|
197
|
-
pollTimeoutMs: 100,
|
|
198
|
-
pollIntervalMs: 0,
|
|
199
|
-
sleepImpl: async () => {},
|
|
200
|
-
runWakeCommand: async () => {
|
|
201
|
-
wakeCalled = true;
|
|
202
|
-
return { exitCode: 0, stdout: "Wake complete.", stderr: "" };
|
|
203
|
-
},
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
expect(wakeCalled).toBe(true);
|
|
207
|
-
expect(result.recovered).toBe(true);
|
|
208
|
-
});
|
|
209
|
-
});
|