@vellumai/assistant 0.4.34 → 0.4.36
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/AGENTS.md +1 -1
- package/ARCHITECTURE.md +44 -49
- package/README.md +32 -20
- package/docs/architecture/keychain-broker.md +186 -0
- package/docs/architecture/security.md +110 -116
- package/docs/runbook-trusted-contacts.md +2 -2
- package/docs/skills.md +25 -25
- package/package.json +4 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
- package/src/__tests__/actor-token-service.test.ts +1 -0
- package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
- package/src/__tests__/assistant-id-boundary-guard.test.ts +91 -43
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/bundle-scanner.test.ts +1 -1
- package/src/__tests__/channel-guardian.test.ts +102 -102
- package/src/__tests__/channel-invite-transport.test.ts +155 -256
- package/src/__tests__/channel-readiness-routes.test.ts +336 -0
- package/src/__tests__/checker.test.ts +6 -6
- package/src/__tests__/chrome-cdp.test.ts +350 -0
- package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
- package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
- package/src/__tests__/config-loader-migration.test.ts +85 -0
- package/src/__tests__/conversation-pairing.test.ts +370 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
- package/src/__tests__/credential-broker-server-use.test.ts +1 -10
- package/src/__tests__/credential-security-e2e.test.ts +7 -1
- package/src/__tests__/credential-security-invariants.test.ts +14 -20
- package/src/__tests__/credential-vault-unit.test.ts +1 -11
- package/src/__tests__/credential-vault.test.ts +5 -19
- package/src/__tests__/credentials-cli.test.ts +806 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
- package/src/__tests__/email-invite-adapter.test.ts +78 -0
- package/src/__tests__/email-service-config-fallback.test.ts +102 -0
- package/src/__tests__/encrypted-store.test.ts +6 -6
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
- package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
- package/src/__tests__/guardian-outbound-http.test.ts +53 -47
- package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
- package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
- package/src/__tests__/handlers-telegram-config.test.ts +8 -2
- package/src/__tests__/handlers-twitter-config.test.ts +2 -2
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
- package/src/__tests__/ingress-reconcile.test.ts +6 -0
- package/src/__tests__/intent-routing.test.ts +23 -4
- package/src/__tests__/invite-routes-http.test.ts +12 -0
- package/src/__tests__/ipc-snapshot.test.ts +8 -2
- package/src/__tests__/keychain-broker-client.test.ts +543 -0
- package/src/__tests__/llm-usage-store.test.ts +344 -0
- package/src/__tests__/mcp-client-auth.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/migration-transport.test.ts +49 -0
- package/src/__tests__/notification-broadcaster.test.ts +205 -5
- package/src/__tests__/notification-deep-link.test.ts +365 -1
- package/src/__tests__/oauth-connect-handler.test.ts +2 -2
- package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
- package/src/__tests__/proxy-approval-callback.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +1 -1
- package/src/__tests__/recording-intent-handler.test.ts +6 -1
- package/src/__tests__/recording-state-machine.test.ts +1 -1
- package/src/__tests__/relay-server.test.ts +9 -1
- package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
- package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +8 -2
- package/src/__tests__/secure-keys.test.ts +175 -216
- package/src/__tests__/session-confirmation-signals.test.ts +1 -1
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/session-queue.test.ts +2 -1
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
- package/src/__tests__/skill-feature-flags.test.ts +12 -9
- package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
- package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
- package/src/__tests__/skills.test.ts +34 -4
- package/src/__tests__/slack-channel-config.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +26 -4
- package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
- package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
- package/src/__tests__/twitter-auth-handler.test.ts +2 -2
- package/src/__tests__/twitter-oauth-client.test.ts +1 -1
- package/src/__tests__/usage-routes.test.ts +339 -0
- package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
- package/src/agent/loop.ts +3 -0
- package/src/amazon/checkout.ts +0 -1
- package/src/approvals/guardian-request-resolvers.ts +9 -1
- package/src/bundler/app-bundler.ts +28 -12
- package/src/bundler/bundle-scanner.ts +1 -1
- package/src/bundler/bundle-signer.ts +3 -3
- package/src/bundler/manifest.ts +1 -1
- package/src/bundler/signature-verifier.ts +3 -3
- package/src/channels/config.ts +1 -1
- package/src/cli/AGENTS.md +63 -0
- package/src/cli/__tests__/notifications.test.ts +470 -0
- package/src/cli/amazon.ts +344 -167
- package/src/cli/audit.ts +85 -0
- package/src/cli/autonomy.ts +369 -0
- package/src/cli/channels.ts +51 -0
- package/src/cli/completions.ts +208 -0
- package/src/cli/config.ts +220 -0
- package/src/cli/contacts.ts +471 -0
- package/src/cli/credentials.ts +564 -0
- package/src/cli/default-action.ts +14 -0
- package/src/cli/dev.ts +131 -0
- package/src/cli/doctor.ts +398 -0
- package/src/cli/email.ts +491 -0
- package/src/cli/influencer.ts +72 -0
- package/src/cli/integrations.ts +248 -57
- package/src/cli/keys.ts +114 -0
- package/src/cli/map.ts +46 -54
- package/src/cli/mcp.ts +111 -3
- package/src/cli/{config-commands.ts → memory.ts} +133 -242
- package/src/cli/notifications.ts +407 -0
- package/src/cli/program.ts +65 -0
- package/src/cli/reference.ts +48 -0
- package/src/cli/sequence.ts +154 -0
- package/src/cli/sessions.ts +262 -0
- package/src/cli/trust.ts +177 -0
- package/src/cli/twitter.ts +323 -106
- package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
- package/src/config/bundled-skills/amazon/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
- package/src/config/bundled-skills/contacts/SKILL.md +178 -10
- package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +175 -145
- package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
- package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/core-schema.ts +7 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +26 -0
- package/src/config/schema.ts +4 -0
- package/src/config/skill-state.ts +0 -13
- package/src/config/system-prompt.ts +27 -0
- package/src/contacts/contact-store.ts +25 -0
- package/src/daemon/computer-use-session.ts +1 -1
- package/src/daemon/handlers/apps.ts +1 -0
- package/src/daemon/handlers/config-channels.ts +3 -3
- package/src/daemon/handlers/config-dispatch.ts +29 -0
- package/src/daemon/handlers/config-inbox.ts +4 -3
- package/src/daemon/handlers/config.ts +3 -43
- package/src/daemon/handlers/contacts.ts +34 -0
- package/src/daemon/handlers/index.ts +17 -3
- package/src/daemon/handlers/session-user-message.ts +7 -0
- package/src/daemon/handlers/sessions.ts +21 -2
- package/src/daemon/handlers/shared.ts +17 -0
- package/src/daemon/ipc-contract/apps.ts +2 -0
- package/src/daemon/ipc-contract/computer-use.ts +9 -0
- package/src/daemon/ipc-contract/contacts.ts +3 -3
- package/src/daemon/ipc-contract/inbox.ts +2 -0
- package/src/daemon/ipc-contract/messages.ts +4 -0
- package/src/daemon/ipc-contract/sessions.ts +8 -0
- package/src/daemon/ipc-contract-inventory.json +1 -0
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/ride-shotgun-handler.ts +139 -25
- package/src/daemon/session-agent-loop-handlers.ts +100 -0
- package/src/daemon/session-agent-loop.ts +72 -0
- package/src/daemon/session-tool-setup.ts +7 -0
- package/src/daemon/session.ts +23 -1
- package/src/daemon/tool-side-effects.ts +39 -1
- package/src/email/service.ts +59 -2
- package/src/index.ts +2 -60
- package/src/mcp/mcp-oauth-provider.ts +90 -8
- package/src/media/app-icon-generator.ts +86 -0
- package/src/memory/db-init.ts +12 -1
- package/src/memory/llm-usage-store.ts +186 -0
- package/src/memory/migrations/026-guardian-verification-sessions.ts +28 -9
- package/src/memory/migrations/027a-guardian-bootstrap-token.ts +16 -3
- package/src/memory/migrations/038-actor-token-records.ts +8 -1
- package/src/memory/migrations/039-actor-refresh-token-records.ts +11 -2
- package/src/memory/migrations/110-channel-guardian.ts +27 -6
- package/src/memory/migrations/112-assistant-inbox.ts +39 -15
- package/src/memory/migrations/114-notifications.ts +37 -15
- package/src/memory/migrations/117-conversation-attention.ts +33 -9
- package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
- package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/migrations/schema-introspection.ts +18 -0
- package/src/memory/schema-migration.ts +1 -0
- package/src/memory/shared-app-links-store.ts +1 -1
- package/src/messaging/registry.ts +27 -0
- package/src/notifications/README.md +79 -70
- package/src/notifications/broadcaster.ts +2 -1
- package/src/notifications/conversation-pairing.ts +147 -13
- package/src/notifications/copy-composer.ts +7 -3
- package/src/notifications/destination-resolver.ts +14 -1
- package/src/notifications/emit-signal.ts +3 -2
- package/src/notifications/signal.ts +105 -1
- package/src/notifications/types.ts +16 -0
- package/src/permissions/checker.ts +29 -3
- package/src/permissions/prompter.ts +11 -3
- package/src/runtime/access-request-helper.ts +2 -1
- package/src/runtime/auth/route-policy.ts +7 -1
- package/src/runtime/channel-invite-transport.ts +40 -63
- package/src/runtime/channel-invite-transports/email.ts +13 -39
- package/src/runtime/channel-invite-transports/slack.ts +5 -34
- package/src/runtime/channel-invite-transports/sms.ts +8 -29
- package/src/runtime/channel-invite-transports/telegram.ts +69 -28
- package/src/runtime/channel-invite-transports/voice.ts +0 -7
- package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
- package/src/runtime/channel-readiness-service.ts +202 -45
- package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
- package/src/runtime/guardian-outbound-actions.ts +8 -5
- package/src/runtime/http-server.ts +5 -9
- package/src/runtime/http-types.ts +13 -1
- package/src/runtime/invite-instruction-generator.ts +178 -0
- package/src/runtime/invite-service.ts +22 -25
- package/src/runtime/migrations/migration-transport.ts +13 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
- package/src/runtime/routes/channel-readiness-routes.ts +30 -11
- package/src/runtime/routes/contact-routes.ts +54 -26
- package/src/runtime/routes/guardian-bootstrap-routes.ts +1 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
- package/src/runtime/routes/integration-routes.ts +1 -1
- package/src/runtime/routes/invite-routes.ts +1 -1
- package/src/runtime/routes/secret-routes.ts +31 -7
- package/src/runtime/routes/surface-content-routes.ts +104 -0
- package/src/runtime/routes/twilio-routes.ts +32 -1
- package/src/runtime/routes/usage-routes.ts +114 -0
- package/src/runtime/tool-grant-request-helper.ts +2 -1
- package/src/security/encrypted-store.ts +9 -5
- package/src/security/keychain-broker-client.ts +393 -0
- package/src/security/secure-keys.ts +106 -321
- package/src/tools/apps/executors.ts +73 -0
- package/src/tools/browser/auto-navigate.ts +15 -6
- package/src/tools/browser/chrome-cdp.ts +211 -0
- package/src/tools/browser/network-recorder.test.ts +83 -0
- package/src/tools/browser/network-recorder.ts +8 -7
- package/src/tools/browser/x-auto-navigate.ts +12 -6
- package/src/tools/credentials/policy-types.ts +24 -0
- package/src/tools/credentials/vault.ts +22 -27
- package/src/tools/network/script-proxy/session-manager.ts +47 -3
- package/src/tools/permission-checker.ts +1 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +1 -2
- package/src/tools/watch/watch-state.ts +2 -0
- package/src/__tests__/key-migration.test.ts +0 -240
- package/src/__tests__/keychain.test.ts +0 -286
- package/src/cli/core-commands.ts +0 -899
- package/src/security/keychain-to-encrypted-migration.ts +0 -66
- package/src/security/keychain.ts +0 -490
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that the channel readiness service returns real readiness snapshots
|
|
3
|
+
* for email and WhatsApp channels (not unsupported placeholders).
|
|
4
|
+
*
|
|
5
|
+
* Uses the same mock approach as channel-readiness-service.test.ts but
|
|
6
|
+
* exercises the createReadinessService factory to verify probe registration.
|
|
7
|
+
*/
|
|
8
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Mocks — must be set up before importing the service
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
let mockTwilioPhoneNumberEnv: string | undefined;
|
|
15
|
+
let mockRawConfig: Record<string, unknown> | undefined;
|
|
16
|
+
let mockSecureKeys: Record<string, string>;
|
|
17
|
+
let mockHasTwilioCredentials: boolean;
|
|
18
|
+
let mockPrimaryInboxAddress: string | undefined;
|
|
19
|
+
|
|
20
|
+
mock.module("../calls/twilio-rest.js", () => ({
|
|
21
|
+
hasTwilioCredentials: () => mockHasTwilioCredentials,
|
|
22
|
+
getPhoneNumberSid: async () => null,
|
|
23
|
+
getTollFreeVerificationStatus: async () => null,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mock.module("../config/env.js", () => ({
|
|
27
|
+
getTwilioPhoneNumberEnv: () => mockTwilioPhoneNumberEnv,
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
mock.module("../config/loader.js", () => ({
|
|
31
|
+
loadRawConfig: () => mockRawConfig,
|
|
32
|
+
getConfig: () => {
|
|
33
|
+
const raw = mockRawConfig ?? {};
|
|
34
|
+
const wa = (raw.whatsapp ?? {}) as Record<string, unknown>;
|
|
35
|
+
return { whatsapp: { phoneNumber: (wa.phoneNumber as string) ?? "" } };
|
|
36
|
+
},
|
|
37
|
+
invalidateConfigCache: () => {},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
41
|
+
getSecureKey: (key: string) => mockSecureKeys[key] ?? null,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
mock.module("../email/service.js", () => ({
|
|
45
|
+
getEmailService: () => ({
|
|
46
|
+
getPrimaryInboxAddress: async () => mockPrimaryInboxAddress,
|
|
47
|
+
}),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Import under test
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
import { createReadinessService } from "../runtime/channel-readiness-service.js";
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Tests
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
describe("channel readiness routes — email and WhatsApp probes", () => {
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
mockTwilioPhoneNumberEnv = undefined;
|
|
63
|
+
mockRawConfig = undefined;
|
|
64
|
+
mockSecureKeys = {};
|
|
65
|
+
mockHasTwilioCredentials = false;
|
|
66
|
+
mockPrimaryInboxAddress = undefined;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// -------------------------------------------------------------------------
|
|
70
|
+
// Email probe
|
|
71
|
+
// -------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
describe("email", () => {
|
|
74
|
+
test("returns real readiness snapshot (not unsupported)", async () => {
|
|
75
|
+
const service = createReadinessService();
|
|
76
|
+
const [snapshot] = await service.getReadiness("email");
|
|
77
|
+
|
|
78
|
+
expect(snapshot.channel).toBe("email");
|
|
79
|
+
// Should have real local checks, not the unsupported placeholder
|
|
80
|
+
expect(snapshot.localChecks.length).toBeGreaterThan(0);
|
|
81
|
+
expect(
|
|
82
|
+
snapshot.reasons.some((r) => r.code === "unsupported_channel"),
|
|
83
|
+
).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("reports not ready when AgentMail API key is missing", async () => {
|
|
87
|
+
mockRawConfig = {};
|
|
88
|
+
const service = createReadinessService();
|
|
89
|
+
const [snapshot] = await service.getReadiness("email");
|
|
90
|
+
|
|
91
|
+
expect(snapshot.ready).toBe(false);
|
|
92
|
+
expect(snapshot.reasons.some((r) => r.code === "agentmail_api_key")).toBe(
|
|
93
|
+
true,
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("checks invite policy", async () => {
|
|
98
|
+
mockSecureKeys = { agentmail: "test-key" };
|
|
99
|
+
mockRawConfig = {
|
|
100
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
101
|
+
};
|
|
102
|
+
const service = createReadinessService();
|
|
103
|
+
const [snapshot] = await service.getReadiness("email");
|
|
104
|
+
|
|
105
|
+
const inviteCheck = snapshot.localChecks.find(
|
|
106
|
+
(c) => c.name === "invite_policy",
|
|
107
|
+
);
|
|
108
|
+
expect(inviteCheck).toBeDefined();
|
|
109
|
+
// Email has codeRedemptionEnabled: true in the channel policy registry
|
|
110
|
+
expect(inviteCheck!.passed).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("checks ingress configuration", async () => {
|
|
114
|
+
mockSecureKeys = { agentmail: "test-key" };
|
|
115
|
+
mockRawConfig = {};
|
|
116
|
+
const service = createReadinessService();
|
|
117
|
+
const [snapshot] = await service.getReadiness("email");
|
|
118
|
+
|
|
119
|
+
const ingressCheck = snapshot.localChecks.find(
|
|
120
|
+
(c) => c.name === "ingress",
|
|
121
|
+
);
|
|
122
|
+
expect(ingressCheck).toBeDefined();
|
|
123
|
+
expect(ingressCheck!.passed).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("ready when all prerequisites are met (including inbox)", async () => {
|
|
127
|
+
mockSecureKeys = { agentmail: "test-key" };
|
|
128
|
+
mockRawConfig = {
|
|
129
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
130
|
+
};
|
|
131
|
+
mockPrimaryInboxAddress = "hello@example.agentmail.to";
|
|
132
|
+
const service = createReadinessService();
|
|
133
|
+
const [snapshot] = await service.getReadiness("email", true);
|
|
134
|
+
|
|
135
|
+
expect(snapshot.ready).toBe(true);
|
|
136
|
+
expect(snapshot.reasons).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("not ready when inbox is missing (remote check)", async () => {
|
|
140
|
+
mockSecureKeys = { agentmail: "test-key" };
|
|
141
|
+
mockRawConfig = {
|
|
142
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
143
|
+
};
|
|
144
|
+
mockPrimaryInboxAddress = undefined;
|
|
145
|
+
const service = createReadinessService();
|
|
146
|
+
const [snapshot] = await service.getReadiness("email", true);
|
|
147
|
+
|
|
148
|
+
expect(snapshot.ready).toBe(false);
|
|
149
|
+
expect(snapshot.reasons.some((r) => r.code === "inbox_configured")).toBe(
|
|
150
|
+
true,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("local-only readiness still passes without inbox check", async () => {
|
|
155
|
+
mockSecureKeys = { agentmail: "test-key" };
|
|
156
|
+
mockRawConfig = {
|
|
157
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
158
|
+
};
|
|
159
|
+
// No inbox configured — explicitly opt out of remote checks
|
|
160
|
+
const service = createReadinessService();
|
|
161
|
+
const [snapshot] = await service.getReadiness("email", false);
|
|
162
|
+
|
|
163
|
+
// Local checks pass — remote inbox check is not included
|
|
164
|
+
expect(snapshot.ready).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// -------------------------------------------------------------------------
|
|
169
|
+
// WhatsApp probe — Meta WhatsApp Business API credentials
|
|
170
|
+
// -------------------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
describe("whatsapp", () => {
|
|
173
|
+
test("returns real readiness snapshot (not unsupported)", async () => {
|
|
174
|
+
const service = createReadinessService();
|
|
175
|
+
const [snapshot] = await service.getReadiness("whatsapp");
|
|
176
|
+
|
|
177
|
+
expect(snapshot.channel).toBe("whatsapp");
|
|
178
|
+
expect(snapshot.localChecks.length).toBeGreaterThan(0);
|
|
179
|
+
expect(
|
|
180
|
+
snapshot.reasons.some((r) => r.code === "unsupported_channel"),
|
|
181
|
+
).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("reports not ready when Meta credentials are missing", async () => {
|
|
185
|
+
mockRawConfig = {};
|
|
186
|
+
const service = createReadinessService();
|
|
187
|
+
const [snapshot] = await service.getReadiness("whatsapp");
|
|
188
|
+
|
|
189
|
+
expect(snapshot.ready).toBe(false);
|
|
190
|
+
expect(
|
|
191
|
+
snapshot.reasons.some((r) => r.code === "whatsapp_phone_number_id"),
|
|
192
|
+
).toBe(true);
|
|
193
|
+
expect(
|
|
194
|
+
snapshot.reasons.some((r) => r.code === "whatsapp_access_token"),
|
|
195
|
+
).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("reports ready when all Meta credentials and display number are configured", async () => {
|
|
199
|
+
mockSecureKeys = {
|
|
200
|
+
"credential:whatsapp:phone_number_id": "123456789",
|
|
201
|
+
"credential:whatsapp:access_token": "EAAxxxxxx",
|
|
202
|
+
"credential:whatsapp:app_secret": "abc123",
|
|
203
|
+
"credential:whatsapp:webhook_verify_token": "my-verify-token",
|
|
204
|
+
};
|
|
205
|
+
mockRawConfig = {
|
|
206
|
+
whatsapp: { phoneNumber: "+15551234567" },
|
|
207
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
208
|
+
};
|
|
209
|
+
const service = createReadinessService();
|
|
210
|
+
const [snapshot] = await service.getReadiness("whatsapp");
|
|
211
|
+
|
|
212
|
+
expect(snapshot.ready).toBe(true);
|
|
213
|
+
expect(snapshot.reasons).toHaveLength(0);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("reports not ready when display phone number is missing", async () => {
|
|
217
|
+
mockSecureKeys = {
|
|
218
|
+
"credential:whatsapp:phone_number_id": "123456789",
|
|
219
|
+
"credential:whatsapp:access_token": "EAAxxxxxx",
|
|
220
|
+
"credential:whatsapp:app_secret": "abc123",
|
|
221
|
+
"credential:whatsapp:webhook_verify_token": "my-verify-token",
|
|
222
|
+
};
|
|
223
|
+
mockRawConfig = {
|
|
224
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
225
|
+
};
|
|
226
|
+
const service = createReadinessService();
|
|
227
|
+
const [snapshot] = await service.getReadiness("whatsapp");
|
|
228
|
+
|
|
229
|
+
expect(snapshot.ready).toBe(false);
|
|
230
|
+
expect(
|
|
231
|
+
snapshot.reasons.some(
|
|
232
|
+
(r) => r.code === "whatsapp_display_phone_number",
|
|
233
|
+
),
|
|
234
|
+
).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("checks each Meta credential individually", async () => {
|
|
238
|
+
mockSecureKeys = {
|
|
239
|
+
"credential:whatsapp:phone_number_id": "123456789",
|
|
240
|
+
// access_token missing
|
|
241
|
+
"credential:whatsapp:app_secret": "abc123",
|
|
242
|
+
"credential:whatsapp:webhook_verify_token": "my-verify-token",
|
|
243
|
+
};
|
|
244
|
+
mockRawConfig = {
|
|
245
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
246
|
+
};
|
|
247
|
+
const service = createReadinessService();
|
|
248
|
+
const [snapshot] = await service.getReadiness("whatsapp");
|
|
249
|
+
|
|
250
|
+
expect(snapshot.ready).toBe(false);
|
|
251
|
+
|
|
252
|
+
const phoneIdCheck = snapshot.localChecks.find(
|
|
253
|
+
(c) => c.name === "whatsapp_phone_number_id",
|
|
254
|
+
);
|
|
255
|
+
expect(phoneIdCheck!.passed).toBe(true);
|
|
256
|
+
|
|
257
|
+
const accessTokenCheck = snapshot.localChecks.find(
|
|
258
|
+
(c) => c.name === "whatsapp_access_token",
|
|
259
|
+
);
|
|
260
|
+
expect(accessTokenCheck!.passed).toBe(false);
|
|
261
|
+
|
|
262
|
+
const appSecretCheck = snapshot.localChecks.find(
|
|
263
|
+
(c) => c.name === "whatsapp_app_secret",
|
|
264
|
+
);
|
|
265
|
+
expect(appSecretCheck!.passed).toBe(true);
|
|
266
|
+
|
|
267
|
+
const webhookCheck = snapshot.localChecks.find(
|
|
268
|
+
(c) => c.name === "whatsapp_webhook_verify_token",
|
|
269
|
+
);
|
|
270
|
+
expect(webhookCheck!.passed).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("checks invite policy", async () => {
|
|
274
|
+
mockSecureKeys = {
|
|
275
|
+
"credential:whatsapp:phone_number_id": "123456789",
|
|
276
|
+
"credential:whatsapp:access_token": "EAAxxxxxx",
|
|
277
|
+
"credential:whatsapp:app_secret": "abc123",
|
|
278
|
+
"credential:whatsapp:webhook_verify_token": "my-verify-token",
|
|
279
|
+
};
|
|
280
|
+
mockRawConfig = {
|
|
281
|
+
whatsapp: { phoneNumber: "+15551234567" },
|
|
282
|
+
ingress: { publicBaseUrl: "https://example.com", enabled: true },
|
|
283
|
+
};
|
|
284
|
+
const service = createReadinessService();
|
|
285
|
+
const [snapshot] = await service.getReadiness("whatsapp");
|
|
286
|
+
|
|
287
|
+
const inviteCheck = snapshot.localChecks.find(
|
|
288
|
+
(c) => c.name === "invite_policy",
|
|
289
|
+
);
|
|
290
|
+
expect(inviteCheck).toBeDefined();
|
|
291
|
+
expect(inviteCheck!.passed).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test("checks ingress configuration", async () => {
|
|
295
|
+
mockSecureKeys = {
|
|
296
|
+
"credential:whatsapp:phone_number_id": "123456789",
|
|
297
|
+
"credential:whatsapp:access_token": "EAAxxxxxx",
|
|
298
|
+
"credential:whatsapp:app_secret": "abc123",
|
|
299
|
+
"credential:whatsapp:webhook_verify_token": "my-verify-token",
|
|
300
|
+
};
|
|
301
|
+
mockRawConfig = {
|
|
302
|
+
whatsapp: { phoneNumber: "+15551234567" },
|
|
303
|
+
};
|
|
304
|
+
const service = createReadinessService();
|
|
305
|
+
const [snapshot] = await service.getReadiness("whatsapp");
|
|
306
|
+
|
|
307
|
+
const ingressCheck = snapshot.localChecks.find(
|
|
308
|
+
(c) => c.name === "ingress",
|
|
309
|
+
);
|
|
310
|
+
expect(ingressCheck).toBeDefined();
|
|
311
|
+
expect(ingressCheck!.passed).toBe(false);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// -------------------------------------------------------------------------
|
|
316
|
+
// Factory coverage — all channels registered
|
|
317
|
+
// -------------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
describe("createReadinessService factory", () => {
|
|
320
|
+
test("registers probes for all deliverable channels including email and whatsapp", async () => {
|
|
321
|
+
const service = createReadinessService();
|
|
322
|
+
const snapshots = await service.getReadiness();
|
|
323
|
+
|
|
324
|
+
const channels = snapshots.map((s) => s.channel).sort();
|
|
325
|
+
expect(channels).toContain("email");
|
|
326
|
+
expect(channels).toContain("whatsapp");
|
|
327
|
+
|
|
328
|
+
// None should be unsupported placeholders
|
|
329
|
+
for (const s of snapshots) {
|
|
330
|
+
expect(s.reasons.some((r) => r.code === "unsupported_channel")).toBe(
|
|
331
|
+
false,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -2570,7 +2570,7 @@ describe("Permission Checker", () => {
|
|
|
2570
2570
|
expect(result.reason).toContain("Strict mode");
|
|
2571
2571
|
});
|
|
2572
2572
|
|
|
2573
|
-
test("
|
|
2573
|
+
test("workspace mode: file_write to skill source still prompts as High risk", async () => {
|
|
2574
2574
|
testConfig.permissions.mode = "workspace";
|
|
2575
2575
|
ensureSkillsDir();
|
|
2576
2576
|
const skillPath = join(
|
|
@@ -2996,7 +2996,7 @@ describe("Permission Checker", () => {
|
|
|
2996
2996
|
ensureSkillsDir();
|
|
2997
2997
|
writeSkill("test-hash-skill", "Test Hash Skill");
|
|
2998
2998
|
|
|
2999
|
-
// skill_load is Low risk, so with no trust rule in
|
|
2999
|
+
// skill_load is Low risk, so with no trust rule in workspace mode it
|
|
3000
3000
|
// auto-allows. We set strict mode and add specific rules to verify
|
|
3001
3001
|
// the correct candidates are generated.
|
|
3002
3002
|
testConfig.permissions.mode = "strict";
|
|
@@ -3332,7 +3332,7 @@ describe("Permission Checker", () => {
|
|
|
3332
3332
|
expect(result.matchedRule!.pattern).toBe("skill_load:pr34-bare-id");
|
|
3333
3333
|
});
|
|
3334
3334
|
|
|
3335
|
-
test("skill_load auto-allows in
|
|
3335
|
+
test("skill_load auto-allows in workspace mode", async () => {
|
|
3336
3336
|
testConfig.permissions.mode = "workspace";
|
|
3337
3337
|
const result = await check("skill_load", { skill: "any-skill" }, "/tmp");
|
|
3338
3338
|
expect(result.decision).toBe("allow");
|
|
@@ -3855,7 +3855,7 @@ describe("Permission Checker", () => {
|
|
|
3855
3855
|
// high-risk allow) if they choose. ────────────────────────────
|
|
3856
3856
|
|
|
3857
3857
|
describe("Invariant 6: user can set broad rules if they choose", () => {
|
|
3858
|
-
test("wildcard allow rule matches any command in
|
|
3858
|
+
test("wildcard allow rule matches any command in workspace mode", async () => {
|
|
3859
3859
|
testConfig.permissions.mode = "workspace";
|
|
3860
3860
|
addRule("bash", "*", "everywhere");
|
|
3861
3861
|
const result = await check(
|
|
@@ -4208,7 +4208,7 @@ describe("Permission Checker", () => {
|
|
|
4208
4208
|
});
|
|
4209
4209
|
}
|
|
4210
4210
|
|
|
4211
|
-
test("browser tools are auto-allowed in
|
|
4211
|
+
test("browser tools are auto-allowed in workspace mode", async () => {
|
|
4212
4212
|
testConfig.permissions = { mode: "workspace" };
|
|
4213
4213
|
for (const toolName of browserToolNames) {
|
|
4214
4214
|
const result = await check(toolName, {}, "/tmp");
|
|
@@ -4404,7 +4404,7 @@ describe("computer-use tool permission defaults", () => {
|
|
|
4404
4404
|
for (const name of cuToolNames) {
|
|
4405
4405
|
const risk = await classifyRisk(name, {});
|
|
4406
4406
|
// CU tools are proxy tools with RiskLevel.Low, but classifyRisk looks them up
|
|
4407
|
-
// in the registry. In
|
|
4407
|
+
// in the registry. In workspace mode, Low risk tools are auto-allowed.
|
|
4408
4408
|
expect(risk).toBe(RiskLevel.Low);
|
|
4409
4409
|
}
|
|
4410
4410
|
});
|