@vellumai/assistant 0.10.3 → 0.10.4-staging.1
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/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cold-cache member-reaction regression.
|
|
3
|
+
*
|
|
4
|
+
* Slack reactions carry the gateway-stamped verdict on `sourceMetadata` but
|
|
5
|
+
* skip `getInboundTrustVerdict`, which is what warms the member-verdict cache
|
|
6
|
+
* the sync trust resolver reads. `handleSlackReactionIntercept` therefore seeds
|
|
7
|
+
* the cache from the stamped verdict before the sync resolve, so an active
|
|
8
|
+
* non-guardian contact's reaction classifies as `trusted_contact` instead of
|
|
9
|
+
* failing closed to `unknown` (and being dropped) on a cold process.
|
|
10
|
+
*/
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
const MEMBER_USER_ID = "U_MEMBER_COLD";
|
|
14
|
+
const MEMBER_CONTACT_ID = "member-contact";
|
|
15
|
+
const MEMBER_CHANNEL_ID = "member-channel";
|
|
16
|
+
const SLACK_CHANNEL_ID = "C0MEMBERCOLD";
|
|
17
|
+
|
|
18
|
+
const MEMBER_CONTACT = {
|
|
19
|
+
id: MEMBER_CONTACT_ID,
|
|
20
|
+
displayName: "Member",
|
|
21
|
+
notes: null,
|
|
22
|
+
lastInteraction: null,
|
|
23
|
+
interactionCount: 0,
|
|
24
|
+
createdAt: 0,
|
|
25
|
+
updatedAt: 0,
|
|
26
|
+
contactType: "human",
|
|
27
|
+
userFile: null,
|
|
28
|
+
channels: [
|
|
29
|
+
{
|
|
30
|
+
id: MEMBER_CHANNEL_ID,
|
|
31
|
+
contactId: MEMBER_CONTACT_ID,
|
|
32
|
+
type: "slack",
|
|
33
|
+
address: MEMBER_USER_ID,
|
|
34
|
+
isPrimary: false,
|
|
35
|
+
externalChatId: SLACK_CHANNEL_ID,
|
|
36
|
+
inviteId: null,
|
|
37
|
+
lastSeenAt: null,
|
|
38
|
+
interactionCount: 0,
|
|
39
|
+
lastInteraction: null,
|
|
40
|
+
updatedAt: null,
|
|
41
|
+
createdAt: 0,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// No guardian for slack — the reactor is a member, not the guardian.
|
|
47
|
+
mock.module("../ipc/gateway-client.js", () => ({
|
|
48
|
+
ipcCall: async () => ({ guardians: [] }),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// The reactor resolves to a local member channel by address.
|
|
52
|
+
mock.module("../contacts/contact-store.js", () => ({
|
|
53
|
+
findContactByAddress: (_channelType: string, address: string) =>
|
|
54
|
+
address === MEMBER_USER_ID ? MEMBER_CONTACT : null,
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
// Stub downstream side effects so the test isolates trust classification.
|
|
58
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
59
|
+
addMessage: async () => ({ id: "msg-1" }),
|
|
60
|
+
}));
|
|
61
|
+
mock.module("../memory/delivery-crud.js", () => ({
|
|
62
|
+
recordInbound: () => ({
|
|
63
|
+
eventId: "evt-1",
|
|
64
|
+
conversationId: "conv-1",
|
|
65
|
+
accepted: true,
|
|
66
|
+
duplicate: false,
|
|
67
|
+
}),
|
|
68
|
+
clearPayload: () => {},
|
|
69
|
+
linkMessage: () => {},
|
|
70
|
+
}));
|
|
71
|
+
mock.module("../memory/delivery-status.js", () => ({
|
|
72
|
+
markProcessed: () => {},
|
|
73
|
+
}));
|
|
74
|
+
mock.module("../memory/external-conversation-store.js", () => ({
|
|
75
|
+
upsertBinding: () => {},
|
|
76
|
+
}));
|
|
77
|
+
mock.module("../daemon/disk-pressure-guard.js", () => ({
|
|
78
|
+
getDiskPressureStatus: () => ({ level: "ok" }),
|
|
79
|
+
}));
|
|
80
|
+
mock.module("../daemon/disk-pressure-policy.js", () => ({
|
|
81
|
+
classifyDiskPressureTurnPolicy: () => ({ action: "allow" }),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
let receivedTrustClass: string | undefined;
|
|
85
|
+
mock.module(
|
|
86
|
+
"../runtime/routes/inbound-stages/guardian-reply-intercept.js",
|
|
87
|
+
() => ({
|
|
88
|
+
handleGuardianReplyIntercept: async (params: { trustClass: string }) => {
|
|
89
|
+
receivedTrustClass = params.trustClass;
|
|
90
|
+
return { response: { accepted: true, canonicalRouter: "applied" } };
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
import { __resetMemberVerdictCacheForTest } from "../runtime/member-verdict-cache.js";
|
|
96
|
+
import { handleSlackReactionIntercept } from "../runtime/routes/inbound-stages/reaction-intercept.js";
|
|
97
|
+
|
|
98
|
+
function buildParams(withStampedVerdict: boolean) {
|
|
99
|
+
return {
|
|
100
|
+
callbackData: "reaction:white_check_mark",
|
|
101
|
+
sourceChannel: "slack" as const,
|
|
102
|
+
sourceInterface: "slack" as const,
|
|
103
|
+
conversationExternalId: SLACK_CHANNEL_ID,
|
|
104
|
+
externalMessageId: `${SLACK_CHANNEL_ID}:1700000000.2:cold`,
|
|
105
|
+
canonicalAssistantId: "assistant-1",
|
|
106
|
+
rawSenderId: MEMBER_USER_ID,
|
|
107
|
+
canonicalSenderId: MEMBER_USER_ID,
|
|
108
|
+
actorDisplayName: "Member",
|
|
109
|
+
actorUsername: undefined,
|
|
110
|
+
replyCallbackUrl: "http://localhost:7830/deliver/slack",
|
|
111
|
+
sourceMetadata: {
|
|
112
|
+
messageId: "1700000000.2",
|
|
113
|
+
chatType: "channel",
|
|
114
|
+
...(withStampedVerdict
|
|
115
|
+
? {
|
|
116
|
+
trustVerdict: {
|
|
117
|
+
trustClass: "trusted_contact",
|
|
118
|
+
canonicalSenderId: MEMBER_USER_ID,
|
|
119
|
+
contactId: MEMBER_CONTACT_ID,
|
|
120
|
+
channelId: MEMBER_CHANNEL_ID,
|
|
121
|
+
type: "slack",
|
|
122
|
+
address: MEMBER_USER_ID,
|
|
123
|
+
status: "active",
|
|
124
|
+
policy: "allow",
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
: {}),
|
|
128
|
+
} as never,
|
|
129
|
+
slackChannelName: "general",
|
|
130
|
+
approvalConversationGenerator: undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
describe("reaction intercept seeds the member-verdict cache from the stamped verdict", () => {
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
__resetMemberVerdictCacheForTest();
|
|
137
|
+
receivedTrustClass = undefined;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("cold cache + stamped verdict: active member reaction classifies as trusted_contact", async () => {
|
|
141
|
+
const result = await handleSlackReactionIntercept(buildParams(true));
|
|
142
|
+
|
|
143
|
+
// Not dropped — the stamped verdict warmed the cache, so the sync resolve
|
|
144
|
+
// found the member as active.
|
|
145
|
+
expect(result.reaction).not.toBe("dropped_unknown_actor");
|
|
146
|
+
expect(receivedTrustClass).toBe("trusted_contact");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("cold cache + no stamped verdict: same member reaction fails closed to unknown", async () => {
|
|
150
|
+
const result = await handleSlackReactionIntercept(buildParams(false));
|
|
151
|
+
|
|
152
|
+
// Negative control: with no stamped verdict and a cold cache, the member
|
|
153
|
+
// can't be classified, so the reaction is dropped — proving the warm above
|
|
154
|
+
// is load-bearing.
|
|
155
|
+
expect(result.reaction).toBe("dropped_unknown_actor");
|
|
156
|
+
expect(receivedTrustClass).toBeUndefined();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -42,9 +42,39 @@ mock.module("../runtime/gateway-client.js", () => ({
|
|
|
42
42
|
deliverChannelReply: async () => {},
|
|
43
43
|
}));
|
|
44
44
|
|
|
45
|
+
// Guardian identity resolves via the gateway delivery cache, not the local
|
|
46
|
+
// contacts DB. Seed it per-test via seedGatewayGuardian so the guardian
|
|
47
|
+
// reactor classifies as `trustClass === "guardian"`.
|
|
48
|
+
interface GatewayGuardian {
|
|
49
|
+
channelType: string;
|
|
50
|
+
address: string;
|
|
51
|
+
principalId?: string | null;
|
|
52
|
+
externalChatId?: string | null;
|
|
53
|
+
status: string;
|
|
54
|
+
}
|
|
55
|
+
let gatewayGuardians: GatewayGuardian[] = [];
|
|
56
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
57
|
+
getGuardianDelivery: async () => gatewayGuardians,
|
|
58
|
+
peekCachedGuardianDelivery: (input?: { channelTypes?: string[] }) => {
|
|
59
|
+
if (!input?.channelTypes) return gatewayGuardians;
|
|
60
|
+
return gatewayGuardians.filter((g) =>
|
|
61
|
+
input.channelTypes!.includes(g.channelType),
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
guardianForChannel: (list: GatewayGuardian[], channelType: string) =>
|
|
65
|
+
list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
66
|
+
anyGuardian: (list: GatewayGuardian[]) => list[0],
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
function seedGatewayGuardian(g: Partial<GatewayGuardian> & {
|
|
70
|
+
channelType: string;
|
|
71
|
+
address: string;
|
|
72
|
+
}): void {
|
|
73
|
+
gatewayGuardians.push({ status: "active", ...g });
|
|
74
|
+
}
|
|
75
|
+
|
|
45
76
|
import { eq } from "drizzle-orm";
|
|
46
77
|
|
|
47
|
-
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
48
78
|
import type { Conversation } from "../daemon/conversation.js";
|
|
49
79
|
import {
|
|
50
80
|
createCanonicalGuardianDelivery,
|
|
@@ -60,7 +90,10 @@ import {
|
|
|
60
90
|
isSlackReactionEvent,
|
|
61
91
|
parseSlackReactionCallbackData,
|
|
62
92
|
} from "../runtime/routes/inbound-stages/reaction-intercept.js";
|
|
63
|
-
import {
|
|
93
|
+
import {
|
|
94
|
+
handleChannelInbound,
|
|
95
|
+
seedContactChannel,
|
|
96
|
+
} from "./helpers/channel-test-adapter.js";
|
|
64
97
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
65
98
|
|
|
66
99
|
await initializeDb();
|
|
@@ -81,10 +114,11 @@ function resetState(): void {
|
|
|
81
114
|
db.run("DELETE FROM conversations");
|
|
82
115
|
db.run("DELETE FROM contact_channels");
|
|
83
116
|
db.run("DELETE FROM contacts");
|
|
117
|
+
gatewayGuardians = [];
|
|
84
118
|
}
|
|
85
119
|
|
|
86
120
|
function seedActiveMember(): void {
|
|
87
|
-
|
|
121
|
+
seedContactChannel({
|
|
88
122
|
sourceChannel: "slack",
|
|
89
123
|
externalUserId: SLACK_USER_ID,
|
|
90
124
|
externalChatId: SLACK_CHANNEL_ID,
|
|
@@ -406,6 +440,12 @@ const GUARDIAN_REACTION_TOOL = "execute_shell";
|
|
|
406
440
|
const GUARDIAN_REACTION_INPUT = { command: "rm -rf /tmp/test" };
|
|
407
441
|
|
|
408
442
|
function seedGuardianForChannel(): void {
|
|
443
|
+
seedGatewayGuardian({
|
|
444
|
+
channelType: "slack",
|
|
445
|
+
address: GUARDIAN_USER_ID,
|
|
446
|
+
principalId: GUARDIAN_USER_ID,
|
|
447
|
+
externalChatId: SLACK_CHANNEL_ID,
|
|
448
|
+
});
|
|
409
449
|
createGuardianBinding({
|
|
410
450
|
channel: "slack",
|
|
411
451
|
guardianExternalUserId: GUARDIAN_USER_ID,
|
|
@@ -478,6 +518,7 @@ describe("guardian approval-by-reaction integration via handleChannelInbound", (
|
|
|
478
518
|
db.run("DELETE FROM contacts");
|
|
479
519
|
db.run("DELETE FROM canonical_guardian_requests");
|
|
480
520
|
db.run("DELETE FROM canonical_guardian_deliveries");
|
|
521
|
+
gatewayGuardians = [];
|
|
481
522
|
pendingInteractions.clear();
|
|
482
523
|
msgCounter = 0;
|
|
483
524
|
});
|
|
@@ -608,6 +649,12 @@ describe("reaction access control (no verification handshake)", () => {
|
|
|
608
649
|
getDb().run("DELETE FROM channel_verification_sessions");
|
|
609
650
|
// The assistant has a guardian (as in production); the reactors below are
|
|
610
651
|
// different users.
|
|
652
|
+
seedGatewayGuardian({
|
|
653
|
+
channelType: "slack",
|
|
654
|
+
address: GUARDIAN_USER_ID,
|
|
655
|
+
principalId: GUARDIAN_USER_ID,
|
|
656
|
+
externalChatId: GUARDIAN_DM_CHAT,
|
|
657
|
+
});
|
|
611
658
|
createGuardianBinding({
|
|
612
659
|
channel: "slack",
|
|
613
660
|
guardianExternalUserId: GUARDIAN_USER_ID,
|
|
@@ -655,7 +702,7 @@ describe("reaction access control (no verification handshake)", () => {
|
|
|
655
702
|
// A pending contact classifies as `unverified_contact` — a known tier, so
|
|
656
703
|
// its reactions are recorded. On a real message it would be re-challenged,
|
|
657
704
|
// but a reaction must not trigger that.
|
|
658
|
-
|
|
705
|
+
seedContactChannel({
|
|
659
706
|
sourceChannel: "slack",
|
|
660
707
|
externalUserId: CONTACT_USER_ID,
|
|
661
708
|
externalChatId: SLACK_CHANNEL_ID,
|
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
test,
|
|
27
27
|
} from "bun:test";
|
|
28
28
|
|
|
29
|
+
import { and, desc, eq } from "drizzle-orm";
|
|
30
|
+
|
|
29
31
|
// ── Platform + logger mocks (must come before any source imports) ────
|
|
30
32
|
|
|
31
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -50,12 +52,32 @@ let inviteClaimCalls = 0;
|
|
|
50
52
|
let inviteClaimGate: Promise<void> | null = null;
|
|
51
53
|
mock.module("../ipc/gateway-client.js", () => ({
|
|
52
54
|
ipcCall: async () => undefined,
|
|
53
|
-
ipcCallPersistent: async (
|
|
55
|
+
ipcCallPersistent: async (
|
|
56
|
+
method: string,
|
|
57
|
+
params?: Record<string, unknown>,
|
|
58
|
+
) => {
|
|
54
59
|
if (method === "record_invite_redemption") {
|
|
55
60
|
inviteClaimCalls += 1;
|
|
56
61
|
if (inviteClaimGate) await inviteClaimGate;
|
|
57
62
|
return { ok: true, updated: true, mirrored: true };
|
|
58
63
|
}
|
|
64
|
+
// The gateway owns the ACL verdict; activation fails closed when the relay
|
|
65
|
+
// does not land, so model a verified upsert for the redemption paths.
|
|
66
|
+
if (method === "upsert_verified_channel") {
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
verified: true,
|
|
70
|
+
channel: {
|
|
71
|
+
id: "gw-channel-id",
|
|
72
|
+
contactId: (params?.contactId as string) ?? "gw-contact",
|
|
73
|
+
type: (params?.type as string) ?? "phone",
|
|
74
|
+
address: (params?.address as string) ?? "gw-addr",
|
|
75
|
+
status: "active",
|
|
76
|
+
verifiedAt: 1,
|
|
77
|
+
verifiedVia: (params?.verifiedVia as string) ?? "invite",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
59
81
|
return undefined;
|
|
60
82
|
},
|
|
61
83
|
}));
|
|
@@ -97,7 +119,12 @@ mock.module("../prompts/user-reference.js", () => ({
|
|
|
97
119
|
// the DB-seeded createGuardianBinding setup. Single mock registration lives
|
|
98
120
|
// below since `mock.module` is process-global and last-write-wins in Bun.
|
|
99
121
|
let mockGuardianDeliveryList:
|
|
100
|
-
| Array<{
|
|
122
|
+
| Array<{
|
|
123
|
+
channelType: string;
|
|
124
|
+
status: string;
|
|
125
|
+
displayName?: string | null;
|
|
126
|
+
address?: string;
|
|
127
|
+
}>
|
|
101
128
|
| null = null;
|
|
102
129
|
|
|
103
130
|
// ── Config mock ─────────────────────────────────────────────────────
|
|
@@ -224,28 +251,54 @@ mock.module("../calls/inbound-trust-reader.js", () => ({
|
|
|
224
251
|
//
|
|
225
252
|
// Guardian identity now resolves via the gateway delivery reader. Derive the
|
|
226
253
|
// list from the DB-seeded guardian bindings so the existing createGuardianBinding
|
|
227
|
-
// setup keeps driving guardian resolution without per-test changes.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
// setup keeps driving guardian resolution without per-test changes. Both the
|
|
255
|
+
// async read and the sync cache peek (read by resolveActorTrust) share the same
|
|
256
|
+
// DB-derived snapshot mirroring the gateway's active-guardian-channel query.
|
|
257
|
+
function deriveGuardianDeliveries(
|
|
258
|
+
channelTypes?: string[],
|
|
259
|
+
): Array<Record<string, unknown>> {
|
|
260
|
+
if (mockGuardianDeliveryList) {
|
|
261
|
+
return channelTypes
|
|
262
|
+
? mockGuardianDeliveryList.filter((g) =>
|
|
263
|
+
channelTypes.includes(g.channelType as string),
|
|
264
|
+
)
|
|
265
|
+
: mockGuardianDeliveryList;
|
|
266
|
+
}
|
|
267
|
+
const rows = getDb()
|
|
268
|
+
.select({ contact: contacts, channel: contactChannels })
|
|
269
|
+
.from(contacts)
|
|
270
|
+
.innerJoin(contactChannels, eq(contacts.id, contactChannels.contactId))
|
|
271
|
+
.where(
|
|
272
|
+
and(eq(contacts.role, "guardian"), eq(contactChannels.status, "active")),
|
|
273
|
+
)
|
|
274
|
+
.orderBy(desc(contactChannels.verifiedAt))
|
|
275
|
+
.all();
|
|
276
|
+
if (rows.length === 0) return [];
|
|
277
|
+
const guardianId = rows[0].contact.id;
|
|
278
|
+
const list = rows
|
|
279
|
+
.filter((r) => r.contact.id === guardianId)
|
|
280
|
+
.map((r) => ({
|
|
281
|
+
channelType: r.channel.type,
|
|
282
|
+
contactId: r.contact.id,
|
|
283
|
+
principalId: r.contact.principalId ?? null,
|
|
284
|
+
displayName: r.contact.displayName ?? null,
|
|
285
|
+
address: r.channel.address,
|
|
286
|
+
externalChatId: r.channel.externalChatId ?? null,
|
|
287
|
+
status: r.channel.status,
|
|
288
|
+
verifiedAt: r.channel.verifiedAt ?? null,
|
|
247
289
|
}));
|
|
248
|
-
|
|
290
|
+
return channelTypes
|
|
291
|
+
? list.filter((g) =>
|
|
292
|
+
channelTypes.includes(g.channelType),
|
|
293
|
+
)
|
|
294
|
+
: list;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
298
|
+
getGuardianDelivery: async (input?: { channelTypes?: string[] }) =>
|
|
299
|
+
deriveGuardianDeliveries(input?.channelTypes),
|
|
300
|
+
peekCachedGuardianDelivery: (input?: { channelTypes?: string[] }) =>
|
|
301
|
+
deriveGuardianDeliveries(input?.channelTypes),
|
|
249
302
|
guardianForChannel: (
|
|
250
303
|
list: Array<{ channelType: string; status: string }>,
|
|
251
304
|
channelType: string,
|
|
@@ -388,7 +441,6 @@ import {
|
|
|
388
441
|
} from "../calls/relay-server.js";
|
|
389
442
|
import { setVoiceBridgeDeps } from "../calls/voice-session-bridge.js";
|
|
390
443
|
import { upsertContact } from "../contacts/contact-store.js";
|
|
391
|
-
import { upsertContactChannel } from "../contacts/contacts-write.js";
|
|
392
444
|
import {
|
|
393
445
|
listCanonicalGuardianRequests,
|
|
394
446
|
resolveCanonicalGuardianRequest,
|
|
@@ -402,7 +454,11 @@ import { getDb } from "../memory/db-connection.js";
|
|
|
402
454
|
import { initializeDb } from "../memory/db-init.js";
|
|
403
455
|
import { createInvite } from "../memory/invite-store.js";
|
|
404
456
|
import { resetTestTables } from "../memory/raw-query.js";
|
|
405
|
-
import {
|
|
457
|
+
import {
|
|
458
|
+
contactChannels,
|
|
459
|
+
contacts,
|
|
460
|
+
conversations,
|
|
461
|
+
} from "../memory/schema.js";
|
|
406
462
|
import {
|
|
407
463
|
createOutboundSession,
|
|
408
464
|
getGuardianBinding,
|
|
@@ -410,6 +466,7 @@ import {
|
|
|
410
466
|
import { generateVoiceCode, hashVoiceCode } from "../util/voice-code.js";
|
|
411
467
|
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
412
468
|
import { createGuardianBinding } from "./helpers/create-guardian-binding.js";
|
|
469
|
+
import { seedContactChannel } from "./helpers/seed-contact-channel.js";
|
|
413
470
|
|
|
414
471
|
await initializeDb();
|
|
415
472
|
|
|
@@ -500,7 +557,7 @@ function createTargetContact(displayName = "Test Contact"): string {
|
|
|
500
557
|
}
|
|
501
558
|
|
|
502
559
|
function addTrustedVoiceContact(phoneNumber: string): void {
|
|
503
|
-
|
|
560
|
+
seedContactChannel({
|
|
504
561
|
sourceChannel: "phone",
|
|
505
562
|
externalUserId: phoneNumber,
|
|
506
563
|
externalChatId: phoneNumber,
|
|
@@ -2811,7 +2868,7 @@ describe("relay-server", () => {
|
|
|
2811
2868
|
toNumber: "+15551111111",
|
|
2812
2869
|
});
|
|
2813
2870
|
|
|
2814
|
-
|
|
2871
|
+
seedContactChannel({
|
|
2815
2872
|
sourceChannel: "phone",
|
|
2816
2873
|
externalUserId: "+15558886666",
|
|
2817
2874
|
externalChatId: "+15558886666",
|
|
@@ -2874,7 +2931,7 @@ describe("relay-server", () => {
|
|
|
2874
2931
|
toNumber: "+15551111111",
|
|
2875
2932
|
});
|
|
2876
2933
|
|
|
2877
|
-
|
|
2934
|
+
seedContactChannel({
|
|
2878
2935
|
sourceChannel: "phone",
|
|
2879
2936
|
externalUserId: "+15558887777",
|
|
2880
2937
|
externalChatId: "+15558887777",
|
|
@@ -3173,7 +3230,7 @@ describe("relay-server", () => {
|
|
|
3173
3230
|
});
|
|
3174
3231
|
|
|
3175
3232
|
// Create a blocked member
|
|
3176
|
-
|
|
3233
|
+
seedContactChannel({
|
|
3177
3234
|
sourceChannel: "phone",
|
|
3178
3235
|
externalUserId: "+15558881111",
|
|
3179
3236
|
externalChatId: "+15558881111",
|
|
@@ -5071,7 +5128,7 @@ describe("relay-server", () => {
|
|
|
5071
5128
|
|
|
5072
5129
|
// Gateway binding carries a different displayName
|
|
5073
5130
|
mockGuardianDeliveryList = [
|
|
5074
|
-
{ channelType: "phone", status: "active", displayName: "Bob" },
|
|
5131
|
+
{ channelType: "phone", status: "active", address: "+15550000001", displayName: "Bob" },
|
|
5075
5132
|
];
|
|
5076
5133
|
|
|
5077
5134
|
ensureConversation("conv-label-user-md");
|
|
@@ -5111,7 +5168,7 @@ describe("relay-server", () => {
|
|
|
5111
5168
|
|
|
5112
5169
|
// Gateway binding carries the guardian displayName
|
|
5113
5170
|
mockGuardianDeliveryList = [
|
|
5114
|
-
{ channelType: "phone", status: "active", displayName: "Charlie" },
|
|
5171
|
+
{ channelType: "phone", status: "active", address: "+15550000002", displayName: "Charlie" },
|
|
5115
5172
|
];
|
|
5116
5173
|
|
|
5117
5174
|
ensureConversation("conv-label-contact");
|
|
@@ -44,7 +44,6 @@ mock.module("../daemon/approval-generators.js", () => ({
|
|
|
44
44
|
createApprovalConversationGenerator: () => undefined,
|
|
45
45
|
}));
|
|
46
46
|
|
|
47
|
-
import { upsertContact } from "../contacts/contact-store.js";
|
|
48
47
|
import {
|
|
49
48
|
linkAttachmentToMessage,
|
|
50
49
|
uploadAttachment,
|
|
@@ -58,7 +57,10 @@ import { resetTestTables } from "../memory/raw-query.js";
|
|
|
58
57
|
import { RuntimeHttpServer } from "../runtime/http-server.js";
|
|
59
58
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
60
59
|
import { resetDbForTesting } from "./db-test-helpers.js";
|
|
61
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
resolveLocalTrustVerdict,
|
|
62
|
+
seedContactChannel,
|
|
63
|
+
} from "./helpers/channel-test-adapter.js";
|
|
62
64
|
|
|
63
65
|
await initializeDb();
|
|
64
66
|
|
|
@@ -251,16 +253,12 @@ describe("WhatsApp channel ingress attachment resolution", () => {
|
|
|
251
253
|
}
|
|
252
254
|
|
|
253
255
|
function ensureWhatsAppContact(): void {
|
|
254
|
-
|
|
256
|
+
seedContactChannel({
|
|
257
|
+
sourceChannel: "whatsapp",
|
|
258
|
+
externalUserId: WHATSAPP_USER_ID,
|
|
255
259
|
displayName: "WhatsApp Test User",
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
type: "whatsapp",
|
|
259
|
-
address: WHATSAPP_USER_ID,
|
|
260
|
-
status: "active",
|
|
261
|
-
policy: "allow",
|
|
262
|
-
},
|
|
263
|
-
],
|
|
260
|
+
status: "active",
|
|
261
|
+
policy: "allow",
|
|
264
262
|
});
|
|
265
263
|
}
|
|
266
264
|
|
|
@@ -17,6 +17,34 @@ mock.module("../util/logger.js", () => ({
|
|
|
17
17
|
}),
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
|
+
// Guardian identity resolves via the gateway delivery cache, not the local
|
|
21
|
+
// contacts DB. Seed it per-test via seedGatewayGuardian; persona resolution
|
|
22
|
+
// joins the local contact (userFile) by the delivery's channelType + address.
|
|
23
|
+
interface GatewayGuardian {
|
|
24
|
+
channelType: string;
|
|
25
|
+
address: string;
|
|
26
|
+
status: string;
|
|
27
|
+
}
|
|
28
|
+
let gatewayGuardians: GatewayGuardian[] = [];
|
|
29
|
+
mock.module("../contacts/guardian-delivery-reader.js", () => ({
|
|
30
|
+
peekCachedGuardianDelivery: (input?: { channelTypes?: string[] }) => {
|
|
31
|
+
if (!input?.channelTypes) return gatewayGuardians;
|
|
32
|
+
return gatewayGuardians.filter((g) =>
|
|
33
|
+
input.channelTypes!.includes(g.channelType),
|
|
34
|
+
);
|
|
35
|
+
},
|
|
36
|
+
guardianForChannel: (list: GatewayGuardian[], channelType: string) =>
|
|
37
|
+
list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
38
|
+
anyGuardian: (list: GatewayGuardian[]) => list[0],
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
function seedGatewayGuardian(g: {
|
|
42
|
+
channelType: string;
|
|
43
|
+
address: string;
|
|
44
|
+
}): void {
|
|
45
|
+
gatewayGuardians.push({ status: "active", ...g });
|
|
46
|
+
}
|
|
47
|
+
|
|
20
48
|
import { getSqlite } from "../memory/db-connection.js";
|
|
21
49
|
import { initializeDb } from "../memory/db-init.js";
|
|
22
50
|
import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js";
|
|
@@ -32,6 +60,7 @@ function resetContactTables(): void {
|
|
|
32
60
|
const sqlite = getSqlite();
|
|
33
61
|
sqlite.run("DELETE FROM contact_channels");
|
|
34
62
|
sqlite.run("DELETE FROM contacts");
|
|
63
|
+
gatewayGuardians = [];
|
|
35
64
|
}
|
|
36
65
|
|
|
37
66
|
// ---------------------------------------------------------------------------
|
|
@@ -76,6 +105,7 @@ describe("GET /workspace-files", () => {
|
|
|
76
105
|
});
|
|
77
106
|
|
|
78
107
|
test("with a guardian: includes users/<slug>.md", async () => {
|
|
108
|
+
seedGatewayGuardian({ channelType: "telegram", address: "Alice" });
|
|
79
109
|
createGuardianBinding({
|
|
80
110
|
channel: "telegram",
|
|
81
111
|
guardianExternalUserId: "Alice",
|
|
@@ -103,6 +133,7 @@ describe("GET /workspace-files", () => {
|
|
|
103
133
|
};
|
|
104
134
|
expect(result.files.map((f) => f.path)).not.toContain("users/alice.md");
|
|
105
135
|
|
|
136
|
+
seedGatewayGuardian({ channelType: "telegram", address: "Alice" });
|
|
106
137
|
createGuardianBinding({
|
|
107
138
|
channel: "telegram",
|
|
108
139
|
guardianExternalUserId: "Alice",
|
|
@@ -130,6 +161,7 @@ describe("GET /workspace-files/read", () => {
|
|
|
130
161
|
});
|
|
131
162
|
|
|
132
163
|
test("reads a guardian users/<slug>.md file", async () => {
|
|
164
|
+
seedGatewayGuardian({ channelType: "telegram", address: "Alice" });
|
|
133
165
|
createGuardianBinding({
|
|
134
166
|
channel: "telegram",
|
|
135
167
|
guardianExternalUserId: "Alice",
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
isSlackCallbackUrl,
|
|
5
|
-
textToSlackBlocks,
|
|
6
|
-
} from "../runtime/slack-block-formatting.js";
|
|
3
|
+
import { textToSlackBlocks } from "../runtime/slack-block-formatting.js";
|
|
7
4
|
|
|
8
5
|
describe("textToSlackBlocks", () => {
|
|
9
6
|
test("returns undefined for empty text", () => {
|
|
@@ -174,37 +171,3 @@ describe("textToSlackBlocks", () => {
|
|
|
174
171
|
expect(section.text.text).toContain("|");
|
|
175
172
|
});
|
|
176
173
|
});
|
|
177
|
-
|
|
178
|
-
describe("isSlackCallbackUrl", () => {
|
|
179
|
-
test("returns true for Slack deliver URLs", () => {
|
|
180
|
-
expect(
|
|
181
|
-
isSlackCallbackUrl(
|
|
182
|
-
"http://127.0.0.1:7830/deliver/slack?threadTs=123&channel=C456",
|
|
183
|
-
),
|
|
184
|
-
).toBe(true);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test("returns true for bare Slack deliver path", () => {
|
|
188
|
-
expect(isSlackCallbackUrl("http://localhost:7830/deliver/slack")).toBe(
|
|
189
|
-
true,
|
|
190
|
-
);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("returns false for non-Slack URLs", () => {
|
|
194
|
-
expect(isSlackCallbackUrl("http://localhost:7830/deliver/telegram")).toBe(
|
|
195
|
-
false,
|
|
196
|
-
);
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test("returns false for invalid URLs", () => {
|
|
200
|
-
expect(isSlackCallbackUrl("not-a-url")).toBe(false);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test("returns false for managed outbound URLs", () => {
|
|
204
|
-
expect(
|
|
205
|
-
isSlackCallbackUrl(
|
|
206
|
-
"http://localhost:7830/v1/internal/managed-gateway/outbound-send/?route_id=r1&assistant_id=a1&source_channel=phone",
|
|
207
|
-
),
|
|
208
|
-
).toBe(false);
|
|
209
|
-
});
|
|
210
|
-
});
|