@vellumai/assistant 0.11.1 → 0.11.2-staging.2
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/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* entirely and use the producer-supplied copy as-is.
|
|
2
|
+
* Tests for the pass-through paths in the notification decision engine. When a
|
|
3
|
+
* producer hands us a verbatim message via contextPayload.requestedMessage, the
|
|
4
|
+
* engine must skip the LLM call entirely and use the copy as-is.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
import { describe, expect, mock, test } from "bun:test";
|
|
7
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
8
|
|
|
10
9
|
// ── Mocks (must precede imports from mocked modules) ──────────────────
|
|
11
10
|
|
|
12
11
|
mock.module("../../channels/config.js", () => ({
|
|
13
|
-
getDeliverableChannels: () => ["vellum", "telegram"],
|
|
12
|
+
getDeliverableChannels: () => ["vellum", "telegram", "platform"],
|
|
14
13
|
}));
|
|
15
14
|
|
|
15
|
+
let persistedDecisions: Array<Record<string, unknown>> = [];
|
|
16
|
+
|
|
16
17
|
mock.module("../decisions-store.js", () => ({
|
|
17
|
-
createDecision: () => {
|
|
18
|
+
createDecision: (row: Record<string, unknown>) => {
|
|
19
|
+
persistedDecisions.push(row);
|
|
20
|
+
},
|
|
18
21
|
}));
|
|
19
22
|
|
|
20
23
|
mock.module("../preference-summary.js", () => ({
|
|
@@ -49,28 +52,34 @@ mock.module("../../contacts/contact-store.js", () => ({
|
|
|
49
52
|
contactInfoFixture[contactId] ?? null,
|
|
50
53
|
}));
|
|
51
54
|
|
|
52
|
-
// Provider mock. By default `sendMessage` throws so the
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
+
// Provider mock. By default `sendMessage` throws so the pass-through paths
|
|
56
|
+
// (which must skip the LLM) fail loudly if they reach the provider. LLM-path
|
|
57
|
+
// tests override `providerSendMessage` to capture inputs.
|
|
58
|
+
type ProviderSendOptions = { systemPrompt?: string; tools?: unknown[] };
|
|
59
|
+
|
|
55
60
|
let providerSendMessage: (
|
|
56
61
|
messages: unknown[],
|
|
57
|
-
opts:
|
|
62
|
+
opts: ProviderSendOptions,
|
|
58
63
|
) => Promise<unknown> = () => {
|
|
59
64
|
throw new Error(
|
|
60
|
-
"provider.sendMessage should NOT be invoked for
|
|
65
|
+
"provider.sendMessage should NOT be invoked for pass-through decisions",
|
|
61
66
|
);
|
|
62
67
|
};
|
|
63
68
|
|
|
69
|
+
// Tool block the engine reads back from a provider response. Null drives the
|
|
70
|
+
// deterministic fallback, so LLM-path tests set it before calling in.
|
|
71
|
+
let toolUseBlock: { input: Record<string, unknown> } | null = null;
|
|
72
|
+
|
|
64
73
|
mock.module("../../providers/provider-send-message.js", () => ({
|
|
65
74
|
getConfiguredProvider: async () => ({
|
|
66
|
-
sendMessage: (messages: unknown[], opts:
|
|
75
|
+
sendMessage: (messages: unknown[], opts: ProviderSendOptions) =>
|
|
67
76
|
providerSendMessage(messages, opts),
|
|
68
77
|
}),
|
|
69
78
|
createTimeout: () => ({
|
|
70
79
|
signal: new AbortController().signal,
|
|
71
80
|
cleanup: () => {},
|
|
72
81
|
}),
|
|
73
|
-
extractToolUse: () =>
|
|
82
|
+
extractToolUse: () => toolUseBlock,
|
|
74
83
|
userMessage: (text: string) => ({ role: "user", content: text }),
|
|
75
84
|
}));
|
|
76
85
|
|
|
@@ -105,6 +114,47 @@ function makeAssistantToolSignal(
|
|
|
105
114
|
};
|
|
106
115
|
}
|
|
107
116
|
|
|
117
|
+
function makeAssistantReplySignal(
|
|
118
|
+
overrides?: Partial<NotificationSignal>,
|
|
119
|
+
): NotificationSignal {
|
|
120
|
+
return {
|
|
121
|
+
signalId: "sig-assistant-reply-test-1",
|
|
122
|
+
createdAt: Date.now(),
|
|
123
|
+
sourceChannel: "vellum",
|
|
124
|
+
sourceContextId: "conv-1",
|
|
125
|
+
sourceEventName: "chat.assistant_reply",
|
|
126
|
+
contextPayload: {
|
|
127
|
+
requestedMessage: "Here is your answer.",
|
|
128
|
+
requestedTitle: "Assistant",
|
|
129
|
+
},
|
|
130
|
+
attentionHints: {
|
|
131
|
+
requiresAction: false,
|
|
132
|
+
urgency: "medium",
|
|
133
|
+
isAsyncBackground: false,
|
|
134
|
+
visibleInSourceNow: false,
|
|
135
|
+
},
|
|
136
|
+
...overrides,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A signal with no verbatim copy, so the engine takes the LLM path. */
|
|
141
|
+
function makeLlmSignal(): NotificationSignal {
|
|
142
|
+
return {
|
|
143
|
+
signalId: "sig-llm-1",
|
|
144
|
+
createdAt: Date.now(),
|
|
145
|
+
sourceChannel: "scheduler",
|
|
146
|
+
sourceContextId: "schedule-1",
|
|
147
|
+
sourceEventName: "schedule.notify",
|
|
148
|
+
contextPayload: {},
|
|
149
|
+
attentionHints: {
|
|
150
|
+
requiresAction: false,
|
|
151
|
+
urgency: "low",
|
|
152
|
+
isAsyncBackground: false,
|
|
153
|
+
visibleInSourceNow: false,
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
108
158
|
// ── Tests ─────────────────────────────────────────────────────────────
|
|
109
159
|
|
|
110
160
|
describe("assistant_tool pass-through in notification decision engine", () => {
|
|
@@ -121,11 +171,25 @@ describe("assistant_tool pass-through in notification decision engine", () => {
|
|
|
121
171
|
expect(decision.renderedCopy.vellum?.title).toBe("Custom Title");
|
|
122
172
|
expect(decision.conversationActions?.vellum?.action).toBe("start_new");
|
|
123
173
|
expect(decision.reasoningSummary).toBe("assistant_tool pass-through");
|
|
174
|
+
expect(decision.verbatimCopy).toBe(true);
|
|
124
175
|
expect(decision.fallbackUsed).toBe(false);
|
|
125
176
|
expect(decision.confidence).toBe(1.0);
|
|
126
177
|
expect(decision.dedupeKey).toBe(signal.signalId);
|
|
127
178
|
});
|
|
128
179
|
|
|
180
|
+
// `notify --dedupe-key` reaches the signal, so the decision has to carry it
|
|
181
|
+
// through; falling back to the per-emit signal id would collapse nothing.
|
|
182
|
+
test("uses the producer-supplied dedupeKey", async () => {
|
|
183
|
+
const signal = makeAssistantToolSignal({
|
|
184
|
+
dedupeKey: "deploy-status:prod",
|
|
185
|
+
});
|
|
186
|
+
const decision = await evaluateSignal(signal, [
|
|
187
|
+
"vellum",
|
|
188
|
+
] as NotificationChannel[]);
|
|
189
|
+
|
|
190
|
+
expect(decision.dedupeKey).toBe("deploy-status:prod");
|
|
191
|
+
});
|
|
192
|
+
|
|
129
193
|
test("derives title from body when requestedTitle is not supplied", async () => {
|
|
130
194
|
const signal = makeAssistantToolSignal({
|
|
131
195
|
contextPayload: {
|
|
@@ -294,24 +358,157 @@ describe("assistant_tool pass-through in notification decision engine", () => {
|
|
|
294
358
|
});
|
|
295
359
|
});
|
|
296
360
|
|
|
297
|
-
describe("
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
361
|
+
describe("chat.assistant_reply pass-through in notification decision engine", () => {
|
|
362
|
+
beforeEach(() => {
|
|
363
|
+
persistedDecisions = [];
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("uses producer-supplied title and body verbatim, no LLM call", async () => {
|
|
367
|
+
const signal = makeAssistantReplySignal();
|
|
368
|
+
const decision = await evaluateSignal(signal, [
|
|
369
|
+
"vellum",
|
|
370
|
+
"platform",
|
|
371
|
+
] as NotificationChannel[]);
|
|
372
|
+
|
|
373
|
+
expect(decision.shouldNotify).toBe(true);
|
|
374
|
+
expect(decision.renderedCopy.platform?.body).toBe("Here is your answer.");
|
|
375
|
+
expect(decision.renderedCopy.platform?.title).toBe("Assistant");
|
|
376
|
+
expect(decision.reasoningSummary).toBe("assistant_reply pass-through");
|
|
377
|
+
expect(decision.verbatimCopy).toBe(true);
|
|
378
|
+
expect(decision.fallbackUsed).toBe(false);
|
|
379
|
+
expect(decision.confidence).toBe(1.0);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("selects exactly the platform channel when platform is available", async () => {
|
|
383
|
+
const decision = await evaluateSignal(makeAssistantReplySignal(), [
|
|
384
|
+
"vellum",
|
|
385
|
+
"telegram",
|
|
386
|
+
"platform",
|
|
387
|
+
] as NotificationChannel[]);
|
|
388
|
+
|
|
389
|
+
expect(decision.selectedChannels).toEqual(["platform"]);
|
|
390
|
+
expect(decision.shouldNotify).toBe(true);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("selects nothing and suppresses when platform is unavailable", async () => {
|
|
394
|
+
const decision = await evaluateSignal(makeAssistantReplySignal(), [
|
|
395
|
+
"vellum",
|
|
396
|
+
"telegram",
|
|
397
|
+
] as NotificationChannel[]);
|
|
398
|
+
|
|
399
|
+
expect(decision.selectedChannels).toEqual([]);
|
|
400
|
+
expect(decision.shouldNotify).toBe(false);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("seeds rendered copy for every available channel, not just the selected one", async () => {
|
|
404
|
+
// A future channel added to ASSISTANT_REPLY_CHANNELS (or appended by a
|
|
405
|
+
// downstream guard) inherits the verbatim copy instead of falling back.
|
|
406
|
+
const available = [
|
|
407
|
+
"vellum",
|
|
408
|
+
"telegram",
|
|
409
|
+
"platform",
|
|
410
|
+
] as NotificationChannel[];
|
|
411
|
+
const decision = await evaluateSignal(
|
|
412
|
+
makeAssistantReplySignal(),
|
|
413
|
+
available,
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
for (const ch of available) {
|
|
417
|
+
expect(decision.renderedCopy[ch]?.body).toBe("Here is your answer.");
|
|
418
|
+
expect(decision.renderedCopy[ch]?.title).toBe("Assistant");
|
|
419
|
+
expect(decision.conversationActions?.[ch]?.action).toBe("start_new");
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("derives the title from the body when requestedTitle is not supplied", async () => {
|
|
424
|
+
const signal = makeAssistantReplySignal({
|
|
425
|
+
contextPayload: {
|
|
426
|
+
requestedMessage: "First sentence. Second sentence follows here.",
|
|
311
427
|
},
|
|
428
|
+
});
|
|
429
|
+
const decision = await evaluateSignal(signal, [
|
|
430
|
+
"platform",
|
|
431
|
+
] as NotificationChannel[]);
|
|
432
|
+
|
|
433
|
+
expect(decision.renderedCopy.platform?.title).toBe("First sentence.");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("uses the producer-supplied dedupeKey", async () => {
|
|
437
|
+
const signal = makeAssistantReplySignal({
|
|
438
|
+
dedupeKey: "chat.assistant_reply:conv-1:msg-1",
|
|
439
|
+
});
|
|
440
|
+
const decision = await evaluateSignal(signal, [
|
|
441
|
+
"platform",
|
|
442
|
+
] as NotificationChannel[]);
|
|
443
|
+
|
|
444
|
+
expect(decision.dedupeKey).toBe("chat.assistant_reply:conv-1:msg-1");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("falls back to the signal id when the producer supplies no dedupeKey", async () => {
|
|
448
|
+
const signal = makeAssistantReplySignal();
|
|
449
|
+
const decision = await evaluateSignal(signal, [
|
|
450
|
+
"platform",
|
|
451
|
+
] as NotificationChannel[]);
|
|
452
|
+
|
|
453
|
+
expect(decision.dedupeKey).toBe(signal.signalId);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("threads contextPayload.deepLinkMetadata through to decision.deepLinkTarget", async () => {
|
|
457
|
+
const signal = makeAssistantReplySignal({
|
|
458
|
+
contextPayload: {
|
|
459
|
+
requestedMessage: "Here is your answer.",
|
|
460
|
+
deepLinkMetadata: { conversationId: "conv-1" },
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
const decision = await evaluateSignal(signal, [
|
|
464
|
+
"platform",
|
|
465
|
+
] as NotificationChannel[]);
|
|
466
|
+
|
|
467
|
+
expect(decision.deepLinkTarget).toEqual({ conversationId: "conv-1" });
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
test("persists the decision", async () => {
|
|
471
|
+
const signal = makeAssistantReplySignal();
|
|
472
|
+
const decision = await evaluateSignal(signal, [
|
|
473
|
+
"platform",
|
|
474
|
+
] as NotificationChannel[]);
|
|
475
|
+
|
|
476
|
+
expect(decision.persistedDecisionId).toBeDefined();
|
|
477
|
+
expect(persistedDecisions.length).toBe(1);
|
|
478
|
+
expect(persistedDecisions[0]?.notificationEventId).toBe(signal.signalId);
|
|
479
|
+
expect(persistedDecisions[0]?.reasoningSummary).toBe(
|
|
480
|
+
"assistant_reply pass-through",
|
|
481
|
+
);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
test("an empty body skips the pass-through branch entirely", async () => {
|
|
485
|
+
const signal = makeAssistantReplySignal({
|
|
486
|
+
contextPayload: { requestedMessage: " ", requestedTitle: "Assistant" },
|
|
487
|
+
});
|
|
488
|
+
const previousSendMessage = providerSendMessage;
|
|
489
|
+
let providerCalled = false;
|
|
490
|
+
providerSendMessage = async () => {
|
|
491
|
+
providerCalled = true;
|
|
492
|
+
return {};
|
|
312
493
|
};
|
|
313
|
-
}
|
|
314
494
|
|
|
495
|
+
try {
|
|
496
|
+
const decision = await evaluateSignal(signal, [
|
|
497
|
+
"platform",
|
|
498
|
+
] as NotificationChannel[]);
|
|
499
|
+
|
|
500
|
+
expect(providerCalled).toBe(true);
|
|
501
|
+
expect(decision.verbatimCopy).toBeUndefined();
|
|
502
|
+
expect(decision.reasoningSummary).not.toBe(
|
|
503
|
+
"assistant_reply pass-through",
|
|
504
|
+
);
|
|
505
|
+
} finally {
|
|
506
|
+
providerSendMessage = previousSendMessage;
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe("recipient notes injection (ACL from gateway, notes joined locally)", () => {
|
|
315
512
|
test("injects the guardian's local notes, resolved via the gateway contactId", async () => {
|
|
316
513
|
guardianDeliveryFixture = [{ contactId: "contact-42" }];
|
|
317
514
|
contactInfoFixture = { "contact-42": { notes: "Prefers terse updates." } };
|
|
@@ -343,3 +540,126 @@ describe("recipient notes injection (ACL from gateway, notes joined locally)", (
|
|
|
343
540
|
expect(capturedSystemPrompt).not.toContain("<recipient-context>");
|
|
344
541
|
});
|
|
345
542
|
});
|
|
543
|
+
|
|
544
|
+
describe("decision tool title field specification", () => {
|
|
545
|
+
test("pins the length, form, and no-echo constraints in the tool schema", async () => {
|
|
546
|
+
guardianDeliveryFixture = [];
|
|
547
|
+
contactInfoFixture = {};
|
|
548
|
+
|
|
549
|
+
let capturedTools: unknown[] | undefined;
|
|
550
|
+
providerSendMessage = async (_messages, opts) => {
|
|
551
|
+
capturedTools = opts.tools;
|
|
552
|
+
return {};
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
await evaluateSignal(makeLlmSignal(), ["vellum"] as NotificationChannel[]);
|
|
556
|
+
|
|
557
|
+
const tool = capturedTools?.[0] as {
|
|
558
|
+
input_schema: {
|
|
559
|
+
properties: {
|
|
560
|
+
renderedCopy: {
|
|
561
|
+
properties: Record<
|
|
562
|
+
string,
|
|
563
|
+
{ properties: { title: { description: string } } }
|
|
564
|
+
>;
|
|
565
|
+
};
|
|
566
|
+
};
|
|
567
|
+
};
|
|
568
|
+
};
|
|
569
|
+
const description =
|
|
570
|
+
tool.input_schema.properties.renderedCopy.properties.vellum.properties
|
|
571
|
+
.title.description;
|
|
572
|
+
|
|
573
|
+
expect(description).toContain("2 to 5 words");
|
|
574
|
+
expect(description).toContain("40 characters");
|
|
575
|
+
expect(description).toContain("noun phrase");
|
|
576
|
+
expect(description).toContain(
|
|
577
|
+
"Do NOT restate, summarize, or echo the body",
|
|
578
|
+
);
|
|
579
|
+
expect(description).toContain("no markdown");
|
|
580
|
+
expect(description).toContain("Missing Context");
|
|
581
|
+
expect(description.match(/NOT '/g)?.length ?? 0).toBeGreaterThanOrEqual(2);
|
|
582
|
+
});
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
describe("title normalization", () => {
|
|
586
|
+
/** Run the LLM path with the given channel copy and return the kept title. */
|
|
587
|
+
async function titleFromModelCopy(
|
|
588
|
+
title: string,
|
|
589
|
+
body: string,
|
|
590
|
+
): Promise<string | undefined> {
|
|
591
|
+
guardianDeliveryFixture = [];
|
|
592
|
+
contactInfoFixture = {};
|
|
593
|
+
const previousSendMessage = providerSendMessage;
|
|
594
|
+
providerSendMessage = async () => ({});
|
|
595
|
+
toolUseBlock = {
|
|
596
|
+
input: {
|
|
597
|
+
shouldNotify: true,
|
|
598
|
+
selectedChannels: ["vellum"],
|
|
599
|
+
reasoningSummary: "model decision",
|
|
600
|
+
dedupeKey: "title-normalization-test",
|
|
601
|
+
renderedCopy: { vellum: { title, body } },
|
|
602
|
+
},
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
try {
|
|
606
|
+
const decision = await evaluateSignal(makeLlmSignal(), [
|
|
607
|
+
"vellum",
|
|
608
|
+
] as NotificationChannel[]);
|
|
609
|
+
return decision.renderedCopy.vellum?.title;
|
|
610
|
+
} finally {
|
|
611
|
+
toolUseBlock = null;
|
|
612
|
+
providerSendMessage = previousSendMessage;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
test("replaces a model title that restates the body with a derived one", async () => {
|
|
617
|
+
const title = "The staging deploy finished and the build is live.";
|
|
618
|
+
const body = `Deploy complete. ${title}`;
|
|
619
|
+
|
|
620
|
+
expect(await titleFromModelCopy(title, body)).toBe("Deploy complete.");
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("keeps a clean model title verbatim", async () => {
|
|
624
|
+
expect(
|
|
625
|
+
await titleFromModelCopy(
|
|
626
|
+
"Staging Deploy Status",
|
|
627
|
+
"The staging deploy finished.",
|
|
628
|
+
),
|
|
629
|
+
).toBe("Staging Deploy Status");
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
test("truncates a model title longer than 40 characters", async () => {
|
|
633
|
+
const kept = await titleFromModelCopy(
|
|
634
|
+
"Quarterly Infrastructure Migration Status Report",
|
|
635
|
+
"The migration is on track for the quarter.",
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
expect(kept).toBe("Quarterly Infrastructure Migration");
|
|
639
|
+
expect(kept?.length).toBeLessThanOrEqual(40);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test("strips markdown from a model title", async () => {
|
|
643
|
+
expect(
|
|
644
|
+
await titleFromModelCopy(
|
|
645
|
+
"**Deploy Status**",
|
|
646
|
+
"The staging deploy finished.",
|
|
647
|
+
),
|
|
648
|
+
).toBe("Deploy Status");
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test("replaces a pass-through requestedTitle that stutters against the body", async () => {
|
|
652
|
+
const requestedTitle = "The nightly backup finished without any errors.";
|
|
653
|
+
const signal = makeAssistantToolSignal({
|
|
654
|
+
contextPayload: {
|
|
655
|
+
requestedMessage: `Backup finished. ${requestedTitle}`,
|
|
656
|
+
requestedTitle,
|
|
657
|
+
},
|
|
658
|
+
});
|
|
659
|
+
const decision = await evaluateSignal(signal, [
|
|
660
|
+
"vellum",
|
|
661
|
+
] as NotificationChannel[]);
|
|
662
|
+
|
|
663
|
+
expect(decision.renderedCopy.vellum?.title).toBe("Backup finished.");
|
|
664
|
+
});
|
|
665
|
+
});
|
|
@@ -241,6 +241,7 @@ describe("checkRenderedCopyQuality (via runDeterministicChecks)", () => {
|
|
|
241
241
|
});
|
|
242
242
|
const decision = makeDecision({
|
|
243
243
|
reasoningSummary: "assistant_tool pass-through",
|
|
244
|
+
verbatimCopy: true,
|
|
244
245
|
renderedCopy: {
|
|
245
246
|
vellum: { title: "Assistant share", body: "assistant share" },
|
|
246
247
|
},
|
|
@@ -253,6 +254,7 @@ describe("checkRenderedCopyQuality (via runDeterministicChecks)", () => {
|
|
|
253
254
|
const signal = makeSignal({ sourceChannel: "assistant_tool" });
|
|
254
255
|
const decision = makeDecision({
|
|
255
256
|
reasoningSummary: "assistant_tool pass-through",
|
|
257
|
+
verbatimCopy: true,
|
|
256
258
|
renderedCopy: {
|
|
257
259
|
vellum: { title: "Reminder", body: "" },
|
|
258
260
|
},
|
|
@@ -262,6 +264,25 @@ describe("checkRenderedCopyQuality (via runDeterministicChecks)", () => {
|
|
|
262
264
|
expect(result.reason).toContain("empty");
|
|
263
265
|
});
|
|
264
266
|
|
|
267
|
+
test("passes assistant_reply pass-through after downstream suffixes mutate the reasoning summary", async () => {
|
|
268
|
+
// `emit-signal` and routing-intent enforcement append suffixes to
|
|
269
|
+
// `reasoningSummary`, so the exemption must key off `verbatimCopy`.
|
|
270
|
+
const signal = makeSignal({
|
|
271
|
+
sourceChannel: "vellum",
|
|
272
|
+
sourceEventName: "chat.assistant_reply",
|
|
273
|
+
});
|
|
274
|
+
const decision = makeDecision({
|
|
275
|
+
reasoningSummary:
|
|
276
|
+
"assistant_reply pass-through (vellum forced: high urgency)",
|
|
277
|
+
verbatimCopy: true,
|
|
278
|
+
renderedCopy: {
|
|
279
|
+
vellum: { title: "Assistant", body: "chat assistant reply" },
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
const result = await runDeterministicChecks(signal, decision, context);
|
|
283
|
+
expect(result.passed).toBe(true);
|
|
284
|
+
});
|
|
285
|
+
|
|
265
286
|
test("still fails non-pass-through decision when body matches event name", async () => {
|
|
266
287
|
// Regression guard: the pass-through short-circuit must not weaken
|
|
267
288
|
// the check for LLM/fallback paths.
|