@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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for handleListMessages provider-error projection.
|
|
3
|
+
*
|
|
4
|
+
* When a turn dies on the provider-error path the agent loop persists the
|
|
5
|
+
* classified error text as a synthetic assistant row stamped with
|
|
6
|
+
* `metadata.messageKind: "provider_error"` (plus the classified
|
|
7
|
+
* code/category). The history projection must surface that marker as the
|
|
8
|
+
* wire `providerError` field, asserted against the FINAL response payload:
|
|
9
|
+
* the final projection rebuilds each message object field by field, so a
|
|
10
|
+
* field carried only on the intermediate object is silently dropped.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
14
|
+
|
|
15
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
16
|
+
|
|
17
|
+
// Keep the memory system off so addMessage skips indexing side effects.
|
|
18
|
+
setConfig("memory", { enabled: false });
|
|
19
|
+
|
|
20
|
+
import { ConversationMessageSchema } from "../api/responses/conversation-message.js";
|
|
21
|
+
import {
|
|
22
|
+
addMessage,
|
|
23
|
+
createConversation,
|
|
24
|
+
PROVIDER_ERROR_MESSAGE_KIND,
|
|
25
|
+
} from "../persistence/conversation-crud.js";
|
|
26
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
27
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
28
|
+
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
29
|
+
|
|
30
|
+
await initializeDb();
|
|
31
|
+
|
|
32
|
+
function resetTables() {
|
|
33
|
+
const db = getDb();
|
|
34
|
+
db.run("DELETE FROM message_attachments");
|
|
35
|
+
db.run("DELETE FROM attachments");
|
|
36
|
+
db.run("DELETE FROM messages");
|
|
37
|
+
db.run("DELETE FROM conversations");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ProjectedMessage {
|
|
41
|
+
role: string;
|
|
42
|
+
providerError?: { code?: string; category?: string };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("handleListMessages provider-error projection", () => {
|
|
46
|
+
beforeEach(resetTables);
|
|
47
|
+
|
|
48
|
+
test("surfaces providerError on tagged rows in the final payload", async () => {
|
|
49
|
+
const conv = createConversation();
|
|
50
|
+
await addMessage(
|
|
51
|
+
conv.id,
|
|
52
|
+
"user",
|
|
53
|
+
JSON.stringify([{ type: "text", text: "hello" }]),
|
|
54
|
+
);
|
|
55
|
+
// Mirror the agent loop's provider-error persist site: a synthetic
|
|
56
|
+
// assistant row carrying the classified error, tagged in metadata.
|
|
57
|
+
await addMessage(
|
|
58
|
+
conv.id,
|
|
59
|
+
"assistant",
|
|
60
|
+
JSON.stringify([
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: "I couldn't reply because you ran out of credits. Add credits in Settings → Billing and we can pick up where we left off.",
|
|
64
|
+
},
|
|
65
|
+
]),
|
|
66
|
+
{
|
|
67
|
+
metadata: {
|
|
68
|
+
messageKind: PROVIDER_ERROR_MESSAGE_KIND,
|
|
69
|
+
providerErrorCode: "PROVIDER_BILLING",
|
|
70
|
+
providerErrorCategory: "credits_exhausted",
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const response = handleListMessages({
|
|
76
|
+
queryParams: { conversationId: conv.id },
|
|
77
|
+
}) as { messages: ProjectedMessage[] };
|
|
78
|
+
|
|
79
|
+
// Every projected message validates against the wire schema.
|
|
80
|
+
for (const message of response.messages) {
|
|
81
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
expect(response.messages).toHaveLength(2);
|
|
85
|
+
const [userRow, errorRow] = response.messages;
|
|
86
|
+
expect(userRow?.providerError).toBeUndefined();
|
|
87
|
+
expect(errorRow?.role).toBe("assistant");
|
|
88
|
+
expect(errorRow?.providerError).toEqual({
|
|
89
|
+
code: "PROVIDER_BILLING",
|
|
90
|
+
category: "credits_exhausted",
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("coerces non-string code/category to absent fields", async () => {
|
|
95
|
+
const conv = createConversation();
|
|
96
|
+
await addMessage(
|
|
97
|
+
conv.id,
|
|
98
|
+
"assistant",
|
|
99
|
+
JSON.stringify([{ type: "text", text: "something went wrong" }]),
|
|
100
|
+
{
|
|
101
|
+
metadata: {
|
|
102
|
+
messageKind: PROVIDER_ERROR_MESSAGE_KIND,
|
|
103
|
+
providerErrorCode: 402,
|
|
104
|
+
providerErrorCategory: null,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const response = handleListMessages({
|
|
110
|
+
queryParams: { conversationId: conv.id },
|
|
111
|
+
}) as { messages: ProjectedMessage[] };
|
|
112
|
+
|
|
113
|
+
expect(response.messages).toHaveLength(1);
|
|
114
|
+
expect(response.messages[0]?.providerError).toEqual({});
|
|
115
|
+
expect(() =>
|
|
116
|
+
ConversationMessageSchema.parse(response.messages[0]),
|
|
117
|
+
).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("omits providerError on untagged rows", async () => {
|
|
121
|
+
const conv = createConversation();
|
|
122
|
+
await addMessage(
|
|
123
|
+
conv.id,
|
|
124
|
+
"user",
|
|
125
|
+
JSON.stringify([{ type: "text", text: "hello" }]),
|
|
126
|
+
);
|
|
127
|
+
await addMessage(
|
|
128
|
+
conv.id,
|
|
129
|
+
"assistant",
|
|
130
|
+
JSON.stringify([{ type: "text", text: "hi there" }]),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const response = handleListMessages({
|
|
134
|
+
queryParams: { conversationId: conv.id },
|
|
135
|
+
}) as { messages: ProjectedMessage[] };
|
|
136
|
+
|
|
137
|
+
expect(response.messages).toHaveLength(2);
|
|
138
|
+
for (const message of response.messages) {
|
|
139
|
+
expect(message.providerError).toBeUndefined();
|
|
140
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for handleListMessages system-card projection.
|
|
3
|
+
*
|
|
4
|
+
* Daemon-authored status cards (the /compact, /clean, and summarize-up-to
|
|
5
|
+
* result cards persisted via `persistCannedAssistantCard`) are stamped
|
|
6
|
+
* `messageKind: "system_card"`. The history projection must surface the
|
|
7
|
+
* `systemCard` flag so clients render them as standalone system notices
|
|
8
|
+
* instead of assistant-persona speech after a reload.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
14
|
+
|
|
15
|
+
// Keep the memory system off so addMessage skips indexing side effects.
|
|
16
|
+
setConfig("memory", { enabled: false });
|
|
17
|
+
|
|
18
|
+
import { ConversationMessageSchema } from "../api/responses/conversation-message.js";
|
|
19
|
+
import {
|
|
20
|
+
addMessage,
|
|
21
|
+
createConversation,
|
|
22
|
+
SYSTEM_CARD_MESSAGE_KIND,
|
|
23
|
+
} from "../persistence/conversation-crud.js";
|
|
24
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
25
|
+
import { initializeDb } from "../persistence/db-init.js";
|
|
26
|
+
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
27
|
+
|
|
28
|
+
await initializeDb();
|
|
29
|
+
|
|
30
|
+
function resetTables() {
|
|
31
|
+
const db = getDb();
|
|
32
|
+
db.run("DELETE FROM message_attachments");
|
|
33
|
+
db.run("DELETE FROM attachments");
|
|
34
|
+
db.run("DELETE FROM messages");
|
|
35
|
+
db.run("DELETE FROM conversations");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface ProjectedMessage {
|
|
39
|
+
id: string;
|
|
40
|
+
role: string;
|
|
41
|
+
systemCard?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("handleListMessages system-card projection", () => {
|
|
45
|
+
beforeEach(resetTables);
|
|
46
|
+
|
|
47
|
+
test("projects systemCard from the persisted card row metadata", async () => {
|
|
48
|
+
const conv = createConversation();
|
|
49
|
+
await addMessage(
|
|
50
|
+
conv.id,
|
|
51
|
+
"user",
|
|
52
|
+
JSON.stringify([{ type: "text", text: "/compact" }]),
|
|
53
|
+
);
|
|
54
|
+
// Mirror persistCannedAssistantCard: an assistant row stamped with the
|
|
55
|
+
// system-card marker.
|
|
56
|
+
const card = await addMessage(
|
|
57
|
+
conv.id,
|
|
58
|
+
"assistant",
|
|
59
|
+
JSON.stringify([{ type: "text", text: "Compacted 12 messages." }]),
|
|
60
|
+
{ metadata: { messageKind: SYSTEM_CARD_MESSAGE_KIND } },
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const response = handleListMessages({
|
|
64
|
+
queryParams: { conversationId: conv.id },
|
|
65
|
+
}) as { messages: ProjectedMessage[] };
|
|
66
|
+
|
|
67
|
+
// Every projected message validates against the wire schema.
|
|
68
|
+
for (const message of response.messages) {
|
|
69
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const cardRow = response.messages.find((m) => m.id === card.id);
|
|
73
|
+
expect(cardRow).toBeDefined();
|
|
74
|
+
expect(cardRow?.systemCard).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("omits systemCard on ordinary user and assistant rows", async () => {
|
|
78
|
+
const conv = createConversation();
|
|
79
|
+
await addMessage(
|
|
80
|
+
conv.id,
|
|
81
|
+
"user",
|
|
82
|
+
JSON.stringify([{ type: "text", text: "hello" }]),
|
|
83
|
+
);
|
|
84
|
+
await addMessage(
|
|
85
|
+
conv.id,
|
|
86
|
+
"assistant",
|
|
87
|
+
JSON.stringify([{ type: "text", text: "hi there" }]),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const response = handleListMessages({
|
|
91
|
+
queryParams: { conversationId: conv.id },
|
|
92
|
+
}) as { messages: ProjectedMessage[] };
|
|
93
|
+
|
|
94
|
+
expect(response.messages).toHaveLength(2);
|
|
95
|
+
for (const message of response.messages) {
|
|
96
|
+
expect(message.systemCard).toBeUndefined();
|
|
97
|
+
expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -6,6 +6,7 @@ import { CODE_DEFAULT_PROFILE_ENTRIES } from "../config/default-profile-catalog.
|
|
|
6
6
|
import {
|
|
7
7
|
type ResolutionFallbackReason,
|
|
8
8
|
resolveCallSiteConfig,
|
|
9
|
+
resolveCallSiteConfigWithProfile,
|
|
9
10
|
resolveDefaultProfileKey,
|
|
10
11
|
resolveEffectiveProfileKey,
|
|
11
12
|
} from "../config/llm-resolver.js";
|
|
@@ -697,9 +698,7 @@ describe("resolveCallSiteConfig", () => {
|
|
|
697
698
|
const resolved = resolveCallSiteConfig("memoryExtraction", llm);
|
|
698
699
|
expect(resolved.provider).toBe("openai");
|
|
699
700
|
expect(resolved.provider_connection).toBe("openai-personal");
|
|
700
|
-
expect(resolved.model).toBe(
|
|
701
|
-
resolveModelIntent("openai", "latency-optimized"),
|
|
702
|
-
);
|
|
701
|
+
expect(resolved.model).toBe(resolveModelIntent("openai", "cost-optimized"));
|
|
703
702
|
});
|
|
704
703
|
|
|
705
704
|
test("BYOK full-workspace: every call site resolves through the default provider, never the managed connection", () => {
|
|
@@ -751,9 +750,7 @@ describe("resolveCallSiteConfig", () => {
|
|
|
751
750
|
|
|
752
751
|
// Cost-optimized call sites resolve the intent through the BYOK provider.
|
|
753
752
|
const costSite = resolveCallSiteConfig("heartbeatAgent", byokConfig);
|
|
754
|
-
expect(costSite.model).toBe(
|
|
755
|
-
resolveModelIntent("openai", "latency-optimized"),
|
|
756
|
-
);
|
|
753
|
+
expect(costSite.model).toBe(resolveModelIntent("openai", "cost-optimized"));
|
|
757
754
|
|
|
758
755
|
// mainAgent uses the user's active profile.
|
|
759
756
|
const balancedSite = resolveCallSiteConfig("mainAgent", byokConfig);
|
|
@@ -770,9 +767,7 @@ describe("resolveCallSiteConfig", () => {
|
|
|
770
767
|
|
|
771
768
|
const resolved = resolveCallSiteConfig("commitMessage", byokConfig);
|
|
772
769
|
expect(resolved.provider).toBe("openai");
|
|
773
|
-
expect(resolved.model).toBe(
|
|
774
|
-
resolveModelIntent("openai", "latency-optimized"),
|
|
775
|
-
);
|
|
770
|
+
expect(resolved.model).toBe(resolveModelIntent("openai", "cost-optimized"));
|
|
776
771
|
expect(resolved.maxTokens).toBe(120);
|
|
777
772
|
expect(resolved.effort).toBe("low");
|
|
778
773
|
expect(resolved.thinking.enabled).toBe(false);
|
|
@@ -859,6 +854,21 @@ describe("mix profiles", () => {
|
|
|
859
854
|
}
|
|
860
855
|
});
|
|
861
856
|
|
|
857
|
+
test("config and profile attribution share one mix selection", () => {
|
|
858
|
+
const selectedArms: string[] = [];
|
|
859
|
+
const resolved = resolveCallSiteConfigWithProfile("mainAgent", mixLlm, {
|
|
860
|
+
onMixSelected: ({ chosenProfile }) => {
|
|
861
|
+
selectedArms.push(chosenProfile);
|
|
862
|
+
},
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
expect(resolved.profileName).toBe("ab");
|
|
866
|
+
expect(selectedArms).toHaveLength(1);
|
|
867
|
+
expect(resolved.config.model).toBe(
|
|
868
|
+
selectedArms[0] === "a" ? "model-a" : "model-b",
|
|
869
|
+
);
|
|
870
|
+
});
|
|
871
|
+
|
|
862
872
|
test("all dereference spots in a turn agree for the same seed", () => {
|
|
863
873
|
// mainAgent (mix as activeProfile) and a non-main call site resolving the
|
|
864
874
|
// same mix as its call-site profile must pick the same arm when given the
|
|
@@ -263,6 +263,29 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
|
|
|
263
263
|
expectNothingCommitted();
|
|
264
264
|
});
|
|
265
265
|
|
|
266
|
+
test("PUT on the code-owned latency profile is rejected, status re-enable included", async () => {
|
|
267
|
+
// Speed fronts live voice, so it takes no writes at all: the re-enable
|
|
268
|
+
// escape hatch the other managed profiles keep would persist a stub that
|
|
269
|
+
// never governs what the name resolves to.
|
|
270
|
+
seedRawConfig({ llm: { profiles: {} } });
|
|
271
|
+
|
|
272
|
+
for (const body of [
|
|
273
|
+
{ label: "Zippy" },
|
|
274
|
+
{ status: "active" as const },
|
|
275
|
+
{ status: null },
|
|
276
|
+
]) {
|
|
277
|
+
await expect(
|
|
278
|
+
replaceRoute.handler({
|
|
279
|
+
pathParams: { name: "latency-optimized" },
|
|
280
|
+
body,
|
|
281
|
+
}),
|
|
282
|
+
).rejects.toThrow(
|
|
283
|
+
'Profile "latency-optimized" is code-owned and cannot be edited.',
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
expectNothingCommitted();
|
|
287
|
+
});
|
|
288
|
+
|
|
266
289
|
test("PUT { status: null } on managed profile clears status (back to active-by-absence)", async () => {
|
|
267
290
|
seedRawConfig({
|
|
268
291
|
llm: {
|
|
@@ -33,11 +33,13 @@ interface FetchCall {
|
|
|
33
33
|
path: string;
|
|
34
34
|
method: string;
|
|
35
35
|
body: Record<string, unknown>;
|
|
36
|
+
hasAbortSignal: boolean;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const fetchCalls: FetchCall[] = [];
|
|
39
40
|
const fetchResponses: Array<{ ok: boolean; status: number; body?: string }> =
|
|
40
41
|
[];
|
|
42
|
+
const fetchErrors: Error[] = [];
|
|
41
43
|
let clientAvailable = true;
|
|
42
44
|
|
|
43
45
|
mock.module("../platform/client.js", () => ({
|
|
@@ -52,7 +54,16 @@ mock.module("../platform/client.js", () => ({
|
|
|
52
54
|
const body = init?.body
|
|
53
55
|
? (JSON.parse(init.body as string) as Record<string, unknown>)
|
|
54
56
|
: {};
|
|
55
|
-
fetchCalls.push({
|
|
57
|
+
fetchCalls.push({
|
|
58
|
+
path,
|
|
59
|
+
method: init?.method ?? "GET",
|
|
60
|
+
body,
|
|
61
|
+
hasAbortSignal: init?.signal instanceof AbortSignal,
|
|
62
|
+
});
|
|
63
|
+
const error = fetchErrors.shift();
|
|
64
|
+
if (error) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
56
67
|
const response = fetchResponses.shift() ?? {
|
|
57
68
|
ok: true,
|
|
58
69
|
status: 200,
|
|
@@ -62,6 +73,7 @@ mock.module("../platform/client.js", () => ({
|
|
|
62
73
|
ok: response.ok,
|
|
63
74
|
status: response.status,
|
|
64
75
|
text: async () => response.body ?? "",
|
|
76
|
+
json: async () => JSON.parse(response.body ?? "") as unknown,
|
|
65
77
|
};
|
|
66
78
|
},
|
|
67
79
|
};
|
|
@@ -82,6 +94,7 @@ function makePayload(
|
|
|
82
94
|
): ChannelDeliveryPayload {
|
|
83
95
|
return {
|
|
84
96
|
deliveryId: "delivery-uuid-1",
|
|
97
|
+
correlationId: "signal-1",
|
|
85
98
|
sourceEventName: "schedule.notify",
|
|
86
99
|
copy: { title: "Reminder", body: "Check the oven!" },
|
|
87
100
|
deepLinkTarget: { type: "conversation", id: "conv-1" },
|
|
@@ -106,6 +119,7 @@ describe("PlatformPushAdapter", () => {
|
|
|
106
119
|
beforeEach(() => {
|
|
107
120
|
fetchCalls.length = 0;
|
|
108
121
|
fetchResponses.length = 0;
|
|
122
|
+
fetchErrors.length = 0;
|
|
109
123
|
clientAvailable = true;
|
|
110
124
|
});
|
|
111
125
|
|
|
@@ -123,7 +137,7 @@ describe("PlatformPushAdapter", () => {
|
|
|
123
137
|
const call = fetchCalls[0]!;
|
|
124
138
|
expect(call.path).toBe("/v1/assistants/test-assistant-id/push/dispatch/");
|
|
125
139
|
expect(call.method).toBe("POST");
|
|
126
|
-
expect(call.body.delivery_id).toBe("
|
|
140
|
+
expect(call.body.delivery_id).toBe("signal-1");
|
|
127
141
|
expect(call.body.source_event_name).toBe("schedule.notify");
|
|
128
142
|
expect(call.body.title).toBe("Reminder");
|
|
129
143
|
expect(call.body.body).toBe("Check the oven!");
|
|
@@ -202,6 +216,28 @@ describe("PlatformPushAdapter", () => {
|
|
|
202
216
|
expect(fetchCalls).toHaveLength(4);
|
|
203
217
|
});
|
|
204
218
|
|
|
219
|
+
test("bounds every attempt with an abort signal", async () => {
|
|
220
|
+
const adapter = new PlatformPushAdapter();
|
|
221
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
222
|
+
|
|
223
|
+
expect(result.success).toBe(true);
|
|
224
|
+
expect(fetchCalls[0]?.hasAbortSignal).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("retries attempts that abort on the per-attempt timeout", async () => {
|
|
228
|
+
fetchErrors.push(
|
|
229
|
+
new DOMException("The operation timed out.", "TimeoutError"),
|
|
230
|
+
);
|
|
231
|
+
fetchResponses.push({ ok: true, status: 200, body: '{"tokens_sent": 1}' });
|
|
232
|
+
|
|
233
|
+
const adapter = new PlatformPushAdapter();
|
|
234
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
235
|
+
|
|
236
|
+
expect(result.success).toBe(true);
|
|
237
|
+
expect(result.remotePushAccepted).toBe(true);
|
|
238
|
+
expect(fetchCalls).toHaveLength(2);
|
|
239
|
+
});
|
|
240
|
+
|
|
205
241
|
test("does not retry on 4xx responses", async () => {
|
|
206
242
|
fetchResponses.push({ ok: false, status: 400, body: "bad request" });
|
|
207
243
|
const adapter = new PlatformPushAdapter();
|
|
@@ -212,6 +248,98 @@ describe("PlatformPushAdapter", () => {
|
|
|
212
248
|
expect(fetchCalls).toHaveLength(1);
|
|
213
249
|
});
|
|
214
250
|
|
|
251
|
+
test("preserves an explicit empty platform list with legacy acceptance", async () => {
|
|
252
|
+
fetchResponses.push({
|
|
253
|
+
ok: true,
|
|
254
|
+
status: 200,
|
|
255
|
+
body: '{"tokens_sent":2,"accepted_platforms":[]}',
|
|
256
|
+
});
|
|
257
|
+
const adapter = new PlatformPushAdapter();
|
|
258
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
259
|
+
|
|
260
|
+
expect(result.success).toBe(true);
|
|
261
|
+
expect(result.remotePushAccepted).toBe(true);
|
|
262
|
+
expect(result.remotePushPlatforms).toEqual([]);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("preserves a successful provider from the final partial 503", async () => {
|
|
266
|
+
fetchResponses.push(
|
|
267
|
+
{
|
|
268
|
+
ok: false,
|
|
269
|
+
status: 503,
|
|
270
|
+
body: '{"accepted_platforms":["ios"]}',
|
|
271
|
+
},
|
|
272
|
+
{ ok: false, status: 503, body: "{}" },
|
|
273
|
+
{ ok: false, status: 503, body: "{}" },
|
|
274
|
+
{
|
|
275
|
+
ok: false,
|
|
276
|
+
status: 503,
|
|
277
|
+
body: '{"accepted_platforms":["android"]}',
|
|
278
|
+
},
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
const result = await new PlatformPushAdapter().send(
|
|
282
|
+
makePayload(),
|
|
283
|
+
makeDestination(),
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
expect(result.success).toBe(false);
|
|
287
|
+
expect(result.remotePushPlatforms).toEqual(["ios", "android"]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("reports remotePushAccepted: false on 202 skipped (flag off)", async () => {
|
|
291
|
+
fetchResponses.push({
|
|
292
|
+
ok: true,
|
|
293
|
+
status: 202,
|
|
294
|
+
body: '{"skipped": "flag_off"}',
|
|
295
|
+
});
|
|
296
|
+
const adapter = new PlatformPushAdapter();
|
|
297
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
298
|
+
|
|
299
|
+
expect(result.success).toBe(true);
|
|
300
|
+
expect(result.remotePushAccepted).toBe(false);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("reports remotePushAccepted: false on 200 with tokens_sent 0", async () => {
|
|
304
|
+
fetchResponses.push({
|
|
305
|
+
ok: true,
|
|
306
|
+
status: 200,
|
|
307
|
+
body: '{"idempotent": true, "tokens_sent": 0}',
|
|
308
|
+
});
|
|
309
|
+
const adapter = new PlatformPushAdapter();
|
|
310
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
311
|
+
|
|
312
|
+
expect(result.success).toBe(true);
|
|
313
|
+
expect(result.remotePushAccepted).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("reports remotePushAccepted: false when the success body is unparseable", async () => {
|
|
317
|
+
fetchResponses.push({ ok: true, status: 200, body: "not json" });
|
|
318
|
+
const adapter = new PlatformPushAdapter();
|
|
319
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
320
|
+
|
|
321
|
+
expect(result.success).toBe(true);
|
|
322
|
+
expect(result.remotePushAccepted).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("leaves remotePushAccepted unset on non-2xx failure", async () => {
|
|
326
|
+
fetchResponses.push({ ok: false, status: 400, body: "bad request" });
|
|
327
|
+
const adapter = new PlatformPushAdapter();
|
|
328
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
329
|
+
|
|
330
|
+
expect(result.success).toBe(false);
|
|
331
|
+
expect(result.remotePushAccepted).toBeUndefined();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("leaves remotePushAccepted unset when the platform client is unavailable", async () => {
|
|
335
|
+
clientAvailable = false;
|
|
336
|
+
const adapter = new PlatformPushAdapter();
|
|
337
|
+
const result = await adapter.send(makePayload(), makeDestination());
|
|
338
|
+
|
|
339
|
+
expect(result.success).toBe(false);
|
|
340
|
+
expect(result.remotePushAccepted).toBeUndefined();
|
|
341
|
+
});
|
|
342
|
+
|
|
215
343
|
test("omits optional fields when absent from payload", async () => {
|
|
216
344
|
const adapter = new PlatformPushAdapter();
|
|
217
345
|
const payload: ChannelDeliveryPayload = {
|
|
@@ -27,6 +27,9 @@ mock.module("../messaging/providers/telegram-bot/send.js", () => ({
|
|
|
27
27
|
text,
|
|
28
28
|
approval: approval as (typeof sendCalls)[0]["approval"],
|
|
29
29
|
});
|
|
30
|
+
// Mirror the real send result: the id of the sent message, which the
|
|
31
|
+
// adapter surfaces so the delivery row can address the card later.
|
|
32
|
+
return { lastMessageId: String(1000 + sendCalls.length) };
|
|
30
33
|
},
|
|
31
34
|
sendTelegramAttachments: async () => ({
|
|
32
35
|
allFailed: false,
|
|
@@ -196,6 +199,9 @@ describe("TelegramAdapter", () => {
|
|
|
196
199
|
const result = await adapter.send(payload, makeDestination());
|
|
197
200
|
|
|
198
201
|
expect(result.success).toBe(true);
|
|
202
|
+
// The sent message's channel-native id is surfaced so the delivery row
|
|
203
|
+
// can address the card for in-place withdrawal later.
|
|
204
|
+
expect(result.messageId).toBe("1001");
|
|
199
205
|
expect(sendCalls).toHaveLength(1);
|
|
200
206
|
|
|
201
207
|
const call = sendCalls[0]!;
|
|
@@ -111,3 +111,48 @@ describe("VellumAdapter silent flag", () => {
|
|
|
111
111
|
expect(intent.silent).toBe(false);
|
|
112
112
|
});
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
describe("VellumAdapter remotePushDispatched pass-through", () => {
|
|
116
|
+
test("broadcasts remote push acceptance fields verbatim", async () => {
|
|
117
|
+
const { adapter, sent } = captureBroadcast();
|
|
118
|
+
await adapter.send(
|
|
119
|
+
makePayload({
|
|
120
|
+
remotePushDispatched: true,
|
|
121
|
+
remotePushPlatforms: ["ios"],
|
|
122
|
+
}),
|
|
123
|
+
makeDestination(),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const intent = sent[0] as Extract<
|
|
127
|
+
AssistantEvent,
|
|
128
|
+
{ type: "notification_intent" }
|
|
129
|
+
>;
|
|
130
|
+
expect(intent.remotePushDispatched).toBe(true);
|
|
131
|
+
expect(intent.remotePushPlatforms).toEqual(["ios"]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("broadcasts remotePushDispatched: false verbatim", async () => {
|
|
135
|
+
const { adapter, sent } = captureBroadcast();
|
|
136
|
+
await adapter.send(
|
|
137
|
+
makePayload({ remotePushDispatched: false }),
|
|
138
|
+
makeDestination(),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const intent = sent[0] as Extract<
|
|
142
|
+
AssistantEvent,
|
|
143
|
+
{ type: "notification_intent" }
|
|
144
|
+
>;
|
|
145
|
+
expect(intent.remotePushDispatched).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("leaves remotePushDispatched undefined when absent from the payload", async () => {
|
|
149
|
+
const { adapter, sent } = captureBroadcast();
|
|
150
|
+
await adapter.send(makePayload(), makeDestination());
|
|
151
|
+
|
|
152
|
+
const intent = sent[0] as Extract<
|
|
153
|
+
AssistantEvent,
|
|
154
|
+
{ type: "notification_intent" }
|
|
155
|
+
>;
|
|
156
|
+
expect(intent.remotePushDispatched).toBeUndefined();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -64,6 +64,7 @@ describe("getModelProfiles", () => {
|
|
|
64
64
|
"balanced",
|
|
65
65
|
"cost-optimized",
|
|
66
66
|
"alpha",
|
|
67
|
+
"latency-optimized",
|
|
67
68
|
"quality-optimized",
|
|
68
69
|
"zeta",
|
|
69
70
|
]);
|
|
@@ -95,6 +96,7 @@ describe("getModelProfiles", () => {
|
|
|
95
96
|
["blend", true],
|
|
96
97
|
["balanced", false],
|
|
97
98
|
["cost-optimized", false],
|
|
99
|
+
["latency-optimized", false],
|
|
98
100
|
["quality-optimized", false],
|
|
99
101
|
]);
|
|
100
102
|
});
|
|
@@ -144,6 +146,7 @@ describe("getModelProfiles", () => {
|
|
|
144
146
|
["beta", true],
|
|
145
147
|
["balanced", false],
|
|
146
148
|
["cost-optimized", false],
|
|
149
|
+
["latency-optimized", false],
|
|
147
150
|
["quality-optimized", false],
|
|
148
151
|
]);
|
|
149
152
|
});
|
|
@@ -188,6 +191,7 @@ describe("getModelProfiles", () => {
|
|
|
188
191
|
["beta", true],
|
|
189
192
|
["balanced", false],
|
|
190
193
|
["cost-optimized", false],
|
|
194
|
+
["latency-optimized", false],
|
|
191
195
|
["quality-optimized", false],
|
|
192
196
|
]);
|
|
193
197
|
});
|
|
@@ -224,6 +228,11 @@ describe("getModelProfiles", () => {
|
|
|
224
228
|
getModelProfiles()
|
|
225
229
|
.map((p) => p.key)
|
|
226
230
|
.sort(),
|
|
227
|
-
).toEqual([
|
|
231
|
+
).toEqual([
|
|
232
|
+
"balanced",
|
|
233
|
+
"cost-optimized",
|
|
234
|
+
"latency-optimized",
|
|
235
|
+
"quality-optimized",
|
|
236
|
+
]);
|
|
228
237
|
});
|
|
229
238
|
});
|
|
@@ -125,6 +125,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
125
125
|
"../../../../daemon/turn-latency-sub-spans.js",
|
|
126
126
|
"../../../../notifications/emit-signal.js",
|
|
127
127
|
"../../../../persistence/checkpoints.js",
|
|
128
|
+
"../../../../persistence/conversation-types.js",
|
|
128
129
|
"../../../../persistence/db-connection.js",
|
|
129
130
|
"../../../../persistence/embeddings/embed.js",
|
|
130
131
|
"../../../../persistence/embeddings/embedding-backend.js",
|
|
@@ -183,6 +184,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
183
184
|
"../../../persistence/checkpoints.js",
|
|
184
185
|
"../../../persistence/cleanup-schedule-state.js",
|
|
185
186
|
"../../../persistence/conversation-crud.js",
|
|
187
|
+
"../../../persistence/conversation-types.js",
|
|
186
188
|
"../../../persistence/db-connection.js",
|
|
187
189
|
"../../../persistence/db-maintenance.js",
|
|
188
190
|
"../../../persistence/embeddings/embedding-backend.js",
|
|
@@ -222,6 +224,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
222
224
|
"../../../util/sqlite-retry.js",
|
|
223
225
|
"../../../util/strip-comment-lines.js",
|
|
224
226
|
"../../../util/truncate.js",
|
|
227
|
+
"../../../util/worker-compute.js",
|
|
225
228
|
"../../../util/worker-memory.js",
|
|
226
229
|
"../../../util/worker-process.js",
|
|
227
230
|
"../../types.js",
|