@vellumai/assistant 0.4.35 → 0.4.37
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 +5 -2
- 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 +29 -0
- 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 +814 -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 +494 -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} +134 -245
- 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 +175 -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 +135 -34
- 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 +11 -0
- package/src/memory/llm-usage-store.ts +186 -0
- 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/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 +2 -0
- 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/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/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
|
@@ -7,7 +7,10 @@ const TEST_DIR = join(tmpdir(), `vellum-dyn-skill-test-${crypto.randomUUID()}`);
|
|
|
7
7
|
|
|
8
8
|
import { mock } from "bun:test";
|
|
9
9
|
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
|
+
const realPlatform = require("../util/platform.js");
|
|
10
12
|
mock.module("../util/platform.js", () => ({
|
|
13
|
+
...realPlatform,
|
|
11
14
|
getRootDir: () => TEST_DIR,
|
|
12
15
|
getDataDir: () => TEST_DIR,
|
|
13
16
|
getWorkspaceDir: () => TEST_DIR,
|
|
@@ -31,19 +34,27 @@ mock.module("../util/platform.js", () => ({
|
|
|
31
34
|
isWindows: () => process.platform === "win32",
|
|
32
35
|
getPlatformName: () => process.platform,
|
|
33
36
|
getClipboardCommand: () => null,
|
|
37
|
+
readSessionToken: () => null,
|
|
34
38
|
removeSocketFile: () => {},
|
|
35
39
|
migratePath: () => {},
|
|
36
40
|
migrateToWorkspaceLayout: () => {},
|
|
37
41
|
migrateToDataLayout: () => {},
|
|
38
42
|
}));
|
|
39
43
|
|
|
44
|
+
const noopLogger = new Proxy({} as Record<string, unknown>, {
|
|
45
|
+
get: (_target, prop) => (prop === "child" ? () => noopLogger : () => {}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
49
|
+
const realLogger = require("../util/logger.js");
|
|
40
50
|
mock.module("../util/logger.js", () => ({
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}),
|
|
51
|
+
...realLogger,
|
|
52
|
+
getLogger: () => noopLogger,
|
|
53
|
+
getCliLogger: () => noopLogger,
|
|
45
54
|
isDebug: () => false,
|
|
46
55
|
truncateForLog: (v: string) => v,
|
|
56
|
+
initLogger: () => {},
|
|
57
|
+
pruneOldLogFiles: () => 0,
|
|
47
58
|
}));
|
|
48
59
|
|
|
49
60
|
mock.module("../config/loader.js", () => ({
|
|
@@ -53,6 +64,14 @@ mock.module("../config/loader.js", () => ({
|
|
|
53
64
|
"feature_flags.browser.enabled": true,
|
|
54
65
|
},
|
|
55
66
|
}),
|
|
67
|
+
loadConfig: () => ({}),
|
|
68
|
+
loadRawConfig: () => ({}),
|
|
69
|
+
saveConfig: () => {},
|
|
70
|
+
saveRawConfig: () => {},
|
|
71
|
+
invalidateConfigCache: () => {},
|
|
72
|
+
getNestedValue: () => undefined,
|
|
73
|
+
setNestedValue: () => {},
|
|
74
|
+
syncConfigToLockfile: () => {},
|
|
56
75
|
}));
|
|
57
76
|
|
|
58
77
|
const { buildSystemPrompt } = await import("../config/system-prompt.js");
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the email invite adapter.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the email adapter resolves the assistant's real inbox
|
|
5
|
+
* address when one is configured and falls back to `undefined` (triggering
|
|
6
|
+
* generic invite instructions) when no inbox exists.
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import { resolveAdapterHandle } from "../runtime/channel-invite-transport.js";
|
|
11
|
+
import { emailInviteAdapter } from "../runtime/channel-invite-transports/email.js";
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Mock the EmailService singleton
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
let mockPrimaryAddress: string | undefined;
|
|
18
|
+
|
|
19
|
+
mock.module("../email/service.js", () => ({
|
|
20
|
+
getEmailService: () => ({
|
|
21
|
+
getPrimaryInboxAddress: async () => mockPrimaryAddress,
|
|
22
|
+
}),
|
|
23
|
+
// Re-export other symbols that callers might need
|
|
24
|
+
EmailService: class {},
|
|
25
|
+
_resetEmailService: () => {},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Tests
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
describe("emailInviteAdapter", () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
mockPrimaryAddress = undefined;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
mockPrimaryAddress = undefined;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns configured email address via resolveChannelHandleAsync", async () => {
|
|
42
|
+
mockPrimaryAddress = "hello@mycompany.agentmail.to";
|
|
43
|
+
|
|
44
|
+
const handle = await resolveAdapterHandle(emailInviteAdapter);
|
|
45
|
+
expect(handle).toBe("hello@mycompany.agentmail.to");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("returns undefined when no inbox is configured", async () => {
|
|
49
|
+
mockPrimaryAddress = undefined;
|
|
50
|
+
|
|
51
|
+
const handle = await resolveAdapterHandle(emailInviteAdapter);
|
|
52
|
+
expect(handle).toBeUndefined();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("adapter channel is email", () => {
|
|
56
|
+
expect(emailInviteAdapter.channel).toBe("email");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("does not define sync resolveChannelHandle", () => {
|
|
60
|
+
// The email adapter uses the async path exclusively
|
|
61
|
+
expect(emailInviteAdapter.resolveChannelHandle).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("does not define buildShareLink or extractInboundToken", () => {
|
|
65
|
+
expect(emailInviteAdapter.buildShareLink).toBeUndefined();
|
|
66
|
+
expect(emailInviteAdapter.extractInboundToken).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("returns config fallback address when provider has no inboxes", async () => {
|
|
70
|
+
// Simulates the config fallback: provider returns no inboxes, but
|
|
71
|
+
// email.address is set in workspace config. The service's
|
|
72
|
+
// getPrimaryInboxAddress() should return the configured address.
|
|
73
|
+
mockPrimaryAddress = "configured@example.com";
|
|
74
|
+
|
|
75
|
+
const handle = await resolveAdapterHandle(emailInviteAdapter);
|
|
76
|
+
expect(handle).toBe("configured@example.com");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that getPrimaryInboxAddress() falls back to the workspace config's
|
|
3
|
+
* email.address when the provider can't list inboxes.
|
|
4
|
+
*/
|
|
5
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Mock dependencies before importing the service
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
let mockProviderThrows = false;
|
|
12
|
+
let mockProviderInboxes: { address: string }[] = [];
|
|
13
|
+
|
|
14
|
+
mock.module("../email/providers/index.js", () => ({
|
|
15
|
+
createProvider: async () => ({
|
|
16
|
+
name: "mock",
|
|
17
|
+
health: async () => {
|
|
18
|
+
if (mockProviderThrows) throw new Error("provider unavailable");
|
|
19
|
+
return { inboxes: mockProviderInboxes, domains: [] };
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
getActiveProviderName: () => "mock",
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
let mockRawConfig: Record<string, unknown> = {};
|
|
26
|
+
|
|
27
|
+
mock.module("../config/loader.js", () => ({
|
|
28
|
+
loadRawConfig: () => mockRawConfig,
|
|
29
|
+
getNestedValue: (obj: Record<string, unknown>, path: string) => {
|
|
30
|
+
const keys = path.split(".");
|
|
31
|
+
let current: unknown = obj;
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
if (current == null || typeof current !== "object") return undefined;
|
|
34
|
+
current = (current as Record<string, unknown>)[key];
|
|
35
|
+
}
|
|
36
|
+
return current;
|
|
37
|
+
},
|
|
38
|
+
saveRawConfig: () => {},
|
|
39
|
+
setNestedValue: () => {},
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Stub guardrails (imported by service.ts but not relevant here)
|
|
43
|
+
mock.module("../cli/email-guardrails.js", () => ({
|
|
44
|
+
addAddressRule: () => ({}),
|
|
45
|
+
checkSendGuardrails: () => null,
|
|
46
|
+
getGuardrailsStatus: () => ({}),
|
|
47
|
+
incrementDailySendCount: () => 0,
|
|
48
|
+
listRules: () => [],
|
|
49
|
+
removeAddressRule: () => false,
|
|
50
|
+
setDailySendCap: () => {},
|
|
51
|
+
setOutboundPaused: () => {},
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
// Now import the service under test
|
|
55
|
+
const { EmailService } = await import("../email/service.js");
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Tests
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
describe("getPrimaryInboxAddress — config fallback", () => {
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
mockProviderThrows = false;
|
|
64
|
+
mockProviderInboxes = [];
|
|
65
|
+
mockRawConfig = {};
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("returns provider inbox address when available", async () => {
|
|
69
|
+
mockProviderInboxes = [{ address: "inbox@provider.example" }];
|
|
70
|
+
const svc = new EmailService();
|
|
71
|
+
|
|
72
|
+
const addr = await svc.getPrimaryInboxAddress();
|
|
73
|
+
expect(addr).toBe("inbox@provider.example");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("falls back to email.address from config when provider returns no inboxes", async () => {
|
|
77
|
+
mockProviderInboxes = [];
|
|
78
|
+
mockRawConfig = { email: { address: "configured@example.com" } };
|
|
79
|
+
const svc = new EmailService();
|
|
80
|
+
|
|
81
|
+
const addr = await svc.getPrimaryInboxAddress();
|
|
82
|
+
expect(addr).toBe("configured@example.com");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("falls back to email.address from config when provider throws", async () => {
|
|
86
|
+
mockProviderThrows = true;
|
|
87
|
+
mockRawConfig = { email: { address: "fallback@example.com" } };
|
|
88
|
+
const svc = new EmailService();
|
|
89
|
+
|
|
90
|
+
const addr = await svc.getPrimaryInboxAddress();
|
|
91
|
+
expect(addr).toBe("fallback@example.com");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("returns undefined when neither provider nor config has an address", async () => {
|
|
95
|
+
mockProviderThrows = true;
|
|
96
|
+
mockRawConfig = {};
|
|
97
|
+
const svc = new EmailService();
|
|
98
|
+
|
|
99
|
+
const addr = await svc.getPrimaryInboxAddress();
|
|
100
|
+
expect(addr).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -106,20 +106,20 @@ describe("encrypted-store", () => {
|
|
|
106
106
|
expect(getKey("anthropic")).toBe("new-value");
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
test("deleteKey removes an entry and returns
|
|
109
|
+
test("deleteKey removes an entry and returns deleted", () => {
|
|
110
110
|
setKey("anthropic", "sk-ant-key123");
|
|
111
111
|
const result = deleteKey("anthropic");
|
|
112
|
-
expect(result).toBe(
|
|
112
|
+
expect(result).toBe("deleted");
|
|
113
113
|
expect(getKey("anthropic")).toBeUndefined();
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
test("deleteKey returns
|
|
116
|
+
test("deleteKey returns not-found for nonexistent key", () => {
|
|
117
117
|
setKey("anthropic", "value");
|
|
118
|
-
expect(deleteKey("nonexistent")).toBe(
|
|
118
|
+
expect(deleteKey("nonexistent")).toBe("not-found");
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
-
test("deleteKey returns
|
|
122
|
-
expect(deleteKey("anything")).toBe(
|
|
121
|
+
test("deleteKey returns not-found when store does not exist", () => {
|
|
122
|
+
expect(deleteKey("anything")).toBe("not-found");
|
|
123
123
|
});
|
|
124
124
|
});
|
|
125
125
|
|
|
@@ -36,7 +36,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
38
|
const testConfig: Record<string, any> = {
|
|
39
|
-
permissions: { mode: "
|
|
39
|
+
permissions: { mode: "workspace" as "strict" | "workspace" },
|
|
40
40
|
skills: { load: { extraDirs: [] as string[] } },
|
|
41
41
|
sandbox: { enabled: false },
|
|
42
42
|
};
|
|
@@ -263,7 +263,7 @@ describe("ephemeral-permissions", () => {
|
|
|
263
263
|
describe("check() with ephemeral rules", () => {
|
|
264
264
|
beforeEach(() => {
|
|
265
265
|
clearCache();
|
|
266
|
-
testConfig.permissions.mode = "
|
|
266
|
+
testConfig.permissions.mode = "workspace";
|
|
267
267
|
});
|
|
268
268
|
|
|
269
269
|
test("ephemeral allow rule auto-allows non-high-risk tool", async () => {
|
|
@@ -327,7 +327,7 @@ describe("ephemeral-permissions", () => {
|
|
|
327
327
|
});
|
|
328
328
|
|
|
329
329
|
afterEach(() => {
|
|
330
|
-
testConfig.permissions.mode = "
|
|
330
|
+
testConfig.permissions.mode = "workspace";
|
|
331
331
|
});
|
|
332
332
|
|
|
333
333
|
test("workspace mode auto-allows workspace-scoped file_write (medium risk)", async () => {
|
|
@@ -128,7 +128,11 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
128
128
|
return true;
|
|
129
129
|
},
|
|
130
130
|
deleteSecureKey: (key: string) => {
|
|
131
|
-
|
|
131
|
+
if (key in secureKeyStore) {
|
|
132
|
+
delete secureKeyStore[key];
|
|
133
|
+
return "deleted";
|
|
134
|
+
}
|
|
135
|
+
return "not-found";
|
|
132
136
|
},
|
|
133
137
|
}));
|
|
134
138
|
|
|
@@ -16,8 +16,13 @@ import { join } from "node:path";
|
|
|
16
16
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
17
17
|
|
|
18
18
|
const testDir = mkdtempSync(join(tmpdir(), "guardian-actions-endpoint-test-"));
|
|
19
|
+
const previousBaseDataDir = process.env.BASE_DATA_DIR;
|
|
20
|
+
process.env.BASE_DATA_DIR = testDir;
|
|
19
21
|
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
+
const realPlatform = require("../util/platform.js");
|
|
20
24
|
mock.module("../util/platform.js", () => ({
|
|
25
|
+
...realPlatform,
|
|
21
26
|
getDataDir: () => testDir,
|
|
22
27
|
isMacOS: () => process.platform === "darwin",
|
|
23
28
|
isLinux: () => process.platform === "linux",
|
|
@@ -29,11 +34,26 @@ mock.module("../util/platform.js", () => ({
|
|
|
29
34
|
ensureDataDir: () => {},
|
|
30
35
|
}));
|
|
31
36
|
|
|
37
|
+
const noopLogger = {
|
|
38
|
+
info: () => {},
|
|
39
|
+
warn: () => {},
|
|
40
|
+
error: () => {},
|
|
41
|
+
debug: () => {},
|
|
42
|
+
trace: () => {},
|
|
43
|
+
fatal: () => {},
|
|
44
|
+
child: () => noopLogger,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
|
+
const realLogger = require("../util/logger.js");
|
|
32
49
|
mock.module("../util/logger.js", () => ({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
50
|
+
...realLogger,
|
|
51
|
+
getLogger: () => noopLogger,
|
|
52
|
+
getCliLogger: () => noopLogger,
|
|
53
|
+
isDebug: () => false,
|
|
54
|
+
truncateForLog: (value: string) => value,
|
|
55
|
+
initLogger: () => {},
|
|
56
|
+
pruneOldLogFiles: () => 0,
|
|
37
57
|
}));
|
|
38
58
|
|
|
39
59
|
// Bypass HTTP auth so requireBoundGuardian does not reject the test principal.
|
|
@@ -106,6 +126,7 @@ const mockAuthContext: AuthContext = {
|
|
|
106
126
|
policyEpoch: 1,
|
|
107
127
|
};
|
|
108
128
|
|
|
129
|
+
resetDb();
|
|
109
130
|
initializeDb();
|
|
110
131
|
|
|
111
132
|
function ensureConversation(id: string): void {
|
|
@@ -172,6 +193,11 @@ function createIpcStub() {
|
|
|
172
193
|
|
|
173
194
|
afterAll(() => {
|
|
174
195
|
resetDb();
|
|
196
|
+
if (previousBaseDataDir === undefined) {
|
|
197
|
+
delete process.env.BASE_DATA_DIR;
|
|
198
|
+
} else {
|
|
199
|
+
process.env.BASE_DATA_DIR = previousBaseDataDir;
|
|
200
|
+
}
|
|
175
201
|
try {
|
|
176
202
|
rmSync(testDir, { recursive: true });
|
|
177
203
|
} catch {
|
|
@@ -184,7 +210,11 @@ afterAll(() => {
|
|
|
184
210
|
// =========================================================================
|
|
185
211
|
|
|
186
212
|
describe("HTTP handleGuardianActionDecision", () => {
|
|
187
|
-
beforeEach(
|
|
213
|
+
beforeEach(() => {
|
|
214
|
+
resetDb();
|
|
215
|
+
initializeDb();
|
|
216
|
+
resetTables();
|
|
217
|
+
});
|
|
188
218
|
|
|
189
219
|
test("rejects missing requestId", async () => {
|
|
190
220
|
const req = new Request("http://localhost/v1/guardian-actions/decision", {
|
|
@@ -474,7 +504,11 @@ describe("HTTP handleGuardianActionDecision", () => {
|
|
|
474
504
|
// =========================================================================
|
|
475
505
|
|
|
476
506
|
describe("HTTP handleGuardianActionsPending", () => {
|
|
477
|
-
beforeEach(
|
|
507
|
+
beforeEach(() => {
|
|
508
|
+
resetDb();
|
|
509
|
+
initializeDb();
|
|
510
|
+
resetTables();
|
|
511
|
+
});
|
|
478
512
|
|
|
479
513
|
test("returns 400 when conversationId is missing", () => {
|
|
480
514
|
const url = new URL("http://localhost/v1/guardian-actions/pending");
|
|
@@ -517,7 +551,11 @@ describe("HTTP handleGuardianActionsPending", () => {
|
|
|
517
551
|
// =========================================================================
|
|
518
552
|
|
|
519
553
|
describe("listGuardianDecisionPrompts", () => {
|
|
520
|
-
beforeEach(
|
|
554
|
+
beforeEach(() => {
|
|
555
|
+
resetDb();
|
|
556
|
+
initializeDb();
|
|
557
|
+
resetTables();
|
|
558
|
+
});
|
|
521
559
|
|
|
522
560
|
test("excludes expired canonical requests", () => {
|
|
523
561
|
ensureConversation("conv-expired");
|
|
@@ -712,7 +750,11 @@ describe("listGuardianDecisionPrompts", () => {
|
|
|
712
750
|
// =========================================================================
|
|
713
751
|
|
|
714
752
|
describe("IPC guardian_action_decision", () => {
|
|
715
|
-
beforeEach(
|
|
753
|
+
beforeEach(() => {
|
|
754
|
+
resetDb();
|
|
755
|
+
initializeDb();
|
|
756
|
+
resetTables();
|
|
757
|
+
});
|
|
716
758
|
|
|
717
759
|
const handler = guardianActionsHandlers.guardian_action_decision;
|
|
718
760
|
|
|
@@ -952,7 +994,11 @@ describe("IPC guardian_action_decision", () => {
|
|
|
952
994
|
// =========================================================================
|
|
953
995
|
|
|
954
996
|
describe("IPC guardian_actions_pending_request", () => {
|
|
955
|
-
beforeEach(
|
|
997
|
+
beforeEach(() => {
|
|
998
|
+
resetDb();
|
|
999
|
+
initializeDb();
|
|
1000
|
+
resetTables();
|
|
1001
|
+
});
|
|
956
1002
|
|
|
957
1003
|
const handler = guardianActionsHandlers.guardian_actions_pending_request;
|
|
958
1004
|
|
|
@@ -1039,7 +1085,11 @@ describe("IPC guardian_actions_pending_request", () => {
|
|
|
1039
1085
|
// =========================================================================
|
|
1040
1086
|
|
|
1041
1087
|
describe("integration: pending_question visible and actionable in guardian thread", () => {
|
|
1042
|
-
beforeEach(
|
|
1088
|
+
beforeEach(() => {
|
|
1089
|
+
resetDb();
|
|
1090
|
+
initializeDb();
|
|
1091
|
+
resetTables();
|
|
1092
|
+
});
|
|
1043
1093
|
|
|
1044
1094
|
test("pending_question delivered to guardian thread is visible via pending endpoint", () => {
|
|
1045
1095
|
createTestCanonicalRequest({
|
|
@@ -1128,7 +1178,11 @@ describe("integration: pending_question visible and actionable in guardian threa
|
|
|
1128
1178
|
// =========================================================================
|
|
1129
1179
|
|
|
1130
1180
|
describe("integration: access_request visible and actionable in guardian thread", () => {
|
|
1131
|
-
beforeEach(
|
|
1181
|
+
beforeEach(() => {
|
|
1182
|
+
resetDb();
|
|
1183
|
+
initializeDb();
|
|
1184
|
+
resetTables();
|
|
1185
|
+
});
|
|
1132
1186
|
|
|
1133
1187
|
test("access_request delivered to guardian thread is visible via pending endpoint", () => {
|
|
1134
1188
|
createTestCanonicalRequest({
|
|
@@ -1265,7 +1319,11 @@ describe("integration: access_request visible and actionable in guardian thread"
|
|
|
1265
1319
|
// =========================================================================
|
|
1266
1320
|
|
|
1267
1321
|
describe("integration: text/code fallback routing remains functional", () => {
|
|
1268
|
-
beforeEach(
|
|
1322
|
+
beforeEach(() => {
|
|
1323
|
+
resetDb();
|
|
1324
|
+
initializeDb();
|
|
1325
|
+
resetTables();
|
|
1326
|
+
});
|
|
1269
1327
|
|
|
1270
1328
|
test("requestCode is always present in prompt for text-based fallback", () => {
|
|
1271
1329
|
createTestCanonicalRequest({
|