@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
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Validates that pairDeliveryWithConversation materializes conversations
|
|
5
5
|
* and messages according to the channel's conversation strategy, handles
|
|
6
|
-
* thread reuse decisions,
|
|
7
|
-
* notification pipeline.
|
|
6
|
+
* thread reuse decisions, binding-key reuse for continue_existing channels,
|
|
7
|
+
* and that errors in pairing never break the notification pipeline.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -70,9 +70,36 @@ mock.module("../memory/conversation-store.js", () => ({
|
|
|
70
70
|
getConversation: getConversationMock,
|
|
71
71
|
}));
|
|
72
72
|
|
|
73
|
+
/** Simulated bindings for external-conversation-store mock. */
|
|
74
|
+
let mockBindings: Record<
|
|
75
|
+
string,
|
|
76
|
+
{ conversationId: string; sourceChannel: string; externalChatId: string }
|
|
77
|
+
> = {};
|
|
78
|
+
|
|
79
|
+
const getBindingByChannelChatMock = mock(
|
|
80
|
+
(sourceChannel: string, externalChatId: string) => {
|
|
81
|
+
const key = `${sourceChannel}:${externalChatId}`;
|
|
82
|
+
return mockBindings[key] ?? null;
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const upsertOutboundBindingMock = mock(
|
|
87
|
+
(_input: {
|
|
88
|
+
conversationId: string;
|
|
89
|
+
sourceChannel: string;
|
|
90
|
+
externalChatId: string;
|
|
91
|
+
}) => {},
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
mock.module("../memory/external-conversation-store.js", () => ({
|
|
95
|
+
getBindingByChannelChat: getBindingByChannelChatMock,
|
|
96
|
+
upsertOutboundBinding: upsertOutboundBindingMock,
|
|
97
|
+
}));
|
|
98
|
+
|
|
73
99
|
import { pairDeliveryWithConversation } from "../notifications/conversation-pairing.js";
|
|
74
100
|
import type { NotificationSignal } from "../notifications/signal.js";
|
|
75
101
|
import type {
|
|
102
|
+
DestinationBindingContext,
|
|
76
103
|
NotificationChannel,
|
|
77
104
|
RenderedChannelCopy,
|
|
78
105
|
ThreadAction,
|
|
@@ -115,11 +142,14 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
115
142
|
createConversationMock.mockClear();
|
|
116
143
|
addMessageMock.mockClear();
|
|
117
144
|
getConversationMock.mockClear();
|
|
145
|
+
getBindingByChannelChatMock.mockClear();
|
|
146
|
+
upsertOutboundBindingMock.mockClear();
|
|
118
147
|
mockConversationId = "conv-001";
|
|
119
148
|
mockMessageId = "msg-001";
|
|
120
149
|
createConversationShouldThrow = false;
|
|
121
150
|
addMessageShouldThrow = false;
|
|
122
151
|
mockExistingConversations = {};
|
|
152
|
+
mockBindings = {};
|
|
123
153
|
});
|
|
124
154
|
|
|
125
155
|
// ── start_new_conversation (vellum) ─────────────────────────────────
|
|
@@ -263,7 +293,7 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
263
293
|
|
|
264
294
|
// ── continue_existing_conversation (telegram) ─────────────────────
|
|
265
295
|
|
|
266
|
-
test("creates a conversation for continue_existing_conversation
|
|
296
|
+
test("creates a conversation for continue_existing_conversation without binding context", async () => {
|
|
267
297
|
const signal = makeSignal();
|
|
268
298
|
const copy = makeCopy();
|
|
269
299
|
|
|
@@ -273,8 +303,6 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
273
303
|
copy,
|
|
274
304
|
);
|
|
275
305
|
|
|
276
|
-
// Currently creates a new conversation even for continue_existing_conversation
|
|
277
|
-
// (true continuation is planned for a future PR)
|
|
278
306
|
expect(result.conversationId).toBe("conv-001");
|
|
279
307
|
expect(result.messageId).toBe("msg-001");
|
|
280
308
|
expect(result.strategy).toBe("continue_existing_conversation");
|
|
@@ -287,6 +315,343 @@ describe("pairDeliveryWithConversation", () => {
|
|
|
287
315
|
expect(callArgs.threadType).toBe("background");
|
|
288
316
|
});
|
|
289
317
|
|
|
318
|
+
// ── Binding-key reuse (continue_existing + bindingContext) ────────
|
|
319
|
+
|
|
320
|
+
test("reuses bound conversation when binding context matches an existing notification conversation", async () => {
|
|
321
|
+
mockExistingConversations["conv-bound"] = {
|
|
322
|
+
id: "conv-bound",
|
|
323
|
+
source: "notification",
|
|
324
|
+
title: "Telegram Thread",
|
|
325
|
+
};
|
|
326
|
+
mockBindings["notification:telegram:chat-123"] = {
|
|
327
|
+
conversationId: "conv-bound",
|
|
328
|
+
sourceChannel: "notification:telegram",
|
|
329
|
+
externalChatId: "chat-123",
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const signal = makeSignal();
|
|
333
|
+
const copy = makeCopy({
|
|
334
|
+
threadSeedMessage: "Second notification to same chat",
|
|
335
|
+
});
|
|
336
|
+
const bindingContext: DestinationBindingContext = {
|
|
337
|
+
sourceChannel: "telegram" as NotificationChannel,
|
|
338
|
+
externalChatId: "chat-123",
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const result = await pairDeliveryWithConversation(
|
|
342
|
+
signal,
|
|
343
|
+
"telegram" as NotificationChannel,
|
|
344
|
+
copy,
|
|
345
|
+
{ bindingContext },
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
expect(result.conversationId).toBe("conv-bound");
|
|
349
|
+
expect(result.messageId).toBe("msg-001");
|
|
350
|
+
expect(result.createdNewConversation).toBe(false);
|
|
351
|
+
expect(result.threadDecisionFallbackUsed).toBe(false);
|
|
352
|
+
expect(result.strategy).toBe("continue_existing_conversation");
|
|
353
|
+
// Should append to existing, not create new
|
|
354
|
+
expect(createConversationMock).not.toHaveBeenCalled();
|
|
355
|
+
expect(addMessageMock).toHaveBeenCalledTimes(1);
|
|
356
|
+
expect(addMessageMock.mock.calls[0]![0]).toBe("conv-bound");
|
|
357
|
+
// Should touch the outbound binding
|
|
358
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("reuses pre-namespace binding when namespaced binding is absent", async () => {
|
|
362
|
+
// Simulate a binding created before the notification: prefix was introduced
|
|
363
|
+
mockExistingConversations["conv-legacy"] = {
|
|
364
|
+
id: "conv-legacy",
|
|
365
|
+
source: "notification",
|
|
366
|
+
title: "Legacy Telegram Thread",
|
|
367
|
+
};
|
|
368
|
+
mockBindings["telegram:chat-legacy"] = {
|
|
369
|
+
conversationId: "conv-legacy",
|
|
370
|
+
sourceChannel: "telegram",
|
|
371
|
+
externalChatId: "chat-legacy",
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const signal = makeSignal();
|
|
375
|
+
const copy = makeCopy({
|
|
376
|
+
threadSeedMessage: "Delivery to legacy binding",
|
|
377
|
+
});
|
|
378
|
+
const bindingContext: DestinationBindingContext = {
|
|
379
|
+
sourceChannel: "telegram" as NotificationChannel,
|
|
380
|
+
externalChatId: "chat-legacy",
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const result = await pairDeliveryWithConversation(
|
|
384
|
+
signal,
|
|
385
|
+
"telegram" as NotificationChannel,
|
|
386
|
+
copy,
|
|
387
|
+
{ bindingContext },
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
expect(result.conversationId).toBe("conv-legacy");
|
|
391
|
+
expect(result.createdNewConversation).toBe(false);
|
|
392
|
+
expect(createConversationMock).not.toHaveBeenCalled();
|
|
393
|
+
// The upsert should write with the new namespaced sourceChannel
|
|
394
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
395
|
+
const upsertArgs = upsertOutboundBindingMock.mock.calls[0]![0] as Record<
|
|
396
|
+
string,
|
|
397
|
+
unknown
|
|
398
|
+
>;
|
|
399
|
+
expect(upsertArgs.sourceChannel).toBe("notification:telegram");
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
test("falls back to new conversation when bound conversation is stale (wrong source)", async () => {
|
|
403
|
+
mockExistingConversations["conv-user-owned"] = {
|
|
404
|
+
id: "conv-user-owned",
|
|
405
|
+
source: "user",
|
|
406
|
+
title: "User Thread",
|
|
407
|
+
};
|
|
408
|
+
mockBindings["notification:sms:+15551234567"] = {
|
|
409
|
+
conversationId: "conv-user-owned",
|
|
410
|
+
sourceChannel: "notification:sms",
|
|
411
|
+
externalChatId: "+15551234567",
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const signal = makeSignal();
|
|
415
|
+
const copy = makeCopy();
|
|
416
|
+
const bindingContext: DestinationBindingContext = {
|
|
417
|
+
sourceChannel: "sms" as NotificationChannel,
|
|
418
|
+
externalChatId: "+15551234567",
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const result = await pairDeliveryWithConversation(
|
|
422
|
+
signal,
|
|
423
|
+
"sms" as NotificationChannel,
|
|
424
|
+
copy,
|
|
425
|
+
{ bindingContext },
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
expect(result.conversationId).toBe("conv-001");
|
|
429
|
+
expect(result.createdNewConversation).toBe(true);
|
|
430
|
+
expect(result.threadDecisionFallbackUsed).toBe(false);
|
|
431
|
+
expect(createConversationMock).toHaveBeenCalledTimes(1);
|
|
432
|
+
// Should upsert the binding for the new conversation
|
|
433
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
434
|
+
const upsertArgs = upsertOutboundBindingMock.mock.calls[0]![0] as Record<
|
|
435
|
+
string,
|
|
436
|
+
unknown
|
|
437
|
+
>;
|
|
438
|
+
expect(upsertArgs.conversationId).toBe("conv-001");
|
|
439
|
+
expect(upsertArgs.sourceChannel).toBe("notification:sms");
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test("falls back to new conversation when bound conversation no longer exists", async () => {
|
|
443
|
+
// Binding exists but conversation was deleted
|
|
444
|
+
mockBindings["notification:telegram:chat-456"] = {
|
|
445
|
+
conversationId: "conv-deleted",
|
|
446
|
+
sourceChannel: "notification:telegram",
|
|
447
|
+
externalChatId: "chat-456",
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const signal = makeSignal();
|
|
451
|
+
const copy = makeCopy();
|
|
452
|
+
const bindingContext: DestinationBindingContext = {
|
|
453
|
+
sourceChannel: "telegram" as NotificationChannel,
|
|
454
|
+
externalChatId: "chat-456",
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const result = await pairDeliveryWithConversation(
|
|
458
|
+
signal,
|
|
459
|
+
"telegram" as NotificationChannel,
|
|
460
|
+
copy,
|
|
461
|
+
{ bindingContext },
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
expect(result.conversationId).toBe("conv-001");
|
|
465
|
+
expect(result.createdNewConversation).toBe(true);
|
|
466
|
+
expect(createConversationMock).toHaveBeenCalledTimes(1);
|
|
467
|
+
// Should upsert the new conversation binding
|
|
468
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("creates new conversation and upserts binding when no prior binding exists", async () => {
|
|
472
|
+
const signal = makeSignal();
|
|
473
|
+
const copy = makeCopy();
|
|
474
|
+
const bindingContext: DestinationBindingContext = {
|
|
475
|
+
sourceChannel: "slack" as NotificationChannel,
|
|
476
|
+
externalChatId: "C0123ABCDEF",
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const result = await pairDeliveryWithConversation(
|
|
480
|
+
signal,
|
|
481
|
+
"slack" as NotificationChannel,
|
|
482
|
+
copy,
|
|
483
|
+
{ bindingContext },
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
expect(result.conversationId).toBe("conv-001");
|
|
487
|
+
expect(result.createdNewConversation).toBe(true);
|
|
488
|
+
expect(createConversationMock).toHaveBeenCalledTimes(1);
|
|
489
|
+
// Should upsert so future deliveries reuse this conversation
|
|
490
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
491
|
+
const upsertArgs = upsertOutboundBindingMock.mock.calls[0]![0] as Record<
|
|
492
|
+
string,
|
|
493
|
+
unknown
|
|
494
|
+
>;
|
|
495
|
+
expect(upsertArgs.conversationId).toBe("conv-001");
|
|
496
|
+
expect(upsertArgs.sourceChannel).toBe("notification:slack");
|
|
497
|
+
expect(upsertArgs.externalChatId).toBe("C0123ABCDEF");
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("reuse_existing rebinds destination when binding context is present", async () => {
|
|
501
|
+
mockExistingConversations["conv-explicit"] = {
|
|
502
|
+
id: "conv-explicit",
|
|
503
|
+
source: "notification",
|
|
504
|
+
title: "Explicit Thread",
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
const signal = makeSignal();
|
|
508
|
+
const copy = makeCopy({
|
|
509
|
+
threadSeedMessage: "Follow-up to explicit reuse target",
|
|
510
|
+
});
|
|
511
|
+
const threadAction: ThreadAction = {
|
|
512
|
+
action: "reuse_existing",
|
|
513
|
+
conversationId: "conv-explicit",
|
|
514
|
+
};
|
|
515
|
+
const bindingContext: DestinationBindingContext = {
|
|
516
|
+
sourceChannel: "telegram" as NotificationChannel,
|
|
517
|
+
externalChatId: "chat-rebind",
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
const result = await pairDeliveryWithConversation(
|
|
521
|
+
signal,
|
|
522
|
+
"telegram" as NotificationChannel,
|
|
523
|
+
copy,
|
|
524
|
+
{ threadAction, bindingContext },
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
expect(result.conversationId).toBe("conv-explicit");
|
|
528
|
+
expect(result.createdNewConversation).toBe(false);
|
|
529
|
+
// Should rebind the destination to the reused conversation
|
|
530
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
531
|
+
const upsertArgs = upsertOutboundBindingMock.mock.calls[0]![0] as Record<
|
|
532
|
+
string,
|
|
533
|
+
unknown
|
|
534
|
+
>;
|
|
535
|
+
expect(upsertArgs.conversationId).toBe("conv-explicit");
|
|
536
|
+
expect(upsertArgs.sourceChannel).toBe("notification:telegram");
|
|
537
|
+
expect(upsertArgs.externalChatId).toBe("chat-rebind");
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
test("reuse_existing fallback rebinds destination when binding context is present", async () => {
|
|
541
|
+
// Target does not exist — falls back to new conversation
|
|
542
|
+
const signal = makeSignal();
|
|
543
|
+
const copy = makeCopy();
|
|
544
|
+
const threadAction: ThreadAction = {
|
|
545
|
+
action: "reuse_existing",
|
|
546
|
+
conversationId: "conv-gone",
|
|
547
|
+
};
|
|
548
|
+
const bindingContext: DestinationBindingContext = {
|
|
549
|
+
sourceChannel: "sms" as NotificationChannel,
|
|
550
|
+
externalChatId: "+15559876543",
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const result = await pairDeliveryWithConversation(
|
|
554
|
+
signal,
|
|
555
|
+
"sms" as NotificationChannel,
|
|
556
|
+
copy,
|
|
557
|
+
{ threadAction, bindingContext },
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
expect(result.conversationId).toBe("conv-001");
|
|
561
|
+
expect(result.createdNewConversation).toBe(true);
|
|
562
|
+
expect(result.threadDecisionFallbackUsed).toBe(true);
|
|
563
|
+
// Should bind the new conversation to the destination
|
|
564
|
+
expect(upsertOutboundBindingMock).toHaveBeenCalledTimes(1);
|
|
565
|
+
const upsertArgs = upsertOutboundBindingMock.mock.calls[0]![0] as Record<
|
|
566
|
+
string,
|
|
567
|
+
unknown
|
|
568
|
+
>;
|
|
569
|
+
expect(upsertArgs.conversationId).toBe("conv-001");
|
|
570
|
+
expect(upsertArgs.sourceChannel).toBe("notification:sms");
|
|
571
|
+
expect(upsertArgs.externalChatId).toBe("+15559876543");
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
test("explicit reuse_existing takes precedence over binding-key reuse", async () => {
|
|
575
|
+
// Both a binding and a reuse_existing target exist — reuse_existing wins
|
|
576
|
+
mockExistingConversations["conv-explicit"] = {
|
|
577
|
+
id: "conv-explicit",
|
|
578
|
+
source: "notification",
|
|
579
|
+
title: "Explicit Thread",
|
|
580
|
+
};
|
|
581
|
+
mockExistingConversations["conv-bound"] = {
|
|
582
|
+
id: "conv-bound",
|
|
583
|
+
source: "notification",
|
|
584
|
+
title: "Bound Thread",
|
|
585
|
+
};
|
|
586
|
+
mockBindings["notification:telegram:chat-789"] = {
|
|
587
|
+
conversationId: "conv-bound",
|
|
588
|
+
sourceChannel: "notification:telegram",
|
|
589
|
+
externalChatId: "chat-789",
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const signal = makeSignal();
|
|
593
|
+
const copy = makeCopy({
|
|
594
|
+
threadSeedMessage: "Message for explicit reuse target",
|
|
595
|
+
});
|
|
596
|
+
const threadAction: ThreadAction = {
|
|
597
|
+
action: "reuse_existing",
|
|
598
|
+
conversationId: "conv-explicit",
|
|
599
|
+
};
|
|
600
|
+
const bindingContext: DestinationBindingContext = {
|
|
601
|
+
sourceChannel: "telegram" as NotificationChannel,
|
|
602
|
+
externalChatId: "chat-789",
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const result = await pairDeliveryWithConversation(
|
|
606
|
+
signal,
|
|
607
|
+
"telegram" as NotificationChannel,
|
|
608
|
+
copy,
|
|
609
|
+
{ threadAction, bindingContext },
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Should use the explicit target, not the binding
|
|
613
|
+
expect(result.conversationId).toBe("conv-explicit");
|
|
614
|
+
expect(result.createdNewConversation).toBe(false);
|
|
615
|
+
expect(createConversationMock).not.toHaveBeenCalled();
|
|
616
|
+
// Binding lookup should not even be attempted since reuse_existing matched first
|
|
617
|
+
expect(getBindingByChannelChatMock).not.toHaveBeenCalled();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
test("binding context does not trigger reuse for start_new_conversation channels", async () => {
|
|
621
|
+
// vellum uses start_new_conversation — binding context should be ignored for reuse
|
|
622
|
+
mockExistingConversations["conv-bound-vellum"] = {
|
|
623
|
+
id: "conv-bound-vellum",
|
|
624
|
+
source: "notification",
|
|
625
|
+
title: "Vellum Thread",
|
|
626
|
+
};
|
|
627
|
+
mockBindings["notification:vellum:device-1"] = {
|
|
628
|
+
conversationId: "conv-bound-vellum",
|
|
629
|
+
sourceChannel: "notification:vellum",
|
|
630
|
+
externalChatId: "device-1",
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
const signal = makeSignal();
|
|
634
|
+
const copy = makeCopy();
|
|
635
|
+
const bindingContext: DestinationBindingContext = {
|
|
636
|
+
sourceChannel: "vellum" as NotificationChannel,
|
|
637
|
+
externalChatId: "device-1",
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
const result = await pairDeliveryWithConversation(
|
|
641
|
+
signal,
|
|
642
|
+
"vellum" as NotificationChannel,
|
|
643
|
+
copy,
|
|
644
|
+
{ bindingContext },
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
// Should still create a new conversation — vellum is start_new_conversation
|
|
648
|
+
expect(result.conversationId).toBe("conv-001");
|
|
649
|
+
expect(result.createdNewConversation).toBe(true);
|
|
650
|
+
expect(createConversationMock).toHaveBeenCalledTimes(1);
|
|
651
|
+
// Binding lookup should not be called for non-continue_existing channels
|
|
652
|
+
expect(getBindingByChannelChatMock).not.toHaveBeenCalled();
|
|
653
|
+
});
|
|
654
|
+
|
|
290
655
|
// ── not_deliverable (voice) ───────────────────────────────────────
|
|
291
656
|
|
|
292
657
|
test("returns null conversationId and messageId for not_deliverable strategy", async () => {
|
|
@@ -25,18 +25,9 @@ mock.module("../util/logger.js", () => ({
|
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
|
-
// Use encrypted backend
|
|
28
|
+
// Use encrypted backend with a temp store path
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
|
-
import { _overrideDeps, _resetDeps } from "../security/keychain.js";
|
|
32
|
-
|
|
33
|
-
_overrideDeps({
|
|
34
|
-
isMacOS: () => false,
|
|
35
|
-
isLinux: () => false,
|
|
36
|
-
execFileSync: (() =>
|
|
37
|
-
"") as unknown as typeof import("node:child_process").execFileSync,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
31
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
41
32
|
import { _resetBackend } from "../security/secure-keys.js";
|
|
42
33
|
|
|
@@ -24,18 +24,9 @@ mock.module("../util/logger.js", () => ({
|
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
|
-
// Use encrypted backend
|
|
27
|
+
// Use encrypted backend with a temp store path
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
29
|
|
|
30
|
-
import { _overrideDeps } from "../security/keychain.js";
|
|
31
|
-
|
|
32
|
-
_overrideDeps({
|
|
33
|
-
isMacOS: () => false,
|
|
34
|
-
isLinux: () => false,
|
|
35
|
-
execFileSync: (() =>
|
|
36
|
-
"") as unknown as typeof import("node:child_process").execFileSync,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
30
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
40
31
|
import { _resetBackend } from "../security/secure-keys.js";
|
|
41
32
|
|
|
@@ -36,7 +36,13 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
36
36
|
storedKeys.set(key, value);
|
|
37
37
|
return true;
|
|
38
38
|
},
|
|
39
|
-
deleteSecureKey: (key: string) =>
|
|
39
|
+
deleteSecureKey: (key: string) => {
|
|
40
|
+
if (storedKeys.has(key)) {
|
|
41
|
+
storedKeys.delete(key);
|
|
42
|
+
return "deleted";
|
|
43
|
+
}
|
|
44
|
+
return "not-found";
|
|
45
|
+
},
|
|
40
46
|
listSecureKeys: () => [...storedKeys.keys()],
|
|
41
47
|
getBackendType: () => "encrypted",
|
|
42
48
|
isDowngradedFromKeychain: () => false,
|
|
@@ -34,21 +34,10 @@ mock.module("../util/logger.js", () => ({
|
|
|
34
34
|
}));
|
|
35
35
|
|
|
36
36
|
// ---------------------------------------------------------------------------
|
|
37
|
-
// Use encrypted backend
|
|
37
|
+
// Use encrypted backend with a temp store path
|
|
38
38
|
// ---------------------------------------------------------------------------
|
|
39
39
|
|
|
40
|
-
import { _overrideDeps, _resetDeps } from "../security/keychain.js";
|
|
41
|
-
|
|
42
|
-
_overrideDeps({
|
|
43
|
-
isMacOS: () => false,
|
|
44
|
-
isLinux: () => false,
|
|
45
|
-
execFileSync: (() =>
|
|
46
|
-
"") as unknown as typeof import("node:child_process").execFileSync,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Restore process-level keychain deps so later test files are not affected
|
|
50
40
|
afterAll(() => {
|
|
51
|
-
_resetDeps();
|
|
52
41
|
mock.restore();
|
|
53
42
|
});
|
|
54
43
|
|
|
@@ -206,8 +195,8 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
206
195
|
expect(browserSrc).not.toContain("getCredentialValue");
|
|
207
196
|
});
|
|
208
197
|
|
|
209
|
-
test("
|
|
210
|
-
// Hard boundary: only these production files may import
|
|
198
|
+
test("secure-keys is only imported by authorized modules", () => {
|
|
199
|
+
// Hard boundary: only these production files may import from secure-keys.
|
|
211
200
|
// Any new import must be reviewed for secret-leak risk and added here.
|
|
212
201
|
const ALLOWED_IMPORTERS = new Set([
|
|
213
202
|
"security/secure-keys.ts", // self (re-export infrastructure)
|
|
@@ -230,11 +219,12 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
230
219
|
"calls/twilio-provider.ts", // call infrastructure credential lookup
|
|
231
220
|
"calls/twilio-rest.ts", // Twilio REST API credential lookup
|
|
232
221
|
"runtime/channel-invite-transports/sms.ts", // SMS invite transport phone number lookup
|
|
233
|
-
"
|
|
222
|
+
"runtime/channel-invite-transports/telegram.ts", // Telegram invite transport bot token lookup
|
|
223
|
+
"cli/keys.ts", // CLI credential management commands
|
|
224
|
+
"cli/credentials.ts", // CLI credential management commands
|
|
234
225
|
"runtime/http-server.ts", // HTTP server credential lookup
|
|
235
226
|
"daemon/handlers/twitter-auth.ts", // Twitter OAuth token storage
|
|
236
227
|
"twitter/oauth-client.ts", // Twitter OAuth API client (reads access token for API calls)
|
|
237
|
-
"cli/config-commands.ts", // CLI config management
|
|
238
228
|
"messaging/providers/telegram-bot/adapter.ts", // Telegram bot token lookup for connectivity check
|
|
239
229
|
"messaging/providers/sms/adapter.ts", // Twilio credential lookup for SMS connectivity check
|
|
240
230
|
"runtime/channel-readiness-service.ts", // channel readiness probes for SMS/Telegram connectivity
|
|
@@ -243,6 +233,11 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
243
233
|
"daemon/handlers/oauth-connect.ts", // OAuth connect handler for integration setup
|
|
244
234
|
"daemon/handlers/config-slack-channel.ts", // Slack channel config credential management
|
|
245
235
|
"providers/managed-proxy/context.ts", // managed proxy API key lookup for provider initialization
|
|
236
|
+
"mcp/mcp-oauth-provider.ts", // MCP OAuth token/client/discovery persistence
|
|
237
|
+
"mcp/client.ts", // MCP client cached-token lookup
|
|
238
|
+
"oauth/token-persistence.ts", // OAuth token persistence (set/delete tokens)
|
|
239
|
+
"runtime/routes/secret-routes.ts", // HTTP secret management routes (set/delete secrets)
|
|
240
|
+
"daemon/session-messaging.ts", // credential storage during session messaging
|
|
246
241
|
]);
|
|
247
242
|
|
|
248
243
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -270,11 +265,10 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
270
265
|
|
|
271
266
|
for (const filePath of allFiles) {
|
|
272
267
|
const content = readFileSync(filePath, "utf-8");
|
|
273
|
-
// Check for
|
|
268
|
+
// Check for any import from the secure-keys module (static import, dynamic import(), or require())
|
|
274
269
|
if (
|
|
275
|
-
content.match(
|
|
276
|
-
|
|
277
|
-
content.match(/(?:import|require)\s*\(\s*['"].*secure-keys/))
|
|
270
|
+
content.match(/from\s+['"].*secure-keys/) ||
|
|
271
|
+
content.match(/(?:import|require)\s*\(\s*['"].*secure-keys/)
|
|
278
272
|
) {
|
|
279
273
|
const relative = filePath.slice(srcDir.length + 1);
|
|
280
274
|
if (!ALLOWED_IMPORTERS.has(relative)) {
|
|
@@ -24,18 +24,9 @@ mock.module("../util/logger.js", () => ({
|
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
|
-
// Use encrypted backend
|
|
27
|
+
// Use encrypted backend with a temp store path
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
29
|
|
|
30
|
-
import { _overrideDeps, _resetDeps } from "../security/keychain.js";
|
|
31
|
-
|
|
32
|
-
_overrideDeps({
|
|
33
|
-
isMacOS: () => false,
|
|
34
|
-
isLinux: () => false,
|
|
35
|
-
execFileSync: (() =>
|
|
36
|
-
"") as unknown as typeof import("node:child_process").execFileSync,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
30
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
40
31
|
import { _resetBackend } from "../security/secure-keys.js";
|
|
41
32
|
|
|
@@ -74,7 +65,6 @@ const _ctx: ToolContext = {
|
|
|
74
65
|
};
|
|
75
66
|
|
|
76
67
|
afterAll(() => {
|
|
77
|
-
_resetDeps();
|
|
78
68
|
mock.restore();
|
|
79
69
|
if (existsSync(TEST_DIR)) {
|
|
80
70
|
rmSync(TEST_DIR, { recursive: true });
|
|
@@ -25,21 +25,11 @@ mock.module("../util/logger.js", () => ({
|
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
|
-
// Use encrypted backend
|
|
28
|
+
// Use encrypted backend with a temp store path
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
|
-
import { _overrideDeps, _resetDeps } from "../security/keychain.js";
|
|
32
|
-
|
|
33
|
-
// Make keychain unavailable so secure-keys always uses encrypted backend
|
|
34
|
-
_overrideDeps({
|
|
35
|
-
isMacOS: () => false,
|
|
36
|
-
isLinux: () => false,
|
|
37
|
-
execFileSync: (() =>
|
|
38
|
-
"") as unknown as typeof import("node:child_process").execFileSync,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
31
|
import { _setStorePath } from "../security/encrypted-store.js";
|
|
42
|
-
import { _resetBackend
|
|
32
|
+
import { _resetBackend } from "../security/secure-keys.js";
|
|
43
33
|
|
|
44
34
|
const TEST_DIR = join(
|
|
45
35
|
tmpdir(),
|
|
@@ -162,8 +152,8 @@ async function executeVault(
|
|
|
162
152
|
}
|
|
163
153
|
|
|
164
154
|
const key = `credential:${service}:${field}`;
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
155
|
+
const result = deleteSecureKey(key);
|
|
156
|
+
if (result !== "deleted") {
|
|
167
157
|
return {
|
|
168
158
|
content: `Error: credential ${service}/${field} not found`,
|
|
169
159
|
isError: true,
|
|
@@ -206,7 +196,6 @@ describe("credential_store tool", () => {
|
|
|
206
196
|
});
|
|
207
197
|
|
|
208
198
|
afterAll(() => {
|
|
209
|
-
_resetDeps();
|
|
210
199
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
211
200
|
});
|
|
212
201
|
|
|
@@ -500,7 +489,7 @@ describe("credential_store tool", () => {
|
|
|
500
489
|
expect(gmail.injection_templates).toBeUndefined();
|
|
501
490
|
});
|
|
502
491
|
|
|
503
|
-
test("works with
|
|
492
|
+
test("works with metadata store fallback when listing secrets", async () => {
|
|
504
493
|
// Store a credential first (on encrypted backend)
|
|
505
494
|
await credentialStoreTool.execute(
|
|
506
495
|
{
|
|
@@ -512,9 +501,6 @@ describe("credential_store tool", () => {
|
|
|
512
501
|
_ctx,
|
|
513
502
|
);
|
|
514
503
|
|
|
515
|
-
// Switch to keychain backend — list should still work via metadata
|
|
516
|
-
_setBackend("keychain");
|
|
517
|
-
|
|
518
504
|
const result = await credentialStoreTool.execute(
|
|
519
505
|
{ action: "list" },
|
|
520
506
|
_ctx,
|