@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
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
isImageUnprocessableError,
|
|
13
13
|
} from "../plugins/defaults/image-recovery/detect.js";
|
|
14
14
|
import { ConnectionResolutionError } from "../providers/connection-resolution.js";
|
|
15
|
+
import { PROVIDER_CATALOG } from "../providers/model-catalog.js";
|
|
15
16
|
import { getProviderRoutingSource } from "../providers/registry.js";
|
|
16
17
|
import { isAbortReason } from "../util/abort-reasons.js";
|
|
17
18
|
import {
|
|
19
|
+
type ProviderCredentialSource,
|
|
18
20
|
ProviderError,
|
|
19
21
|
type ProviderErrorReason,
|
|
20
22
|
ProviderNotConfiguredError,
|
|
@@ -63,6 +65,14 @@ export interface ClassifiedConversationError {
|
|
|
63
65
|
export interface ConversationErrorAttribution {
|
|
64
66
|
connectionName?: string;
|
|
65
67
|
profileName?: string;
|
|
68
|
+
/** Whether the resolved turn route uses Vellum-managed inference. */
|
|
69
|
+
isManagedRoute?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Which credential the failed request presented. Splits the non-managed side
|
|
72
|
+
* of `isManagedRoute` into personal keys, subscription logins, and keyless
|
|
73
|
+
* endpoints so rejection copy names the right thing to fix.
|
|
74
|
+
*/
|
|
75
|
+
credentialSource?: ProviderCredentialSource;
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
// Network-level error patterns (connection refused, timeout, DNS, reset)
|
|
@@ -181,6 +191,11 @@ export interface ErrorContext {
|
|
|
181
191
|
* underlying connection name is generic.
|
|
182
192
|
*/
|
|
183
193
|
profileName?: string;
|
|
194
|
+
/**
|
|
195
|
+
* Whether the resolved turn route uses Vellum-managed inference. Turn routing
|
|
196
|
+
* takes precedence over the provider registry's boot-time default.
|
|
197
|
+
*/
|
|
198
|
+
isManagedRoute?: boolean;
|
|
184
199
|
}
|
|
185
200
|
|
|
186
201
|
/**
|
|
@@ -253,9 +268,22 @@ export function classifyConversationError(
|
|
|
253
268
|
// credential-related classifications can name the exact slot to fix.
|
|
254
269
|
// A `ProviderNotConfiguredError` instance carries its own attribution
|
|
255
270
|
// (from the throw site) which takes priority over context when present.
|
|
271
|
+
// The failed call's own route wins field by field — it is resolved at
|
|
272
|
+
// dispatch, while context describes the turn and can be stale for this
|
|
273
|
+
// call. Per-field, so a route carrying only some fields doesn't blank the
|
|
274
|
+
// rest.
|
|
275
|
+
const providerRoute =
|
|
276
|
+
error instanceof ProviderError ? error.routeAttribution : undefined;
|
|
277
|
+
const connectionName = providerRoute?.connectionName ?? ctx.connectionName;
|
|
278
|
+
const profileName = providerRoute?.profileName ?? ctx.profileName;
|
|
279
|
+
const isManagedRoute = providerRoute?.isManagedRoute ?? ctx.isManagedRoute;
|
|
256
280
|
const attribution: ConversationErrorAttribution = {
|
|
257
|
-
...(
|
|
258
|
-
...(
|
|
281
|
+
...(connectionName ? { connectionName } : {}),
|
|
282
|
+
...(profileName ? { profileName } : {}),
|
|
283
|
+
...(isManagedRoute !== undefined ? { isManagedRoute } : {}),
|
|
284
|
+
...(providerRoute?.credentialSource
|
|
285
|
+
? { credentialSource: providerRoute.credentialSource }
|
|
286
|
+
: {}),
|
|
259
287
|
};
|
|
260
288
|
|
|
261
289
|
// Dedicated classification for missing provider API key
|
|
@@ -354,15 +382,28 @@ function classifyCore(
|
|
|
354
382
|
message: string,
|
|
355
383
|
attribution: ConversationErrorAttribution = {},
|
|
356
384
|
): Omit<ClassifiedConversationError, "debugDetails"> {
|
|
385
|
+
const isManagedRoute =
|
|
386
|
+
error instanceof ProviderError &&
|
|
387
|
+
(attribution.isManagedRoute ??
|
|
388
|
+
getProviderRoutingSource(error.provider) === "managed-proxy");
|
|
389
|
+
// Which credential the request actually presented. Only rejection copy needs
|
|
390
|
+
// this finer axis; every other branch keys off `isManagedRoute`. Routes that
|
|
391
|
+
// predate per-request stamping collapse to the managed/personal split.
|
|
392
|
+
const credentialSource: ProviderCredentialSource =
|
|
393
|
+
attribution.credentialSource ??
|
|
394
|
+
(isManagedRoute ? "vellum-managed" : "byok");
|
|
395
|
+
|
|
357
396
|
// Prefer the semantic reason stamped by the provider layer, regardless of
|
|
358
397
|
// HTTP status — statusless errors (e.g. SDK streaming failures) still carry a
|
|
359
398
|
// reason. Reasons that map cleanly win here; `bad_request`/`unknown` (and a
|
|
360
399
|
// reason-less error) fall through to the status switch + regex battery below.
|
|
361
400
|
if (error instanceof ProviderError && error.reason) {
|
|
362
401
|
const c = reasonToClassification(error.reason, {
|
|
363
|
-
|
|
402
|
+
isManagedRoute,
|
|
403
|
+
credentialSource,
|
|
364
404
|
attribution,
|
|
365
405
|
message,
|
|
406
|
+
providerName: error.provider,
|
|
366
407
|
});
|
|
367
408
|
if (c) {
|
|
368
409
|
return c;
|
|
@@ -376,26 +417,24 @@ function classifyCore(
|
|
|
376
417
|
return contextTooLargeClassification();
|
|
377
418
|
}
|
|
378
419
|
if (error.statusCode === 401 || error.statusCode === 403) {
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
// credential
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
return invalidApiKeyClassification(attribution);
|
|
420
|
+
// Managed routes through the assistant API key; if that credential is
|
|
421
|
+
// stale, the user cannot fix it from model settings. Everything else is
|
|
422
|
+
// a credential the user owns, so the copy names which one to update and
|
|
423
|
+
// the chat banner points at Settings.
|
|
424
|
+
return rejectedCredentialClassification(
|
|
425
|
+
error.provider,
|
|
426
|
+
credentialSource,
|
|
427
|
+
attribution,
|
|
428
|
+
);
|
|
390
429
|
}
|
|
391
430
|
if (error.statusCode === 402) {
|
|
392
|
-
if (
|
|
431
|
+
if (isManagedRoute) {
|
|
393
432
|
return managedBalanceClassification();
|
|
394
433
|
}
|
|
395
434
|
return providerBillingClassification();
|
|
396
435
|
}
|
|
397
436
|
if (error.statusCode === 429) {
|
|
398
|
-
if (isManagedUsageLimitError(
|
|
437
|
+
if (isManagedUsageLimitError(message, isManagedRoute)) {
|
|
399
438
|
return managedUsageLimitClassification();
|
|
400
439
|
}
|
|
401
440
|
return rateLimitClassification();
|
|
@@ -452,7 +491,11 @@ function classifyCore(
|
|
|
452
491
|
// Mirror the 401/403 branch: a credential-shaped 4xx is an
|
|
453
492
|
// "invalid key" surface (banner: "Invalid API key"), distinct
|
|
454
493
|
// from "no key configured" (banner: "API key required").
|
|
455
|
-
return
|
|
494
|
+
return rejectedCredentialClassification(
|
|
495
|
+
error.provider,
|
|
496
|
+
credentialSource,
|
|
497
|
+
attribution,
|
|
498
|
+
);
|
|
456
499
|
}
|
|
457
500
|
if (isImageDimensionsTooLargeError(message)) {
|
|
458
501
|
return {
|
|
@@ -508,7 +551,7 @@ function classifyCore(
|
|
|
508
551
|
}
|
|
509
552
|
|
|
510
553
|
// Regex fallback for non-ProviderError or ProviderError without statusCode
|
|
511
|
-
return classifyByMessage(
|
|
554
|
+
return classifyByMessage(message, isManagedRoute);
|
|
512
555
|
}
|
|
513
556
|
|
|
514
557
|
/**
|
|
@@ -540,17 +583,21 @@ function extractProviderDetail(message: string): string | undefined {
|
|
|
540
583
|
function reasonToClassification(
|
|
541
584
|
reason: ProviderErrorReason,
|
|
542
585
|
args: {
|
|
543
|
-
|
|
586
|
+
isManagedRoute: boolean;
|
|
587
|
+
credentialSource: ProviderCredentialSource;
|
|
544
588
|
attribution?: ConversationErrorAttribution;
|
|
545
589
|
message: string;
|
|
590
|
+
providerName: string;
|
|
546
591
|
},
|
|
547
592
|
): Omit<ClassifiedConversationError, "debugDetails"> | null {
|
|
548
|
-
const managed = args.
|
|
593
|
+
const managed = args.isManagedRoute;
|
|
549
594
|
switch (reason) {
|
|
550
595
|
case "invalid_credentials":
|
|
551
|
-
return
|
|
552
|
-
|
|
553
|
-
|
|
596
|
+
return rejectedCredentialClassification(
|
|
597
|
+
args.providerName,
|
|
598
|
+
args.credentialSource,
|
|
599
|
+
args.attribution,
|
|
600
|
+
);
|
|
554
601
|
case "rate_limited":
|
|
555
602
|
// Match managed usage-limit body patterns, as the legacy path does.
|
|
556
603
|
if (
|
|
@@ -670,18 +717,13 @@ function isStreamingError(message: string): boolean {
|
|
|
670
717
|
return STREAMING_ERROR_PATTERNS.some((p) => p.test(message));
|
|
671
718
|
}
|
|
672
719
|
|
|
673
|
-
function isManagedUsageLimitError(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return MANAGED_USAGE_LIMIT_PATTERNS.some((p) => p.test(message));
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function isManagedBalanceError(error: ProviderError): boolean {
|
|
684
|
-
return getProviderRoutingSource(error.provider) === "managed-proxy";
|
|
720
|
+
function isManagedUsageLimitError(
|
|
721
|
+
message: string,
|
|
722
|
+
isManagedRoute: boolean,
|
|
723
|
+
): boolean {
|
|
724
|
+
return (
|
|
725
|
+
isManagedRoute || MANAGED_USAGE_LIMIT_PATTERNS.some((p) => p.test(message))
|
|
726
|
+
);
|
|
685
727
|
}
|
|
686
728
|
|
|
687
729
|
function isProviderBillingError(message: string): boolean {
|
|
@@ -694,8 +736,14 @@ function managedBalanceClassification(): Omit<
|
|
|
694
736
|
> {
|
|
695
737
|
return {
|
|
696
738
|
code: "PROVIDER_BILLING",
|
|
739
|
+
// This classification feeds two surfaces with different semantics: the
|
|
740
|
+
// terminal provider-error path (the turn ends with no reply) and the
|
|
741
|
+
// non-terminal memory-v3 degraded notice (a normal reply still follows).
|
|
742
|
+
// Keep the wording context-neutral so it is true in both places; the
|
|
743
|
+
// terminal persist site in conversation-agent-loop.ts swaps in
|
|
744
|
+
// assistant-voice copy for the synthetic assistant row.
|
|
697
745
|
userMessage:
|
|
698
|
-
"You'
|
|
746
|
+
"You're out of credits. Add credits in Settings → Billing to continue.",
|
|
699
747
|
retryable: false,
|
|
700
748
|
errorCategory: "credits_exhausted",
|
|
701
749
|
};
|
|
@@ -756,13 +804,21 @@ function managedUsageLimitClassification(): Omit<
|
|
|
756
804
|
};
|
|
757
805
|
}
|
|
758
806
|
|
|
807
|
+
/**
|
|
808
|
+
* Deliberately instruction-free. The assistant API key is provisioned and
|
|
809
|
+
* pushed by the platform — the assistant only ever reads it — so there is no
|
|
810
|
+
* Settings affordance to re-provision it and no user action that would help.
|
|
811
|
+
* The copy's job is to rule out the user's own provider key as the cause;
|
|
812
|
+
* clients that can offer a real next step attach one (web renders Doctor).
|
|
813
|
+
*/
|
|
759
814
|
function managedKeyInvalidClassification(): Omit<
|
|
760
815
|
ClassifiedConversationError,
|
|
761
816
|
"debugDetails"
|
|
762
817
|
> {
|
|
763
818
|
return {
|
|
764
819
|
code: "MANAGED_KEY_INVALID",
|
|
765
|
-
userMessage:
|
|
820
|
+
userMessage:
|
|
821
|
+
"Vellum's managed inference credential was rejected. This isn't a personal provider API key — Vellum provisions this one, so there's nothing to update in Settings.",
|
|
766
822
|
retryable: false,
|
|
767
823
|
errorCategory: "managed_key_invalid",
|
|
768
824
|
};
|
|
@@ -845,6 +901,76 @@ function describeAttribution(
|
|
|
845
901
|
return "";
|
|
846
902
|
}
|
|
847
903
|
|
|
904
|
+
function providerDisplayName(providerName: string): string {
|
|
905
|
+
return (
|
|
906
|
+
PROVIDER_CATALOG.find((provider) => provider.id === providerName)
|
|
907
|
+
?.displayName ?? providerName
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function classificationAttribution(
|
|
912
|
+
attribution: ConversationErrorAttribution | undefined,
|
|
913
|
+
): Pick<ClassifiedConversationError, "connectionName" | "profileName"> {
|
|
914
|
+
return {
|
|
915
|
+
...(attribution?.connectionName
|
|
916
|
+
? { connectionName: attribution.connectionName }
|
|
917
|
+
: {}),
|
|
918
|
+
...(attribution?.profileName
|
|
919
|
+
? { profileName: attribution.profileName }
|
|
920
|
+
: {}),
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function rejectedCredentialClassification(
|
|
925
|
+
providerName: string,
|
|
926
|
+
credentialSource: ProviderCredentialSource,
|
|
927
|
+
attribution?: ConversationErrorAttribution,
|
|
928
|
+
): Omit<ClassifiedConversationError, "debugDetails"> {
|
|
929
|
+
switch (credentialSource) {
|
|
930
|
+
case "vellum-managed":
|
|
931
|
+
return managedKeyInvalidClassification();
|
|
932
|
+
case "oauth-subscription":
|
|
933
|
+
return subscriptionLoginRejectedClassification(providerName, attribution);
|
|
934
|
+
case "no-auth":
|
|
935
|
+
return endpointAuthenticationRequiredClassification(
|
|
936
|
+
providerName,
|
|
937
|
+
attribution,
|
|
938
|
+
);
|
|
939
|
+
case "byok":
|
|
940
|
+
return invalidApiKeyClassification(providerName, attribution);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function endpointAuthenticationRequiredClassification(
|
|
945
|
+
providerName: string,
|
|
946
|
+
attribution?: ConversationErrorAttribution,
|
|
947
|
+
): Omit<ClassifiedConversationError, "debugDetails"> {
|
|
948
|
+
const provider = providerDisplayName(providerName);
|
|
949
|
+
const target = describeAttribution(attribution);
|
|
950
|
+
return {
|
|
951
|
+
code: "PROVIDER_API",
|
|
952
|
+
userMessage: `The ${provider} endpoint${target} requires authentication, but that connection is configured without credentials. Configure authentication for that endpoint in Settings → Models & Services.`,
|
|
953
|
+
retryable: false,
|
|
954
|
+
errorCategory: "provider_endpoint_auth_required",
|
|
955
|
+
...classificationAttribution(attribution),
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function subscriptionLoginRejectedClassification(
|
|
960
|
+
providerName: string,
|
|
961
|
+
attribution?: ConversationErrorAttribution,
|
|
962
|
+
): Omit<ClassifiedConversationError, "debugDetails"> {
|
|
963
|
+
const provider = providerDisplayName(providerName);
|
|
964
|
+
const target = describeAttribution(attribution);
|
|
965
|
+
return {
|
|
966
|
+
code: "PROVIDER_API",
|
|
967
|
+
userMessage: `Your ${provider} subscription login${target} was rejected by ${provider}. Reconnect that account in Settings → Models & Services.`,
|
|
968
|
+
retryable: false,
|
|
969
|
+
errorCategory: "provider_subscription_auth",
|
|
970
|
+
...classificationAttribution(attribution),
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
848
974
|
/**
|
|
849
975
|
* Classification for an invalid (rejected by the upstream provider, e.g.
|
|
850
976
|
* Anthropic 401/403) API key. Distinct from `PROVIDER_NOT_CONFIGURED`
|
|
@@ -853,22 +979,17 @@ function describeAttribution(
|
|
|
853
979
|
* different recovery actions (update vs. add).
|
|
854
980
|
*/
|
|
855
981
|
function invalidApiKeyClassification(
|
|
982
|
+
providerName: string,
|
|
856
983
|
attribution?: ConversationErrorAttribution,
|
|
857
984
|
): Omit<ClassifiedConversationError, "debugDetails"> {
|
|
985
|
+
const provider = providerDisplayName(providerName);
|
|
858
986
|
const target = describeAttribution(attribution);
|
|
859
987
|
return {
|
|
860
988
|
code: "PROVIDER_INVALID_KEY",
|
|
861
|
-
userMessage: target
|
|
862
|
-
? `The API key${target} was rejected by the provider. Update it in Settings → Models & Services.`
|
|
863
|
-
: "Your API key was rejected by the provider. Update it in Settings → Models & Services.",
|
|
989
|
+
userMessage: `Your personal ${provider} API key${target} was rejected by ${provider}. Update that key in Settings → Models & Services.`,
|
|
864
990
|
retryable: false,
|
|
865
991
|
errorCategory: "provider_invalid_key",
|
|
866
|
-
...(attribution
|
|
867
|
-
? { connectionName: attribution.connectionName }
|
|
868
|
-
: {}),
|
|
869
|
-
...(attribution?.profileName
|
|
870
|
-
? { profileName: attribution.profileName }
|
|
871
|
-
: {}),
|
|
992
|
+
...classificationAttribution(attribution),
|
|
872
993
|
};
|
|
873
994
|
}
|
|
874
995
|
|
|
@@ -906,8 +1027,8 @@ function providerNotConfiguredClassification(
|
|
|
906
1027
|
* short-circuit and the status switch both decline to classify.
|
|
907
1028
|
*/
|
|
908
1029
|
function classifyByMessage(
|
|
909
|
-
error: unknown,
|
|
910
1030
|
message: string,
|
|
1031
|
+
isManagedRoute: boolean,
|
|
911
1032
|
): Omit<ClassifiedConversationError, "debugDetails"> {
|
|
912
1033
|
// Empty-request-messages is always an internal construction failure — check
|
|
913
1034
|
// it before the provider/network patterns so a ProviderError that carries the
|
|
@@ -924,7 +1045,7 @@ function classifyByMessage(
|
|
|
924
1045
|
// Check rate limit first (before network, since 429 could match both)
|
|
925
1046
|
for (const pattern of RATE_LIMIT_PATTERNS) {
|
|
926
1047
|
if (pattern.test(message)) {
|
|
927
|
-
if (isManagedUsageLimitError(
|
|
1048
|
+
if (isManagedUsageLimitError(message, isManagedRoute)) {
|
|
928
1049
|
return managedUsageLimitClassification();
|
|
929
1050
|
}
|
|
930
1051
|
return rateLimitClassification();
|
|
@@ -670,6 +670,35 @@ export interface PersistMessageOptions {
|
|
|
670
670
|
* memory or search; see `ProcessMessageOptions.skipUserMessageIndexing`.
|
|
671
671
|
*/
|
|
672
672
|
skipIndexing?: boolean;
|
|
673
|
+
/**
|
|
674
|
+
* True when this turn was auto-sent on the user's behalf rather than typed
|
|
675
|
+
* by them: onboarding research prompts, the personality `<system-message>`,
|
|
676
|
+
* research corrections, hidden kickoff greetings, the legacy pre-chat
|
|
677
|
+
* bootstrap, and `[User action on ...]` surface synthetics.
|
|
678
|
+
*
|
|
679
|
+
* Stamped onto `messages.metadata.scripted` and forwarded to
|
|
680
|
+
* `TurnTelemetryEvent.scripted`, where activation metrics exclude it. This
|
|
681
|
+
* is the consent-independent replacement for classifying turns by
|
|
682
|
+
* text-matching their content in diagnostics-gated traces. That classifier
|
|
683
|
+
* can only see owners who opted into diagnostics, so it silently counted
|
|
684
|
+
* scripted turns as real messages for everyone else (ANT-10).
|
|
685
|
+
*
|
|
686
|
+
* Defaults to `false`: a daemon that knows about the field asserts "the user
|
|
687
|
+
* typed this" for ordinary sends, which is what makes a user's activation
|
|
688
|
+
* measurable. This is only safe because every auto-send path is marked at
|
|
689
|
+
* its source. See the merged-metadata note below for the list.
|
|
690
|
+
*
|
|
691
|
+
* Callers persisting machine-authored content into a `standard` conversation
|
|
692
|
+
* MUST pass `true`. A wrong `false` is trusted downstream and re-inflates
|
|
693
|
+
* activation. (Machine-authored turns in `background` / `scheduled`
|
|
694
|
+
* conversations are already excluded from activation by conversation type,
|
|
695
|
+
* and the `assert_scripted_signals_agree` dbt test catches any straggler
|
|
696
|
+
* whose text matches a known template.)
|
|
697
|
+
*
|
|
698
|
+
* May also be carried in the `metadata` bag, which is how queued sends
|
|
699
|
+
* thread it: the queue round-trips `metadata`, not these options.
|
|
700
|
+
*/
|
|
701
|
+
scripted?: boolean;
|
|
673
702
|
}
|
|
674
703
|
|
|
675
704
|
// ── persistUserMessage ───────────────────────────────────────────────
|
|
@@ -805,15 +834,46 @@ export async function persistQueuedMessageBody(
|
|
|
805
834
|
// The caller-supplied metadata may include it (channel ingress threads it
|
|
806
835
|
// through `Server.processMessage`); we materialize it into the typed
|
|
807
836
|
// `slackMeta` sub-key below when the turn channel is Slack.
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
837
|
+
// `scripted` is pulled out of the raw bag alongside `slackInbound` so the
|
|
838
|
+
// spread below can never re-introduce an unvalidated value. Letting a
|
|
839
|
+
// non-boolean through would be worse than dropping it: sqlite stores it
|
|
840
|
+
// verbatim, and `turn-events-store` narrows anything that isn't 1 to
|
|
841
|
+
// `false`, turning a junk string into a confident "the user typed this".
|
|
842
|
+
const {
|
|
843
|
+
slackInbound: rawSlackInbound,
|
|
844
|
+
scripted: rawScriptedFromMetadata,
|
|
845
|
+
...metadataWithoutSlackInbound
|
|
846
|
+
} = (metadata ?? {}) as Record<string, unknown> & {
|
|
847
|
+
slackInbound?: SlackInboundMessageMetadata;
|
|
848
|
+
scripted?: unknown;
|
|
849
|
+
};
|
|
812
850
|
const slackMeta = buildSlackMetaForPersistence({
|
|
813
851
|
slackInbound: rawSlackInbound,
|
|
814
852
|
turnChannel: turnCtx?.userMessageChannel,
|
|
815
853
|
});
|
|
816
854
|
|
|
855
|
+
// See the `scripted` note on the merged metadata below. Only a real
|
|
856
|
+
// boolean in the bag counts: a stray truthy string must not be read as a
|
|
857
|
+
// scripted assertion.
|
|
858
|
+
const scriptedFromMetadata =
|
|
859
|
+
typeof rawScriptedFromMetadata === "boolean"
|
|
860
|
+
? rawScriptedFromMetadata
|
|
861
|
+
: undefined;
|
|
862
|
+
// `automated` (machine-authored, set by the messaging skill and the memory
|
|
863
|
+
// skill-card) implies scripted: it is by definition not a turn the user
|
|
864
|
+
// typed. Only a DEFAULT: an explicit `scripted` wins, so a caller can
|
|
865
|
+
// mark an automated message as a real turn if that is ever right. Note the
|
|
866
|
+
// two flags are not interchangeable in the other direction: `automated`
|
|
867
|
+
// also suppresses memory extraction, so scripted onboarding turns that
|
|
868
|
+
// should still be indexed must not be marked automated to get counted out.
|
|
869
|
+
const scriptedFromAutomated =
|
|
870
|
+
metadataWithoutSlackInbound.automated === true ? true : undefined;
|
|
871
|
+
const resolvedScripted =
|
|
872
|
+
options.scripted ??
|
|
873
|
+
scriptedFromMetadata ??
|
|
874
|
+
scriptedFromAutomated ??
|
|
875
|
+
false;
|
|
876
|
+
|
|
817
877
|
// Client attribution for turn telemetry, stored under the `client`
|
|
818
878
|
// metadata bag which `turn-events-store` forwards onto
|
|
819
879
|
// `TurnTelemetryEvent.client`. The bag merges two sources per key:
|
|
@@ -853,6 +913,29 @@ export async function persistQueuedMessageBody(
|
|
|
853
913
|
...clientBag,
|
|
854
914
|
...(imageSourcePaths ? { imageSourcePaths } : {}),
|
|
855
915
|
...(slackMeta ? { slackMeta } : {}),
|
|
916
|
+
// Scripted-turn marker, forwarded by `turn-events-store` onto
|
|
917
|
+
// `TurnTelemetryEvent.scripted`. Written LAST so it cannot be
|
|
918
|
+
// half-overwritten by the raw metadata spread above.
|
|
919
|
+
//
|
|
920
|
+
// Resolved from the typed option first, then the metadata bag. The bag
|
|
921
|
+
// is how queued sends carry it, since the queue round-trips `metadata`
|
|
922
|
+
// but not `PersistMessageOptions` (same carrier as the `hidden` flag).
|
|
923
|
+
//
|
|
924
|
+
// Always stamped, including the `false` default: a daemon that knows
|
|
925
|
+
// about the field asserts "the user typed this" for ordinary sends, and
|
|
926
|
+
// that assertion is what makes a user's activation MEASURABLE
|
|
927
|
+
// downstream. Absent would mean "unknown", which is strictly worse
|
|
928
|
+
// information than a truthful false.
|
|
929
|
+
//
|
|
930
|
+
// Defaulting to false is only safe because every auto-send path is now
|
|
931
|
+
// marked at its source: the web onboarding flows (research prompt,
|
|
932
|
+
// kickoff, personality, corrections, legacy bootstrap), `[User action
|
|
933
|
+
// on ...]` surface synthetics, and anything flagged `automated`. A new
|
|
934
|
+
// auto-send path that forgets to mark itself lands here as a false and
|
|
935
|
+
// is believed. The `assert_scripted_signals_agree` dbt test is the
|
|
936
|
+
// backstop: it fires when a turn claiming `false` matches a known
|
|
937
|
+
// scripted template.
|
|
938
|
+
scripted: resolvedScripted,
|
|
856
939
|
};
|
|
857
940
|
|
|
858
941
|
// Materialize each attachment into an attachment-store row up front so the
|
|
@@ -24,12 +24,14 @@ import { extractPreferences } from "../notifications/preference-extractor.js";
|
|
|
24
24
|
import { createPreference } from "../notifications/preferences-store.js";
|
|
25
25
|
import {
|
|
26
26
|
addMessage,
|
|
27
|
+
isEchoSuppressedUserMessage,
|
|
27
28
|
isHiddenMessageMetadata,
|
|
28
29
|
provenanceFromTrustContext,
|
|
29
30
|
recordConversationPersistedSeq,
|
|
30
31
|
setConversationOriginChannelIfUnset,
|
|
31
32
|
setConversationOriginInterfaceIfUnset,
|
|
32
33
|
} from "../persistence/conversation-crud.js";
|
|
34
|
+
import { isReplyPushIneligibleUserMessage } from "../persistence/conversation-types.js";
|
|
33
35
|
import type { ContextWindowResult } from "../plugins/defaults/compaction/window-manager.js";
|
|
34
36
|
import { getCurrentSeq } from "../runtime/assistant-stream-state.js";
|
|
35
37
|
import {
|
|
@@ -63,48 +65,6 @@ import { resolveVerificationSessionIntent } from "./verification-session-intent.
|
|
|
63
65
|
|
|
64
66
|
const log = getLogger("conversation-process");
|
|
65
67
|
|
|
66
|
-
/**
|
|
67
|
-
* Daemon-injected run lifecycle notifications — subagent (`subagentNotification`),
|
|
68
|
-
* ACP run (`acpNotification`), and any wake trigger (the persisted
|
|
69
|
-
* `<background_event source="...">` row every wake reads) — are persisted into
|
|
70
|
-
* the parent conversation so the orchestrator wakes and reads the trigger, but
|
|
71
|
-
* they are internal scaffolding: the user sees the wake through its inline card
|
|
72
|
-
* ("Conversation Woke", or a terminal card for a backgrounded bash run), not a
|
|
73
|
-
* chat turn. Skip the `user_message_echo` broadcast for these so they never
|
|
74
|
-
* render as a live user bubble; the persisted row is filtered from the rendered
|
|
75
|
-
* transcript on the client.
|
|
76
|
-
*
|
|
77
|
-
* Messages explicitly flagged `hidden` (a hidden `POST /messages` send that
|
|
78
|
-
* queued behind an in-flight turn, e.g. the channel-setup wizard-close
|
|
79
|
-
* marker) are suppressed the same way — the immediate route path already
|
|
80
|
-
* skips their echo, and the persisted `hidden` metadata keeps them out of
|
|
81
|
-
* the fetched transcript.
|
|
82
|
-
*/
|
|
83
|
-
export function isEchoSuppressedUserMessage(
|
|
84
|
-
metadata: Record<string, unknown> | undefined,
|
|
85
|
-
): boolean {
|
|
86
|
-
return (
|
|
87
|
-
isHiddenMessageMetadata(metadata) ||
|
|
88
|
-
metadata?.subagentNotification != null ||
|
|
89
|
-
metadata?.acpNotification != null ||
|
|
90
|
-
isBackgroundEventMetadata(metadata)
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* True when the row is a persisted `<background_event source="...">` trigger —
|
|
96
|
-
* every wake, scheduled run, and backgrounded-tool completion stamps one (see
|
|
97
|
-
* {@link persistWakeTriggerMessage}). The permission mode such a turn ran under
|
|
98
|
-
* varies (most run interactive; clientless/headless wakes do not) and is
|
|
99
|
-
* recorded separately in `backgroundEventInteractive`; this predicate only
|
|
100
|
-
* identifies the row as a background event.
|
|
101
|
-
*/
|
|
102
|
-
export function isBackgroundEventMetadata(
|
|
103
|
-
metadata: Record<string, unknown> | undefined,
|
|
104
|
-
): boolean {
|
|
105
|
-
return typeof metadata?.backgroundEventSource === "string";
|
|
106
|
-
}
|
|
107
|
-
|
|
108
68
|
/** Locale-formatted count for the user-facing context stats cards. */
|
|
109
69
|
const fmt = (n: number | undefined) => (n ?? 0).toLocaleString("en-US");
|
|
110
70
|
|
|
@@ -331,6 +291,12 @@ async function buildPassthroughBatch(
|
|
|
331
291
|
if (candidate.transport?.clientOs !== head.transport?.clientOs) {
|
|
332
292
|
break;
|
|
333
293
|
}
|
|
294
|
+
// Same head-wins problem for the app on screen: batching a message sent
|
|
295
|
+
// while a different app was open would run it against the head's
|
|
296
|
+
// `visible_app`, pointing "the app" at the wrong one.
|
|
297
|
+
if (candidate.transport?.visibleAppId !== head.transport?.visibleAppId) {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
334
300
|
if (candidate.sourceActorPrincipalId !== head.sourceActorPrincipalId) {
|
|
335
301
|
break;
|
|
336
302
|
}
|
|
@@ -769,6 +735,7 @@ async function drainSingleMessage(
|
|
|
769
735
|
conversation.applyHostEnvFromTransport(next.transport);
|
|
770
736
|
conversation.applyClientTimezoneFromTransport(next.transport);
|
|
771
737
|
conversation.applyClientOsFromTransport(next.transport);
|
|
738
|
+
conversation.applyVisibleAppFromTransport(next.transport);
|
|
772
739
|
}
|
|
773
740
|
|
|
774
741
|
conversation.currentTurnAuthContext = next.authContext;
|
|
@@ -963,7 +930,11 @@ async function drainSingleMessage(
|
|
|
963
930
|
conversation.emitActivityState("thinking", "context_compacting", {
|
|
964
931
|
requestId: next.requestId,
|
|
965
932
|
});
|
|
966
|
-
|
|
933
|
+
// Push the usage refresh to the queued item's own sink, the one the
|
|
934
|
+
// result card below streams on. `sendToClient` is reset to a no-op once
|
|
935
|
+
// an interactive turn finishes, so a `/compact` draining behind one
|
|
936
|
+
// would otherwise refresh nothing.
|
|
937
|
+
const result = await conversation.forceCompact(next.onEvent);
|
|
967
938
|
const responseText = formatCompactResult(result);
|
|
968
939
|
|
|
969
940
|
const assistantMsg = createAssistantMessage(responseText);
|
|
@@ -1345,6 +1316,7 @@ async function drainBatch(
|
|
|
1345
1316
|
conversation.applyHostEnvFromTransport(head.transport);
|
|
1346
1317
|
conversation.applyClientTimezoneFromTransport(head.transport);
|
|
1347
1318
|
conversation.applyClientOsFromTransport(head.transport);
|
|
1319
|
+
conversation.applyVisibleAppFromTransport(head.transport);
|
|
1348
1320
|
}
|
|
1349
1321
|
|
|
1350
1322
|
conversation.currentTurnAuthContext = head.authContext;
|
|
@@ -1392,6 +1364,11 @@ async function drainBatch(
|
|
|
1392
1364
|
let lastSuccessfulCurrentPage: string | undefined;
|
|
1393
1365
|
let lastSuccessfulContent: string | undefined;
|
|
1394
1366
|
let lastUserMessageId: string | undefined;
|
|
1367
|
+
// `messages.id` of the last member the reply-push producer would actually
|
|
1368
|
+
// notify about. Selected with the producer's own eligibility predicate so a
|
|
1369
|
+
// trailing row it suppresses (a hidden marker, a channel send) cannot stand
|
|
1370
|
+
// in for the prompt ahead of it and swallow that prompt's push.
|
|
1371
|
+
let lastPushEligibleUserMessageId: string | undefined;
|
|
1395
1372
|
// Members whose persist succeeded. `fanOutOnEvent` below must only
|
|
1396
1373
|
// broadcast agent-loop events to these — clients whose persist failed
|
|
1397
1374
|
// already received an error event and must not also receive the
|
|
@@ -1557,6 +1534,10 @@ async function drainBatch(
|
|
|
1557
1534
|
continue;
|
|
1558
1535
|
}
|
|
1559
1536
|
|
|
1537
|
+
if (!isReplyPushIneligibleUserMessage(qm.metadata)) {
|
|
1538
|
+
lastPushEligibleUserMessageId = lastUserMessageId;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1560
1541
|
// Broadcast the user message to all hub subscribers so passive devices
|
|
1561
1542
|
// see each batched user turn before the assistant reply starts streaming.
|
|
1562
1543
|
if (!isEchoSuppressedUserMessage(qm.metadata)) {
|
|
@@ -1693,7 +1674,13 @@ async function drainBatch(
|
|
|
1693
1674
|
isUserMessage?: boolean;
|
|
1694
1675
|
titleText?: string;
|
|
1695
1676
|
isHiddenPrompt?: boolean;
|
|
1696
|
-
|
|
1677
|
+
notifyUserMessageId?: string;
|
|
1678
|
+
} = {
|
|
1679
|
+
isUserMessage: true,
|
|
1680
|
+
};
|
|
1681
|
+
if (lastPushEligibleUserMessageId !== undefined) {
|
|
1682
|
+
drainLoopOptions.notifyUserMessageId = lastPushEligibleUserMessageId;
|
|
1683
|
+
}
|
|
1697
1684
|
// Source interactive flag from the last successfully-persisted sibling so
|
|
1698
1685
|
// a trailing failed tail doesn't flip the agent loop's interactivity.
|
|
1699
1686
|
const lastSuccessfulBatchEntry =
|
|
@@ -1761,6 +1748,26 @@ export interface ProcessMessageOptions {
|
|
|
1761
1748
|
displayContent?: string;
|
|
1762
1749
|
/** JWT-verified committer principal for turn-scoped host-proxy authorization. */
|
|
1763
1750
|
sourceActorPrincipalId?: string;
|
|
1751
|
+
/**
|
|
1752
|
+
* True when this turn was auto-sent on the user's behalf rather than typed
|
|
1753
|
+
* (see `PersistMessageOptions.scripted`). Forwarded to persistence so the
|
|
1754
|
+
* turn is excluded from activation counts. Defaults to false. A caller
|
|
1755
|
+
* sending machine-authored content into a `standard` conversation must set
|
|
1756
|
+
* it explicitly.
|
|
1757
|
+
*
|
|
1758
|
+
* Related to `metadata.automated` below but not the same knob: `automated`
|
|
1759
|
+
* implies scripted (machine-authored is by definition not typed), while
|
|
1760
|
+
* `scripted` carries no memory-indexing side effect. A caller that wants a
|
|
1761
|
+
* turn excluded from activation but still indexed sets this, not that.
|
|
1762
|
+
*/
|
|
1763
|
+
scripted?: boolean;
|
|
1764
|
+
/**
|
|
1765
|
+
* Extra metadata stamped onto the persisted user row alongside the channel
|
|
1766
|
+
* and provenance fields the turn derives. Callers that drive a turn on
|
|
1767
|
+
* someone's behalf use it to mark the row's provenance (e.g. the plugin-api
|
|
1768
|
+
* facade stamps `automated`).
|
|
1769
|
+
*/
|
|
1770
|
+
metadata?: Record<string, unknown>;
|
|
1764
1771
|
}
|
|
1765
1772
|
|
|
1766
1773
|
// ── processMessage ───────────────────────────────────────────────────
|
|
@@ -1785,6 +1792,8 @@ export async function processMessage(
|
|
|
1785
1792
|
overrideProfile,
|
|
1786
1793
|
displayContent,
|
|
1787
1794
|
sourceActorPrincipalId,
|
|
1795
|
+
scripted,
|
|
1796
|
+
metadata: callerMetadata,
|
|
1788
1797
|
} = options;
|
|
1789
1798
|
await conversation.ensureActorScopedHistory();
|
|
1790
1799
|
// Snapshot persona context at turn start so later tool turns can't pick up
|
|
@@ -2057,7 +2066,8 @@ export async function processMessage(
|
|
|
2057
2066
|
conversation.emitActivityState("thinking", "context_compacting", {
|
|
2058
2067
|
requestId,
|
|
2059
2068
|
});
|
|
2060
|
-
|
|
2069
|
+
// Same sink the result card below streams on (see the drain branch).
|
|
2070
|
+
const result = await conversation.forceCompact(onEvent);
|
|
2061
2071
|
const responseText = formatCompactResult(result);
|
|
2062
2072
|
|
|
2063
2073
|
const assistantMsg = createAssistantMessage(responseText);
|
|
@@ -2196,6 +2206,8 @@ export async function processMessage(
|
|
|
2196
2206
|
attachments,
|
|
2197
2207
|
requestId,
|
|
2198
2208
|
displayContent,
|
|
2209
|
+
scripted,
|
|
2210
|
+
...(callerMetadata ? { metadata: callerMetadata } : {}),
|
|
2199
2211
|
});
|
|
2200
2212
|
publishConversationMessagesChanged(conversation.conversationId);
|
|
2201
2213
|
} catch (err) {
|