@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.
Files changed (239) hide show
  1. package/AGENTS.md +1 -1
  2. package/ARCHITECTURE.md +44 -49
  3. package/README.md +32 -20
  4. package/docs/architecture/keychain-broker.md +186 -0
  5. package/docs/architecture/security.md +110 -116
  6. package/docs/runbook-trusted-contacts.md +2 -2
  7. package/docs/skills.md +25 -25
  8. package/package.json +5 -2
  9. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +11 -2
  10. package/src/__tests__/actor-token-service.test.ts +1 -0
  11. package/src/__tests__/amazon-cdp-integration.test.ts +74 -0
  12. package/src/__tests__/assistant-feature-flags-integration.test.ts +38 -9
  13. package/src/__tests__/assistant-id-boundary-guard.test.ts +29 -0
  14. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  15. package/src/__tests__/bundle-scanner.test.ts +1 -1
  16. package/src/__tests__/channel-guardian.test.ts +102 -102
  17. package/src/__tests__/channel-invite-transport.test.ts +155 -256
  18. package/src/__tests__/channel-readiness-routes.test.ts +336 -0
  19. package/src/__tests__/checker.test.ts +6 -6
  20. package/src/__tests__/chrome-cdp.test.ts +350 -0
  21. package/src/__tests__/computer-use-session-lifecycle.test.ts +3 -3
  22. package/src/__tests__/computer-use-session-working-dir.test.ts +86 -52
  23. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +1 -1
  24. package/src/__tests__/config-loader-migration.test.ts +85 -0
  25. package/src/__tests__/conversation-pairing.test.ts +370 -5
  26. package/src/__tests__/credential-broker-browser-fill.test.ts +1 -10
  27. package/src/__tests__/credential-broker-server-use.test.ts +1 -10
  28. package/src/__tests__/credential-security-e2e.test.ts +7 -1
  29. package/src/__tests__/credential-security-invariants.test.ts +14 -20
  30. package/src/__tests__/credential-vault-unit.test.ts +1 -11
  31. package/src/__tests__/credential-vault.test.ts +5 -19
  32. package/src/__tests__/credentials-cli.test.ts +814 -0
  33. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +23 -4
  34. package/src/__tests__/email-invite-adapter.test.ts +78 -0
  35. package/src/__tests__/email-service-config-fallback.test.ts +102 -0
  36. package/src/__tests__/encrypted-store.test.ts +6 -6
  37. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  38. package/src/__tests__/gateway-only-enforcement.test.ts +5 -1
  39. package/src/__tests__/guardian-actions-endpoint.test.ts +70 -12
  40. package/src/__tests__/guardian-outbound-http.test.ts +53 -47
  41. package/src/__tests__/handle-user-message-secret-resume.test.ts +23 -0
  42. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +32 -23
  43. package/src/__tests__/handlers-telegram-config.test.ts +8 -2
  44. package/src/__tests__/handlers-twitter-config.test.ts +2 -2
  45. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +108 -7
  46. package/src/__tests__/ingress-reconcile.test.ts +6 -0
  47. package/src/__tests__/intent-routing.test.ts +23 -4
  48. package/src/__tests__/invite-routes-http.test.ts +12 -0
  49. package/src/__tests__/ipc-snapshot.test.ts +8 -2
  50. package/src/__tests__/keychain-broker-client.test.ts +543 -0
  51. package/src/__tests__/llm-usage-store.test.ts +344 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +2 -2
  53. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  54. package/src/__tests__/migration-transport.test.ts +49 -0
  55. package/src/__tests__/notification-broadcaster.test.ts +205 -5
  56. package/src/__tests__/notification-deep-link.test.ts +365 -1
  57. package/src/__tests__/oauth-connect-handler.test.ts +2 -2
  58. package/src/__tests__/onboarding-starter-tasks.test.ts +17 -4
  59. package/src/__tests__/proxy-approval-callback.test.ts +1 -1
  60. package/src/__tests__/recording-handler.test.ts +1 -1
  61. package/src/__tests__/recording-intent-handler.test.ts +6 -1
  62. package/src/__tests__/recording-state-machine.test.ts +1 -1
  63. package/src/__tests__/relay-server.test.ts +9 -1
  64. package/src/__tests__/ride-shotgun-handler.test.ts +499 -0
  65. package/src/__tests__/runtime-attachment-metadata.test.ts +160 -1
  66. package/src/__tests__/script-proxy-injection-runtime.test.ts +299 -2
  67. package/src/__tests__/script-proxy-profile-template-fallback.test.ts +1 -1
  68. package/src/__tests__/secret-onetime-send.test.ts +8 -2
  69. package/src/__tests__/secure-keys.test.ts +175 -216
  70. package/src/__tests__/session-confirmation-signals.test.ts +1 -1
  71. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -1
  72. package/src/__tests__/session-queue.test.ts +2 -1
  73. package/src/__tests__/session-tool-setup-app-refresh.test.ts +2 -2
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +29 -4
  75. package/src/__tests__/skill-feature-flags.test.ts +12 -9
  76. package/src/__tests__/skill-load-feature-flag.test.ts +26 -5
  77. package/src/__tests__/skill-projection.benchmark.test.ts +0 -1
  78. package/src/__tests__/skills.test.ts +34 -4
  79. package/src/__tests__/slack-channel-config.test.ts +2 -2
  80. package/src/__tests__/system-prompt.test.ts +26 -4
  81. package/src/__tests__/telegram-bot-username-resolution.test.ts +212 -0
  82. package/src/__tests__/telegram-invite-adapter.test.ts +164 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  84. package/src/__tests__/tool-permission-simulate-handler.test.ts +8 -2
  85. package/src/__tests__/trusted-contact-approval-notifier.test.ts +9 -1
  86. package/src/__tests__/twitter-auth-handler.test.ts +2 -2
  87. package/src/__tests__/twitter-oauth-client.test.ts +1 -1
  88. package/src/__tests__/usage-routes.test.ts +339 -0
  89. package/src/__tests__/whatsapp-invite-adapter.test.ts +94 -0
  90. package/src/agent/loop.ts +3 -0
  91. package/src/amazon/checkout.ts +0 -1
  92. package/src/approvals/guardian-request-resolvers.ts +9 -1
  93. package/src/bundler/app-bundler.ts +28 -12
  94. package/src/bundler/bundle-scanner.ts +1 -1
  95. package/src/bundler/bundle-signer.ts +3 -3
  96. package/src/bundler/manifest.ts +1 -1
  97. package/src/bundler/signature-verifier.ts +3 -3
  98. package/src/channels/config.ts +1 -1
  99. package/src/cli/AGENTS.md +63 -0
  100. package/src/cli/__tests__/notifications.test.ts +470 -0
  101. package/src/cli/amazon.ts +344 -167
  102. package/src/cli/audit.ts +85 -0
  103. package/src/cli/autonomy.ts +369 -0
  104. package/src/cli/channels.ts +51 -0
  105. package/src/cli/completions.ts +208 -0
  106. package/src/cli/config.ts +220 -0
  107. package/src/cli/contacts.ts +471 -0
  108. package/src/cli/credentials.ts +564 -0
  109. package/src/cli/default-action.ts +14 -0
  110. package/src/cli/dev.ts +131 -0
  111. package/src/cli/doctor.ts +398 -0
  112. package/src/cli/email.ts +494 -0
  113. package/src/cli/influencer.ts +72 -0
  114. package/src/cli/integrations.ts +248 -57
  115. package/src/cli/keys.ts +114 -0
  116. package/src/cli/map.ts +46 -54
  117. package/src/cli/mcp.ts +111 -3
  118. package/src/cli/{config-commands.ts → memory.ts} +134 -245
  119. package/src/cli/notifications.ts +407 -0
  120. package/src/cli/program.ts +65 -0
  121. package/src/cli/reference.ts +48 -0
  122. package/src/cli/sequence.ts +154 -0
  123. package/src/cli/sessions.ts +262 -0
  124. package/src/cli/trust.ts +175 -0
  125. package/src/cli/twitter.ts +323 -106
  126. package/src/config/__tests__/build-cli-reference-section.test.ts +49 -0
  127. package/src/config/bundled-skills/amazon/SKILL.md +2 -2
  128. package/src/config/bundled-skills/app-builder/TOOLS.json +26 -0
  129. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +13 -0
  130. package/src/config/bundled-skills/contacts/SKILL.md +178 -10
  131. package/src/config/bundled-skills/doordash/doordash-cli.ts +23 -168
  132. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +135 -34
  133. package/src/config/bundled-skills/messaging/tools/shared.ts +4 -1
  134. package/src/config/bundled-skills/twilio-setup/SKILL.md +70 -17
  135. package/src/config/bundled-tool-registry.ts +2 -0
  136. package/src/config/core-schema.ts +7 -0
  137. package/src/config/feature-flag-registry.json +16 -0
  138. package/src/config/loader.ts +26 -0
  139. package/src/config/schema.ts +4 -0
  140. package/src/config/skill-state.ts +0 -13
  141. package/src/config/system-prompt.ts +27 -0
  142. package/src/contacts/contact-store.ts +25 -0
  143. package/src/daemon/computer-use-session.ts +1 -1
  144. package/src/daemon/handlers/apps.ts +1 -0
  145. package/src/daemon/handlers/config-channels.ts +3 -3
  146. package/src/daemon/handlers/config-dispatch.ts +29 -0
  147. package/src/daemon/handlers/config-inbox.ts +4 -3
  148. package/src/daemon/handlers/config.ts +3 -43
  149. package/src/daemon/handlers/contacts.ts +34 -0
  150. package/src/daemon/handlers/index.ts +17 -3
  151. package/src/daemon/handlers/session-user-message.ts +7 -0
  152. package/src/daemon/handlers/sessions.ts +21 -2
  153. package/src/daemon/handlers/shared.ts +17 -0
  154. package/src/daemon/ipc-contract/apps.ts +2 -0
  155. package/src/daemon/ipc-contract/computer-use.ts +9 -0
  156. package/src/daemon/ipc-contract/contacts.ts +3 -3
  157. package/src/daemon/ipc-contract/inbox.ts +2 -0
  158. package/src/daemon/ipc-contract/messages.ts +4 -0
  159. package/src/daemon/ipc-contract/sessions.ts +8 -0
  160. package/src/daemon/ipc-contract-inventory.json +1 -0
  161. package/src/daemon/lifecycle.ts +0 -5
  162. package/src/daemon/ride-shotgun-handler.ts +139 -25
  163. package/src/daemon/session-agent-loop-handlers.ts +100 -0
  164. package/src/daemon/session-agent-loop.ts +72 -0
  165. package/src/daemon/session-tool-setup.ts +7 -0
  166. package/src/daemon/session.ts +23 -1
  167. package/src/daemon/tool-side-effects.ts +39 -1
  168. package/src/email/service.ts +59 -2
  169. package/src/index.ts +2 -60
  170. package/src/mcp/mcp-oauth-provider.ts +90 -8
  171. package/src/media/app-icon-generator.ts +86 -0
  172. package/src/memory/db-init.ts +11 -0
  173. package/src/memory/llm-usage-store.ts +186 -0
  174. package/src/memory/migrations/137-usage-dashboard-indexes.ts +26 -0
  175. package/src/memory/migrations/139-drop-usage-composite-indexes.ts +30 -0
  176. package/src/memory/migrations/index.ts +2 -0
  177. package/src/memory/schema-migration.ts +1 -0
  178. package/src/memory/shared-app-links-store.ts +1 -1
  179. package/src/messaging/registry.ts +27 -0
  180. package/src/notifications/README.md +79 -70
  181. package/src/notifications/broadcaster.ts +2 -1
  182. package/src/notifications/conversation-pairing.ts +147 -13
  183. package/src/notifications/copy-composer.ts +7 -3
  184. package/src/notifications/destination-resolver.ts +14 -1
  185. package/src/notifications/emit-signal.ts +3 -2
  186. package/src/notifications/signal.ts +105 -1
  187. package/src/notifications/types.ts +16 -0
  188. package/src/permissions/checker.ts +29 -3
  189. package/src/permissions/prompter.ts +11 -3
  190. package/src/runtime/access-request-helper.ts +2 -1
  191. package/src/runtime/auth/route-policy.ts +7 -1
  192. package/src/runtime/channel-invite-transport.ts +40 -63
  193. package/src/runtime/channel-invite-transports/email.ts +13 -39
  194. package/src/runtime/channel-invite-transports/slack.ts +5 -34
  195. package/src/runtime/channel-invite-transports/sms.ts +8 -29
  196. package/src/runtime/channel-invite-transports/telegram.ts +69 -28
  197. package/src/runtime/channel-invite-transports/voice.ts +0 -7
  198. package/src/runtime/channel-invite-transports/whatsapp.ts +43 -0
  199. package/src/runtime/channel-readiness-service.ts +202 -45
  200. package/src/runtime/confirmation-request-guardian-bridge.ts +2 -1
  201. package/src/runtime/guardian-outbound-actions.ts +8 -5
  202. package/src/runtime/http-server.ts +2 -0
  203. package/src/runtime/invite-instruction-generator.ts +178 -0
  204. package/src/runtime/invite-service.ts +22 -25
  205. package/src/runtime/migrations/migration-transport.ts +13 -0
  206. package/src/runtime/routes/app-routes.ts +1 -1
  207. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +8 -7
  208. package/src/runtime/routes/channel-readiness-routes.ts +30 -11
  209. package/src/runtime/routes/contact-routes.ts +54 -26
  210. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +1 -1
  211. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +2 -1
  212. package/src/runtime/routes/inbound-stages/verification-intercept.ts +2 -1
  213. package/src/runtime/routes/integration-routes.ts +1 -1
  214. package/src/runtime/routes/invite-routes.ts +1 -1
  215. package/src/runtime/routes/secret-routes.ts +31 -7
  216. package/src/runtime/routes/twilio-routes.ts +32 -1
  217. package/src/runtime/routes/usage-routes.ts +114 -0
  218. package/src/runtime/tool-grant-request-helper.ts +2 -1
  219. package/src/security/encrypted-store.ts +9 -5
  220. package/src/security/keychain-broker-client.ts +393 -0
  221. package/src/security/secure-keys.ts +106 -321
  222. package/src/tools/apps/executors.ts +73 -0
  223. package/src/tools/browser/auto-navigate.ts +15 -6
  224. package/src/tools/browser/chrome-cdp.ts +211 -0
  225. package/src/tools/browser/network-recorder.test.ts +83 -0
  226. package/src/tools/browser/network-recorder.ts +8 -7
  227. package/src/tools/browser/x-auto-navigate.ts +12 -6
  228. package/src/tools/credentials/policy-types.ts +24 -0
  229. package/src/tools/credentials/vault.ts +22 -27
  230. package/src/tools/network/script-proxy/session-manager.ts +47 -3
  231. package/src/tools/permission-checker.ts +1 -0
  232. package/src/tools/types.ts +2 -0
  233. package/src/tools/ui-surface/definitions.ts +1 -2
  234. package/src/tools/watch/watch-state.ts +2 -0
  235. package/src/__tests__/key-migration.test.ts +0 -240
  236. package/src/__tests__/keychain.test.ts +0 -286
  237. package/src/cli/core-commands.ts +0 -899
  238. package/src/security/keychain-to-encrypted-migration.ts +0 -66
  239. 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
- getLogger: () =>
42
- new Proxy({} as Record<string, unknown>, {
43
- get: () => () => {},
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 true", () => {
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(true);
112
+ expect(result).toBe("deleted");
113
113
  expect(getKey("anthropic")).toBeUndefined();
114
114
  });
115
115
 
116
- test("deleteKey returns false for nonexistent key", () => {
116
+ test("deleteKey returns not-found for nonexistent key", () => {
117
117
  setKey("anthropic", "value");
118
- expect(deleteKey("nonexistent")).toBe(false);
118
+ expect(deleteKey("nonexistent")).toBe("not-found");
119
119
  });
120
120
 
121
- test("deleteKey returns false when store does not exist", () => {
122
- expect(deleteKey("anything")).toBe(false);
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: "legacy" as "legacy" | "strict" | "workspace" },
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 = "legacy";
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 = "legacy";
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
- delete secureKeyStore[key];
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
- getLogger: () =>
34
- new Proxy({} as Record<string, unknown>, {
35
- get: () => () => {},
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(resetTables);
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(resetTables);
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(resetTables);
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(resetTables);
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(resetTables);
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(resetTables);
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(resetTables);
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(resetTables);
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({