@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
|
@@ -17,7 +17,6 @@ mock.module("../../../util/platform.js", () => ({
|
|
|
17
17
|
getDataDir: () => testDir,
|
|
18
18
|
getDbPath: () => join(testDir, "test.db"),
|
|
19
19
|
normalizeAssistantId: (id: string) => (id === "self" ? "self" : id),
|
|
20
|
-
readLockfile: () => ({ assistants: [{ assistantId: "vellum-test-eel" }] }),
|
|
21
20
|
isMacOS: () => process.platform === "darwin",
|
|
22
21
|
isLinux: () => process.platform === "linux",
|
|
23
22
|
isWindows: () => process.platform === "win32",
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for getExternalAssistantId
|
|
2
|
+
* Tests for getExternalAssistantId.
|
|
3
3
|
*/
|
|
4
|
-
import { afterEach, describe, expect,
|
|
5
|
-
|
|
6
|
-
mock.module("../../../util/logger.js", () => ({
|
|
7
|
-
getLogger: () =>
|
|
8
|
-
new Proxy({} as Record<string, unknown>, {
|
|
9
|
-
get: () => () => {},
|
|
10
|
-
}),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
// Controllable mock for readLockfile — defaults to null (no lockfile data)
|
|
14
|
-
const mockReadLockfile = mock(() => null as Record<string, unknown> | null);
|
|
15
|
-
|
|
16
|
-
mock.module("../../../util/platform.js", () => ({
|
|
17
|
-
readLockfile: mockReadLockfile,
|
|
18
|
-
}));
|
|
4
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
19
5
|
|
|
20
6
|
import {
|
|
21
7
|
getExternalAssistantId,
|
|
@@ -24,65 +10,24 @@ import {
|
|
|
24
10
|
|
|
25
11
|
afterEach(() => {
|
|
26
12
|
resetExternalAssistantIdCache();
|
|
27
|
-
|
|
28
|
-
mockReadLockfile.mockImplementation(() => null);
|
|
29
|
-
delete process.env.BASE_DATA_DIR;
|
|
13
|
+
delete process.env.VELLUM_ASSISTANT_NAME;
|
|
30
14
|
});
|
|
31
15
|
|
|
32
16
|
describe("getExternalAssistantId", () => {
|
|
33
|
-
test("resolves from
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
{ assistantId: "vellum-old-fox", hatchedAt: "2025-01-01T00:00:00Z" },
|
|
37
|
-
{ assistantId: "vellum-new-eel", hatchedAt: "2025-06-15T12:00:00Z" },
|
|
38
|
-
],
|
|
39
|
-
}));
|
|
40
|
-
expect(getExternalAssistantId()).toBe("vellum-new-eel");
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("resolves from lockfile with single assistant entry", () => {
|
|
44
|
-
mockReadLockfile.mockImplementation(() => ({
|
|
45
|
-
assistants: [
|
|
46
|
-
{ assistantId: "vellum-solo-cat", hatchedAt: "2025-03-01T00:00:00Z" },
|
|
47
|
-
],
|
|
48
|
-
}));
|
|
49
|
-
expect(getExternalAssistantId()).toBe("vellum-solo-cat");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("resolves from BASE_DATA_DIR when lockfile has no data", () => {
|
|
53
|
-
process.env.BASE_DATA_DIR = "/tmp/vellum/assistants/vellum-true-eel";
|
|
54
|
-
expect(getExternalAssistantId()).toBe("vellum-true-eel");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("resolves from BASE_DATA_DIR with trailing slash", () => {
|
|
58
|
-
process.env.BASE_DATA_DIR = "/tmp/vellum/assistants/vellum-cool-heron/";
|
|
59
|
-
expect(getExternalAssistantId()).toBe("vellum-cool-heron");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("resolves from BASE_DATA_DIR with Windows-style backslashes", () => {
|
|
63
|
-
process.env.BASE_DATA_DIR =
|
|
64
|
-
"C:\\Users\\user\\.local\\share\\vellum\\assistants\\vellum-nice-fox";
|
|
65
|
-
expect(getExternalAssistantId()).toBe("vellum-nice-fox");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("resolves from BASE_DATA_DIR with /instances/<name> path", () => {
|
|
69
|
-
process.env.BASE_DATA_DIR = "/home/user/.vellum/instances/vellum-swift-owl";
|
|
70
|
-
expect(getExternalAssistantId()).toBe("vellum-swift-owl");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("resolves from BASE_DATA_DIR with /instances/<name> trailing slash", () => {
|
|
74
|
-
process.env.BASE_DATA_DIR =
|
|
75
|
-
"/home/user/.vellum/instances/vellum-swift-owl/";
|
|
76
|
-
expect(getExternalAssistantId()).toBe("vellum-swift-owl");
|
|
17
|
+
test("resolves from VELLUM_ASSISTANT_NAME env var", () => {
|
|
18
|
+
process.env.VELLUM_ASSISTANT_NAME = "vellum-cool-eel";
|
|
19
|
+
expect(getExternalAssistantId()).toBe("vellum-cool-eel");
|
|
77
20
|
});
|
|
78
21
|
|
|
79
|
-
test("
|
|
80
|
-
process.env.
|
|
81
|
-
expect(getExternalAssistantId()).toBe(
|
|
22
|
+
test("caches the resolved value", () => {
|
|
23
|
+
process.env.VELLUM_ASSISTANT_NAME = "vellum-cool-eel";
|
|
24
|
+
expect(getExternalAssistantId()).toBe("vellum-cool-eel");
|
|
25
|
+
// Change env var — cached value should still be returned
|
|
26
|
+
process.env.VELLUM_ASSISTANT_NAME = "vellum-other-fox";
|
|
27
|
+
expect(getExternalAssistantId()).toBe("vellum-cool-eel");
|
|
82
28
|
});
|
|
83
29
|
|
|
84
|
-
test("
|
|
85
|
-
delete process.env.BASE_DATA_DIR;
|
|
30
|
+
test("returns undefined when env var is not set", () => {
|
|
86
31
|
expect(getExternalAssistantId()).toBe(undefined);
|
|
87
32
|
});
|
|
88
33
|
});
|
|
@@ -6,81 +6,35 @@
|
|
|
6
6
|
* must identify which assistant the token belongs to, while the daemon
|
|
7
7
|
* internally uses 'self'.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 2. Most recently hatched entry in lockfile assistants array
|
|
12
|
-
* (sorted by `hatchedAt` descending) → assistantId
|
|
13
|
-
* 3. BASE_DATA_DIR path matching `/assistants/<name>` or `/instances/<name>` suffix
|
|
14
|
-
* 4. `undefined` — callers must handle the missing value
|
|
9
|
+
* Reads from the VELLUM_ASSISTANT_NAME env var, which is set by CLI
|
|
10
|
+
* hatch and Docker setup. Returns `undefined` if the env var is not set.
|
|
15
11
|
*
|
|
16
|
-
* The value is cached in memory after the first
|
|
12
|
+
* The value is cached in memory after the first read.
|
|
17
13
|
*/
|
|
18
14
|
|
|
19
|
-
import { getBaseDataDir } from "../../config/env-registry.js";
|
|
20
15
|
import { getLogger } from "../../util/logger.js";
|
|
21
|
-
import { readLockfile } from "../../util/platform.js";
|
|
22
16
|
|
|
23
17
|
const log = getLogger("external-assistant-id");
|
|
24
18
|
|
|
25
19
|
let cached: string | null | undefined;
|
|
26
20
|
|
|
27
21
|
/**
|
|
28
|
-
* Get the external assistant ID.
|
|
29
|
-
*
|
|
30
|
-
* Resolution order:
|
|
31
|
-
* 1. Cached in-memory value (populated on first call)
|
|
32
|
-
* 2. Most recently hatched entry in lockfile assistants array
|
|
33
|
-
* (sorted by `hatchedAt` descending) → assistantId
|
|
34
|
-
* 3. BASE_DATA_DIR path matching `/assistants/<name>` or `/instances/<name>` suffix
|
|
35
|
-
* 4. `undefined` when resolution fails entirely
|
|
22
|
+
* Get the external assistant ID from the VELLUM_ASSISTANT_NAME env var.
|
|
23
|
+
* Returns `undefined` when the env var is not set.
|
|
36
24
|
*/
|
|
37
25
|
export function getExternalAssistantId(): string | undefined {
|
|
38
26
|
if (cached !== undefined) {
|
|
39
27
|
return cached ?? undefined;
|
|
40
28
|
}
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// matching the pattern used elsewhere in the codebase.
|
|
51
|
-
const sorted = [...assistants].sort((a, b) => {
|
|
52
|
-
const dateA = new Date((a.hatchedAt as string) || 0).getTime();
|
|
53
|
-
const dateB = new Date((b.hatchedAt as string) || 0).getTime();
|
|
54
|
-
return dateB - dateA;
|
|
55
|
-
});
|
|
56
|
-
const latest = sorted[0];
|
|
57
|
-
if (typeof latest.assistantId === "string") {
|
|
58
|
-
cached = latest.assistantId;
|
|
59
|
-
log.info(
|
|
60
|
-
{ externalAssistantId: cached },
|
|
61
|
-
"Resolved external assistant ID from lockfile",
|
|
62
|
-
);
|
|
63
|
-
return cached;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
} catch (err) {
|
|
68
|
-
log.warn({ err }, "Failed to read lockfile for external assistant ID");
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Fallback: derive from BASE_DATA_DIR path
|
|
72
|
-
const base = getBaseDataDir();
|
|
73
|
-
if (base && typeof base === "string") {
|
|
74
|
-
const normalized = base.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
75
|
-
const match = normalized.match(/\/(?:assistants|instances)\/([^/]+)$/);
|
|
76
|
-
if (match) {
|
|
77
|
-
cached = match[1];
|
|
78
|
-
log.info(
|
|
79
|
-
{ externalAssistantId: cached },
|
|
80
|
-
"Resolved external assistant ID from BASE_DATA_DIR",
|
|
81
|
-
);
|
|
82
|
-
return cached;
|
|
83
|
-
}
|
|
30
|
+
const envName = process.env.VELLUM_ASSISTANT_NAME;
|
|
31
|
+
if (envName) {
|
|
32
|
+
cached = envName;
|
|
33
|
+
log.info(
|
|
34
|
+
{ externalAssistantId: cached },
|
|
35
|
+
"Resolved external assistant ID from VELLUM_ASSISTANT_NAME",
|
|
36
|
+
);
|
|
37
|
+
return cached;
|
|
84
38
|
}
|
|
85
39
|
|
|
86
40
|
cached = null;
|
|
@@ -352,6 +352,10 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
352
352
|
{ endpoint: "config/permissions/skip:GET", scopes: ["settings.read"] },
|
|
353
353
|
{ endpoint: "config/permissions/skip:PUT", scopes: ["settings.write"] },
|
|
354
354
|
|
|
355
|
+
// Generic config read/patch
|
|
356
|
+
{ endpoint: "config:GET", scopes: ["settings.read"] },
|
|
357
|
+
{ endpoint: "config:PATCH", scopes: ["settings.write"] },
|
|
358
|
+
|
|
355
359
|
// Conversation management
|
|
356
360
|
{ endpoint: "conversations:DELETE", scopes: ["chat.write"] },
|
|
357
361
|
{ endpoint: "conversations/wipe", scopes: ["chat.write"] },
|
|
@@ -366,6 +370,7 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
366
370
|
// Message content
|
|
367
371
|
{ endpoint: "messages/content", scopes: ["chat.read"] },
|
|
368
372
|
{ endpoint: "messages/llm-context", scopes: ["chat.read"] },
|
|
373
|
+
{ endpoint: "messages/tts", scopes: ["chat.read"] },
|
|
369
374
|
|
|
370
375
|
// Queued message deletion
|
|
371
376
|
{ endpoint: "messages/queued", scopes: ["chat.write"] },
|
|
@@ -443,7 +448,6 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
443
448
|
|
|
444
449
|
// Diagnostics
|
|
445
450
|
{ endpoint: "export", scopes: ["settings.read"] },
|
|
446
|
-
{ endpoint: "diagnostics/export", scopes: ["settings.read"] },
|
|
447
451
|
{ endpoint: "diagnostics/env-vars", scopes: ["settings.read"] },
|
|
448
452
|
|
|
449
453
|
// Dictation
|
|
@@ -512,3 +516,13 @@ registerPolicy("admin/upgrade-broadcast", {
|
|
|
512
516
|
requiredScopes: ["internal.write"],
|
|
513
517
|
allowedPrincipalTypes: ["svc_gateway"],
|
|
514
518
|
});
|
|
519
|
+
|
|
520
|
+
registerPolicy("admin/workspace-commit", {
|
|
521
|
+
requiredScopes: ["internal.write"],
|
|
522
|
+
allowedPrincipalTypes: ["svc_gateway"],
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
registerPolicy("admin/rollback-migrations", {
|
|
526
|
+
requiredScopes: ["internal.write"],
|
|
527
|
+
allowedPrincipalTypes: ["svc_gateway"],
|
|
528
|
+
});
|
|
@@ -28,22 +28,6 @@ import type { ScopeProfile, TokenAudience, TokenClaims } from "./types.js";
|
|
|
28
28
|
|
|
29
29
|
const log = getLogger("token-service");
|
|
30
30
|
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Bootstrap sentinel error
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Thrown when the gateway's signing-key bootstrap endpoint returns 403,
|
|
37
|
-
* indicating that bootstrap has already completed (daemon restart case).
|
|
38
|
-
* The caller should fall back to loading the key from disk.
|
|
39
|
-
*/
|
|
40
|
-
export class BootstrapAlreadyCompleted extends Error {
|
|
41
|
-
constructor() {
|
|
42
|
-
super("Gateway signing key bootstrap already completed");
|
|
43
|
-
this.name = "BootstrapAlreadyCompleted";
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
31
|
// ---------------------------------------------------------------------------
|
|
48
32
|
// Signing key management
|
|
49
33
|
// ---------------------------------------------------------------------------
|
|
@@ -58,28 +42,44 @@ function getSigningKeyPath(): string {
|
|
|
58
42
|
return join(getRootDir(), "protected", "actor-token-signing-key");
|
|
59
43
|
}
|
|
60
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Load a signing key from disk. Returns the key buffer if found and valid,
|
|
47
|
+
* or undefined if the file does not exist or is invalid.
|
|
48
|
+
*
|
|
49
|
+
* Used in the Docker 403-fallback path where generating a new key would
|
|
50
|
+
* create a mismatch with the gateway's already-bootstrapped key.
|
|
51
|
+
*/
|
|
52
|
+
export function loadSigningKey(): Buffer | undefined {
|
|
53
|
+
const keyPath = getSigningKeyPath();
|
|
54
|
+
if (!existsSync(keyPath)) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const raw = readFileSync(keyPath);
|
|
59
|
+
if (raw.length === 32) {
|
|
60
|
+
log.info("Auth signing key loaded from disk");
|
|
61
|
+
return raw;
|
|
62
|
+
}
|
|
63
|
+
log.warn("Signing key file has unexpected length");
|
|
64
|
+
return undefined;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
log.warn({ err }, "Failed to read signing key file");
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
/**
|
|
62
72
|
* Load a signing key from disk or generate and persist a new one.
|
|
63
73
|
* Uses atomic-write + chmod 0o600 for safe persistence.
|
|
64
74
|
*/
|
|
65
75
|
export function loadOrCreateSigningKey(): Buffer {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (existsSync(keyPath)) {
|
|
70
|
-
try {
|
|
71
|
-
const raw = readFileSync(keyPath);
|
|
72
|
-
if (raw.length === 32) {
|
|
73
|
-
log.info("Auth signing key loaded from disk");
|
|
74
|
-
return raw;
|
|
75
|
-
}
|
|
76
|
-
log.warn("Signing key file has unexpected length, regenerating");
|
|
77
|
-
} catch (err) {
|
|
78
|
-
log.warn({ err }, "Failed to read signing key file, regenerating");
|
|
79
|
-
}
|
|
76
|
+
const existing = loadSigningKey();
|
|
77
|
+
if (existing) {
|
|
78
|
+
return existing;
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
// Generate and persist a new key
|
|
82
|
+
const keyPath = getSigningKeyPath();
|
|
83
83
|
const newKey = randomBytes(32);
|
|
84
84
|
const dir = dirname(keyPath);
|
|
85
85
|
if (!existsSync(dir)) {
|
|
@@ -94,120 +94,25 @@ export function loadOrCreateSigningKey(): Buffer {
|
|
|
94
94
|
return newKey;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/**
|
|
98
|
-
* Fetch the shared signing key from the gateway's bootstrap endpoint.
|
|
99
|
-
*
|
|
100
|
-
* Used in Docker mode where the gateway owns the signing key and the daemon
|
|
101
|
-
* must fetch it at startup. Retries up to 30 times with 1s intervals to
|
|
102
|
-
* tolerate gateway startup delays.
|
|
103
|
-
*
|
|
104
|
-
* @returns A 32-byte Buffer containing the signing key.
|
|
105
|
-
* @throws {BootstrapAlreadyCompleted} If the gateway returns 403 (bootstrap
|
|
106
|
-
* already completed — daemon restart case). Caller should fall back to
|
|
107
|
-
* loading the key from disk.
|
|
108
|
-
* @throws {Error} If the gateway is unreachable after all retry attempts.
|
|
109
|
-
*/
|
|
110
|
-
export async function fetchSigningKeyFromGateway(): Promise<Buffer> {
|
|
111
|
-
const gatewayUrl = process.env.GATEWAY_INTERNAL_URL;
|
|
112
|
-
if (!gatewayUrl) {
|
|
113
|
-
throw new Error("GATEWAY_INTERNAL_URL not set — cannot fetch signing key");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const maxAttempts = 30;
|
|
117
|
-
const intervalMs = 1000;
|
|
118
|
-
|
|
119
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
120
|
-
let resp: Response | undefined;
|
|
121
|
-
try {
|
|
122
|
-
resp = await fetch(`${gatewayUrl}/internal/signing-key-bootstrap`, {
|
|
123
|
-
signal: AbortSignal.timeout(5000),
|
|
124
|
-
});
|
|
125
|
-
} catch (err) {
|
|
126
|
-
log.warn(
|
|
127
|
-
{ err, attempt },
|
|
128
|
-
"Signing key bootstrap: connection failed, retrying",
|
|
129
|
-
);
|
|
130
|
-
await Bun.sleep(intervalMs);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (resp.ok) {
|
|
135
|
-
const body = (await resp.json()) as { key: string };
|
|
136
|
-
const keyBuf = Buffer.from(body.key, "hex");
|
|
137
|
-
if (keyBuf.length !== 32) {
|
|
138
|
-
throw new Error(`Invalid signing key length: ${keyBuf.length}`);
|
|
139
|
-
}
|
|
140
|
-
log.info("Signing key fetched from gateway bootstrap endpoint");
|
|
141
|
-
return keyBuf;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (resp.status === 403) {
|
|
145
|
-
// Bootstrap already completed — fall through to file-based load.
|
|
146
|
-
// This happens on daemon restart when the gateway lockfile persists.
|
|
147
|
-
log.info(
|
|
148
|
-
"Gateway signing key bootstrap already completed — loading from disk",
|
|
149
|
-
);
|
|
150
|
-
throw new BootstrapAlreadyCompleted();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
log.warn(
|
|
154
|
-
{ status: resp.status, attempt },
|
|
155
|
-
"Signing key bootstrap: gateway not ready, retrying",
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
await Bun.sleep(intervalMs);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
throw new Error("Signing key bootstrap: timed out waiting for gateway");
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Persist a signing key to disk using an atomic-write pattern.
|
|
166
|
-
* Used after fetching the key from the gateway so daemon restarts can
|
|
167
|
-
* load it from disk when the gateway returns 403.
|
|
168
|
-
*/
|
|
169
|
-
function persistSigningKey(key: Buffer): void {
|
|
170
|
-
const keyPath = getSigningKeyPath();
|
|
171
|
-
const dir = dirname(keyPath);
|
|
172
|
-
if (!existsSync(dir)) {
|
|
173
|
-
mkdirSync(dir, { recursive: true });
|
|
174
|
-
}
|
|
175
|
-
const tmpPath = keyPath + ".tmp." + process.pid;
|
|
176
|
-
writeFileSync(tmpPath, key, { mode: 0o600 });
|
|
177
|
-
renameSync(tmpPath, keyPath);
|
|
178
|
-
chmodSync(keyPath, 0o600);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
97
|
/**
|
|
182
98
|
* Resolve the signing key for the current environment.
|
|
183
99
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
* loading the key from disk.
|
|
188
|
-
*
|
|
189
|
-
* In local mode, delegates to the existing file-based loadOrCreateSigningKey().
|
|
100
|
+
* Resolution order:
|
|
101
|
+
* 1. ACTOR_TOKEN_SIGNING_KEY env var (hex-encoded, set by CLI for Docker)
|
|
102
|
+
* 2. File-based load/create (~/.vellum/protected/actor-token-signing-key)
|
|
190
103
|
*/
|
|
191
|
-
export
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// Persist locally so daemon restarts (where gateway returns 403) load from disk.
|
|
199
|
-
persistSigningKey(key);
|
|
200
|
-
return key;
|
|
201
|
-
} catch (err) {
|
|
202
|
-
if (err instanceof BootstrapAlreadyCompleted) {
|
|
203
|
-
// Gateway already bootstrapped (daemon restart) — load from disk.
|
|
204
|
-
return loadOrCreateSigningKey();
|
|
205
|
-
}
|
|
206
|
-
throw err;
|
|
104
|
+
export function resolveSigningKey(): Buffer {
|
|
105
|
+
const envKey = process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
106
|
+
if (envKey) {
|
|
107
|
+
if (!/^[0-9a-f]{64}$/i.test(envKey)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Invalid ACTOR_TOKEN_SIGNING_KEY: expected 64 hex characters, got ${envKey.length} chars`,
|
|
110
|
+
);
|
|
207
111
|
}
|
|
112
|
+
log.info("Signing key loaded from ACTOR_TOKEN_SIGNING_KEY env var");
|
|
113
|
+
return Buffer.from(envKey, "hex");
|
|
208
114
|
}
|
|
209
115
|
|
|
210
|
-
// Local mode: use file-based load/create (unchanged behavior).
|
|
211
116
|
return loadOrCreateSigningKey();
|
|
212
117
|
}
|
|
213
118
|
|
|
@@ -246,7 +151,7 @@ export function isSigningKeyInitialized(): boolean {
|
|
|
246
151
|
|
|
247
152
|
/**
|
|
248
153
|
* Returns a short hex fingerprint of the current signing key.
|
|
249
|
-
* Used by
|
|
154
|
+
* Used by assistant_status to let clients detect instance switches.
|
|
250
155
|
*/
|
|
251
156
|
export function getSigningKeyFingerprint(): string {
|
|
252
157
|
return createHash("sha256")
|
|
@@ -15,7 +15,6 @@ import type {
|
|
|
15
15
|
ReadinessCheckResult,
|
|
16
16
|
SetupStatus,
|
|
17
17
|
} from "./channel-readiness-types.js";
|
|
18
|
-
import { probeLocalGatewayHealth } from "./local-gateway-health.js";
|
|
19
18
|
|
|
20
19
|
/** Remote check results are cached for 5 minutes before being considered stale. */
|
|
21
20
|
export const REMOTE_TTL_MS = 5 * 60 * 1000;
|
|
@@ -82,7 +81,7 @@ const voiceProbe: ChannelProbe = {
|
|
|
82
81
|
const hasPhone = !!resolveTwilioPhoneNumber();
|
|
83
82
|
const ingress = checkIngress();
|
|
84
83
|
|
|
85
|
-
|
|
84
|
+
return [
|
|
86
85
|
check(
|
|
87
86
|
"twilio_credentials",
|
|
88
87
|
hasCreds,
|
|
@@ -97,20 +96,6 @@ const voiceProbe: ChannelProbe = {
|
|
|
97
96
|
),
|
|
98
97
|
ingress,
|
|
99
98
|
];
|
|
100
|
-
|
|
101
|
-
if (ingress.passed) {
|
|
102
|
-
const gw = await probeLocalGatewayHealth();
|
|
103
|
-
checks.push(
|
|
104
|
-
check(
|
|
105
|
-
"gateway_health",
|
|
106
|
-
gw.healthy,
|
|
107
|
-
`Local gateway is serving requests at ${gw.target}`,
|
|
108
|
-
`Local gateway is not serving requests at ${gw.target}${gw.error ? `: ${gw.error}` : ""}`,
|
|
109
|
-
),
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return checks;
|
|
114
99
|
},
|
|
115
100
|
};
|
|
116
101
|
|
|
@@ -106,6 +106,7 @@ import { handleServePage } from "./routes/app-routes.js";
|
|
|
106
106
|
import { appRouteDefinitions } from "./routes/app-routes.js";
|
|
107
107
|
import { approvalRouteDefinitions } from "./routes/approval-routes.js";
|
|
108
108
|
import { attachmentRouteDefinitions } from "./routes/attachment-routes.js";
|
|
109
|
+
import { handleGetAudio } from "./routes/audio-routes.js";
|
|
109
110
|
import { avatarRouteDefinitions } from "./routes/avatar-routes.js";
|
|
110
111
|
import { brainGraphRouteDefinitions } from "./routes/brain-graph-routes.js";
|
|
111
112
|
import { btwRouteDefinitions } from "./routes/btw-routes.js";
|
|
@@ -144,16 +145,19 @@ import { handleGuardianRefresh } from "./routes/guardian-refresh-routes.js";
|
|
|
144
145
|
import { hostBashRouteDefinitions } from "./routes/host-bash-routes.js";
|
|
145
146
|
import { hostCuRouteDefinitions } from "./routes/host-cu-routes.js";
|
|
146
147
|
import { hostFileRouteDefinitions } from "./routes/host-file-routes.js";
|
|
147
|
-
import { handleHealth } from "./routes/identity-routes.js";
|
|
148
|
+
import { handleHealth, handleReadyz } from "./routes/identity-routes.js";
|
|
148
149
|
import { identityRouteDefinitions } from "./routes/identity-routes.js";
|
|
149
150
|
import { slackChannelRouteDefinitions } from "./routes/integrations/slack/channel.js";
|
|
150
151
|
import { slackShareRouteDefinitions } from "./routes/integrations/slack/share.js";
|
|
151
152
|
import { telegramRouteDefinitions } from "./routes/integrations/telegram.js";
|
|
152
153
|
import { twilioRouteDefinitions } from "./routes/integrations/twilio.js";
|
|
154
|
+
import { vercelRouteDefinitions } from "./routes/integrations/vercel.js";
|
|
153
155
|
import { inviteRouteDefinitions } from "./routes/invite-routes.js";
|
|
154
156
|
import { logExportRouteDefinitions } from "./routes/log-export-routes.js";
|
|
155
157
|
import { memoryItemRouteDefinitions } from "./routes/memory-item-routes.js";
|
|
158
|
+
import { migrationRollbackRouteDefinitions } from "./routes/migration-rollback-routes.js";
|
|
156
159
|
import { migrationRouteDefinitions } from "./routes/migration-routes.js";
|
|
160
|
+
import { notificationRouteDefinitions } from "./routes/notification-routes.js";
|
|
157
161
|
import { oauthAppsRouteDefinitions } from "./routes/oauth-apps.js";
|
|
158
162
|
import type { PairingHandlerContext } from "./routes/pairing-routes.js";
|
|
159
163
|
import {
|
|
@@ -172,10 +176,12 @@ import { surfaceContentRouteDefinitions } from "./routes/surface-content-routes.
|
|
|
172
176
|
import { telemetryRouteDefinitions } from "./routes/telemetry-routes.js";
|
|
173
177
|
import { traceEventRouteDefinitions } from "./routes/trace-event-routes.js";
|
|
174
178
|
import { trustRulesRouteDefinitions } from "./routes/trust-rules-routes.js";
|
|
179
|
+
import { ttsRouteDefinitions } from "./routes/tts-routes.js";
|
|
175
180
|
import { upgradeBroadcastRouteDefinitions } from "./routes/upgrade-broadcast-routes.js";
|
|
176
181
|
import { usageRouteDefinitions } from "./routes/usage-routes.js";
|
|
177
182
|
import { watchRouteDefinitions } from "./routes/watch-routes.js";
|
|
178
183
|
import { workItemRouteDefinitions } from "./routes/work-items-routes.js";
|
|
184
|
+
import { workspaceCommitRouteDefinitions } from "./routes/workspace-commit-routes.js";
|
|
179
185
|
import { workspaceRouteDefinitions } from "./routes/workspace-routes.js";
|
|
180
186
|
|
|
181
187
|
// Re-export for consumers
|
|
@@ -464,7 +470,10 @@ export class RuntimeHttpServer {
|
|
|
464
470
|
server.timeout(req, 1800);
|
|
465
471
|
// Skip request logging for health-check probes to reduce log noise.
|
|
466
472
|
const url = new URL(req.url);
|
|
467
|
-
if (
|
|
473
|
+
if (
|
|
474
|
+
(url.pathname === "/healthz" || url.pathname === "/readyz") &&
|
|
475
|
+
req.method === "GET"
|
|
476
|
+
) {
|
|
468
477
|
return this.routeRequest(req, server);
|
|
469
478
|
}
|
|
470
479
|
return withRequestLogging(req, () => this.routeRequest(req, server));
|
|
@@ -481,6 +490,10 @@ export class RuntimeHttpServer {
|
|
|
481
490
|
return handleHealth();
|
|
482
491
|
}
|
|
483
492
|
|
|
493
|
+
if (path === "/readyz" && req.method === "GET") {
|
|
494
|
+
return handleReadyz();
|
|
495
|
+
}
|
|
496
|
+
|
|
484
497
|
// WebSocket upgrade for the Chrome extension browser relay.
|
|
485
498
|
if (
|
|
486
499
|
path === "/v1/browser-relay" &&
|
|
@@ -503,6 +516,13 @@ export class RuntimeHttpServer {
|
|
|
503
516
|
const twilioResponse = await this.handleTwilioWebhook(req, path);
|
|
504
517
|
if (twilioResponse) return twilioResponse;
|
|
505
518
|
|
|
519
|
+
// Audio serving endpoint — before auth check because Twilio
|
|
520
|
+
// fetches these URLs directly. The audioId is an unguessable UUID.
|
|
521
|
+
const audioMatch = path.match(/^\/v1\/audio\/([^/]+)$/);
|
|
522
|
+
if (audioMatch && req.method === "GET") {
|
|
523
|
+
return handleGetAudio(audioMatch[1]);
|
|
524
|
+
}
|
|
525
|
+
|
|
506
526
|
// Pairing endpoints (unauthenticated, secret-gated)
|
|
507
527
|
if (path === "/v1/pairing/request" && req.method === "POST") {
|
|
508
528
|
return await handlePairingRequest(req, this.pairingContext);
|
|
@@ -920,6 +940,8 @@ export class RuntimeHttpServer {
|
|
|
920
940
|
}),
|
|
921
941
|
...identityRouteDefinitions(),
|
|
922
942
|
...upgradeBroadcastRouteDefinitions(),
|
|
943
|
+
...workspaceCommitRouteDefinitions(),
|
|
944
|
+
...migrationRollbackRouteDefinitions(),
|
|
923
945
|
...debugRouteDefinitions(),
|
|
924
946
|
...usageRouteDefinitions(),
|
|
925
947
|
...telemetryRouteDefinitions(),
|
|
@@ -931,6 +953,7 @@ export class RuntimeHttpServer {
|
|
|
931
953
|
...scheduleRouteDefinitions({
|
|
932
954
|
sendMessageDeps: this.sendMessageDeps,
|
|
933
955
|
}),
|
|
956
|
+
...notificationRouteDefinitions(),
|
|
934
957
|
...diagnosticsRouteDefinitions(),
|
|
935
958
|
...logExportRouteDefinitions(),
|
|
936
959
|
...documentRouteDefinitions(),
|
|
@@ -961,6 +984,7 @@ export class RuntimeHttpServer {
|
|
|
961
984
|
}
|
|
962
985
|
: undefined,
|
|
963
986
|
}),
|
|
987
|
+
...ttsRouteDefinitions(),
|
|
964
988
|
|
|
965
989
|
// Browser relay — not extracted into a domain module because
|
|
966
990
|
// these two routes depend on the in-process extensionRelayServer
|
|
@@ -1165,6 +1189,7 @@ export class RuntimeHttpServer {
|
|
|
1165
1189
|
...slackChannelRouteDefinitions(),
|
|
1166
1190
|
...slackShareRouteDefinitions(),
|
|
1167
1191
|
...twilioRouteDefinitions(),
|
|
1192
|
+
...vercelRouteDefinitions(),
|
|
1168
1193
|
...channelReadinessRouteDefinitions(),
|
|
1169
1194
|
...oauthAppsRouteDefinitions(),
|
|
1170
1195
|
...attachmentRouteDefinitions(),
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
ConfigError,
|
|
7
|
-
IngressBlockedError,
|
|
8
7
|
ProviderNotConfiguredError,
|
|
9
8
|
} from "../../util/errors.js";
|
|
10
9
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -14,7 +13,7 @@ const log = getLogger("runtime-http");
|
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Wrap an async endpoint handler with standard error handling.
|
|
17
|
-
* Catches
|
|
16
|
+
* Catches ConfigError (422) and generic errors (500).
|
|
18
17
|
*/
|
|
19
18
|
export async function withErrorHandling(
|
|
20
19
|
endpoint: string,
|
|
@@ -23,13 +22,6 @@ export async function withErrorHandling(
|
|
|
23
22
|
try {
|
|
24
23
|
return await handler();
|
|
25
24
|
} catch (err) {
|
|
26
|
-
if (err instanceof IngressBlockedError) {
|
|
27
|
-
log.warn(
|
|
28
|
-
{ endpoint, detectedTypes: err.detectedTypes },
|
|
29
|
-
"Blocked HTTP request containing secrets",
|
|
30
|
-
);
|
|
31
|
-
return httpError("UNPROCESSABLE_ENTITY", err.message, 422);
|
|
32
|
-
}
|
|
33
25
|
if (err instanceof ProviderNotConfiguredError) {
|
|
34
26
|
log.warn({ err, endpoint }, "No LLM provider configured");
|
|
35
27
|
return httpError(
|