@vellumai/assistant 0.4.48 → 0.4.49
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/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +280 -49
- package/src/__tests__/credential-vault.test.ts +138 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.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__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +1 -6
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +2 -7
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +36 -68
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +2 -9
- package/src/daemon/session.ts +138 -15
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/logfire.ts +16 -5
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +126 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +28 -40
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/retry.ts +1 -27
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/http-server.ts +8 -6
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +119 -46
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +94 -167
- package/src/tools/registry.ts +2 -7
- package/src/tools/skills/load.ts +62 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -501,8 +501,8 @@ describe("host_bash — environment setup", () => {
|
|
|
501
501
|
});
|
|
502
502
|
|
|
503
503
|
test("injects INTERNAL_GATEWAY_BASE_URL for host_bash commands", async () => {
|
|
504
|
-
const
|
|
505
|
-
process.env.
|
|
504
|
+
const originalGatewayPort = process.env.GATEWAY_PORT;
|
|
505
|
+
process.env.GATEWAY_PORT = "9000";
|
|
506
506
|
try {
|
|
507
507
|
const result = await hostShellTool.execute(
|
|
508
508
|
{
|
|
@@ -511,12 +511,12 @@ describe("host_bash — environment setup", () => {
|
|
|
511
511
|
makeContext(),
|
|
512
512
|
);
|
|
513
513
|
expect(result.isError).toBe(false);
|
|
514
|
-
expect(result.content.trim()).toBe("http://
|
|
514
|
+
expect(result.content.trim()).toBe("http://127.0.0.1:9000");
|
|
515
515
|
} finally {
|
|
516
|
-
if (
|
|
517
|
-
delete process.env.
|
|
516
|
+
if (originalGatewayPort === undefined) {
|
|
517
|
+
delete process.env.GATEWAY_PORT;
|
|
518
518
|
} else {
|
|
519
|
-
process.env.
|
|
519
|
+
process.env.GATEWAY_PORT = originalGatewayPort;
|
|
520
520
|
}
|
|
521
521
|
}
|
|
522
522
|
});
|
|
@@ -695,7 +695,11 @@ describe("host_bash — spawn error handling", () => {
|
|
|
695
695
|
describe("host_bash — proxy delegation", () => {
|
|
696
696
|
function makeMockProxy(result: ToolExecutionResult) {
|
|
697
697
|
const calls: Array<{
|
|
698
|
-
input: {
|
|
698
|
+
input: {
|
|
699
|
+
command: string;
|
|
700
|
+
working_dir?: string;
|
|
701
|
+
timeout_seconds?: number;
|
|
702
|
+
};
|
|
699
703
|
sessionId: string;
|
|
700
704
|
}> = [];
|
|
701
705
|
|
|
@@ -703,7 +707,11 @@ describe("host_bash — proxy delegation", () => {
|
|
|
703
707
|
proxy: {
|
|
704
708
|
isAvailable: () => true,
|
|
705
709
|
request: async (
|
|
706
|
-
input: {
|
|
710
|
+
input: {
|
|
711
|
+
command: string;
|
|
712
|
+
working_dir?: string;
|
|
713
|
+
timeout_seconds?: number;
|
|
714
|
+
},
|
|
707
715
|
sessionId: string,
|
|
708
716
|
_signal?: AbortSignal,
|
|
709
717
|
) => {
|
|
@@ -748,7 +756,10 @@ describe("host_bash — proxy delegation", () => {
|
|
|
748
756
|
});
|
|
749
757
|
|
|
750
758
|
test("still validates input before proxying (null bytes in command)", async () => {
|
|
751
|
-
const proxyResult: ToolExecutionResult = {
|
|
759
|
+
const proxyResult: ToolExecutionResult = {
|
|
760
|
+
content: "proxied",
|
|
761
|
+
isError: false,
|
|
762
|
+
};
|
|
752
763
|
const { proxy, calls } = makeMockProxy(proxyResult);
|
|
753
764
|
|
|
754
765
|
const ctx: ToolContext = {
|
|
@@ -756,10 +767,7 @@ describe("host_bash — proxy delegation", () => {
|
|
|
756
767
|
hostBashProxy: proxy as unknown as ToolContext["hostBashProxy"],
|
|
757
768
|
};
|
|
758
769
|
|
|
759
|
-
const result = await hostShellTool.execute(
|
|
760
|
-
{ command: "echo \0evil" },
|
|
761
|
-
ctx,
|
|
762
|
-
);
|
|
770
|
+
const result = await hostShellTool.execute({ command: "echo \0evil" }, ctx);
|
|
763
771
|
|
|
764
772
|
expect(result.isError).toBe(true);
|
|
765
773
|
expect(result.content).toContain("null bytes");
|
|
@@ -768,7 +776,10 @@ describe("host_bash — proxy delegation", () => {
|
|
|
768
776
|
});
|
|
769
777
|
|
|
770
778
|
test("still validates input before proxying (relative working_dir)", async () => {
|
|
771
|
-
const proxyResult: ToolExecutionResult = {
|
|
779
|
+
const proxyResult: ToolExecutionResult = {
|
|
780
|
+
content: "proxied",
|
|
781
|
+
isError: false,
|
|
782
|
+
};
|
|
772
783
|
const { proxy, calls } = makeMockProxy(proxyResult);
|
|
773
784
|
|
|
774
785
|
const ctx: ToolContext = {
|
|
@@ -803,7 +814,8 @@ describe("host_bash — proxy delegation", () => {
|
|
|
803
814
|
|
|
804
815
|
const ctx: ToolContext = {
|
|
805
816
|
...makeContext(),
|
|
806
|
-
hostBashProxy:
|
|
817
|
+
hostBashProxy:
|
|
818
|
+
unavailableProxy as unknown as ToolContext["hostBashProxy"],
|
|
807
819
|
};
|
|
808
820
|
|
|
809
821
|
spawnCalls.length = 0;
|
|
@@ -169,6 +169,7 @@ function makeSession(overrides: Record<string, unknown> = {}) {
|
|
|
169
169
|
updateClient: () => {},
|
|
170
170
|
setHostBashProxy: () => {},
|
|
171
171
|
setHostFileProxy: () => {},
|
|
172
|
+
setHostCuProxy: () => {},
|
|
172
173
|
emitConfirmationStateChanged: () => {},
|
|
173
174
|
emitActivityState: () => {},
|
|
174
175
|
setTurnChannelContext: () => {},
|
|
@@ -26,6 +26,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
26
26
|
getLogger: () => makeLoggerStub(),
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
|
+
import { setIngressPublicBaseUrl } from "../config/env.js";
|
|
29
30
|
import {
|
|
30
31
|
getPublicBaseUrl,
|
|
31
32
|
getTwilioStatusCallbackUrl,
|
|
@@ -78,19 +79,12 @@ function computeTwilioSignature(
|
|
|
78
79
|
// ---------------------------------------------------------------------------
|
|
79
80
|
|
|
80
81
|
describe("Ingress URL consistency between assistant and gateway", () => {
|
|
81
|
-
let savedIngressEnv: string | undefined;
|
|
82
|
-
|
|
83
82
|
beforeEach(() => {
|
|
84
|
-
|
|
85
|
-
delete process.env.INGRESS_PUBLIC_BASE_URL;
|
|
83
|
+
setIngressPublicBaseUrl(undefined);
|
|
86
84
|
});
|
|
87
85
|
|
|
88
86
|
afterEach(() => {
|
|
89
|
-
|
|
90
|
-
process.env.INGRESS_PUBLIC_BASE_URL = savedIngressEnv;
|
|
91
|
-
} else {
|
|
92
|
-
delete process.env.INGRESS_PUBLIC_BASE_URL;
|
|
93
|
-
}
|
|
87
|
+
setIngressPublicBaseUrl(undefined);
|
|
94
88
|
});
|
|
95
89
|
|
|
96
90
|
test("assistant callback URL and gateway signature reconstruction use same base when config is set", () => {
|
|
@@ -104,9 +98,8 @@ describe("Ingress URL consistency between assistant and gateway", () => {
|
|
|
104
98
|
"session-abc",
|
|
105
99
|
);
|
|
106
100
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// config.ingressPublicBaseUrl.
|
|
101
|
+
// The gateway reads config.ingress.publicBaseUrl from the workspace config
|
|
102
|
+
// file via ConfigFileCache.
|
|
110
103
|
const gatewayIngressPublicBaseUrl = getPublicBaseUrl(config);
|
|
111
104
|
|
|
112
105
|
// When Twilio calls the gateway, the gateway reconstructs the canonical URL
|
|
@@ -147,7 +140,7 @@ describe("Ingress URL consistency between assistant and gateway", () => {
|
|
|
147
140
|
const localRequestUrl = "http://127.0.0.1:7830/webhooks/twilio/status";
|
|
148
141
|
|
|
149
142
|
// Gateway reconstructs the canonical URL using its configured base
|
|
150
|
-
// (which was
|
|
143
|
+
// (which was read from the workspace config file via ConfigFileCache)
|
|
151
144
|
const gatewayIngressPublicBaseUrl = getPublicBaseUrl(config);
|
|
152
145
|
const canonicalUrl = reconstructGatewayCanonicalUrl(
|
|
153
146
|
gatewayIngressPublicBaseUrl,
|
|
@@ -208,20 +201,20 @@ describe("Ingress URL consistency between assistant and gateway", () => {
|
|
|
208
201
|
expect(recomputedWith).toBe(twilioSignature);
|
|
209
202
|
});
|
|
210
203
|
|
|
211
|
-
test("
|
|
212
|
-
// When no config.ingress.publicBaseUrl is set,
|
|
213
|
-
//
|
|
214
|
-
|
|
204
|
+
test("module-level state fallback produces consistent URLs across assistant and gateway", () => {
|
|
205
|
+
// When no config.ingress.publicBaseUrl is set, the assistant falls back
|
|
206
|
+
// to the module-level ingress state.
|
|
207
|
+
setIngressPublicBaseUrl("https://env-tunnel.example.com");
|
|
215
208
|
|
|
216
209
|
const config: IngressConfig = {};
|
|
217
210
|
|
|
218
|
-
// Assistant resolves the base URL from
|
|
211
|
+
// Assistant resolves the base URL from module state
|
|
219
212
|
const assistantBase = getPublicBaseUrl(config);
|
|
220
213
|
expect(assistantBase).toBe("https://env-tunnel.example.com");
|
|
221
214
|
|
|
222
|
-
// Gateway would
|
|
223
|
-
//
|
|
224
|
-
const gatewayIngressPublicBaseUrl =
|
|
215
|
+
// Gateway would read the same value from the workspace config file
|
|
216
|
+
// via ConfigFileCache.getString("ingress", "publicBaseUrl").
|
|
217
|
+
const gatewayIngressPublicBaseUrl = "https://env-tunnel.example.com";
|
|
225
218
|
|
|
226
219
|
// Callback URL generated by assistant
|
|
227
220
|
const callbackUrl = getTwilioVoiceWebhookUrl(config, "session-xyz");
|
|
@@ -5,6 +5,9 @@ import { credentialKey } from "../security/credential-key.js";
|
|
|
5
5
|
const secureKeyValues = new Map<string, string>();
|
|
6
6
|
let mockTwilioAccountSid: string | undefined;
|
|
7
7
|
|
|
8
|
+
/** Set of providers that should report as connected via isProviderConnected(). */
|
|
9
|
+
const connectedProviders = new Set<string>();
|
|
10
|
+
|
|
8
11
|
mock.module("../security/secure-keys.js", () => ({
|
|
9
12
|
getSecureKey: (account: string) => secureKeyValues.get(account),
|
|
10
13
|
}));
|
|
@@ -17,12 +20,27 @@ mock.module("../config/loader.js", () => ({
|
|
|
17
20
|
}),
|
|
18
21
|
}));
|
|
19
22
|
|
|
23
|
+
mock.module("../oauth/oauth-store.js", () => ({
|
|
24
|
+
isProviderConnected: (providerKey: string) =>
|
|
25
|
+
connectedProviders.has(providerKey),
|
|
26
|
+
getConnectionByProvider: (providerKey: string) =>
|
|
27
|
+
connectedProviders.has(providerKey)
|
|
28
|
+
? { id: `conn-${providerKey}`, status: "active" }
|
|
29
|
+
: undefined,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
/** Mark a provider as fully connected (active row + access token). */
|
|
33
|
+
function setOAuthConnected(providerKey: string): void {
|
|
34
|
+
connectedProviders.add(providerKey);
|
|
35
|
+
}
|
|
36
|
+
|
|
20
37
|
const { getIntegrationSummary, formatIntegrationSummary, hasCapability } =
|
|
21
38
|
await import("../schedule/integration-status.js");
|
|
22
39
|
|
|
23
40
|
describe("integration-status", () => {
|
|
24
41
|
beforeEach(() => {
|
|
25
42
|
secureKeyValues.clear();
|
|
43
|
+
connectedProviders.clear();
|
|
26
44
|
mockTwilioAccountSid = undefined;
|
|
27
45
|
});
|
|
28
46
|
|
|
@@ -38,21 +56,11 @@ describe("integration-status", () => {
|
|
|
38
56
|
});
|
|
39
57
|
|
|
40
58
|
test("returns all connected when all keys are set", () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"tok",
|
|
44
|
-
);
|
|
45
|
-
secureKeyValues.set(
|
|
46
|
-
credentialKey("integration:slack", "access_token"),
|
|
47
|
-
"tok",
|
|
48
|
-
);
|
|
59
|
+
setOAuthConnected("integration:gmail");
|
|
60
|
+
setOAuthConnected("integration:slack");
|
|
49
61
|
mockTwilioAccountSid = "sid";
|
|
50
62
|
secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
|
|
51
|
-
|
|
52
|
-
secureKeyValues.set(
|
|
53
|
-
credentialKey("telegram", "webhook_secret"),
|
|
54
|
-
"secret",
|
|
55
|
-
);
|
|
63
|
+
setOAuthConnected("telegram");
|
|
56
64
|
|
|
57
65
|
const summary = getIntegrationSummary();
|
|
58
66
|
expect(summary.every((s: { connected: boolean }) => s.connected)).toBe(
|
|
@@ -63,11 +71,7 @@ describe("integration-status", () => {
|
|
|
63
71
|
test("returns mixed status", () => {
|
|
64
72
|
mockTwilioAccountSid = "sid";
|
|
65
73
|
secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
|
|
66
|
-
|
|
67
|
-
secureKeyValues.set(
|
|
68
|
-
credentialKey("telegram", "webhook_secret"),
|
|
69
|
-
"secret",
|
|
70
|
-
);
|
|
74
|
+
setOAuthConnected("telegram");
|
|
71
75
|
|
|
72
76
|
const summary = getIntegrationSummary();
|
|
73
77
|
const connected = summary.filter(
|
|
@@ -95,9 +99,8 @@ describe("integration-status", () => {
|
|
|
95
99
|
expect(twilio?.connected).toBe(false);
|
|
96
100
|
});
|
|
97
101
|
|
|
98
|
-
test("Telegram disconnected when
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
test("Telegram disconnected when no connection record exists", () => {
|
|
103
|
+
// No oauth_connection record for telegram — should be disconnected
|
|
101
104
|
const summary = getIntegrationSummary();
|
|
102
105
|
const telegram = summary.find(
|
|
103
106
|
(s: { name: string }) => s.name === "Telegram",
|
|
@@ -110,11 +113,7 @@ describe("integration-status", () => {
|
|
|
110
113
|
test("shows checkmarks and crosses", () => {
|
|
111
114
|
mockTwilioAccountSid = "sid";
|
|
112
115
|
secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
|
|
113
|
-
|
|
114
|
-
secureKeyValues.set(
|
|
115
|
-
credentialKey("telegram", "webhook_secret"),
|
|
116
|
-
"secret",
|
|
117
|
-
);
|
|
116
|
+
setOAuthConnected("telegram");
|
|
118
117
|
|
|
119
118
|
const result = formatIntegrationSummary();
|
|
120
119
|
expect(result).toBe(
|
|
@@ -130,21 +129,11 @@ describe("integration-status", () => {
|
|
|
130
129
|
});
|
|
131
130
|
|
|
132
131
|
test("all connected", () => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
"tok",
|
|
136
|
-
);
|
|
137
|
-
secureKeyValues.set(
|
|
138
|
-
credentialKey("integration:slack", "access_token"),
|
|
139
|
-
"tok",
|
|
140
|
-
);
|
|
132
|
+
setOAuthConnected("integration:gmail");
|
|
133
|
+
setOAuthConnected("integration:slack");
|
|
141
134
|
mockTwilioAccountSid = "sid";
|
|
142
135
|
secureKeyValues.set(credentialKey("twilio", "auth_token"), "auth");
|
|
143
|
-
|
|
144
|
-
secureKeyValues.set(
|
|
145
|
-
credentialKey("telegram", "webhook_secret"),
|
|
146
|
-
"secret",
|
|
147
|
-
);
|
|
136
|
+
setOAuthConnected("telegram");
|
|
148
137
|
|
|
149
138
|
const result = formatIntegrationSummary();
|
|
150
139
|
expect(result).toBe(
|
|
@@ -160,17 +149,12 @@ describe("integration-status", () => {
|
|
|
160
149
|
});
|
|
161
150
|
|
|
162
151
|
test("returns true when any integration in category is connected", () => {
|
|
163
|
-
|
|
164
|
-
secureKeyValues.set(
|
|
165
|
-
credentialKey("telegram", "webhook_secret"),
|
|
166
|
-
"secret",
|
|
167
|
-
);
|
|
152
|
+
setOAuthConnected("telegram");
|
|
168
153
|
expect(hasCapability("messaging")).toBe(true);
|
|
169
154
|
});
|
|
170
155
|
|
|
171
|
-
test("returns false when
|
|
172
|
-
|
|
173
|
-
// Missing webhook_secret — Telegram should not count as connected
|
|
156
|
+
test("returns false when no connection record exists for category integrations", () => {
|
|
157
|
+
// No oauth_connection record for telegram — should not count as connected
|
|
174
158
|
expect(hasCapability("messaging")).toBe(false);
|
|
175
159
|
});
|
|
176
160
|
|
|
@@ -179,10 +163,7 @@ describe("integration-status", () => {
|
|
|
179
163
|
});
|
|
180
164
|
|
|
181
165
|
test("email category checks Gmail", () => {
|
|
182
|
-
|
|
183
|
-
credentialKey("integration:gmail", "access_token"),
|
|
184
|
-
"tok",
|
|
185
|
-
);
|
|
166
|
+
setOAuthConnected("integration:gmail");
|
|
186
167
|
expect(hasCapability("email")).toBe(true);
|
|
187
168
|
});
|
|
188
169
|
});
|
|
@@ -24,8 +24,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
26
|
// Prevent ensureTelegramBotUsernameResolved() from reading real credentials
|
|
27
|
-
// and calling the Telegram API
|
|
28
|
-
// with the real bot username and shadow the env var override in tests.
|
|
27
|
+
// and calling the Telegram API.
|
|
29
28
|
mock.module("../security/secure-keys.js", () => ({
|
|
30
29
|
getSecureKey: () => undefined,
|
|
31
30
|
setSecureKey: () => {},
|
|
@@ -35,6 +34,13 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
35
34
|
deleteSecureKeyAsync: async () => {},
|
|
36
35
|
}));
|
|
37
36
|
|
|
37
|
+
// Mock getTelegramBotUsername — the env var fallback was removed so we
|
|
38
|
+
// control the return value directly via a mutable variable.
|
|
39
|
+
let mockTelegramBotUsername: string | undefined;
|
|
40
|
+
mock.module("../telegram/bot-username.js", () => ({
|
|
41
|
+
getTelegramBotUsername: () => mockTelegramBotUsername,
|
|
42
|
+
}));
|
|
43
|
+
|
|
38
44
|
import { getSqlite, initializeDb, resetDb } from "../memory/db.js";
|
|
39
45
|
import {
|
|
40
46
|
handleCreateInvite,
|
|
@@ -94,8 +100,7 @@ describe("ingress invite HTTP routes", () => {
|
|
|
94
100
|
});
|
|
95
101
|
|
|
96
102
|
test("POST /v1/contacts/invites — includes canonical share URL when bot username is configured", async () => {
|
|
97
|
-
|
|
98
|
-
process.env.TELEGRAM_BOT_USERNAME = "test_invite_bot";
|
|
103
|
+
mockTelegramBotUsername = "test_invite_bot";
|
|
99
104
|
|
|
100
105
|
try {
|
|
101
106
|
const req = new Request("http://localhost/v1/contacts/invites", {
|
|
@@ -121,11 +126,7 @@ describe("ingress invite HTTP routes", () => {
|
|
|
121
126
|
expect(share.url).toBe(`https://t.me/test_invite_bot?start=iv_${token}`);
|
|
122
127
|
expect(typeof share.displayText).toBe("string");
|
|
123
128
|
} finally {
|
|
124
|
-
|
|
125
|
-
delete process.env.TELEGRAM_BOT_USERNAME;
|
|
126
|
-
} else {
|
|
127
|
-
process.env.TELEGRAM_BOT_USERNAME = prevBotUsername;
|
|
128
|
-
}
|
|
129
|
+
mockTelegramBotUsername = undefined;
|
|
129
130
|
}
|
|
130
131
|
});
|
|
131
132
|
|
|
@@ -36,7 +36,7 @@ const TEST_DIR = join(
|
|
|
36
36
|
);
|
|
37
37
|
const TOKEN_DIR = join(TEST_DIR, ".vellum", "protected");
|
|
38
38
|
const TOKEN_PATH = join(TOKEN_DIR, "keychain-broker.token");
|
|
39
|
-
const SOCKET_PATH = join(TEST_DIR, "broker.sock");
|
|
39
|
+
const SOCKET_PATH = join(TEST_DIR, ".vellum", "keychain-broker.sock");
|
|
40
40
|
const TEST_TOKEN = "test-auth-token-abc123";
|
|
41
41
|
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
@@ -106,14 +106,11 @@ function createMockBroker(): {
|
|
|
106
106
|
// Setup / teardown
|
|
107
107
|
// ---------------------------------------------------------------------------
|
|
108
108
|
|
|
109
|
-
let originalEnv: string | undefined;
|
|
110
|
-
|
|
111
109
|
beforeAll(() => {
|
|
112
110
|
mkdirSync(TOKEN_DIR, { recursive: true });
|
|
113
111
|
});
|
|
114
112
|
|
|
115
113
|
beforeEach(() => {
|
|
116
|
-
originalEnv = process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
|
|
117
114
|
// Clean up socket file from prior test
|
|
118
115
|
try {
|
|
119
116
|
rmSync(SOCKET_PATH, { force: true });
|
|
@@ -122,14 +119,6 @@ beforeEach(() => {
|
|
|
122
119
|
}
|
|
123
120
|
});
|
|
124
121
|
|
|
125
|
-
afterEach(() => {
|
|
126
|
-
if (originalEnv === undefined) {
|
|
127
|
-
delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
|
|
128
|
-
} else {
|
|
129
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = originalEnv;
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
122
|
afterAll(() => {
|
|
134
123
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
135
124
|
});
|
|
@@ -157,15 +146,15 @@ describe("keychain-broker-client", () => {
|
|
|
157
146
|
// isAvailable()
|
|
158
147
|
// -----------------------------------------------------------------------
|
|
159
148
|
describe("isAvailable", () => {
|
|
160
|
-
test("returns false when
|
|
161
|
-
delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
|
|
149
|
+
test("returns false when socket file does not exist", () => {
|
|
162
150
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
163
151
|
const client = createBrokerClient();
|
|
164
152
|
expect(client.isAvailable()).toBe(false);
|
|
165
153
|
});
|
|
166
154
|
|
|
167
155
|
test("returns false when token file does not exist", () => {
|
|
168
|
-
|
|
156
|
+
// Create the socket file so that check passes
|
|
157
|
+
writeFileSync(SOCKET_PATH, "");
|
|
169
158
|
try {
|
|
170
159
|
rmSync(TOKEN_PATH, { force: true });
|
|
171
160
|
} catch {
|
|
@@ -175,8 +164,8 @@ describe("keychain-broker-client", () => {
|
|
|
175
164
|
expect(client.isAvailable()).toBe(false);
|
|
176
165
|
});
|
|
177
166
|
|
|
178
|
-
test("returns true when both
|
|
179
|
-
|
|
167
|
+
test("returns true when both socket file and token file exist", () => {
|
|
168
|
+
writeFileSync(SOCKET_PATH, "");
|
|
180
169
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
181
170
|
const client = createBrokerClient();
|
|
182
171
|
expect(client.isAvailable()).toBe(true);
|
|
@@ -190,7 +179,6 @@ describe("keychain-broker-client", () => {
|
|
|
190
179
|
let broker: ReturnType<typeof createMockBroker>;
|
|
191
180
|
|
|
192
181
|
beforeEach(async () => {
|
|
193
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
194
182
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
195
183
|
broker = createMockBroker();
|
|
196
184
|
});
|
|
@@ -343,7 +331,6 @@ describe("keychain-broker-client", () => {
|
|
|
343
331
|
let broker: ReturnType<typeof createMockBroker>;
|
|
344
332
|
|
|
345
333
|
beforeEach(async () => {
|
|
346
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
347
334
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
348
335
|
broker = createMockBroker();
|
|
349
336
|
});
|
|
@@ -381,7 +368,6 @@ describe("keychain-broker-client", () => {
|
|
|
381
368
|
let broker: ReturnType<typeof createMockBroker>;
|
|
382
369
|
|
|
383
370
|
beforeEach(async () => {
|
|
384
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
385
371
|
writeFileSync(TOKEN_PATH, "old-token");
|
|
386
372
|
broker = createMockBroker();
|
|
387
373
|
});
|
|
@@ -441,59 +427,42 @@ describe("keychain-broker-client", () => {
|
|
|
441
427
|
// Graceful degradation
|
|
442
428
|
// -----------------------------------------------------------------------
|
|
443
429
|
describe("graceful degradation", () => {
|
|
444
|
-
test("get returns null when
|
|
445
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
430
|
+
test("get returns null when socket file does not exist", async () => {
|
|
446
431
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
447
432
|
const client = createBrokerClient();
|
|
448
433
|
const result = await client.get("test-key");
|
|
449
434
|
expect(result).toBeNull();
|
|
450
435
|
});
|
|
451
436
|
|
|
452
|
-
test("set returns false when
|
|
453
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
437
|
+
test("set returns false when socket file does not exist", async () => {
|
|
454
438
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
455
439
|
const client = createBrokerClient();
|
|
456
440
|
const result = await client.set("test-key", "value");
|
|
457
441
|
expect(result).toBe(false);
|
|
458
442
|
});
|
|
459
443
|
|
|
460
|
-
test("del returns false when
|
|
461
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
444
|
+
test("del returns false when socket file does not exist", async () => {
|
|
462
445
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
463
446
|
const client = createBrokerClient();
|
|
464
447
|
const result = await client.del("test-key");
|
|
465
448
|
expect(result).toBe(false);
|
|
466
449
|
});
|
|
467
450
|
|
|
468
|
-
test("list returns empty array when
|
|
469
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
451
|
+
test("list returns empty array when socket file does not exist", async () => {
|
|
470
452
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
471
453
|
const client = createBrokerClient();
|
|
472
454
|
const result = await client.list();
|
|
473
455
|
expect(result).toEqual([]);
|
|
474
456
|
});
|
|
475
457
|
|
|
476
|
-
test("ping returns null when
|
|
477
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
458
|
+
test("ping returns null when socket file does not exist", async () => {
|
|
478
459
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
479
460
|
const client = createBrokerClient();
|
|
480
461
|
const result = await client.ping();
|
|
481
462
|
expect(result).toBeNull();
|
|
482
463
|
});
|
|
483
464
|
|
|
484
|
-
test("returns fallbacks when socket path env var is unset", async () => {
|
|
485
|
-
delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
|
|
486
|
-
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
487
|
-
const client = createBrokerClient();
|
|
488
|
-
expect(await client.get("key")).toBeNull();
|
|
489
|
-
expect(await client.set("key", "val")).toBe(false);
|
|
490
|
-
expect(await client.del("key")).toBe(false);
|
|
491
|
-
expect(await client.list()).toEqual([]);
|
|
492
|
-
expect(await client.ping()).toBeNull();
|
|
493
|
-
});
|
|
494
|
-
|
|
495
465
|
test("returns fallbacks when token file is missing", async () => {
|
|
496
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
497
466
|
try {
|
|
498
467
|
rmSync(TOKEN_PATH, { force: true });
|
|
499
468
|
} catch {
|
|
@@ -515,7 +484,6 @@ describe("keychain-broker-client", () => {
|
|
|
515
484
|
let broker: ReturnType<typeof createMockBroker>;
|
|
516
485
|
|
|
517
486
|
beforeEach(async () => {
|
|
518
|
-
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = SOCKET_PATH;
|
|
519
487
|
writeFileSync(TOKEN_PATH, TEST_TOKEN);
|
|
520
488
|
broker = createMockBroker();
|
|
521
489
|
});
|