@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.
- package/bun.lock +62 -349
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/keychain-broker.md +94 -29
- package/docs/architecture/security.md +2 -2
- package/knip.json +7 -29
- package/package.json +2 -9
- package/src/__tests__/agent-loop.test.ts +1 -1
- package/src/__tests__/app-git-history.test.ts +0 -2
- package/src/__tests__/app-git-service.test.ts +1 -6
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -6
- package/src/__tests__/call-domain.test.ts +0 -1
- package/src/__tests__/call-routes-http.test.ts +0 -1
- package/src/__tests__/channel-guardian.test.ts +4 -4
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +0 -1
- package/src/__tests__/checker.test.ts +13 -11
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -1
- package/src/__tests__/claude-code-tool-profiles.test.ts +1 -2
- package/src/__tests__/config-loader-backfill.test.ts +0 -3
- package/src/__tests__/config-schema.test.ts +3 -9
- package/src/__tests__/config-watcher.test.ts +11 -3
- package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
- package/src/__tests__/credential-broker-server-use.test.ts +60 -24
- package/src/__tests__/credential-security-e2e.test.ts +1 -6
- package/src/__tests__/credential-security-invariants.test.ts +13 -8
- package/src/__tests__/credential-vault-unit.test.ts +28 -12
- package/src/__tests__/credential-vault.test.ts +40 -28
- package/src/__tests__/credentials-cli.test.ts +1 -21
- package/src/__tests__/email-invite-adapter.test.ts +0 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
- package/src/__tests__/gateway-only-enforcement.test.ts +1 -21
- package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
- package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
- package/src/__tests__/guardian-action-store.test.ts +0 -57
- package/src/__tests__/guardian-outbound-http.test.ts +1 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
- package/src/__tests__/hooks-blocking.test.ts +1 -1
- package/src/__tests__/hooks-config.test.ts +5 -29
- package/src/__tests__/hooks-discovery.test.ts +1 -1
- package/src/__tests__/hooks-integration.test.ts +1 -1
- package/src/__tests__/hooks-manager.test.ts +1 -1
- package/src/__tests__/hooks-runner.test.ts +1 -23
- package/src/__tests__/hooks-settings.test.ts +1 -1
- package/src/__tests__/hooks-templates.test.ts +1 -1
- package/src/__tests__/integration-status.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +0 -3
- package/src/__tests__/list-messages-attachments.test.ts +4 -4
- package/src/__tests__/llm-usage-store.test.ts +50 -0
- package/src/__tests__/managed-proxy-context.test.ts +41 -41
- package/src/__tests__/media-generate-image.test.ts +2 -2
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -6
- package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
- package/src/__tests__/memory-regressions.test.ts +27 -27
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
- package/src/__tests__/notification-decision-fallback.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +1 -4
- package/src/__tests__/oauth-store.test.ts +1 -3
- package/src/__tests__/openai-provider.test.ts +7 -7
- package/src/__tests__/platform.test.ts +14 -4
- package/src/__tests__/pricing.test.ts +0 -223
- package/src/__tests__/provider-commit-message-generator.test.ts +1 -4
- package/src/__tests__/provider-fail-open-selection.test.ts +58 -54
- package/src/__tests__/provider-managed-proxy-integration.test.ts +63 -63
- package/src/__tests__/provider-registry-ollama.test.ts +3 -3
- package/src/__tests__/public-ingress-urls.test.ts +1 -1
- package/src/__tests__/registry.test.ts +3 -103
- package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
- package/src/__tests__/secret-onetime-send.test.ts +1 -6
- package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -13
- package/src/__tests__/secure-keys.test.ts +241 -229
- package/src/__tests__/session-abort-tool-results.test.ts +0 -1
- package/src/__tests__/session-confirmation-signals.test.ts +0 -1
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
- package/src/__tests__/session-pre-run-repair.test.ts +0 -1
- package/src/__tests__/session-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/session-queue.test.ts +2 -4
- package/src/__tests__/session-slash-known.test.ts +0 -1
- package/src/__tests__/session-slash-queue.test.ts +0 -1
- package/src/__tests__/session-slash-unknown.test.ts +0 -1
- package/src/__tests__/session-workspace-injection.test.ts +0 -1
- package/src/__tests__/session-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/slack-channel-config.test.ts +1 -7
- package/src/__tests__/swarm-recursion.test.ts +0 -1
- package/src/__tests__/swarm-session-integration.test.ts +0 -1
- package/src/__tests__/swarm-tool.test.ts +0 -1
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
- package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
- package/src/__tests__/tool-executor.test.ts +1 -1
- package/src/__tests__/trust-store.test.ts +3 -82
- package/src/__tests__/twilio-config.test.ts +0 -1
- package/src/__tests__/twilio-provider.test.ts +0 -5
- package/src/__tests__/twilio-routes.test.ts +0 -1
- package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
- package/src/calls/guardian-question-copy.ts +1 -1
- package/src/cli/commands/bash.ts +3 -0
- package/src/cli/commands/doctor.ts +10 -34
- package/src/cli/commands/memory.ts +3 -5
- package/src/cli/commands/sessions.ts +1 -1
- package/src/cli/commands/usage.ts +359 -0
- package/src/cli/http-client.ts +22 -12
- package/src/cli/program.ts +2 -0
- package/src/cli/reference.ts +1 -0
- package/src/cli.ts +251 -181
- package/src/config/assistant-feature-flags.ts +0 -7
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +0 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -1
- package/src/config/bundled-skills/sequences/SKILL.md +0 -1
- package/src/config/env.ts +13 -0
- package/src/config/feature-flag-registry.json +9 -41
- package/src/config/schemas/security.ts +1 -2
- package/src/config/skills.ts +1 -1
- package/src/contacts/contact-store.ts +0 -50
- package/src/daemon/approved-devices-store.ts +0 -44
- package/src/daemon/classifier.ts +1 -1
- package/src/daemon/config-watcher.ts +14 -8
- package/src/daemon/handlers/config-model.ts +1 -1
- package/src/daemon/handlers/sessions.ts +4 -116
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/lifecycle.ts +13 -15
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +20 -3
- package/src/daemon/session-slash.ts +2 -2
- package/src/daemon/shutdown-handlers.ts +15 -0
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/guardrails.ts +1 -1
- package/src/email/service.ts +0 -5
- package/src/hooks/templates.ts +1 -1
- package/src/media/app-icon-generator.ts +2 -2
- package/src/media/avatar-router.ts +2 -2
- package/src/media/gemini-image-service.ts +5 -5
- package/src/memory/admin.ts +2 -2
- package/src/memory/app-git-service.ts +0 -7
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-title-service.ts +2 -2
- package/src/memory/embedding-backend.ts +30 -26
- package/src/memory/external-conversation-store.ts +0 -30
- package/src/memory/guardian-action-store.ts +0 -31
- package/src/memory/guardian-approvals.ts +1 -56
- package/src/memory/indexer.ts +4 -3
- package/src/memory/items-extractor.ts +1 -1
- package/src/memory/job-handlers/backfill.ts +5 -2
- package/src/memory/job-handlers/index-maintenance.ts +2 -2
- package/src/memory/job-handlers/media-processing.ts +2 -2
- package/src/memory/job-handlers/summarization.ts +1 -1
- package/src/memory/job-utils.ts +1 -2
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/llm-usage-store.ts +57 -11
- package/src/memory/media-store.ts +4 -535
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
- package/src/memory/migrations/110-channel-guardian.ts +0 -1
- package/src/memory/published-pages-store.ts +0 -83
- package/src/memory/qdrant-circuit-breaker.ts +0 -8
- package/src/memory/retriever.ts +1 -1
- package/src/memory/schema/calls.ts +0 -67
- package/src/memory/search/semantic.ts +1 -8
- package/src/memory/shared-app-links-store.ts +0 -15
- package/src/messaging/registry.ts +0 -5
- package/src/messaging/style-analyzer.ts +1 -1
- package/src/notifications/copy-composer.ts +5 -13
- package/src/notifications/decision-engine.ts +2 -2
- package/src/notifications/deliveries-store.ts +0 -39
- package/src/notifications/guardian-question-mode.ts +6 -10
- package/src/notifications/preference-extractor.ts +1 -1
- package/src/oauth/byo-connection.test.ts +29 -20
- package/src/oauth/provider-behaviors.ts +1 -1
- package/src/permissions/checker.ts +1 -1
- package/src/permissions/shell-identity.ts +0 -5
- package/src/permissions/trust-store.ts +0 -37
- package/src/prompts/system-prompt.ts +4 -4
- package/src/prompts/templates/SOUL.md +1 -1
- package/src/providers/managed-proxy/constants.ts +8 -10
- package/src/providers/managed-proxy/context.ts +14 -9
- package/src/providers/provider-send-message.ts +4 -52
- package/src/providers/registry.ts +16 -50
- package/src/runtime/actor-token-store.ts +0 -23
- package/src/runtime/auth/__tests__/guard-tests.test.ts +64 -0
- package/src/runtime/http-router.ts +5 -1
- package/src/runtime/http-server.ts +101 -4
- package/src/runtime/invite-instruction-generator.ts +25 -51
- package/src/runtime/invite-service.ts +0 -20
- package/src/runtime/routes/attachment-routes.ts +1 -1
- package/src/runtime/routes/brain-graph-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +32 -11
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +2 -2
- package/src/runtime/routes/documents-routes.ts +3 -3
- package/src/runtime/routes/global-search-routes.ts +1 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
- package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
- package/src/runtime/routes/secret-routes.ts +4 -4
- package/src/runtime/routes/session-management-routes.ts +27 -0
- package/src/runtime/routes/trust-rules-routes.ts +1 -1
- package/src/security/credential-backend.ts +148 -0
- package/src/security/oauth2.ts +1 -1
- package/src/security/secret-allowlist.ts +1 -1
- package/src/security/secure-keys.ts +98 -160
- package/src/security/token-manager.ts +0 -7
- package/src/sequence/guardrails.ts +0 -4
- package/src/sequence/store.ts +1 -20
- package/src/sequence/types.ts +1 -36
- package/src/signals/bash.ts +33 -0
- package/src/signals/cancel.ts +69 -0
- package/src/signals/conversation-undo.ts +127 -0
- package/src/signals/trust-rule.ts +174 -0
- package/src/skills/clawhub.ts +5 -5
- package/src/skills/managed-store.ts +4 -4
- package/src/subagent/manager.ts +8 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
- package/src/telemetry/usage-telemetry-reporter.ts +181 -0
- package/src/tools/claude-code/claude-code.ts +2 -2
- package/src/tools/credentials/vault.ts +8 -4
- package/src/tools/memory/handlers.test.ts +24 -26
- package/src/tools/memory/handlers.ts +1 -13
- package/src/tools/registry.ts +5 -100
- package/src/tools/terminal/parser.ts +34 -4
- package/src/tools/tool-manifest.ts +0 -10
- package/src/usage/actors.ts +0 -12
- package/src/util/canonicalize-identity.ts +0 -9
- package/src/util/errors.ts +0 -3
- package/src/util/platform.ts +24 -7
- package/src/util/pricing.ts +0 -38
- package/src/watcher/constants.ts +0 -7
- package/src/watcher/providers/linear.ts +1 -1
- package/src/work-items/work-item-store.ts +4 -4
- package/src/workspace/commit-message-provider.ts +1 -1
- package/src/workspace/git-service.ts +44 -1
- package/src/workspace/provider-commit-message-generator.ts +1 -1
- package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
- package/src/browser-extension-relay/client.ts +0 -155
- package/src/contacts/index.ts +0 -18
- package/src/daemon/tls-certs.ts +0 -270
- package/src/errors.ts +0 -41
- package/src/events/index.ts +0 -18
- package/src/followups/index.ts +0 -10
- package/src/playbooks/index.ts +0 -10
- package/src/runtime/auth/index.ts +0 -44
- package/src/tasks/candidate-store.ts +0 -95
- package/src/tools/browser/api-map.ts +0 -313
- package/src/tools/browser/auto-navigate.ts +0 -469
- package/src/tools/browser/headless-browser.ts +0 -590
- package/src/tools/browser/recording-store.ts +0 -75
- package/src/tools/computer-use/registry.ts +0 -21
- 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 {
|
|
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
|
-
|
|
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(
|
|
472
|
-
"prompt
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
778
|
-
expect(
|
|
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
|
-
|
|
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(
|
|
1233
|
+
expect(await getSecureKeyAsync(credentialKey("gmail", "password"))).toBe(
|
|
1230
1234
|
"gmail-pass",
|
|
1231
1235
|
);
|
|
1232
|
-
expect(
|
|
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(
|
|
1252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1347
|
+
await setSecureKeyAsync(
|
|
1339
1348
|
`oauth_connection/${connId}/refresh_token`,
|
|
1340
1349
|
"valid-refresh-token",
|
|
1341
1350
|
);
|
|
1342
|
-
|
|
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
|
-
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -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" | "
|
|
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: "
|
|
126
|
-
component: "
|
|
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
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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 =
|
|
403
|
-
expect(found).
|
|
404
|
-
expect(found
|
|
402
|
+
const found = getFollowupDeliveriesByConversation(deliveryConvId);
|
|
403
|
+
expect(found).toHaveLength(1);
|
|
404
|
+
expect(found[0].id).toBe(delivery.id);
|
|
405
405
|
});
|
|
406
406
|
|
|
407
|
-
test("
|
|
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 =
|
|
413
|
-
expect(found).
|
|
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
|
-
// ──
|
|
182
|
+
// ── getExpiredDeliveriesByConversation (singular-to-plural migration) ──
|
|
184
183
|
|
|
185
|
-
test("
|
|
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 =
|
|
191
|
-
expect(found).
|
|
192
|
-
expect(found
|
|
189
|
+
const found = getExpiredDeliveriesByConversation(deliveryConvId);
|
|
190
|
+
expect(found).toHaveLength(1);
|
|
191
|
+
expect(found[0].id).toBe(delivery.id);
|
|
193
192
|
});
|
|
194
193
|
|
|
195
|
-
test("
|
|
194
|
+
test("getExpiredDeliveriesByConversation returns empty for non-matching conversation", () => {
|
|
196
195
|
createExpiredRequest("conv-late-5", { conversationId: "mac-conv-2" });
|
|
197
196
|
|
|
198
|
-
const found =
|
|
199
|
-
expect(found).
|
|
197
|
+
const found = getExpiredDeliveriesByConversation("nonexistent-conv");
|
|
198
|
+
expect(found).toHaveLength(0);
|
|
200
199
|
});
|
|
201
200
|
|
|
202
|
-
test("
|
|
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 =
|
|
210
|
-
expect(found).
|
|
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 =
|
|
313
|
-
expect(expiredByConv).
|
|
311
|
+
const expiredByConv = getExpiredDeliveriesByConversation(answeredConvId);
|
|
312
|
+
expect(expiredByConv).toHaveLength(0);
|
|
314
313
|
});
|
|
315
314
|
|
|
316
315
|
// ── Composed follow-up text verification ──────────────────────────
|