@vellumai/assistant 0.4.53 → 0.4.55

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 (255) hide show
  1. package/bun.lock +62 -349
  2. package/docs/architecture/integrations.md +1 -1
  3. package/docs/architecture/keychain-broker.md +94 -29
  4. package/docs/architecture/security.md +2 -2
  5. package/knip.json +7 -29
  6. package/package.json +2 -9
  7. package/src/__tests__/agent-loop.test.ts +1 -1
  8. package/src/__tests__/app-git-history.test.ts +0 -2
  9. package/src/__tests__/app-git-service.test.ts +1 -6
  10. package/src/__tests__/approval-cascade.test.ts +0 -1
  11. package/src/__tests__/avatar-e2e.test.ts +0 -1
  12. package/src/__tests__/browser-fill-credential.test.ts +1 -6
  13. package/src/__tests__/call-domain.test.ts +0 -1
  14. package/src/__tests__/call-routes-http.test.ts +0 -1
  15. package/src/__tests__/channel-guardian.test.ts +4 -4
  16. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  17. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  18. package/src/__tests__/checker.test.ts +13 -11
  19. package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +0 -3
  22. package/src/__tests__/config-schema.test.ts +3 -9
  23. package/src/__tests__/config-watcher.test.ts +11 -3
  24. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  25. package/src/__tests__/credential-broker-server-use.test.ts +60 -24
  26. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  27. package/src/__tests__/credential-security-invariants.test.ts +13 -8
  28. package/src/__tests__/credential-vault-unit.test.ts +28 -12
  29. package/src/__tests__/credential-vault.test.ts +40 -28
  30. package/src/__tests__/credentials-cli.test.ts +1 -21
  31. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  32. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  34. package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
  35. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  36. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  37. package/src/__tests__/guardian-action-store.test.ts +0 -57
  38. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  39. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  40. package/src/__tests__/hooks-blocking.test.ts +1 -1
  41. package/src/__tests__/hooks-config.test.ts +5 -29
  42. package/src/__tests__/hooks-discovery.test.ts +1 -1
  43. package/src/__tests__/hooks-integration.test.ts +1 -1
  44. package/src/__tests__/hooks-manager.test.ts +1 -1
  45. package/src/__tests__/hooks-runner.test.ts +1 -23
  46. package/src/__tests__/hooks-settings.test.ts +1 -1
  47. package/src/__tests__/hooks-templates.test.ts +1 -1
  48. package/src/__tests__/integration-status.test.ts +0 -1
  49. package/src/__tests__/invite-routes-http.test.ts +0 -3
  50. package/src/__tests__/list-messages-attachments.test.ts +4 -4
  51. package/src/__tests__/llm-usage-store.test.ts +50 -0
  52. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  53. package/src/__tests__/media-generate-image.test.ts +2 -2
  54. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
  55. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  56. package/src/__tests__/memory-regressions.test.ts +27 -27
  57. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  58. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  59. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  60. package/src/__tests__/oauth-cli.test.ts +1 -4
  61. package/src/__tests__/oauth-store.test.ts +1 -3
  62. package/src/__tests__/openai-provider.test.ts +7 -7
  63. package/src/__tests__/platform.test.ts +14 -4
  64. package/src/__tests__/pricing.test.ts +0 -223
  65. package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
  66. package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
  67. package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
  68. package/src/__tests__/provider-registry-ollama.test.ts +3 -3
  69. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  70. package/src/__tests__/registry.test.ts +3 -103
  71. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  72. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  73. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
  74. package/src/__tests__/secure-keys.test.ts +241 -229
  75. package/src/__tests__/session-abort-tool-results.test.ts +0 -1
  76. package/src/__tests__/session-confirmation-signals.test.ts +0 -1
  77. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  78. package/src/__tests__/session-pre-run-repair.test.ts +0 -1
  79. package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
  80. package/src/__tests__/session-queue.test.ts +2 -4
  81. package/src/__tests__/session-slash-known.test.ts +0 -1
  82. package/src/__tests__/session-slash-queue.test.ts +0 -1
  83. package/src/__tests__/session-slash-unknown.test.ts +0 -1
  84. package/src/__tests__/session-workspace-injection.test.ts +0 -1
  85. package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  87. package/src/__tests__/slack-channel-config.test.ts +1 -7
  88. package/src/__tests__/swarm-recursion.test.ts +0 -1
  89. package/src/__tests__/swarm-session-integration.test.ts +0 -1
  90. package/src/__tests__/swarm-tool.test.ts +0 -1
  91. package/src/__tests__/task-compiler.test.ts +1 -1
  92. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  93. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  94. package/src/__tests__/tool-executor.test.ts +1 -1
  95. package/src/__tests__/trust-store.test.ts +3 -82
  96. package/src/__tests__/twilio-config.test.ts +0 -1
  97. package/src/__tests__/twilio-provider.test.ts +0 -5
  98. package/src/__tests__/twilio-routes.test.ts +0 -1
  99. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  100. package/src/calls/guardian-question-copy.ts +1 -1
  101. package/src/cli/commands/bash.ts +3 -0
  102. package/src/cli/commands/doctor.ts +10 -34
  103. package/src/cli/commands/memory.ts +3 -5
  104. package/src/cli/commands/sessions.ts +1 -1
  105. package/src/cli/commands/usage.ts +359 -0
  106. package/src/cli/http-client.ts +22 -12
  107. package/src/cli/program.ts +2 -0
  108. package/src/cli/reference.ts +1 -0
  109. package/src/cli.ts +251 -181
  110. package/src/config/assistant-feature-flags.ts +0 -7
  111. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  112. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  113. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  114. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  115. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  116. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  117. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  118. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  119. package/src/config/env.ts +13 -0
  120. package/src/config/feature-flag-registry.json +9 -41
  121. package/src/config/schemas/security.ts +1 -2
  122. package/src/config/skills.ts +1 -1
  123. package/src/contacts/contact-store.ts +0 -50
  124. package/src/daemon/approved-devices-store.ts +0 -44
  125. package/src/daemon/classifier.ts +1 -1
  126. package/src/daemon/config-watcher.ts +14 -8
  127. package/src/daemon/handlers/config-model.ts +1 -1
  128. package/src/daemon/handlers/sessions.ts +4 -116
  129. package/src/daemon/handlers/skills.ts +1 -1
  130. package/src/daemon/lifecycle.ts +13 -15
  131. package/src/daemon/providers-setup.ts +1 -1
  132. package/src/daemon/server.ts +20 -3
  133. package/src/daemon/session-slash.ts +2 -2
  134. package/src/daemon/shutdown-handlers.ts +15 -0
  135. package/src/daemon/watch-handler.ts +2 -2
  136. package/src/email/guardrails.ts +1 -1
  137. package/src/email/service.ts +0 -5
  138. package/src/hooks/templates.ts +1 -1
  139. package/src/media/app-icon-generator.ts +2 -2
  140. package/src/media/avatar-router.ts +2 -2
  141. package/src/media/gemini-image-service.ts +5 -5
  142. package/src/memory/admin.ts +2 -2
  143. package/src/memory/app-git-service.ts +0 -7
  144. package/src/memory/conversation-crud.ts +1 -1
  145. package/src/memory/conversation-title-service.ts +2 -2
  146. package/src/memory/embedding-backend.ts +30 -26
  147. package/src/memory/external-conversation-store.ts +0 -30
  148. package/src/memory/guardian-action-store.ts +0 -31
  149. package/src/memory/guardian-approvals.ts +1 -56
  150. package/src/memory/indexer.ts +4 -3
  151. package/src/memory/items-extractor.ts +1 -1
  152. package/src/memory/job-handlers/backfill.ts +5 -2
  153. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  154. package/src/memory/job-handlers/media-processing.ts +2 -2
  155. package/src/memory/job-handlers/summarization.ts +1 -1
  156. package/src/memory/job-utils.ts +1 -2
  157. package/src/memory/jobs-worker.ts +2 -2
  158. package/src/memory/llm-usage-store.ts +57 -11
  159. package/src/memory/media-store.ts +4 -535
  160. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  161. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  162. package/src/memory/published-pages-store.ts +0 -83
  163. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  164. package/src/memory/retriever.ts +1 -1
  165. package/src/memory/schema/calls.ts +0 -67
  166. package/src/memory/search/semantic.ts +1 -8
  167. package/src/memory/shared-app-links-store.ts +0 -15
  168. package/src/messaging/registry.ts +0 -5
  169. package/src/messaging/style-analyzer.ts +1 -1
  170. package/src/notifications/copy-composer.ts +5 -13
  171. package/src/notifications/decision-engine.ts +2 -2
  172. package/src/notifications/deliveries-store.ts +0 -39
  173. package/src/notifications/guardian-question-mode.ts +6 -10
  174. package/src/notifications/preference-extractor.ts +1 -1
  175. package/src/oauth/byo-connection.test.ts +29 -20
  176. package/src/oauth/provider-behaviors.ts +1 -1
  177. package/src/permissions/checker.ts +1 -1
  178. package/src/permissions/shell-identity.ts +0 -5
  179. package/src/permissions/trust-store.ts +0 -37
  180. package/src/prompts/system-prompt.ts +4 -4
  181. package/src/prompts/templates/SOUL.md +1 -1
  182. package/src/providers/managed-proxy/constants.ts +8 -10
  183. package/src/providers/managed-proxy/context.ts +14 -9
  184. package/src/providers/provider-send-message.ts +4 -52
  185. package/src/providers/registry.ts +16 -50
  186. package/src/runtime/actor-token-store.ts +0 -23
  187. package/src/runtime/auth/__tests__/guard-tests.test.ts +64 -0
  188. package/src/runtime/http-router.ts +5 -1
  189. package/src/runtime/http-server.ts +101 -4
  190. package/src/runtime/invite-instruction-generator.ts +25 -51
  191. package/src/runtime/invite-service.ts +0 -20
  192. package/src/runtime/routes/attachment-routes.ts +1 -1
  193. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  194. package/src/runtime/routes/call-routes.ts +1 -1
  195. package/src/runtime/routes/conversation-routes.ts +32 -11
  196. package/src/runtime/routes/debug-routes.ts +1 -1
  197. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  198. package/src/runtime/routes/documents-routes.ts +3 -3
  199. package/src/runtime/routes/global-search-routes.ts +1 -1
  200. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  201. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  202. package/src/runtime/routes/secret-routes.ts +4 -4
  203. package/src/runtime/routes/session-management-routes.ts +27 -0
  204. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  205. package/src/security/credential-backend.ts +148 -0
  206. package/src/security/oauth2.ts +1 -1
  207. package/src/security/secret-allowlist.ts +1 -1
  208. package/src/security/secure-keys.ts +98 -160
  209. package/src/security/token-manager.ts +0 -7
  210. package/src/sequence/guardrails.ts +0 -4
  211. package/src/sequence/store.ts +1 -20
  212. package/src/sequence/types.ts +1 -36
  213. package/src/signals/bash.ts +33 -0
  214. package/src/signals/cancel.ts +69 -0
  215. package/src/signals/conversation-undo.ts +127 -0
  216. package/src/signals/trust-rule.ts +174 -0
  217. package/src/skills/clawhub.ts +5 -5
  218. package/src/skills/managed-store.ts +4 -4
  219. package/src/subagent/manager.ts +8 -1
  220. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  221. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  222. package/src/tools/claude-code/claude-code.ts +2 -2
  223. package/src/tools/credentials/vault.ts +8 -4
  224. package/src/tools/memory/handlers.test.ts +24 -26
  225. package/src/tools/memory/handlers.ts +1 -13
  226. package/src/tools/registry.ts +5 -100
  227. package/src/tools/terminal/parser.ts +34 -4
  228. package/src/tools/tool-manifest.ts +0 -10
  229. package/src/usage/actors.ts +0 -12
  230. package/src/util/canonicalize-identity.ts +0 -9
  231. package/src/util/errors.ts +0 -3
  232. package/src/util/platform.ts +24 -7
  233. package/src/util/pricing.ts +0 -38
  234. package/src/watcher/constants.ts +0 -7
  235. package/src/watcher/providers/linear.ts +1 -1
  236. package/src/work-items/work-item-store.ts +4 -4
  237. package/src/workspace/commit-message-provider.ts +1 -1
  238. package/src/workspace/git-service.ts +44 -1
  239. package/src/workspace/provider-commit-message-generator.ts +1 -1
  240. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  241. package/src/browser-extension-relay/client.ts +0 -155
  242. package/src/contacts/index.ts +0 -18
  243. package/src/daemon/tls-certs.ts +0 -270
  244. package/src/errors.ts +0 -41
  245. package/src/events/index.ts +0 -18
  246. package/src/followups/index.ts +0 -10
  247. package/src/playbooks/index.ts +0 -10
  248. package/src/runtime/auth/index.ts +0 -44
  249. package/src/tasks/candidate-store.ts +0 -95
  250. package/src/tools/browser/api-map.ts +0 -313
  251. package/src/tools/browser/auto-navigate.ts +0 -469
  252. package/src/tools/browser/headless-browser.ts +0 -590
  253. package/src/tools/browser/recording-store.ts +0 -75
  254. package/src/tools/computer-use/registry.ts +0 -21
  255. package/src/tools/tasks/index.ts +0 -27
@@ -105,7 +105,10 @@ mock.module("../security/oauth2.js", () => ({
105
105
  // ---------------------------------------------------------------------------
106
106
 
107
107
  import { credentialKey } from "../security/credential-key.js";
108
- import { getSecureKey, setSecureKey } from "../security/secure-keys.js";
108
+ import {
109
+ getSecureKeyAsync,
110
+ setSecureKeyAsync,
111
+ } from "../security/secure-keys.js";
109
112
  import { CredentialBroker } from "../tools/credentials/broker.js";
110
113
  import {
111
114
  _setMetadataPath,
@@ -205,7 +208,7 @@ describe("CredentialBroker transient credentials", () => {
205
208
  upsertCredentialMetadata("github", "token", {
206
209
  allowedTools: ["browser_fill_credential"],
207
210
  });
208
- setSecureKey(credentialKey("github", "token"), "stored-value");
211
+ await setSecureKeyAsync(credentialKey("github", "token"), "stored-value");
209
212
  broker.injectTransient("github", "token", "transient-value");
210
213
 
211
214
  // First fill uses transient
@@ -468,9 +471,9 @@ describe("credential_store tool — prompt action", () => {
468
471
  expect(result.content).not.toContain("prompt-secret-val");
469
472
 
470
473
  // Verify stored
471
- expect(getSecureKey(credentialKey("test-prompt", "api_key"))).toBe(
472
- "prompt-secret-val",
473
- );
474
+ expect(
475
+ await getSecureKeyAsync(credentialKey("test-prompt", "api_key")),
476
+ ).toBe("prompt-secret-val");
474
477
  });
475
478
 
476
479
  test("prompt with policy fields persists metadata", async () => {
@@ -702,7 +705,10 @@ describe("credential_store tool — oauth2_connect error paths", () => {
702
705
  callbackTransport: "loopback",
703
706
  loopbackPort: 8756,
704
707
  }));
705
- setSecureKey("oauth_app/test-app-id/client_secret", "test-secret");
708
+ await setSecureKeyAsync(
709
+ "oauth_app/test-app-id/client_secret",
710
+ "test-secret",
711
+ );
706
712
 
707
713
  const result = await credentialStoreTool.execute(
708
714
  {
@@ -756,7 +762,10 @@ describe("credential_store tool — oauth2_connect error paths", () => {
756
762
  callbackTransport: "loopback",
757
763
  loopbackPort: 8756,
758
764
  }));
759
- setSecureKey("oauth_app/matched-app-id/client_secret", "matched-secret");
765
+ await setSecureKeyAsync(
766
+ "oauth_app/matched-app-id/client_secret",
767
+ "matched-secret",
768
+ );
760
769
 
761
770
  const result = await credentialStoreTool.execute(
762
771
  {
@@ -797,7 +806,10 @@ describe("credential_store tool — oauth2_connect error paths", () => {
797
806
  callbackTransport: "loopback",
798
807
  loopbackPort: 8756,
799
808
  }));
800
- setSecureKey("oauth_app/recent-app-id/client_secret", "recent-secret");
809
+ await setSecureKeyAsync(
810
+ "oauth_app/recent-app-id/client_secret",
811
+ "recent-secret",
812
+ );
801
813
 
802
814
  const result = await credentialStoreTool.execute(
803
815
  {
@@ -1024,7 +1036,9 @@ describe("credential_store tool — store validation edge cases", () => {
1024
1036
  );
1025
1037
 
1026
1038
  // Verify stored
1027
- expect(getSecureKey(credentialKey("del-test", "key"))).toBe("secret");
1039
+ expect(await getSecureKeyAsync(credentialKey("del-test", "key"))).toBe(
1040
+ "secret",
1041
+ );
1028
1042
  const { getCredentialMetadata } =
1029
1043
  await import("../tools/credentials/metadata-store.js");
1030
1044
  expect(getCredentialMetadata("del-test", "key")).toBeDefined();
@@ -1041,7 +1055,9 @@ describe("credential_store tool — store validation edge cases", () => {
1041
1055
  expect(result.isError).toBe(false);
1042
1056
 
1043
1057
  // Both should be gone
1044
- expect(getSecureKey(credentialKey("del-test", "key"))).toBeUndefined();
1058
+ expect(
1059
+ await getSecureKeyAsync(credentialKey("del-test", "key")),
1060
+ ).toBeUndefined();
1045
1061
  expect(getCredentialMetadata("del-test", "key")).toBeUndefined();
1046
1062
  });
1047
1063
  });
@@ -1130,7 +1146,7 @@ describe("CredentialBroker — serverUseById edge cases", () => {
1130
1146
  },
1131
1147
  ],
1132
1148
  });
1133
- setSecureKey(credentialKey("multi", "api_key"), "multi-secret");
1149
+ await setSecureKeyAsync(credentialKey("multi", "api_key"), "multi-secret");
1134
1150
 
1135
1151
  const result = await broker.serverUseById({
1136
1152
  credentialId: meta.credentialId,
@@ -1150,7 +1166,7 @@ describe("CredentialBroker — serverUseById edge cases", () => {
1150
1166
  const meta = upsertCredentialMetadata("fal", "api_key", {
1151
1167
  allowedTools: ["proxy"],
1152
1168
  });
1153
- // No setSecureKey — metadata exists but value doesn't
1169
+ // No setSecureKeyAsync — metadata exists but value doesn't
1154
1170
 
1155
1171
  const result = await broker.serverUseById({
1156
1172
  credentialId: meta.credentialId,
@@ -132,13 +132,13 @@ mock.module("../oauth/oauth-store.js", () => {
132
132
  // Import the module under test
133
133
  // ---------------------------------------------------------------------------
134
134
 
135
- // getCredentialValue is no longer exported (sealed in PR 17) — use getSecureKey directly
135
+ // getCredentialValue is no longer exported (sealed in PR 17) — use getSecureKeyAsync directly
136
136
 
137
137
  import { credentialKey } from "../security/credential-key.js";
138
138
  import {
139
- deleteSecureKey,
140
- getSecureKey,
141
- setSecureKey,
139
+ deleteSecureKeyAsync,
140
+ getSecureKeyAsync,
141
+ setSecureKeyAsync,
142
142
  } from "../security/secure-keys.js";
143
143
  import {
144
144
  _resetInflightRefreshes,
@@ -210,7 +210,7 @@ async function executeVault(
210
210
  }
211
211
 
212
212
  const key = credentialKey(service, field);
213
- const ok = setSecureKey(key, value);
213
+ const ok = await setSecureKeyAsync(key, value);
214
214
  if (!ok) {
215
215
  return { content: "Error: failed to store credential", isError: true };
216
216
  }
@@ -241,7 +241,7 @@ async function executeVault(
241
241
  }
242
242
 
243
243
  const key = credentialKey(service, field);
244
- const result = deleteSecureKey(key);
244
+ const result = await deleteSecureKeyAsync(key);
245
245
  if (result !== "deleted") {
246
246
  return {
247
247
  content: `Error: credential ${service}/${field} not found`,
@@ -645,7 +645,7 @@ describe("credential_store tool", () => {
645
645
 
646
646
  // Delete the secret directly without going through the tool (simulates
647
647
  // a divergence where metadata write failed after secret deletion)
648
- deleteSecureKey(credentialKey("svc-a", "key"));
648
+ await deleteSecureKeyAsync(credentialKey("svc-a", "key"));
649
649
 
650
650
  const result = await credentialStoreTool.execute(
651
651
  { action: "list" },
@@ -688,7 +688,7 @@ describe("credential_store tool", () => {
688
688
  // -----------------------------------------------------------------------
689
689
  describe("delete action", () => {
690
690
  test("deletes a stored credential", async () => {
691
- setSecureKey(credentialKey("gmail", "password"), "secret");
691
+ await setSecureKeyAsync(credentialKey("gmail", "password"), "secret");
692
692
 
693
693
  const result = await executeVault({
694
694
  action: "delete",
@@ -699,7 +699,9 @@ describe("credential_store tool", () => {
699
699
  expect(result.content).toBe("Deleted credential for gmail/password.");
700
700
 
701
701
  // Verify it's actually gone
702
- expect(getSecureKey(credentialKey("gmail", "password"))).toBeUndefined();
702
+ expect(
703
+ await getSecureKeyAsync(credentialKey("gmail", "password")),
704
+ ).toBeUndefined();
703
705
  });
704
706
 
705
707
  test("returns error for non-existent credential", async () => {
@@ -773,14 +775,16 @@ describe("credential_store tool", () => {
773
775
  // Credential value access (sealed — only via secure-keys internally)
774
776
  // -----------------------------------------------------------------------
775
777
  describe("credential value access", () => {
776
- test("credential values are stored via secure keys", () => {
777
- setSecureKey(credentialKey("github", "token"), "ghp_abc123");
778
- expect(getSecureKey(credentialKey("github", "token"))).toBe("ghp_abc123");
778
+ test("credential values are stored via secure keys", async () => {
779
+ await setSecureKeyAsync(credentialKey("github", "token"), "ghp_abc123");
780
+ expect(await getSecureKeyAsync(credentialKey("github", "token"))).toBe(
781
+ "ghp_abc123",
782
+ );
779
783
  });
780
784
 
781
- test("returns undefined for non-existent credential", () => {
785
+ test("returns undefined for non-existent credential", async () => {
782
786
  expect(
783
- getSecureKey(credentialKey("nonexistent", "field")),
787
+ await getSecureKeyAsync(credentialKey("nonexistent", "field")),
784
788
  ).toBeUndefined();
785
789
  });
786
790
  });
@@ -1226,10 +1230,10 @@ describe("credential_store tool", () => {
1226
1230
  value: "github-pass",
1227
1231
  });
1228
1232
 
1229
- expect(getSecureKey(credentialKey("gmail", "password"))).toBe(
1233
+ expect(await getSecureKeyAsync(credentialKey("gmail", "password"))).toBe(
1230
1234
  "gmail-pass",
1231
1235
  );
1232
- expect(getSecureKey(credentialKey("github", "password"))).toBe(
1236
+ expect(await getSecureKeyAsync(credentialKey("github", "password"))).toBe(
1233
1237
  "github-pass",
1234
1238
  );
1235
1239
  });
@@ -1248,10 +1252,12 @@ describe("credential_store tool", () => {
1248
1252
  value: "backup@example.com",
1249
1253
  });
1250
1254
 
1251
- expect(getSecureKey(credentialKey("gmail", "password"))).toBe("pass123");
1252
- expect(getSecureKey(credentialKey("gmail", "recovery_email"))).toBe(
1253
- "backup@example.com",
1255
+ expect(await getSecureKeyAsync(credentialKey("gmail", "password"))).toBe(
1256
+ "pass123",
1254
1257
  );
1258
+ expect(
1259
+ await getSecureKeyAsync(credentialKey("gmail", "recovery_email")),
1260
+ ).toBe("backup@example.com");
1255
1261
  });
1256
1262
  });
1257
1263
  });
@@ -1303,7 +1309,7 @@ describe("withValidToken refresh deduplication", () => {
1303
1309
  * OAuth-specific fields (tokenUrl, clientId, expiresAt) are now stored
1304
1310
  * in the SQLite oauth-store. The mock maps simulate the DB layer.
1305
1311
  */
1306
- function setupService(
1312
+ async function setupService(
1307
1313
  service: string,
1308
1314
  opts?: { expired?: boolean; accessToken?: string },
1309
1315
  ) {
@@ -1315,7 +1321,10 @@ describe("withValidToken refresh deduplication", () => {
1315
1321
 
1316
1322
  // Store access token under the oauth_connection key path that
1317
1323
  // withValidToken reads (not the legacy credentialKey path).
1318
- setSecureKey(`oauth_connection/${connId}/access_token`, accessToken);
1324
+ await setSecureKeyAsync(
1325
+ `oauth_connection/${connId}/access_token`,
1326
+ accessToken,
1327
+ );
1319
1328
  mockProviders.set(service, {
1320
1329
  key: service,
1321
1330
  tokenUrl: "https://oauth.example.com/token",
@@ -1335,15 +1344,18 @@ describe("withValidToken refresh deduplication", () => {
1335
1344
  : Date.now() + 3600_000, // expires in 1 hour
1336
1345
  });
1337
1346
  // Store refresh token and client_secret in secure keys (token-manager reads them)
1338
- setSecureKey(
1347
+ await setSecureKeyAsync(
1339
1348
  `oauth_connection/${connId}/refresh_token`,
1340
1349
  "valid-refresh-token",
1341
1350
  );
1342
- setSecureKey(`oauth_app/${appId}/client_secret`, "test-client-secret");
1351
+ await setSecureKeyAsync(
1352
+ `oauth_app/${appId}/client_secret`,
1353
+ "test-client-secret",
1354
+ );
1343
1355
  }
1344
1356
 
1345
1357
  test("3 concurrent 401 refreshes for the same service call doRefresh exactly once", async () => {
1346
- setupService("integration:google");
1358
+ await setupService("integration:google");
1347
1359
 
1348
1360
  let resolveRefresh!: (value: {
1349
1361
  accessToken: string;
@@ -1391,8 +1403,8 @@ describe("withValidToken refresh deduplication", () => {
1391
1403
  });
1392
1404
 
1393
1405
  test("concurrent refreshes for different services proceed independently", async () => {
1394
- setupService("integration:google");
1395
- setupService("integration:slack");
1406
+ await setupService("integration:google");
1407
+ await setupService("integration:slack");
1396
1408
 
1397
1409
  let resolveGmail!: (value: {
1398
1410
  accessToken: string;
@@ -1455,7 +1467,7 @@ describe("withValidToken refresh deduplication", () => {
1455
1467
  });
1456
1468
 
1457
1469
  test("deduplication cleans up after refresh completes, allowing subsequent refreshes", async () => {
1458
- setupService("integration:google");
1470
+ await setupService("integration:google");
1459
1471
 
1460
1472
  let refreshCount = 0;
1461
1473
  mockRefreshOAuth2Token.mockImplementation(() => {
@@ -1495,7 +1507,7 @@ describe("withValidToken refresh deduplication", () => {
1495
1507
  });
1496
1508
 
1497
1509
  test("deduplication propagates refresh errors to all waiting callers", async () => {
1498
- setupService("integration:google");
1510
+ await setupService("integration:google");
1499
1511
 
1500
1512
  mockRefreshOAuth2Token.mockImplementation(() =>
1501
1513
  Promise.reject(
@@ -31,15 +31,6 @@ let _getMetadataByIdCalls = 0;
31
31
  // ---------------------------------------------------------------------------
32
32
 
33
33
  mock.module("../security/secure-keys.js", () => ({
34
- getSecureKey: (account: string): string | undefined => {
35
- _getSecureKeyCalls += 1;
36
- return secureKeyStore.get(account);
37
- },
38
- setSecureKey: (account: string, value: string): boolean => {
39
- _setSecureKeyCalls += 1;
40
- secureKeyStore.set(account, value);
41
- return true;
42
- },
43
34
  setSecureKeyAsync: async (
44
35
  account: string,
45
36
  value: string,
@@ -48,14 +39,6 @@ mock.module("../security/secure-keys.js", () => ({
48
39
  secureKeyStore.set(account, value);
49
40
  return true;
50
41
  },
51
- deleteSecureKey: (account: string): "deleted" | "not-found" | "error" => {
52
- _deleteSecureKeyCalls += 1;
53
- if (secureKeyStore.has(account)) {
54
- secureKeyStore.delete(account);
55
- return "deleted";
56
- }
57
- return "not-found";
58
- },
59
42
  deleteSecureKeyAsync: async (
60
43
  account: string,
61
44
  ): Promise<"deleted" | "not-found" | "error"> => {
@@ -66,17 +49,14 @@ mock.module("../security/secure-keys.js", () => ({
66
49
  }
67
50
  return "not-found";
68
51
  },
69
- listSecureKeys: (): string[] => {
52
+ listSecureKeysAsync: async (): Promise<string[]> => {
70
53
  return [...secureKeyStore.keys()];
71
54
  },
72
55
  getSecureKeyAsync: async (account: string): Promise<string | undefined> => {
73
56
  _getSecureKeyCalls += 1;
74
57
  return secureKeyStore.get(account);
75
58
  },
76
- getBackendType: (): "broker" | "encrypted" | null => null,
77
- isDowngradedFromKeychain: (): boolean => false,
78
59
  _resetBackend: (): void => {},
79
- _setBackend: (): void => {},
80
60
  }));
81
61
 
82
62
  // ---------------------------------------------------------------------------
@@ -22,7 +22,6 @@ mock.module("../email/service.js", () => ({
22
22
  }),
23
23
  // Re-export other symbols that callers might need
24
24
  EmailService: class {},
25
- _resetEmailService: () => {},
26
25
  }));
27
26
 
28
27
  // ---------------------------------------------------------------------------
@@ -100,7 +100,7 @@ export const directReadCases: DirectReadCase[] = [
100
100
  export interface LogLeakageCase {
101
101
  label: string;
102
102
  /** Component where logging might leak credentials */
103
- component: "daemon_handler" | "prompter" | "tool_executor" | "ipc_decode";
103
+ component: "daemon_handler" | "prompter" | "tool_executor" | "message_decode";
104
104
  /** Description of what log output is checked */
105
105
  logCheck: string;
106
106
  }
@@ -122,8 +122,8 @@ export const logLeakageCases: LogLeakageCase[] = [
122
122
  logCheck: "password/token/value fields masked",
123
123
  },
124
124
  {
125
- label: "IPC decode failure does not dump raw line",
126
- component: "ipc_decode",
125
+ label: "message decode failure does not dump raw line",
126
+ component: "message_decode",
127
127
  logCheck: "malformed line not logged verbatim",
128
128
  },
129
129
  ];
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Shared fixtures for media-reuse testing.
3
3
  *
4
- * Provides deterministic attachment blobs, message links, and approval
5
- * response helpers used by the proxy and asset tool test suites.
4
+ * Provides deterministic attachment blobs and approval response helpers
5
+ * used by the proxy and asset tool test suites.
6
6
  */
7
7
 
8
8
  import type { StoredAttachment } from "../../memory/attachments-store.js";
@@ -16,12 +16,6 @@ import type { UserDecision } from "../../permissions/types.js";
16
16
  export const TINY_PNG_BASE64 =
17
17
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
18
18
 
19
- /** A tiny 1x1 JPEG pixel, base64-encoded. */
20
- export const TINY_JPEG_BASE64 =
21
- "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMCwsKCwsM" +
22
- "CgwMDQwMCwwMDQsLCwwODQoMEAwMEQ4ODwwLDgz/2wBDAQMEBAUEBQkFBQkMCwkLDAwMDAwMDAwMDAwM" +
23
- "DAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCAABAAEDASIAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACf/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8AKwA//9k=";
24
-
25
19
  // ---------------------------------------------------------------------------
26
20
  // Deterministic attachment records
27
21
  // ---------------------------------------------------------------------------
@@ -39,60 +33,11 @@ export const FAKE_SELFIE_ATTACHMENT: StoredAttachment = {
39
33
  createdAt: NOW,
40
34
  };
41
35
 
42
- /** A fake document attachment. */
43
- export const FAKE_DOCUMENT_ATTACHMENT: StoredAttachment = {
44
- id: "att-doc-001",
45
- originalFilename: "report.pdf",
46
- mimeType: "application/pdf",
47
- sizeBytes: 4096,
48
- kind: "document",
49
- thumbnailBase64: null,
50
- createdAt: NOW,
51
- };
52
-
53
- /** A fake JPEG photo attachment. */
54
- export const FAKE_PHOTO_ATTACHMENT: StoredAttachment = {
55
- id: "att-photo-001",
56
- originalFilename: "photo.jpg",
57
- mimeType: "image/jpeg",
58
- sizeBytes: Buffer.from(TINY_JPEG_BASE64, "base64").length,
59
- kind: "image",
60
- thumbnailBase64: null,
61
- createdAt: NOW,
62
- };
63
-
64
- // ---------------------------------------------------------------------------
65
- // Message link helpers
66
- // ---------------------------------------------------------------------------
67
-
68
- export interface FakeMessageLink {
69
- messageId: string;
70
- attachmentId: string;
71
- conversationId: string;
72
- position: number;
73
- }
74
-
75
- /** A deterministic message-attachment link for the selfie. */
76
- export const FAKE_SELFIE_LINK: FakeMessageLink = {
77
- messageId: "msg-001",
78
- attachmentId: FAKE_SELFIE_ATTACHMENT.id,
79
- conversationId: "conv-001",
80
- position: 0,
81
- };
82
-
83
- /** A second link showing multiple attachments on one message. */
84
- export const FAKE_DOCUMENT_LINK: FakeMessageLink = {
85
- messageId: "msg-001",
86
- attachmentId: FAKE_DOCUMENT_ATTACHMENT.id,
87
- conversationId: "conv-001",
88
- position: 1,
89
- };
90
-
91
36
  // ---------------------------------------------------------------------------
92
37
  // Approval response helpers
93
38
  // ---------------------------------------------------------------------------
94
39
 
95
- export interface FakeApprovalResponse {
40
+ interface FakeApprovalResponse {
96
41
  decision: UserDecision;
97
42
  /** Pattern for "always allow" decisions (undefined for one-shot allow/deny). */
98
43
  pattern?: string;
@@ -109,24 +54,3 @@ export function fakeAllowOnce(): FakeApprovalResponse {
109
54
  export function fakeDeny(): FakeApprovalResponse {
110
55
  return { decision: "deny" };
111
56
  }
112
-
113
- /** Returns an "always allow" decision with a pattern for the trust rule. */
114
- export function fakeAlwaysAllow(
115
- pattern: string,
116
- scope = "/tmp/test-project",
117
- ): FakeApprovalResponse {
118
- return { decision: "always_allow", pattern, scope };
119
- }
120
-
121
- /** Returns an "always allow high risk" decision. */
122
- export function fakeAlwaysAllowHighRisk(
123
- pattern: string,
124
- scope = "/tmp/test-project",
125
- ): FakeApprovalResponse {
126
- return { decision: "always_allow_high_risk", pattern, scope };
127
- }
128
-
129
- /** Returns an "always deny" decision. */
130
- export function fakeAlwaysDeny(): FakeApprovalResponse {
131
- return { decision: "always_deny" };
132
- }
@@ -110,27 +110,7 @@ mock.module("../calls/twilio-provider.js", () => ({
110
110
  },
111
111
  }));
112
112
 
113
- import { credentialKey } from "../security/credential-key.js";
114
-
115
- const secureKeyStore: Record<string, string | undefined> = {
116
- [credentialKey("twilio", "account_sid")]: "AC_test",
117
- [credentialKey("twilio", "auth_token")]: "test_token",
118
- };
119
-
120
- mock.module("../security/secure-keys.js", () => ({
121
- getSecureKey: (key: string) => secureKeyStore[key] ?? null,
122
- setSecureKey: (key: string, value: string) => {
123
- secureKeyStore[key] = value;
124
- return true;
125
- },
126
- deleteSecureKey: (key: string) => {
127
- if (key in secureKeyStore) {
128
- delete secureKeyStore[key];
129
- return "deleted";
130
- }
131
- return "not-found";
132
- },
133
- }));
113
+ mock.module("../security/secure-keys.js", () => ({}));
134
114
 
135
115
  // NOTE: Do NOT mock '../inbound/public-ingress-urls.js' here.
136
116
  // Those are pure functions that derive URLs from the config object returned by
@@ -34,8 +34,8 @@ import {
34
34
  createGuardianActionDelivery,
35
35
  createGuardianActionRequest,
36
36
  finalizeFollowup,
37
+ getFollowupDeliveriesByConversation,
37
38
  getFollowupDeliveriesByDestination,
38
- getFollowupDeliveryByConversation,
39
39
  getGuardianActionRequest,
40
40
  markTimedOutWithReason,
41
41
  progressFollowupState,
@@ -391,7 +391,7 @@ describe("guardian-action-conversation-turn", () => {
391
391
  expect(deliveries).toHaveLength(0);
392
392
  });
393
393
 
394
- test("getFollowupDeliveryByConversation returns delivery in awaiting_guardian_choice", () => {
394
+ test("getFollowupDeliveriesByConversation returns delivery in awaiting_guardian_choice", () => {
395
395
  const { delivery, deliveryConvId } = createAwaitingChoiceRequest(
396
396
  "conv-turn-4",
397
397
  {
@@ -399,18 +399,18 @@ describe("guardian-action-conversation-turn", () => {
399
399
  },
400
400
  );
401
401
 
402
- const found = getFollowupDeliveryByConversation(deliveryConvId);
403
- expect(found).not.toBeNull();
404
- expect(found!.id).toBe(delivery.id);
402
+ const found = getFollowupDeliveriesByConversation(deliveryConvId);
403
+ expect(found).toHaveLength(1);
404
+ expect(found[0].id).toBe(delivery.id);
405
405
  });
406
406
 
407
- test("getFollowupDeliveryByConversation returns null for non-matching conversation", () => {
407
+ test("getFollowupDeliveriesByConversation returns empty for non-matching conversation", () => {
408
408
  createAwaitingChoiceRequest("conv-turn-5", {
409
409
  conversationId: "mac-conv-2",
410
410
  });
411
411
 
412
- const found = getFollowupDeliveryByConversation("nonexistent-conv");
413
- expect(found).toBeNull();
412
+ const found = getFollowupDeliveriesByConversation("nonexistent-conv");
413
+ expect(found).toHaveLength(0);
414
414
  });
415
415
 
416
416
  // ── State transitions from conversation engine results ──────────────
@@ -41,7 +41,6 @@ import {
41
41
  expireGuardianActionRequest,
42
42
  getExpiredDeliveriesByConversation,
43
43
  getExpiredDeliveriesByDestination,
44
- getExpiredDeliveryByConversation,
45
44
  getFollowupDeliveriesByConversation,
46
45
  getGuardianActionRequest,
47
46
  getPendingDeliveriesByConversation,
@@ -180,34 +179,34 @@ describe("guardian-action-late-reply", () => {
180
179
  expect(deliveries).toHaveLength(0);
181
180
  });
182
181
 
183
- // ── getExpiredDeliveryByConversation ───────────────────────────────
182
+ // ── getExpiredDeliveriesByConversation (singular-to-plural migration) ──
184
183
 
185
- test("getExpiredDeliveryByConversation returns expired delivery for mac channel", () => {
184
+ test("getExpiredDeliveriesByConversation returns expired delivery for mac channel", () => {
186
185
  const { delivery, deliveryConvId } = createExpiredRequest("conv-late-4", {
187
186
  conversationId: "mac-conv-1",
188
187
  });
189
188
 
190
- const found = getExpiredDeliveryByConversation(deliveryConvId);
191
- expect(found).not.toBeNull();
192
- expect(found!.id).toBe(delivery.id);
189
+ const found = getExpiredDeliveriesByConversation(deliveryConvId);
190
+ expect(found).toHaveLength(1);
191
+ expect(found[0].id).toBe(delivery.id);
193
192
  });
194
193
 
195
- test("getExpiredDeliveryByConversation returns null for non-matching conversation", () => {
194
+ test("getExpiredDeliveriesByConversation returns empty for non-matching conversation", () => {
196
195
  createExpiredRequest("conv-late-5", { conversationId: "mac-conv-2" });
197
196
 
198
- const found = getExpiredDeliveryByConversation("nonexistent-conv");
199
- expect(found).toBeNull();
197
+ const found = getExpiredDeliveriesByConversation("nonexistent-conv");
198
+ expect(found).toHaveLength(0);
200
199
  });
201
200
 
202
- test("getExpiredDeliveryByConversation returns null when followup already started", () => {
201
+ test("getExpiredDeliveriesByConversation returns empty when followup already started", () => {
203
202
  const { request, deliveryConvId } = createExpiredRequest("conv-late-6", {
204
203
  conversationId: "mac-conv-3",
205
204
  });
206
205
 
207
206
  startFollowupFromExpiredRequest(request.id, "already answered");
208
207
 
209
- const found = getExpiredDeliveryByConversation(deliveryConvId);
210
- expect(found).toBeNull();
208
+ const found = getExpiredDeliveriesByConversation(deliveryConvId);
209
+ expect(found).toHaveLength(0);
211
210
  });
212
211
 
213
212
  // ── startFollowupFromExpiredRequest ───────────────────────────────
@@ -309,8 +308,8 @@ describe("guardian-action-late-reply", () => {
309
308
  );
310
309
  expect(expiredByDest).toHaveLength(0);
311
310
 
312
- const expiredByConv = getExpiredDeliveryByConversation(answeredConvId);
313
- expect(expiredByConv).toBeNull();
311
+ const expiredByConv = getExpiredDeliveriesByConversation(answeredConvId);
312
+ expect(expiredByConv).toHaveLength(0);
314
313
  });
315
314
 
316
315
  // ── Composed follow-up text verification ──────────────────────────