@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
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
|
|
4
|
+
import type { GuardianDelivery } from "@vellumai/gateway-client";
|
|
5
|
+
|
|
6
|
+
import { findContactByAddress } from "../contacts/contact-store.js";
|
|
4
7
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from "../contacts/
|
|
8
|
+
anyGuardian,
|
|
9
|
+
guardianForChannel,
|
|
10
|
+
peekCachedGuardianDelivery,
|
|
11
|
+
} from "../contacts/guardian-delivery-reader.js";
|
|
9
12
|
import type { ChannelCapabilities } from "../daemon/conversation-runtime-assembly.js";
|
|
10
13
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
11
14
|
import { getLogger } from "../util/logger.js";
|
|
@@ -87,8 +90,38 @@ function resolveGuardianUserFile(trustContext: TrustContext): string | null {
|
|
|
87
90
|
return guardianContact.userFile ?? "guardian.md";
|
|
88
91
|
}
|
|
89
92
|
}
|
|
90
|
-
const guardian =
|
|
91
|
-
return guardian ? (guardian
|
|
93
|
+
const guardian = peekGuardianForChannel(trustContext.sourceChannel);
|
|
94
|
+
return guardian ? (guardianDeliveryUserFile(guardian) ?? "guardian.md") : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the local INFO `userFile` for a gateway guardian delivery. The
|
|
99
|
+
* gateway carries identity (channel + address) but not local INFO, so we join
|
|
100
|
+
* the local contact by the guardian's channel address. Returns `undefined` when
|
|
101
|
+
* no local contact matches.
|
|
102
|
+
*/
|
|
103
|
+
function guardianDeliveryUserFile(
|
|
104
|
+
guardian: GuardianDelivery,
|
|
105
|
+
): string | undefined {
|
|
106
|
+
const contact = findContactByAddress(
|
|
107
|
+
guardian.channelType,
|
|
108
|
+
guardian.address,
|
|
109
|
+
);
|
|
110
|
+
return contact?.userFile ?? undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Active guardian for a channel from the IO-free delivery cache. */
|
|
114
|
+
function peekGuardianForChannel(
|
|
115
|
+
channelType: string,
|
|
116
|
+
): GuardianDelivery | undefined {
|
|
117
|
+
const cached = peekCachedGuardianDelivery({ channelTypes: [channelType] });
|
|
118
|
+
return cached ? guardianForChannel(cached, channelType) : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** First guardian across all channels from the IO-free delivery cache. */
|
|
122
|
+
function peekAnyGuardian(): GuardianDelivery | undefined {
|
|
123
|
+
const cached = peekCachedGuardianDelivery();
|
|
124
|
+
return cached ? anyGuardian(cached) : undefined;
|
|
92
125
|
}
|
|
93
126
|
|
|
94
127
|
/**
|
|
@@ -102,12 +135,11 @@ function resolveUserFilename(
|
|
|
102
135
|
|
|
103
136
|
try {
|
|
104
137
|
if (trustContext === undefined) {
|
|
105
|
-
// Desktop / native
|
|
106
|
-
// preferring the vellum-channel guardian
|
|
107
|
-
const
|
|
108
|
-
const guardian = vellumGuardian ?? listGuardianChannels();
|
|
138
|
+
// Desktop / native — resolve via the gateway guardian delivery cache,
|
|
139
|
+
// preferring the vellum-channel guardian, then any guardian.
|
|
140
|
+
const guardian = peekGuardianForChannel("vellum") ?? peekAnyGuardian();
|
|
109
141
|
if (guardian) {
|
|
110
|
-
filename = guardian
|
|
142
|
+
filename = guardianDeliveryUserFile(guardian) ?? "guardian.md";
|
|
111
143
|
}
|
|
112
144
|
} else if (trustContext.requesterExternalUserId) {
|
|
113
145
|
// Channel-routed request — look up contact by channel identity
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "./connection-resolution.js";
|
|
35
35
|
import { listConnections } from "./inference/connections.js";
|
|
36
36
|
import type { ProvidersConfig } from "./registry.js";
|
|
37
|
+
import { shouldUseNativeWebSearch } from "./registry.js";
|
|
37
38
|
import type {
|
|
38
39
|
Message,
|
|
39
40
|
Provider,
|
|
@@ -130,6 +131,46 @@ export class CallSiteRoutingProvider implements Provider {
|
|
|
130
131
|
: doSend();
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Native web-search capability of the provider/model THIS call routes to.
|
|
136
|
+
*
|
|
137
|
+
* `selectProvider` picks the transport from the routed connection, but each
|
|
138
|
+
* leaf provider's static `supportsNativeWebSearch` was fixed to the DEFAULT
|
|
139
|
+
* (provider, model) at boot. Resolving the call-site here — same
|
|
140
|
+
* `resolveCallSiteConfig` inputs `selectProvider` uses — and recomputing
|
|
141
|
+
* `shouldUseNativeWebSearch(resolved.provider, resolved.model)` yields the
|
|
142
|
+
* capability of the routed target instead of the construction-time default.
|
|
143
|
+
*
|
|
144
|
+
* Falls back to the default provider's static flag when no `callSite` is set
|
|
145
|
+
* (the legacy short-circuit `selectProvider` also takes).
|
|
146
|
+
*
|
|
147
|
+
* Known limitation: this reports the *resolved* target's capability and does
|
|
148
|
+
* not replay `selectProvider`'s async soft-credential fallback. If the routed
|
|
149
|
+
* connection has a transient credential failure at send time, `selectProvider`
|
|
150
|
+
* falls back to the default provider while this probe still reports the routed
|
|
151
|
+
* target — so a non-native default + native routed target with a credential
|
|
152
|
+
* blip can attach `web_search` to the fallback non-native provider. The probe
|
|
153
|
+
* stays sync (the loop assembles tools synchronously) and the worst case is
|
|
154
|
+
* bounded: the advisor consult that hits it degrades benignly (the unhandled
|
|
155
|
+
* tool surfaces as a caught failure → "(advisor unavailable)"), not a crash.
|
|
156
|
+
*/
|
|
157
|
+
supportsNativeWebSearchFor(options?: SendMessageOptions): boolean {
|
|
158
|
+
const callSite = options?.config?.callSite;
|
|
159
|
+
if (!callSite) {
|
|
160
|
+
return this.defaultProvider.supportsNativeWebSearch === true;
|
|
161
|
+
}
|
|
162
|
+
const resolved = resolveCallSiteConfig(callSite, getConfig().llm, {
|
|
163
|
+
overrideProfile: options?.config?.overrideProfile,
|
|
164
|
+
forceOverrideProfile: options?.config?.forceOverrideProfile,
|
|
165
|
+
selectionSeed: options?.config?.selectionSeed,
|
|
166
|
+
});
|
|
167
|
+
return shouldUseNativeWebSearch(
|
|
168
|
+
getConfig(),
|
|
169
|
+
resolved.provider,
|
|
170
|
+
resolved.model,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
133
174
|
/**
|
|
134
175
|
* Pick the provider to route this call through.
|
|
135
176
|
*
|
|
@@ -58,6 +58,12 @@ export class CallSiteConfiguredProvider implements Provider {
|
|
|
58
58
|
this.supportsNativeWebSearch = inner.supportsNativeWebSearch;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
supportsNativeWebSearchFor(options?: SendMessageOptions): boolean {
|
|
62
|
+
return this.inner.supportsNativeWebSearchFor
|
|
63
|
+
? this.inner.supportsNativeWebSearchFor(options)
|
|
64
|
+
: this.inner.supportsNativeWebSearch === true;
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
sendMessage(
|
|
62
68
|
messages: Message[],
|
|
63
69
|
options?: SendMessageOptions,
|
|
@@ -27,6 +27,12 @@ export class RateLimitProvider implements Provider {
|
|
|
27
27
|
return this.inner.supportsNativeWebSearch;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
supportsNativeWebSearchFor(options?: SendMessageOptions): boolean {
|
|
31
|
+
return this.inner.supportsNativeWebSearchFor
|
|
32
|
+
? this.inner.supportsNativeWebSearchFor(options)
|
|
33
|
+
: this.inner.supportsNativeWebSearch === true;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
private requestTimestamps: number[];
|
|
31
37
|
|
|
32
38
|
// Forward the optional token-counting endpoint so the capability survives
|
package/src/providers/retry.ts
CHANGED
|
@@ -622,6 +622,12 @@ export class RetryProvider implements Provider {
|
|
|
622
622
|
return this.inner.supportsNativeWebSearch;
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
+
supportsNativeWebSearchFor(options?: SendMessageOptions): boolean {
|
|
626
|
+
return this.inner.supportsNativeWebSearchFor
|
|
627
|
+
? this.inner.supportsNativeWebSearchFor(options)
|
|
628
|
+
: this.inner.supportsNativeWebSearch === true;
|
|
629
|
+
}
|
|
630
|
+
|
|
625
631
|
// Forward the optional token-counting endpoint so the capability survives
|
|
626
632
|
// the wrapper chain (callers gate on its presence). Bound straight to the
|
|
627
633
|
// inner provider — count_tokens is a cheap separate endpoint and its caller
|
package/src/providers/types.ts
CHANGED
|
@@ -274,6 +274,19 @@ export interface Provider {
|
|
|
274
274
|
* unexecutable client tool call. Absent/false on providers without it.
|
|
275
275
|
*/
|
|
276
276
|
supportsNativeWebSearch?: boolean;
|
|
277
|
+
/**
|
|
278
|
+
* Per-call native web-search capability for the provider/model this specific
|
|
279
|
+
* request will route to. Unlike the static {@link supportsNativeWebSearch}
|
|
280
|
+
* flag — fixed to the DEFAULT provider/model at construction — this consults
|
|
281
|
+
* the resolved call-site (`options.config.callSite` + `overrideProfile`) so a
|
|
282
|
+
* routing wrapper reports the ROUTED target's capability. Callers that gate a
|
|
283
|
+
* `web_search` server tool on a possibly-routed call (e.g. the advisor
|
|
284
|
+
* consult, whose `advisorProfile` may point at a different provider/model)
|
|
285
|
+
* must use this rather than the construction-time snapshot. Optional: wrappers
|
|
286
|
+
* forward it to their inner provider; leaf providers may omit it, in which
|
|
287
|
+
* case callers fall back to {@link supportsNativeWebSearch}.
|
|
288
|
+
*/
|
|
289
|
+
supportsNativeWebSearchFor?(options?: SendMessageOptions): boolean;
|
|
277
290
|
sendMessage(
|
|
278
291
|
messages: Message[],
|
|
279
292
|
options?: SendMessageOptions,
|
|
@@ -35,6 +35,12 @@ export class UsageTrackingProvider implements Provider {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
supportsNativeWebSearchFor(options?: SendMessageOptions): boolean {
|
|
39
|
+
return this.inner.supportsNativeWebSearchFor
|
|
40
|
+
? this.inner.supportsNativeWebSearchFor(options)
|
|
41
|
+
: this.inner.supportsNativeWebSearch === true;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
async sendMessage(
|
|
39
45
|
messages: Message[],
|
|
40
46
|
options?: SendMessageOptions,
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
* `reResolveTrustOnResetDrift`.
|
|
4
4
|
*
|
|
5
5
|
* The real helper runs against mocked leaf deps: the gateway guardian read
|
|
6
|
-
* (`getGuardianDelivery`/`guardianForChannel`)
|
|
7
|
-
*
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* (`getGuardianDelivery`/`guardianForChannel`) supplies the authoritative
|
|
7
|
+
* principal, the local-mirror heal target is resolved via
|
|
8
|
+
* `findContactByAddress` (keyed on the gateway guardian's channel address) and
|
|
9
|
+
* written via `updateContactPrincipalAndChannel` (the real
|
|
10
|
+
* `healGuardianBindingDrift` drives this), and the local trust resolver
|
|
11
|
+
* (`resolveTrustContext`) closes the loop. Heal invocations are observed via
|
|
12
|
+
* the contact-store write mock.
|
|
11
13
|
*/
|
|
12
14
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
15
|
|
|
@@ -26,16 +28,17 @@ mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
|
26
28
|
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
27
29
|
}));
|
|
28
30
|
|
|
29
|
-
// Local mirror the real heal
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
// Local mirror the real heal repairs. `findContactByAddress` returns the local
|
|
32
|
+
// contact (with its vellum channel) the heal writes to;
|
|
33
|
+
// `updateContactPrincipalAndChannel` records heal writes.
|
|
34
|
+
let mockLocalContact: {
|
|
35
|
+
id: string;
|
|
36
|
+
channels: Array<{ id: string; type: string }>;
|
|
34
37
|
} | null = null;
|
|
35
38
|
const healWrites: Array<{ principalId: string }> = [];
|
|
36
39
|
|
|
37
40
|
mock.module("../../contacts/contact-store.js", () => ({
|
|
38
|
-
|
|
41
|
+
findContactByAddress: () => mockLocalContact,
|
|
39
42
|
updateContactPrincipalAndChannel: (
|
|
40
43
|
_contactId: string,
|
|
41
44
|
_channelId: string,
|
|
@@ -74,25 +77,25 @@ function gatewayGuardian(principalId: string): Record<string, unknown> {
|
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
function localGuardian(
|
|
80
|
+
function localGuardian() {
|
|
78
81
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
id: "contact-1",
|
|
83
|
+
channels: [{ id: "channel-1", type: "vellum" }],
|
|
81
84
|
};
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
describe("reResolveTrustOnResetDrift", () => {
|
|
85
88
|
beforeEach(() => {
|
|
86
89
|
mockGuardianList = [];
|
|
87
|
-
|
|
90
|
+
mockLocalContact = null;
|
|
88
91
|
healWrites.length = 0;
|
|
89
92
|
});
|
|
90
93
|
|
|
91
94
|
test("reset drift: heals and returns the re-resolved guardian ctx", async () => {
|
|
92
|
-
//
|
|
93
|
-
//
|
|
95
|
+
// Gateway principal diverges from the incoming JWT; heal repairs the local
|
|
96
|
+
// mirror toward the incoming actor.
|
|
94
97
|
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
95
|
-
|
|
98
|
+
mockLocalContact = localGuardian();
|
|
96
99
|
|
|
97
100
|
const ctx = await reResolveTrustOnResetDrift(
|
|
98
101
|
"vellum-principal-old",
|
|
@@ -103,11 +106,11 @@ describe("reResolveTrustOnResetDrift", () => {
|
|
|
103
106
|
expect(healWrites).toEqual([{ principalId: "vellum-principal-old" }]);
|
|
104
107
|
});
|
|
105
108
|
|
|
106
|
-
test("
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
+
test("no local mirror to repair still returns the guardian ctx", async () => {
|
|
110
|
+
// The gate passes and the re-resolve yields guardian even when there is no
|
|
111
|
+
// local mirror row for heal to write.
|
|
109
112
|
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
110
|
-
|
|
113
|
+
mockLocalContact = null;
|
|
111
114
|
|
|
112
115
|
const ctx = await reResolveTrustOnResetDrift(
|
|
113
116
|
"vellum-principal-old",
|
|
@@ -120,7 +123,7 @@ describe("reResolveTrustOnResetDrift", () => {
|
|
|
120
123
|
|
|
121
124
|
test("gateway unreachable (null): returns null, heal not called", async () => {
|
|
122
125
|
mockGuardianList = null;
|
|
123
|
-
|
|
126
|
+
mockLocalContact = localGuardian();
|
|
124
127
|
|
|
125
128
|
const ctx = await reResolveTrustOnResetDrift(
|
|
126
129
|
"vellum-principal-old",
|
|
@@ -133,7 +136,7 @@ describe("reResolveTrustOnResetDrift", () => {
|
|
|
133
136
|
|
|
134
137
|
test("empty/revoked gateway (no active guardian): returns null, heal not called", async () => {
|
|
135
138
|
mockGuardianList = [];
|
|
136
|
-
|
|
139
|
+
mockLocalContact = localGuardian();
|
|
137
140
|
|
|
138
141
|
const ctx = await reResolveTrustOnResetDrift(
|
|
139
142
|
"vellum-principal-old",
|
|
@@ -146,7 +149,7 @@ describe("reResolveTrustOnResetDrift", () => {
|
|
|
146
149
|
|
|
147
150
|
test("gateway guardian is a real (non vellum-principal-*) id: returns null", async () => {
|
|
148
151
|
mockGuardianList = [gatewayGuardian("user@example.com")];
|
|
149
|
-
|
|
152
|
+
mockLocalContact = localGuardian();
|
|
150
153
|
|
|
151
154
|
const ctx = await reResolveTrustOnResetDrift(
|
|
152
155
|
"vellum-principal-old",
|
|
@@ -159,7 +162,7 @@ describe("reResolveTrustOnResetDrift", () => {
|
|
|
159
162
|
|
|
160
163
|
test("incoming principal is not vellum-principal-*: returns null", async () => {
|
|
161
164
|
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
162
|
-
|
|
165
|
+
mockLocalContact = localGuardian();
|
|
163
166
|
|
|
164
167
|
const ctx = await reResolveTrustOnResetDrift("user@example.com", "vellum");
|
|
165
168
|
|
|
@@ -169,7 +172,7 @@ describe("reResolveTrustOnResetDrift", () => {
|
|
|
169
172
|
|
|
170
173
|
test("threads sourceChannel into the returned ctx", async () => {
|
|
171
174
|
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
172
|
-
|
|
175
|
+
mockLocalContact = localGuardian();
|
|
173
176
|
|
|
174
177
|
const ctx = await reResolveTrustOnResetDrift(
|
|
175
178
|
"vellum-principal-old",
|
|
@@ -6,20 +6,28 @@ import type { ChannelId } from "../../channels/types.js";
|
|
|
6
6
|
// unreachable), [] = authoritatively no guardian, one active entry = bound.
|
|
7
7
|
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
8
8
|
|
|
9
|
+
// Both the async path (resolveLocalPrincipalTrustContext) and the sync
|
|
10
|
+
// resolveActorTrust read the same gateway delivery list — async via
|
|
11
|
+
// getGuardianDelivery, sync via the peek snapshot.
|
|
9
12
|
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
10
13
|
getGuardianDelivery: (_input?: { channelTypes?: string[] }) =>
|
|
11
14
|
Promise.resolve(mockGuardianList),
|
|
15
|
+
peekCachedGuardianDelivery: (input?: { channelTypes?: string[] }) => {
|
|
16
|
+
if (mockGuardianList == null) return undefined;
|
|
17
|
+
if (!input?.channelTypes) return mockGuardianList;
|
|
18
|
+
return mockGuardianList.filter((g) =>
|
|
19
|
+
input.channelTypes!.includes(g.channelType as string),
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
guardianForChannel: (
|
|
23
|
+
list: Array<Record<string, unknown>>,
|
|
24
|
+
channelType: string,
|
|
25
|
+
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
12
26
|
}));
|
|
13
27
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
let mockGuardianRecord: {
|
|
17
|
-
contact: Record<string, unknown>;
|
|
18
|
-
channel: Record<string, unknown>;
|
|
19
|
-
} | null = null;
|
|
20
|
-
|
|
28
|
+
// Member ACL rides on memberRecord, sourced from the member-verdict cache; this
|
|
29
|
+
// suite only exercises the gateway-guardian path, so no member resolves.
|
|
21
30
|
mock.module("../../contacts/contact-store.js", () => ({
|
|
22
|
-
findGuardianForChannel: (_channelType: string) => mockGuardianRecord,
|
|
23
31
|
findContactByAddress: (_channelType: string, _address: string) => null,
|
|
24
32
|
}));
|
|
25
33
|
|
|
@@ -39,7 +47,6 @@ const GUARDIAN_CHAT_ID = "guardian-chat";
|
|
|
39
47
|
describe("resolveLocalPrincipalTrustContext", () => {
|
|
40
48
|
beforeEach(() => {
|
|
41
49
|
mockGuardianList = [];
|
|
42
|
-
mockGuardianRecord = null;
|
|
43
50
|
});
|
|
44
51
|
|
|
45
52
|
test("principal matching the gateway guardian → guardian ctx", async () => {
|
|
@@ -83,15 +90,6 @@ describe("resolveLocalPrincipalTrustContext", () => {
|
|
|
83
90
|
status: "active",
|
|
84
91
|
},
|
|
85
92
|
];
|
|
86
|
-
mockGuardianRecord = {
|
|
87
|
-
contact: { id: "contact-1", principalId: GUARDIAN_ADDRESS },
|
|
88
|
-
channel: {
|
|
89
|
-
type: "vellum",
|
|
90
|
-
address: GUARDIAN_ADDRESS,
|
|
91
|
-
externalChatId: GUARDIAN_CHAT_ID,
|
|
92
|
-
status: "active",
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
93
|
|
|
96
94
|
const expected = toTrustContext(
|
|
97
95
|
resolveActorTrust({
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the in-memory member-verdict cache: set→get round-trip, TTL
|
|
3
|
+
* expiry, memberless verdicts not cached, and max-size eviction.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import type { TrustVerdict } from "@vellumai/gateway-client";
|
|
9
|
+
|
|
10
|
+
mock.module("../../util/logger.js", () => ({
|
|
11
|
+
getLogger: () =>
|
|
12
|
+
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
__resetMemberVerdictCacheForTest,
|
|
17
|
+
getCachedMemberAcl,
|
|
18
|
+
setMemberVerdict,
|
|
19
|
+
} from "../member-verdict-cache.js";
|
|
20
|
+
|
|
21
|
+
const PHONE = "+15559871234";
|
|
22
|
+
|
|
23
|
+
function memberVerdict(
|
|
24
|
+
overrides: Partial<TrustVerdict> = {},
|
|
25
|
+
): TrustVerdict {
|
|
26
|
+
return {
|
|
27
|
+
trustClass: "trusted_contact",
|
|
28
|
+
canonicalSenderId: PHONE,
|
|
29
|
+
contactId: "contact-1",
|
|
30
|
+
channelId: "ch-1",
|
|
31
|
+
status: "active",
|
|
32
|
+
policy: "allow",
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const realNow = Date.now;
|
|
38
|
+
|
|
39
|
+
describe("member-verdict-cache", () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
__resetMemberVerdictCacheForTest();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
Date.now = realNow;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("set then get returns the derived ACL view", () => {
|
|
49
|
+
setMemberVerdict("phone", PHONE, memberVerdict());
|
|
50
|
+
expect(getCachedMemberAcl("phone", PHONE)).toEqual({
|
|
51
|
+
status: "active",
|
|
52
|
+
policy: "allow",
|
|
53
|
+
role: "contact",
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("guardian trustClass derives the guardian role", () => {
|
|
58
|
+
setMemberVerdict("phone", PHONE, memberVerdict({ trustClass: "guardian" }));
|
|
59
|
+
expect(getCachedMemberAcl("phone", PHONE)?.role).toBe("guardian");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("read canonicalizes the actor id like the write", () => {
|
|
63
|
+
// Phone numbers normalize to E.164; a raw-format write is readable by the
|
|
64
|
+
// same raw-format read.
|
|
65
|
+
setMemberVerdict("phone", "(555) 987-1234", memberVerdict());
|
|
66
|
+
expect(getCachedMemberAcl("phone", "(555) 987-1234")).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("empty actor id is a no-op on set and get", () => {
|
|
70
|
+
setMemberVerdict("phone", undefined, memberVerdict());
|
|
71
|
+
expect(getCachedMemberAcl("phone", undefined)).toBeUndefined();
|
|
72
|
+
expect(getCachedMemberAcl("phone", " ")).toBeUndefined();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("memberless verdict is not cached", () => {
|
|
76
|
+
setMemberVerdict(
|
|
77
|
+
"phone",
|
|
78
|
+
PHONE,
|
|
79
|
+
memberVerdict({ contactId: undefined, channelId: undefined }),
|
|
80
|
+
);
|
|
81
|
+
expect(getCachedMemberAcl("phone", PHONE)).toBeUndefined();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("memberless verdict clears a stale active entry for the actor", () => {
|
|
85
|
+
setMemberVerdict("phone", PHONE, memberVerdict());
|
|
86
|
+
expect(getCachedMemberAcl("phone", PHONE)).toBeDefined();
|
|
87
|
+
// A later memberless verdict (deleted contact / stranger) must invalidate
|
|
88
|
+
// the stale active ACL, not leave it readable for the rest of the TTL.
|
|
89
|
+
setMemberVerdict(
|
|
90
|
+
"phone",
|
|
91
|
+
PHONE,
|
|
92
|
+
memberVerdict({ contactId: undefined, channelId: undefined }),
|
|
93
|
+
);
|
|
94
|
+
expect(getCachedMemberAcl("phone", PHONE)).toBeUndefined();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("expired entry returns undefined", () => {
|
|
98
|
+
const t0 = realNow();
|
|
99
|
+
Date.now = () => t0;
|
|
100
|
+
setMemberVerdict("phone", PHONE, memberVerdict());
|
|
101
|
+
Date.now = () => t0 + 300_001;
|
|
102
|
+
expect(getCachedMemberAcl("phone", PHONE)).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("evicts the oldest-expiring entry past the bound", () => {
|
|
106
|
+
const t0 = realNow();
|
|
107
|
+
// telegram IDs pass through unchanged, so each key is distinct. Fill past
|
|
108
|
+
// capacity with monotonically increasing expiry stamps.
|
|
109
|
+
for (let i = 0; i < 2001; i++) {
|
|
110
|
+
Date.now = () => t0 + i;
|
|
111
|
+
setMemberVerdict("telegram", `tg-${i}`, memberVerdict());
|
|
112
|
+
}
|
|
113
|
+
Date.now = () => t0 + 2001;
|
|
114
|
+
// The oldest-expiring entry is evicted on the over-capacity insert; the
|
|
115
|
+
// latest survives.
|
|
116
|
+
expect(getCachedMemberAcl("telegram", "tg-0")).toBeUndefined();
|
|
117
|
+
expect(getCachedMemberAcl("telegram", "tg-2000")).toBeDefined();
|
|
118
|
+
});
|
|
119
|
+
});
|