@vellumai/assistant 0.9.0-dev.202606181735.ae6e1d8 → 0.9.0-dev.202606181858.5b26a8a
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/docs/activation-funnel-telemetry.md +24 -18
- package/openapi.yaml +146 -0
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +6 -8
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +0 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +0 -3
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/trusted-contact-verification.test.ts +2 -4
- package/src/__tests__/voice-invite-redemption.test.ts +0 -1
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +6 -6
- package/src/contacts/contact-store.ts +2 -10
- package/src/contacts/contacts-write.ts +1 -2
- package/src/contacts/types.ts +0 -1
- package/src/context/token-estimator.ts +1 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +5 -10
- package/src/daemon/lifecycle.ts +23 -12
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/db-init.ts +2 -0
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -1
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/permissions/checker.ts +22 -7
- package/src/permissions/ipc-risk-types.ts +10 -0
- package/src/platform/client.test.ts +96 -0
- package/src/platform/client.ts +52 -0
- package/src/platform/consent-cache.test.ts +159 -0
- package/src/platform/consent-cache.ts +107 -0
- package/src/prompts/persona-resolver.ts +2 -2
- package/src/runtime/access-request-helper.ts +2 -3
- package/src/runtime/actor-trust-resolver.ts +1 -2
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/channel-verification-service.ts +1 -2
- package/src/runtime/invite-redemption-service.ts +6 -22
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/telemetry/usage-telemetry-reporter.test.ts +40 -25
- package/src/telemetry/usage-telemetry-reporter.ts +24 -23
- package/src/tools/registry.ts +20 -0
- package/src/util/platform.ts +8 -1
- package/src/watcher/telemetry.ts +2 -2
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/registry.ts +2 -0
|
@@ -50,8 +50,9 @@ Flow, end to end:
|
|
|
50
50
|
`assistant/src/telemetry/activation-funnel.ts`;
|
|
51
51
|
- emission is best-effort (wrapped in try/catch) and never blocks or alters
|
|
52
52
|
the surface-action flow;
|
|
53
|
-
- `recordActivationEvent` respects the `
|
|
54
|
-
|
|
53
|
+
- `recordActivationEvent` respects the platform `share_analytics` consent gate
|
|
54
|
+
via `getCachedShareAnalytics()` (returns `null` / no row when the platform
|
|
55
|
+
user has not consented; default-off when there is no platform session).
|
|
55
56
|
2. **Reporter flushes** every ~5 min: `usage-telemetry-reporter.ts`
|
|
56
57
|
(`REPORT_INTERVAL_MS = 5 * 60 * 1000`, with a one-time
|
|
57
58
|
`INITIAL_FLUSH_DELAY_MS = 30_000` after startup) POSTs unreported onboarding
|
|
@@ -172,22 +173,27 @@ When the flag is ON, the web prechat context selects
|
|
|
172
173
|
`BOOTSTRAP-ACTIVATION-RAIL.md` as the bootstrap template, and the daemon marks
|
|
173
174
|
the conversation in `activation_sessions` on first build of the system prompt.
|
|
174
175
|
|
|
175
|
-
### 5.2 Confirm
|
|
176
|
-
|
|
177
|
-
Two
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
`
|
|
190
|
-
|
|
176
|
+
### 5.2 Confirm share_analytics consent is on — and pick the right daemon mode
|
|
177
|
+
|
|
178
|
+
Two distinct concerns: whether record-time rows reach SQLite (consent gate), and
|
|
179
|
+
whether those rows reach BigQuery (flush gate, dev-disabled).
|
|
180
|
+
|
|
181
|
+
1. `recordActivationEvent` no-ops when the platform `share_analytics` consent is
|
|
182
|
+
off (it reads `getCachedShareAnalytics()`), and the consent is **default-off
|
|
183
|
+
when there is no platform session**. So the dev session must be signed in to
|
|
184
|
+
the platform with `share_analytics` consent enabled, or no rows are written to
|
|
185
|
+
SQLite. The consent cache is refreshed by `startConsentRefresh()` in
|
|
186
|
+
`assistant/src/daemon/lifecycle.ts`, which runs **regardless of dev mode** — so
|
|
187
|
+
a dev session signed in with `share_analytics` consent enabled writes
|
|
188
|
+
record-time rows to SQLite, where they can be inspected directly.
|
|
189
|
+
2. **Dev mode disables the flush entirely.** When the daemon runs in dev mode
|
|
190
|
+
(`VELLUM_DEV=1`), `assistant/src/daemon/lifecycle.ts` never starts the
|
|
191
|
+
`UsageTelemetryReporter` — rows accumulate in SQLite (consent permitting) but
|
|
192
|
+
are never POSTed, and the "wait/restart for flush" steps below will never reach
|
|
193
|
+
BigQuery. For an end-to-end smoke test, run the daemon **outside dev mode** (so
|
|
194
|
+
the reporter starts), or explicitly invoke the reporter's `flush()` via a dev
|
|
195
|
+
hook. The SQLite rows can be inspected directly in dev mode, but the BigQuery
|
|
196
|
+
verification (§6) requires a real flush.
|
|
191
197
|
|
|
192
198
|
### 5.3 Start a fresh activation conversation and capture its id
|
|
193
199
|
|
package/openapi.yaml
CHANGED
|
@@ -14569,6 +14569,54 @@ paths:
|
|
|
14569
14569
|
responses:
|
|
14570
14570
|
"200":
|
|
14571
14571
|
description: Successful response
|
|
14572
|
+
content:
|
|
14573
|
+
application/json:
|
|
14574
|
+
schema:
|
|
14575
|
+
type: object
|
|
14576
|
+
properties:
|
|
14577
|
+
servers:
|
|
14578
|
+
type: array
|
|
14579
|
+
items:
|
|
14580
|
+
type: object
|
|
14581
|
+
properties:
|
|
14582
|
+
id:
|
|
14583
|
+
type: string
|
|
14584
|
+
status:
|
|
14585
|
+
type: string
|
|
14586
|
+
transport:
|
|
14587
|
+
type: object
|
|
14588
|
+
properties:
|
|
14589
|
+
type:
|
|
14590
|
+
type: string
|
|
14591
|
+
enum:
|
|
14592
|
+
- stdio
|
|
14593
|
+
- sse
|
|
14594
|
+
- streamable-http
|
|
14595
|
+
required:
|
|
14596
|
+
- type
|
|
14597
|
+
additionalProperties: {}
|
|
14598
|
+
enabled:
|
|
14599
|
+
type: boolean
|
|
14600
|
+
defaultRiskLevel:
|
|
14601
|
+
type: string
|
|
14602
|
+
allowedTools:
|
|
14603
|
+
type: array
|
|
14604
|
+
items:
|
|
14605
|
+
type: string
|
|
14606
|
+
blockedTools:
|
|
14607
|
+
type: array
|
|
14608
|
+
items:
|
|
14609
|
+
type: string
|
|
14610
|
+
required:
|
|
14611
|
+
- id
|
|
14612
|
+
- status
|
|
14613
|
+
- transport
|
|
14614
|
+
- enabled
|
|
14615
|
+
- defaultRiskLevel
|
|
14616
|
+
additionalProperties: false
|
|
14617
|
+
required:
|
|
14618
|
+
- servers
|
|
14619
|
+
additionalProperties: false
|
|
14572
14620
|
/v1/internal/mcp/reload:
|
|
14573
14621
|
post:
|
|
14574
14622
|
operationId: internal_mcp_reload_post
|
|
@@ -14600,6 +14648,104 @@ paths:
|
|
|
14600
14648
|
responses:
|
|
14601
14649
|
"200":
|
|
14602
14650
|
description: Successful response
|
|
14651
|
+
/v1/internal/mcp/tools-summary:
|
|
14652
|
+
get:
|
|
14653
|
+
operationId: internal_mcp_toolssummary_get
|
|
14654
|
+
summary: Per-server MCP tool counts and token estimates
|
|
14655
|
+
description:
|
|
14656
|
+
Returns registered tool counts, individual tool details, and estimated token overhead for each connected
|
|
14657
|
+
MCP server.
|
|
14658
|
+
tags:
|
|
14659
|
+
- internal
|
|
14660
|
+
responses:
|
|
14661
|
+
"200":
|
|
14662
|
+
description: Successful response
|
|
14663
|
+
content:
|
|
14664
|
+
application/json:
|
|
14665
|
+
schema:
|
|
14666
|
+
type: object
|
|
14667
|
+
properties:
|
|
14668
|
+
servers:
|
|
14669
|
+
type: array
|
|
14670
|
+
items:
|
|
14671
|
+
type: object
|
|
14672
|
+
properties:
|
|
14673
|
+
serverId:
|
|
14674
|
+
type: string
|
|
14675
|
+
toolCount:
|
|
14676
|
+
type: number
|
|
14677
|
+
estimatedTokens:
|
|
14678
|
+
type: number
|
|
14679
|
+
tools:
|
|
14680
|
+
type: array
|
|
14681
|
+
items:
|
|
14682
|
+
type: object
|
|
14683
|
+
properties:
|
|
14684
|
+
name:
|
|
14685
|
+
type: string
|
|
14686
|
+
description:
|
|
14687
|
+
type: string
|
|
14688
|
+
estimatedTokens:
|
|
14689
|
+
type: number
|
|
14690
|
+
required:
|
|
14691
|
+
- name
|
|
14692
|
+
- description
|
|
14693
|
+
- estimatedTokens
|
|
14694
|
+
additionalProperties: false
|
|
14695
|
+
required:
|
|
14696
|
+
- serverId
|
|
14697
|
+
- toolCount
|
|
14698
|
+
- estimatedTokens
|
|
14699
|
+
- tools
|
|
14700
|
+
additionalProperties: false
|
|
14701
|
+
totalToolCount:
|
|
14702
|
+
type: number
|
|
14703
|
+
totalEstimatedTokens:
|
|
14704
|
+
type: number
|
|
14705
|
+
required:
|
|
14706
|
+
- servers
|
|
14707
|
+
- totalToolCount
|
|
14708
|
+
- totalEstimatedTokens
|
|
14709
|
+
additionalProperties: false
|
|
14710
|
+
/v1/internal/mcp/update:
|
|
14711
|
+
post:
|
|
14712
|
+
operationId: internal_mcp_update_post
|
|
14713
|
+
summary: Update an MCP server configuration
|
|
14714
|
+
description: Updates fields on an existing MCP server config entry and triggers a reload.
|
|
14715
|
+
tags:
|
|
14716
|
+
- internal
|
|
14717
|
+
requestBody:
|
|
14718
|
+
required: true
|
|
14719
|
+
content:
|
|
14720
|
+
application/json:
|
|
14721
|
+
schema:
|
|
14722
|
+
type: object
|
|
14723
|
+
properties:
|
|
14724
|
+
name:
|
|
14725
|
+
type: string
|
|
14726
|
+
enabled:
|
|
14727
|
+
type: boolean
|
|
14728
|
+
defaultRiskLevel:
|
|
14729
|
+
type: string
|
|
14730
|
+
maxTools:
|
|
14731
|
+
type: number
|
|
14732
|
+
allowedTools:
|
|
14733
|
+
anyOf:
|
|
14734
|
+
- type: array
|
|
14735
|
+
items:
|
|
14736
|
+
type: string
|
|
14737
|
+
- type: "null"
|
|
14738
|
+
blockedTools:
|
|
14739
|
+
anyOf:
|
|
14740
|
+
- type: array
|
|
14741
|
+
items:
|
|
14742
|
+
type: string
|
|
14743
|
+
- type: "null"
|
|
14744
|
+
required:
|
|
14745
|
+
- name
|
|
14746
|
+
responses:
|
|
14747
|
+
"200":
|
|
14748
|
+
description: Successful response
|
|
14603
14749
|
/v1/internal/oauth/callback:
|
|
14604
14750
|
post:
|
|
14605
14751
|
operationId: internal_oauth_callback_post
|
package/package.json
CHANGED
|
@@ -43,7 +43,6 @@ const PHONE = "+15559871234";
|
|
|
43
43
|
function makeContact(
|
|
44
44
|
role: "guardian" | "contact" = "contact",
|
|
45
45
|
status: "unverified" | "active" = "unverified",
|
|
46
|
-
externalUserId: string | null = null,
|
|
47
46
|
): ContactWithChannels {
|
|
48
47
|
const channelId = "ch-test";
|
|
49
48
|
return {
|
|
@@ -64,7 +63,6 @@ function makeContact(
|
|
|
64
63
|
contactId: "contact-test",
|
|
65
64
|
type: "phone",
|
|
66
65
|
address: PHONE,
|
|
67
|
-
externalUserId,
|
|
68
66
|
externalChatId: null,
|
|
69
67
|
isPrimary: true,
|
|
70
68
|
status,
|
|
@@ -94,7 +92,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
94
92
|
|
|
95
93
|
test("finds unverified channel via address when externalUserId is null", () => {
|
|
96
94
|
// Simulate a contact registered by name-capture: address set, externalUserId null.
|
|
97
|
-
_byAddress = makeContact("contact", "unverified"
|
|
95
|
+
_byAddress = makeContact("contact", "unverified");
|
|
98
96
|
|
|
99
97
|
const result = resolveActorTrust({
|
|
100
98
|
assistantId: "asst-1",
|
|
@@ -112,7 +110,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
112
110
|
});
|
|
113
111
|
|
|
114
112
|
test("address lookup is the sole member resolution path", () => {
|
|
115
|
-
_byAddress = makeContact("contact", "active"
|
|
113
|
+
_byAddress = makeContact("contact", "active");
|
|
116
114
|
|
|
117
115
|
const result = resolveActorTrust({
|
|
118
116
|
assistantId: "asst-1",
|
|
@@ -142,7 +140,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
142
140
|
test("address-found active channel elevates trust to trusted_contact", () => {
|
|
143
141
|
// An active channel found via address (e.g. after manual verify without externalUserId set)
|
|
144
142
|
// should still yield trusted_contact trust class.
|
|
145
|
-
_byAddress = makeContact("contact", "active"
|
|
143
|
+
_byAddress = makeContact("contact", "active");
|
|
146
144
|
|
|
147
145
|
const result = resolveActorTrust({
|
|
148
146
|
assistantId: "asst-1",
|
|
@@ -159,7 +157,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
159
157
|
test("pending-status member is classified as unverified_contact", () => {
|
|
160
158
|
// Mirrors the unverified branch but for `pending` status (e.g. a phone
|
|
161
159
|
// contact registered by name-capture awaiting the DTMF challenge).
|
|
162
|
-
const contact = makeContact("contact", "unverified"
|
|
160
|
+
const contact = makeContact("contact", "unverified");
|
|
163
161
|
// Override status to "pending" — makeContact only accepts unverified/active
|
|
164
162
|
contact.channels[0]!.status = "pending";
|
|
165
163
|
_byAddress = contact;
|
|
@@ -178,7 +176,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
178
176
|
test("blocked-status member is classified as unknown (not unverified_contact)", () => {
|
|
179
177
|
// Hard-deny statuses (blocked, revoked) stay `unknown` — admission-layer
|
|
180
178
|
// re-checks channel.status and emits the hard-deny reasons.
|
|
181
|
-
const contact = makeContact("contact", "unverified"
|
|
179
|
+
const contact = makeContact("contact", "unverified");
|
|
182
180
|
contact.channels[0]!.status = "blocked";
|
|
183
181
|
_byAddress = contact;
|
|
184
182
|
|
|
@@ -194,7 +192,7 @@ describe("resolveActorTrust — address fallback", () => {
|
|
|
194
192
|
});
|
|
195
193
|
|
|
196
194
|
test("revoked-status member is classified as unknown", () => {
|
|
197
|
-
const contact = makeContact("contact", "unverified"
|
|
195
|
+
const contact = makeContact("contact", "unverified");
|
|
198
196
|
contact.channels[0]!.status = "revoked";
|
|
199
197
|
_byAddress = contact;
|
|
200
198
|
|
|
@@ -7,18 +7,10 @@ mock.module("../util/logger.js", () => ({
|
|
|
7
7
|
}),
|
|
8
8
|
}));
|
|
9
9
|
|
|
10
|
-
let
|
|
10
|
+
let shareAnalytics = true;
|
|
11
11
|
|
|
12
|
-
mock.module("../
|
|
13
|
-
|
|
14
|
-
ui: {},
|
|
15
|
-
model: "test",
|
|
16
|
-
provider: "test",
|
|
17
|
-
memory: { enabled: false },
|
|
18
|
-
rateLimit: { maxRequestsPerMinute: 0 },
|
|
19
|
-
secretDetection: { enabled: false },
|
|
20
|
-
collectUsageData,
|
|
21
|
-
}),
|
|
12
|
+
mock.module("../platform/consent-cache.js", () => ({
|
|
13
|
+
getCachedShareAnalytics: () => shareAnalytics,
|
|
22
14
|
}));
|
|
23
15
|
|
|
24
16
|
import {
|
|
@@ -53,7 +45,7 @@ const SAMPLE: AuthFallbackCount[] = [
|
|
|
53
45
|
|
|
54
46
|
describe("auth-fallback-events-store", () => {
|
|
55
47
|
beforeEach(() => {
|
|
56
|
-
|
|
48
|
+
shareAnalytics = true;
|
|
57
49
|
resetTable();
|
|
58
50
|
});
|
|
59
51
|
|
|
@@ -78,8 +70,8 @@ describe("auth-fallback-events-store", () => {
|
|
|
78
70
|
});
|
|
79
71
|
});
|
|
80
72
|
|
|
81
|
-
test("honors the
|
|
82
|
-
|
|
73
|
+
test("honors the share_analytics opt-out (records nothing)", () => {
|
|
74
|
+
shareAnalytics = false;
|
|
83
75
|
const recorded = recordAuthFallbackCounts(1000, 2000, SAMPLE);
|
|
84
76
|
expect(recorded).toBe(0);
|
|
85
77
|
expect(queryUnreportedAuthFallbackEvents(0, undefined, 100).length).toBe(0);
|
|
@@ -224,14 +224,12 @@ function ensureTestContact(): void {
|
|
|
224
224
|
{
|
|
225
225
|
type: "telegram",
|
|
226
226
|
address: "telegram-user-default",
|
|
227
|
-
externalUserId: "telegram-user-default",
|
|
228
227
|
status: "active",
|
|
229
228
|
policy: "allow",
|
|
230
229
|
},
|
|
231
230
|
{
|
|
232
231
|
type: "slack",
|
|
233
232
|
address: "slack-user-default",
|
|
234
|
-
externalUserId: "slack-user-default",
|
|
235
233
|
status: "active",
|
|
236
234
|
policy: "allow",
|
|
237
235
|
},
|
|
@@ -2054,7 +2052,6 @@ describe("requester cancel of guardian-gated pending request", () => {
|
|
|
2054
2052
|
{
|
|
2055
2053
|
type: "telegram",
|
|
2056
2054
|
address: "requester-cancel-user",
|
|
2057
|
-
externalUserId: "requester-cancel-user",
|
|
2058
2055
|
status: "active",
|
|
2059
2056
|
policy: "allow",
|
|
2060
2057
|
},
|
|
@@ -2981,7 +2978,6 @@ describe("trusted-contact self-approval blocked before guardian approval row exi
|
|
|
2981
2978
|
{
|
|
2982
2979
|
type: "telegram",
|
|
2983
2980
|
address: "tc-selfapproval-user",
|
|
2984
|
-
externalUserId: "tc-selfapproval-user",
|
|
2985
2981
|
status: "active",
|
|
2986
2982
|
policy: "allow",
|
|
2987
2983
|
},
|
|
@@ -97,7 +97,6 @@ function seedTrustedContact(policy: "allow" | "escalate" = "allow"): void {
|
|
|
97
97
|
{
|
|
98
98
|
type: "telegram",
|
|
99
99
|
address: "telegram-user-1",
|
|
100
|
-
externalUserId: "telegram-user-1",
|
|
101
100
|
status: "active",
|
|
102
101
|
policy,
|
|
103
102
|
},
|
|
@@ -244,7 +243,6 @@ describe("channel inbound disk pressure gate", () => {
|
|
|
244
243
|
{
|
|
245
244
|
type: "slack",
|
|
246
245
|
address: "slack-user-1",
|
|
247
|
-
externalUserId: "slack-user-1",
|
|
248
246
|
status: "active",
|
|
249
247
|
policy: "allow",
|
|
250
248
|
},
|
|
@@ -75,7 +75,6 @@ describe("upsertContact user_file selection", () => {
|
|
|
75
75
|
{
|
|
76
76
|
type: "vellum",
|
|
77
77
|
address: "vellum-principal-abc",
|
|
78
|
-
externalUserId: "vellum-principal-abc",
|
|
79
78
|
},
|
|
80
79
|
],
|
|
81
80
|
});
|
|
@@ -91,7 +90,6 @@ describe("upsertContact user_file selection", () => {
|
|
|
91
90
|
{
|
|
92
91
|
type: "slack",
|
|
93
92
|
address: "u123456",
|
|
94
|
-
externalUserId: "U123456",
|
|
95
93
|
externalChatId: "D987654",
|
|
96
94
|
},
|
|
97
95
|
],
|
|
@@ -109,7 +107,6 @@ describe("upsertContact user_file selection", () => {
|
|
|
109
107
|
{
|
|
110
108
|
type: "slack",
|
|
111
109
|
address: "ualice",
|
|
112
|
-
externalUserId: "UALICE",
|
|
113
110
|
externalChatId: "DALICE",
|
|
114
111
|
},
|
|
115
112
|
],
|
|
@@ -125,7 +122,6 @@ describe("upsertContact user_file selection", () => {
|
|
|
125
122
|
{
|
|
126
123
|
type: "slack",
|
|
127
124
|
address: "ubob1",
|
|
128
|
-
externalUserId: "UBOB1",
|
|
129
125
|
externalChatId: "DBOB1",
|
|
130
126
|
},
|
|
131
127
|
],
|
|
@@ -137,7 +133,6 @@ describe("upsertContact user_file selection", () => {
|
|
|
137
133
|
{
|
|
138
134
|
type: "slack",
|
|
139
135
|
address: "ubob2",
|
|
140
|
-
externalUserId: "UBOB2",
|
|
141
136
|
externalChatId: "DBOB2",
|
|
142
137
|
},
|
|
143
138
|
],
|
|
@@ -164,7 +159,6 @@ describe("upsertContact user_file selection", () => {
|
|
|
164
159
|
{
|
|
165
160
|
type: "phone",
|
|
166
161
|
address: "+15550000",
|
|
167
|
-
externalUserId: "+15550000",
|
|
168
162
|
externalChatId: "+15550000",
|
|
169
163
|
},
|
|
170
164
|
],
|
|
@@ -240,6 +240,35 @@ describe("contact_search tool", () => {
|
|
|
240
240
|
});
|
|
241
241
|
});
|
|
242
242
|
|
|
243
|
+
// ── search_contacts route (HTTP/IPC compat shim) ─────────────────────
|
|
244
|
+
|
|
245
|
+
describe("search_contacts route", () => {
|
|
246
|
+
beforeEach(clearContacts);
|
|
247
|
+
|
|
248
|
+
test("includes externalUserId (= address) on channels for older clients", () => {
|
|
249
|
+
const seeded = upsertFixture({
|
|
250
|
+
display_name: "Dana",
|
|
251
|
+
channels: [{ type: "slack", address: "U12345ABC" }],
|
|
252
|
+
});
|
|
253
|
+
const seededAddress = seeded.channels[0]!.address;
|
|
254
|
+
|
|
255
|
+
const searchRoute = ROUTES.find(
|
|
256
|
+
(r) => r.operationId === "search_contacts",
|
|
257
|
+
)!;
|
|
258
|
+
const contacts = searchRoute.handler({
|
|
259
|
+
body: { channelAddress: seededAddress },
|
|
260
|
+
}) as unknown as Array<{
|
|
261
|
+
channels: Array<{ address: string; externalUserId?: string }>;
|
|
262
|
+
}>;
|
|
263
|
+
|
|
264
|
+
expect(contacts.length).toBeGreaterThanOrEqual(1);
|
|
265
|
+
const channel = contacts[0]!.channels[0]!;
|
|
266
|
+
// The route re-derives the compat field from address, so SDK/macOS
|
|
267
|
+
// clients that read externalUserId keep working.
|
|
268
|
+
expect(channel.externalUserId).toBe(channel.address);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
243
272
|
// ── contact_merge ───────────────────────────────────────────────────
|
|
244
273
|
|
|
245
274
|
describe("contact_merge tool", () => {
|
|
@@ -10,9 +10,11 @@ mock.module("../util/logger.js", () => ({
|
|
|
10
10
|
}),
|
|
11
11
|
}));
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// Analytics consent is granted so recordActivationEvent writes rows.
|
|
14
|
+
let shareAnalytics = true;
|
|
15
|
+
|
|
16
|
+
mock.module("../platform/consent-cache.js", () => ({
|
|
17
|
+
getCachedShareAnalytics: () => shareAnalytics,
|
|
16
18
|
}));
|
|
17
19
|
|
|
18
20
|
let broadcastedMessages: ServerMessage[] = [];
|
|
@@ -117,6 +119,7 @@ async function showTaggedChoice(
|
|
|
117
119
|
|
|
118
120
|
describe("activation moment emission from ui_show surface commits", () => {
|
|
119
121
|
beforeEach(() => {
|
|
122
|
+
shareAnalytics = true;
|
|
120
123
|
broadcastedMessages = [];
|
|
121
124
|
resetTables();
|
|
122
125
|
});
|
|
@@ -44,7 +44,7 @@ describe("healGuardianBindingDrift", () => {
|
|
|
44
44
|
const guardian = findGuardianForChannel("vellum");
|
|
45
45
|
expect(guardian).not.toBeNull();
|
|
46
46
|
expect(guardian!.contact.principalId).toBe("vellum-principal-old-uuid");
|
|
47
|
-
expect(guardian!.channel.
|
|
47
|
+
expect(guardian!.channel.address).toBe("vellum-principal-old-uuid");
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test("no-op when principals already match", () => {
|
|
@@ -7,23 +7,15 @@ mock.module("../util/logger.js", () => ({
|
|
|
7
7
|
}),
|
|
8
8
|
}));
|
|
9
9
|
|
|
10
|
-
// Toggle for the
|
|
10
|
+
// Toggle for the share_analytics opt-out the real store consults. The store
|
|
11
11
|
// module is intentionally NOT mocked here — it has its own DB-backed tests, and
|
|
12
12
|
// Bun's `mock.module` is process-global, so mocking it would leak into those
|
|
13
13
|
// tests when files share an invocation. Exercising the real store keeps every
|
|
14
14
|
// auth-fallback test order-independent.
|
|
15
|
-
let
|
|
15
|
+
let shareAnalytics = true;
|
|
16
16
|
|
|
17
|
-
mock.module("../
|
|
18
|
-
|
|
19
|
-
ui: {},
|
|
20
|
-
model: "test",
|
|
21
|
-
provider: "test",
|
|
22
|
-
memory: { enabled: false },
|
|
23
|
-
rateLimit: { maxRequestsPerMinute: 0 },
|
|
24
|
-
secretDetection: { enabled: false },
|
|
25
|
-
collectUsageData,
|
|
26
|
-
}),
|
|
17
|
+
mock.module("../platform/consent-cache.js", () => ({
|
|
18
|
+
getCachedShareAnalytics: () => shareAnalytics,
|
|
27
19
|
}));
|
|
28
20
|
|
|
29
21
|
import { queryUnreportedAuthFallbackEvents } from "../memory/auth-fallback-events-store.js";
|
|
@@ -61,7 +53,7 @@ const VALID_BODY = {
|
|
|
61
53
|
|
|
62
54
|
describe("internal-telemetry-routes: auth-fallback", () => {
|
|
63
55
|
beforeEach(() => {
|
|
64
|
-
|
|
56
|
+
shareAnalytics = true;
|
|
65
57
|
getDb().delete(authFallbackEvents).run();
|
|
66
58
|
});
|
|
67
59
|
|
|
@@ -90,7 +82,7 @@ describe("internal-telemetry-routes: auth-fallback", () => {
|
|
|
90
82
|
});
|
|
91
83
|
|
|
92
84
|
test("returns skipped and persists nothing under the opt-out", () => {
|
|
93
|
-
|
|
85
|
+
shareAnalytics = false;
|
|
94
86
|
expect(call(VALID_BODY)).toEqual({ skipped: true });
|
|
95
87
|
expect(queryUnreportedAuthFallbackEvents(0, undefined, 100).length).toBe(0);
|
|
96
88
|
});
|
|
@@ -292,7 +292,6 @@ describe("invite-redemption-service", () => {
|
|
|
292
292
|
{
|
|
293
293
|
type: "telegram",
|
|
294
294
|
address: "guardian-tg-id",
|
|
295
|
-
externalUserId: "guardian-tg-id",
|
|
296
295
|
status: "revoked",
|
|
297
296
|
},
|
|
298
297
|
],
|
|
@@ -346,7 +345,6 @@ describe("invite-redemption-service", () => {
|
|
|
346
345
|
{
|
|
347
346
|
type: "telegram",
|
|
348
347
|
address: "guardian-own-id",
|
|
349
|
-
externalUserId: "guardian-own-id",
|
|
350
348
|
status: "revoked",
|
|
351
349
|
},
|
|
352
350
|
],
|
|
@@ -381,7 +379,6 @@ describe("invite-redemption-service", () => {
|
|
|
381
379
|
{
|
|
382
380
|
type: "telegram",
|
|
383
381
|
address: "guardian-code-id",
|
|
384
|
-
externalUserId: "guardian-code-id",
|
|
385
382
|
status: "revoked",
|
|
386
383
|
},
|
|
387
384
|
],
|
|
@@ -75,28 +75,28 @@ describe("checkServerHealth (via internal_mcp_list route)", () => {
|
|
|
75
75
|
mockLastError = null;
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
-
test("returns
|
|
78
|
+
test("returns connected when server connects successfully", async () => {
|
|
79
79
|
mockConnect.mockResolvedValue(undefined);
|
|
80
80
|
mockDisconnect.mockResolvedValue(undefined);
|
|
81
81
|
|
|
82
82
|
const result = (await listHandler({})) as {
|
|
83
83
|
servers: { status: string }[];
|
|
84
84
|
};
|
|
85
|
-
expect(result.servers[0].status).
|
|
85
|
+
expect(result.servers[0].status).toBe("connected");
|
|
86
86
|
expect(mockDisconnect).toHaveBeenCalled();
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
test("returns
|
|
89
|
+
test("returns needs-auth when isConnected is false and no lastError", async () => {
|
|
90
90
|
mockConnect.mockResolvedValue(undefined);
|
|
91
91
|
mockIsConnected = false;
|
|
92
92
|
|
|
93
93
|
const result = (await listHandler({})) as {
|
|
94
94
|
servers: { status: string }[];
|
|
95
95
|
};
|
|
96
|
-
expect(result.servers[0].status).
|
|
96
|
+
expect(result.servers[0].status).toBe("needs-auth");
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
test("returns
|
|
99
|
+
test("returns error when connect fails with lastError", async () => {
|
|
100
100
|
mockConnect.mockResolvedValue(undefined);
|
|
101
101
|
mockIsConnected = false;
|
|
102
102
|
mockLastError = new Error("Connection refused");
|
|
@@ -105,7 +105,6 @@ describe("checkServerHealth (via internal_mcp_list route)", () => {
|
|
|
105
105
|
const result = (await listHandler({})) as {
|
|
106
106
|
servers: { status: string }[];
|
|
107
107
|
};
|
|
108
|
-
expect(result.servers[0].status).
|
|
109
|
-
expect(result.servers[0].status).toContain("Connection refused");
|
|
108
|
+
expect(result.servers[0].status).toBe("error");
|
|
110
109
|
});
|
|
111
110
|
});
|