@vellumai/assistant 0.11.8 → 0.11.9-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/ARCHITECTURE.md +2 -2
- package/Dockerfile +8 -48
- package/docker-entrypoint.sh +5 -1
- package/docker-kata-apt-env.sh +3 -0
- package/docker-kata-apt-shims.sh +127 -0
- package/docker-kata-apt-wrapper.sh +45 -0
- package/docker-kata-chroot-exec.sh +35 -0
- package/docker-kata-pip.sh +8 -2
- package/docs/architecture/memory.md +8 -4
- package/docs/guardian-request-flow.md +18 -14
- package/docs/trusted-contact-access.md +1 -7
- package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
- package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
- package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
- package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
- package/node_modules/@vellumai/service-contracts/package.json +4 -1
- package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
- package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
- package/openapi.yaml +193 -4
- package/package.json +2 -2
- package/src/__tests__/access-request-card-view.test.ts +6 -5
- package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
- package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
- package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
- package/src/__tests__/attachments-store.test.ts +27 -0
- package/src/__tests__/attachments.test.ts +43 -0
- package/src/__tests__/canned-reply-release.test.ts +48 -5
- package/src/__tests__/channel-delivery-store.test.ts +129 -18
- package/src/__tests__/channel-reply-delivery.test.ts +553 -93
- package/src/__tests__/channel-retry-sweep.test.ts +199 -0
- package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
- package/src/__tests__/conversation-error.test.ts +15 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
- package/src/__tests__/conversation-pairing.test.ts +141 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
- package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
- package/src/__tests__/conversation-sync-tags.test.ts +2 -37
- package/src/__tests__/credential-health-service.test.ts +98 -10
- package/src/__tests__/delete-propagation.test.ts +469 -0
- package/src/__tests__/dm-persistence.test.ts +16 -0
- package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
- package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
- package/src/__tests__/gemini-provider.test.ts +46 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
- package/src/__tests__/guardian-gateway-sim.ts +0 -23
- package/src/__tests__/guardian-question-mode.test.ts +108 -0
- package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
- package/src/__tests__/host-proxy-interface.test.ts +10 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
- package/src/__tests__/injector-v3-suppression.test.ts +43 -9
- package/src/__tests__/list-messages-page-latest.test.ts +127 -0
- package/src/__tests__/list-messages-system-card.test.ts +103 -0
- package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
- package/src/__tests__/notification-decision-fallback.test.ts +199 -77
- package/src/__tests__/notification-decision-strategy.test.ts +198 -213
- package/src/__tests__/notification-discord-adapter.test.ts +25 -0
- package/src/__tests__/notification-slack-adapter.test.ts +133 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
- package/src/__tests__/openai-provider.test.ts +5 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
- package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
- package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +11 -0
- package/src/__tests__/process-message-display-content.test.ts +20 -9
- package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
- package/src/__tests__/provider-meta-persistence.test.ts +67 -0
- package/src/__tests__/reaction-persistence.test.ts +22 -198
- package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
- package/src/__tests__/skill-load-tool.test.ts +27 -0
- package/src/__tests__/skills.test.ts +34 -1
- package/src/__tests__/strip-memory-injections.test.ts +3 -4
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/thread-backfill.test.ts +5 -3
- package/src/__tests__/unified-turn-context-location.test.ts +76 -0
- package/src/__tests__/voice-session-bridge.test.ts +18 -0
- package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
- package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
- package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
- package/src/agent/attachments.ts +13 -2
- package/src/agent/loop.ts +3 -19
- package/src/api/README.md +9 -5
- package/src/api/index.ts +9 -8
- package/src/api/package.json +1 -0
- package/src/api/responses/conversation-message.ts +8 -0
- package/src/api/responses/home.ts +7 -18
- package/src/api/surfaces.ts +114 -7
- package/src/approvals/AGENTS.md +1 -1
- package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
- package/src/channels/__tests__/types.test.ts +22 -1
- package/src/channels/gateway-guardian-requests.ts +0 -22
- package/src/channels/types.ts +8 -6
- package/src/cli/__tests__/catalog-search-help.test.ts +18 -0
- package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
- package/src/cli/commands/channels/index.help.ts +19 -6
- package/src/cli/commands/channels/index.ts +6 -2
- package/src/cli/commands/db/__tests__/status.test.ts +22 -0
- package/src/cli/commands/db/index.help.ts +1 -1
- package/src/cli/commands/db/status.ts +172 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
- package/src/cli/commands/platform/connect.ts +14 -5
- package/src/cli/commands/plugins.help.ts +6 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
- package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
- package/src/cli/lib/bundled-marketplace.json +14 -0
- package/src/cli/lib/install-from-github.ts +9 -5
- package/src/cli/lib/install-from-platform.ts +12 -1
- package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
- package/src/config/assistant-initiated-threads-gate.ts +50 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -3
- package/src/config/bundled-skills/schedule/SKILL.md +25 -11
- package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
- package/src/config/call-site-defaults.ts +4 -1
- package/src/config/feature-flag-registry.json +21 -4
- package/src/config/schemas/memory-v3.ts +4 -3
- package/src/context/strip-injections.ts +17 -56
- package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
- package/src/conversations/message-consolidation.ts +4 -3
- package/src/credential-health/credential-health-service.ts +130 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
- package/src/daemon/conversation-error.ts +2 -0
- package/src/daemon/conversation-messaging.ts +195 -47
- package/src/daemon/conversation-process.ts +20 -5
- package/src/daemon/conversation-runtime-assembly.ts +48 -39
- package/src/daemon/conversation-store.ts +159 -11
- package/src/daemon/conversation-tool-setup.ts +9 -2
- package/src/daemon/conversation.ts +290 -19
- package/src/daemon/dictation-text-processing.ts +2 -7
- package/src/daemon/handlers/config-channels.ts +33 -3
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/shared.ts +9 -0
- package/src/daemon/port-oversized-content.test.ts +117 -0
- package/src/daemon/port-oversized-content.ts +110 -0
- package/src/daemon/process-message.ts +87 -16
- package/src/daemon/reaction-record.test.ts +23 -11
- package/src/daemon/reaction-record.ts +6 -9
- package/src/documents/document-store.ts +1 -5
- package/src/home/conversation-starter-validation.ts +1 -5
- package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
- package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
- package/src/live-voice/live-voice-photo.ts +544 -61
- package/src/live-voice/live-voice-session.ts +6 -2
- package/src/live-voice/protocol.ts +20 -1
- package/src/messaging/provider-message-metadata.ts +40 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
- package/src/messaging/providers/channel-transport.ts +28 -0
- package/src/messaging/providers/discord/send.ts +3 -2
- package/src/messaging/providers/slack/api.ts +11 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
- package/src/messaging/providers/slack/message-metadata.ts +141 -1
- package/src/messaging/providers/slack/send.test.ts +58 -0
- package/src/messaging/providers/slack/send.ts +4 -4
- package/src/messaging/providers/slack/transport.ts +28 -2
- package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
- package/src/messaging/providers/telegram-bot/send.ts +93 -0
- package/src/messaging/providers/telegram-bot/transport.ts +71 -0
- package/src/messaging/reaction-envelopes.test.ts +73 -0
- package/src/messaging/reaction-envelopes.ts +33 -5
- package/src/messaging/read-provider-metadata.ts +4 -3
- package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
- package/src/monitoring/recovery/db.ts +35 -0
- package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
- package/src/monitoring/recovery/run-recovery.ts +6 -2
- package/src/monitoring/recovery/stale-processing.ts +3 -20
- package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
- package/src/notifications/AGENTS.md +2 -2
- package/src/notifications/README.md +2 -2
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
- package/src/notifications/__tests__/broadcaster.test.ts +35 -4
- package/src/notifications/__tests__/notification-utils.test.ts +31 -0
- package/src/notifications/access-request-copy.ts +126 -114
- package/src/notifications/adapters/discord.ts +9 -5
- package/src/notifications/adapters/shared.ts +17 -4
- package/src/notifications/adapters/slack.ts +14 -4
- package/src/notifications/adapters/telegram.ts +8 -4
- package/src/notifications/approval-card-data.ts +5 -2
- package/src/notifications/assistant-reply-producer.ts +7 -7
- package/src/notifications/broadcaster.ts +57 -25
- package/src/notifications/conversation-pairing.ts +68 -2
- package/src/notifications/copy-composer.ts +13 -32
- package/src/notifications/decision-engine.ts +56 -237
- package/src/notifications/guardian-delivery-recorder.ts +5 -9
- package/src/notifications/guardian-feed-projection.ts +0 -15
- package/src/notifications/guardian-question-mode.ts +147 -6
- package/src/notifications/notification-utils.ts +117 -1
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -1
- package/src/persistence/conversation-crud.ts +93 -14
- package/src/persistence/conversation-queries.ts +144 -17
- package/src/persistence/conversation-types.ts +39 -5
- package/src/persistence/delivery-crud.ts +224 -28
- package/src/persistence/delivery-status.ts +21 -2
- package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
- package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
- package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
- package/src/persistence/schema/conversations.ts +71 -2
- package/src/persistence/schema-contract.test.ts +78 -0
- package/src/persistence/schema-contract.ts +96 -0
- package/src/persistence/steps.ts +4 -0
- package/src/platform/client.ts +55 -0
- package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
- package/src/plugins/defaults/injector-order.ts +2 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
- package/src/plugins/defaults/memory/injectors.ts +1 -1
- package/src/plugins/defaults/memory/memory-marker.ts +17 -7
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +17 -0
- package/src/plugins/defaults/memory/substrate/skill-content.ts +7 -0
- package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
- package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +22 -0
- package/src/plugins/defaults/memory/v3/injector.ts +14 -15
- package/src/plugins/defaults/memory/v3/prune.test.ts +20 -2
- package/src/plugins/defaults/memory/v3/render-injection.ts +10 -1
- package/src/plugins/defaults/memory/v3/types.ts +15 -4
- package/src/plugins/defaults/turn-context/injectors.ts +3 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
- package/src/plugins/mcp-servers.ts +14 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
- package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
- package/src/providers/anthropic/client.ts +21 -24
- package/src/providers/call-site-routing.ts +5 -4
- package/src/providers/connection-resolution.ts +29 -1
- package/src/providers/content-block-size.test.ts +82 -0
- package/src/providers/content-block-size.ts +99 -0
- package/src/providers/file-block-text.test.ts +64 -0
- package/src/providers/file-block-text.ts +28 -0
- package/src/providers/gemini/client.ts +15 -9
- package/src/providers/inference/auth.ts +6 -6
- package/src/providers/media-resolve.ts +14 -0
- package/src/providers/model-catalog.ts +47 -34
- package/src/providers/openai/chat-completions-provider.ts +9 -25
- package/src/providers/openai/responses-provider.ts +11 -18
- package/src/providers/registry.ts +10 -7
- package/src/providers/routing-identity.ts +2 -1
- package/src/providers/types.ts +1 -4
- package/src/providers/vellum-model-routing.ts +3 -2
- package/src/runtime/AGENTS.md +40 -15
- package/src/runtime/approval-message-composer.ts +1 -6
- package/src/runtime/assistant-event-hub.ts +2 -39
- package/src/runtime/channel-approval-types.ts +1 -5
- package/src/runtime/channel-reply-delivery.ts +182 -115
- package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
- package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
- package/src/runtime/channel-retry-sweep.ts +102 -1
- package/src/runtime/finalize-event-delivery.ts +4 -4
- package/src/runtime/guardian-action-message-composer.ts +1 -4
- package/src/runtime/guardian-reply-router.ts +4 -75
- package/src/runtime/http-router.ts +1 -5
- package/src/runtime/question-request-guardian-bridge.ts +2 -3
- package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
- package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
- package/src/runtime/routes/acp-routes.ts +1 -1
- package/src/runtime/routes/canned-reply-release.ts +19 -9
- package/src/runtime/routes/channel-route-shared.ts +0 -39
- package/src/runtime/routes/channel-verification-routes.ts +4 -1
- package/src/runtime/routes/conversation-list-routes.ts +44 -3
- package/src/runtime/routes/conversation-management-routes.ts +2 -1
- package/src/runtime/routes/conversation-routes.ts +138 -59
- package/src/runtime/routes/diagnostics-routes.ts +111 -60
- package/src/runtime/routes/guardian-action-routes.ts +10 -14
- package/src/runtime/routes/guardian-approval-interception.ts +0 -5
- package/src/runtime/routes/inbound-message-handler.ts +170 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/platform-routes.ts +99 -10
- package/src/runtime/routes/sight-frame-routes.ts +136 -0
- package/src/runtime/sync/resource-sync-events.ts +14 -37
- package/src/runtime/sync/sync-publisher.test.ts +5 -4
- package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
- package/src/tools/client-os.ts +11 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.ts +2 -2
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/tool-input-schemas.ts +2 -0
- package/src/tools/tool-manifest.ts +2 -0
- package/src/tools/ui-surface/definitions.ts +4 -3
- package/src/tools/watch/watch-retro-report.ts +205 -0
- package/src/watch/__tests__/watch-retro.test.ts +268 -40
- package/src/watch/watch-retro.ts +190 -77
- package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
- package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +4 -0
- package/docker-kata-pip-chroot.sh +0 -22
- package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
- package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
- package/src/api/events/conversation-list-invalidated.ts +0 -38
|
@@ -144,12 +144,12 @@ export type ConnectionProvider = string;
|
|
|
144
144
|
export const CHATGPT_SUBSCRIPTION_CONNECTION_NAME = "chatgpt-subscription";
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
|
-
* Provider values that
|
|
148
|
-
*
|
|
149
|
-
* chatgpt
|
|
150
|
-
*
|
|
151
|
-
* profiles carry no provider_connection;
|
|
152
|
-
* not stamp one.
|
|
147
|
+
* Provider values that name a routing identity. Dispatch translates them
|
|
148
|
+
* to a connection row and an upstream factory id via
|
|
149
|
+
* `resolveRoutingIdentity`. `chatgpt` has no adapter factory (upstream is
|
|
150
|
+
* always openai). `vellum` is also a catalog factory id when the derived
|
|
151
|
+
* upstream is vellum. Identity profiles carry no provider_connection;
|
|
152
|
+
* backfill and materialization must not stamp one.
|
|
153
153
|
*/
|
|
154
154
|
export const ROUTING_IDENTITY_PROVIDERS: ReadonlySet<string> = new Set([
|
|
155
155
|
"vellum",
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
sniffImageMimeType,
|
|
29
29
|
} from "../util/image-conversion.js";
|
|
30
30
|
import { getLogger } from "../util/logger.js";
|
|
31
|
+
import { keepFileAsWorkspaceRef } from "./content-block-size.js";
|
|
31
32
|
import {
|
|
32
33
|
attachmentIdFragment,
|
|
33
34
|
type Base64MediaSource,
|
|
@@ -265,6 +266,16 @@ async function resolveImageBlock(
|
|
|
265
266
|
}
|
|
266
267
|
|
|
267
268
|
function resolveFileBlock(block: FileContent): ContentBlock {
|
|
269
|
+
// Video and over-cap text stay a workspace file. Do not load bytes or
|
|
270
|
+
// carry extracted_text into the provider prompt: serializers name the
|
|
271
|
+
// file and stop there.
|
|
272
|
+
if (keepFileAsWorkspaceRef(block.source)) {
|
|
273
|
+
if (block.extracted_text === undefined) {
|
|
274
|
+
return block;
|
|
275
|
+
}
|
|
276
|
+
const { extracted_text: _extracted, ...rest } = block;
|
|
277
|
+
return rest;
|
|
278
|
+
}
|
|
268
279
|
if (block.source.type === "base64") {
|
|
269
280
|
return block;
|
|
270
281
|
}
|
|
@@ -356,6 +367,9 @@ function contentNeedsResolution(
|
|
|
356
367
|
: base64ImageNeedsRewrite(block.source, options);
|
|
357
368
|
}
|
|
358
369
|
if (block.type === "file") {
|
|
370
|
+
if (keepFileAsWorkspaceRef(block.source)) {
|
|
371
|
+
return block.extracted_text !== undefined;
|
|
372
|
+
}
|
|
359
373
|
return block.source.type === "workspace_ref";
|
|
360
374
|
}
|
|
361
375
|
if (block.type === "tool_result" && block.contentBlocks?.length) {
|
|
@@ -631,6 +631,22 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
631
631
|
linkLabel: "Open Google AI Studio",
|
|
632
632
|
},
|
|
633
633
|
models: [
|
|
634
|
+
{
|
|
635
|
+
id: "gemini-3.8-flash",
|
|
636
|
+
displayName: "Gemini 3.8 Flash",
|
|
637
|
+
contextWindowTokens: 1048576,
|
|
638
|
+
maxOutputTokens: 65536,
|
|
639
|
+
supportsThinking: true,
|
|
640
|
+
thinkingFloor: "low",
|
|
641
|
+
supportsCaching: true,
|
|
642
|
+
supportsVision: true,
|
|
643
|
+
supportsToolUse: true,
|
|
644
|
+
pricing: {
|
|
645
|
+
inputPer1mTokens: 1.5,
|
|
646
|
+
outputPer1mTokens: 7.5,
|
|
647
|
+
cacheReadPer1mTokens: 0.15,
|
|
648
|
+
},
|
|
649
|
+
},
|
|
634
650
|
{
|
|
635
651
|
id: "gemini-3.7-flash",
|
|
636
652
|
displayName: "Gemini 3.7 Flash",
|
|
@@ -928,23 +944,6 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
928
944
|
cacheReadPer1mTokens: 0.16,
|
|
929
945
|
},
|
|
930
946
|
},
|
|
931
|
-
{
|
|
932
|
-
id: "accounts/fireworks/models/glm-5p2",
|
|
933
|
-
displayName: "GLM 5.2",
|
|
934
|
-
// Fireworks serves GLM 5.2 with a 1,040K input window.
|
|
935
|
-
contextWindowTokens: 1040000,
|
|
936
|
-
maxOutputTokens: 131072,
|
|
937
|
-
supportsThinking: true,
|
|
938
|
-
supportsCaching: true,
|
|
939
|
-
supportsVision: false,
|
|
940
|
-
supportsToolUse: true,
|
|
941
|
-
maxEffort: "max",
|
|
942
|
-
pricing: {
|
|
943
|
-
inputPer1mTokens: 1.4,
|
|
944
|
-
outputPer1mTokens: 4.4,
|
|
945
|
-
cacheReadPer1mTokens: 0.26,
|
|
946
|
-
},
|
|
947
|
-
},
|
|
948
947
|
{
|
|
949
948
|
id: "accounts/fireworks/models/glm-5p3",
|
|
950
949
|
displayName: "GLM 5.3",
|
|
@@ -984,6 +983,23 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
984
983
|
cacheReadPer1mTokens: 0.029,
|
|
985
984
|
},
|
|
986
985
|
},
|
|
986
|
+
{
|
|
987
|
+
id: "accounts/fireworks/models/glm-5p2",
|
|
988
|
+
displayName: "GLM 5.2",
|
|
989
|
+
// Fireworks serves GLM 5.2 with a 1,040K input window.
|
|
990
|
+
contextWindowTokens: 1040000,
|
|
991
|
+
maxOutputTokens: 131072,
|
|
992
|
+
supportsThinking: true,
|
|
993
|
+
supportsCaching: true,
|
|
994
|
+
supportsVision: false,
|
|
995
|
+
supportsToolUse: true,
|
|
996
|
+
maxEffort: "max",
|
|
997
|
+
pricing: {
|
|
998
|
+
inputPer1mTokens: 1.4,
|
|
999
|
+
outputPer1mTokens: 4.4,
|
|
1000
|
+
cacheReadPer1mTokens: 0.26,
|
|
1001
|
+
},
|
|
1002
|
+
},
|
|
987
1003
|
// Kimi K2.5 (accounts/fireworks/models/kimi-k2p5) is intentionally
|
|
988
1004
|
// absent: Fireworks serves it on-demand/dedicated only, so serverless
|
|
989
1005
|
// chat/completions calls 404 ("not found, inaccessible, and/or not
|
|
@@ -1006,28 +1022,25 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1006
1022
|
cacheReadPer1mTokens: 0.06,
|
|
1007
1023
|
},
|
|
1008
1024
|
},
|
|
1025
|
+
// MiniMax M2.7 (accounts/fireworks/models/minimax-m2p7) is
|
|
1026
|
+
// intentionally absent: Fireworks has no serverless deployment for
|
|
1027
|
+
// it (the model page claims serverless support, but the serving API
|
|
1028
|
+
// returns 404).
|
|
1009
1029
|
{
|
|
1010
|
-
id: "accounts/fireworks/models/
|
|
1011
|
-
displayName: "MiniMax M2.7",
|
|
1012
|
-
contextWindowTokens: 196608,
|
|
1013
|
-
maxOutputTokens: 25000,
|
|
1014
|
-
supportsThinking: false,
|
|
1015
|
-
supportsCaching: false,
|
|
1016
|
-
supportsVision: false,
|
|
1017
|
-
supportsToolUse: true,
|
|
1018
|
-
pricing: { inputPer1mTokens: 0.3, outputPer1mTokens: 1.2 },
|
|
1019
|
-
},
|
|
1020
|
-
{
|
|
1021
|
-
id: "accounts/fireworks/models/deepseek-v4-pro",
|
|
1030
|
+
id: "accounts/fireworks/models/deepseek-v4-pro-0813",
|
|
1022
1031
|
displayName: "DeepSeek V4 Pro",
|
|
1023
1032
|
contextWindowTokens: 1040000,
|
|
1024
1033
|
maxOutputTokens: 131072,
|
|
1025
1034
|
supportsThinking: true,
|
|
1026
|
-
supportsCaching:
|
|
1035
|
+
supportsCaching: true,
|
|
1027
1036
|
supportsVision: false,
|
|
1028
1037
|
supportsToolUse: true,
|
|
1029
1038
|
maxEffort: "max",
|
|
1030
|
-
pricing: {
|
|
1039
|
+
pricing: {
|
|
1040
|
+
inputPer1mTokens: 1.32,
|
|
1041
|
+
outputPer1mTokens: 3.96,
|
|
1042
|
+
cacheReadPer1mTokens: 0.044,
|
|
1043
|
+
},
|
|
1031
1044
|
},
|
|
1032
1045
|
{
|
|
1033
1046
|
id: "accounts/fireworks/models/deepseek-v4-flash-0731",
|
|
@@ -1834,7 +1847,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1834
1847
|
// Z.ai
|
|
1835
1848
|
{
|
|
1836
1849
|
id: "z-ai/glm-5.3",
|
|
1837
|
-
displayName: "GLM
|
|
1850
|
+
displayName: "GLM 5.3",
|
|
1838
1851
|
contextWindowTokens: 1048576,
|
|
1839
1852
|
maxOutputTokens: 131072,
|
|
1840
1853
|
supportsThinking: true,
|
|
@@ -1849,7 +1862,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1849
1862
|
},
|
|
1850
1863
|
{
|
|
1851
1864
|
id: "z-ai/glm-5.3-flash",
|
|
1852
|
-
displayName: "GLM
|
|
1865
|
+
displayName: "GLM 5.3 Flash",
|
|
1853
1866
|
contextWindowTokens: 1310720,
|
|
1854
1867
|
maxOutputTokens: 131072,
|
|
1855
1868
|
supportsThinking: true,
|
|
@@ -1864,7 +1877,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1864
1877
|
},
|
|
1865
1878
|
{
|
|
1866
1879
|
id: "z-ai/glm-5.2",
|
|
1867
|
-
displayName: "GLM
|
|
1880
|
+
displayName: "GLM 5.2",
|
|
1868
1881
|
contextWindowTokens: 1048576,
|
|
1869
1882
|
maxOutputTokens: 131072,
|
|
1870
1883
|
supportsThinking: true,
|
|
@@ -7,7 +7,8 @@ import { getLogger } from "../../util/logger.js";
|
|
|
7
7
|
import { isChatTemplateFailureError } from "../../util/provider-error-patterns.js";
|
|
8
8
|
import { extractRetryAfterMs } from "../../util/retry.js";
|
|
9
9
|
import { partialTagSuffix as sharedPartialTagSuffix } from "../../util/think-tag-stream.js";
|
|
10
|
-
import {
|
|
10
|
+
import { clampProviderString } from "../content-block-size.js";
|
|
11
|
+
import { fileBlockToProviderText } from "../file-block-text.js";
|
|
11
12
|
import {
|
|
12
13
|
base64Source,
|
|
13
14
|
mediaSourceByteLength,
|
|
@@ -691,9 +692,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
691
692
|
private requestHeaders: Record<string, string>;
|
|
692
693
|
private parseThinkTags: boolean;
|
|
693
694
|
private assistantReasoningField:
|
|
694
|
-
| "
|
|
695
|
-
| "reasoning_content"
|
|
696
|
-
| undefined;
|
|
695
|
+
"reasoning" | "reasoning_content" | undefined;
|
|
697
696
|
private coerceObjectArgsToJsonString: boolean;
|
|
698
697
|
private omitToolChoiceWhenReasoning: boolean;
|
|
699
698
|
|
|
@@ -741,15 +740,12 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
741
740
|
const modelOverride = configObj?.model as string | undefined;
|
|
742
741
|
const effort = configObj?.effort as string | undefined;
|
|
743
742
|
const logitBias = configObj?.logit_bias as
|
|
744
|
-
|
|
745
|
-
| undefined;
|
|
743
|
+
Record<string, number> | undefined;
|
|
746
744
|
const topP = configObj?.top_p as number | undefined;
|
|
747
745
|
const usageAttributionHeaders = configObj?.usageAttributionHeaders as
|
|
748
|
-
|
|
749
|
-
| undefined;
|
|
746
|
+
Record<string, string> | undefined;
|
|
750
747
|
const perRequestHeaders = configObj?.requestHeaders as
|
|
751
|
-
|
|
752
|
-
| undefined;
|
|
748
|
+
Record<string, string> | undefined;
|
|
753
749
|
|
|
754
750
|
// Per-tool keys whose object schemas were rewritten to JSON strings for the
|
|
755
751
|
// wire, to be decoded back on the response. Empty unless
|
|
@@ -1580,14 +1576,14 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
1580
1576
|
): OpenAI.Chat.Completions.ChatCompletionUserMessageParam {
|
|
1581
1577
|
// If only a single text block, use plain string (simpler, fewer tokens)
|
|
1582
1578
|
if (blocks.length === 1 && blocks[0].type === "text") {
|
|
1583
|
-
return { role: "user", content: blocks[0].text };
|
|
1579
|
+
return { role: "user", content: clampProviderString(blocks[0].text) };
|
|
1584
1580
|
}
|
|
1585
1581
|
|
|
1586
1582
|
const parts: OpenAI.Chat.Completions.ChatCompletionContentPart[] = [];
|
|
1587
1583
|
for (const block of blocks) {
|
|
1588
1584
|
switch (block.type) {
|
|
1589
1585
|
case "text":
|
|
1590
|
-
parts.push({ type: "text", text: block.text });
|
|
1586
|
+
parts.push({ type: "text", text: clampProviderString(block.text) });
|
|
1591
1587
|
break;
|
|
1592
1588
|
case "image":
|
|
1593
1589
|
if (!OPENAI_SUPPORTED_IMAGE_TYPES.has(block.source.media_type)) {
|
|
@@ -1622,7 +1618,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
1622
1618
|
} else {
|
|
1623
1619
|
parts.push({
|
|
1624
1620
|
type: "text",
|
|
1625
|
-
text:
|
|
1621
|
+
text: fileBlockToProviderText(block),
|
|
1626
1622
|
});
|
|
1627
1623
|
}
|
|
1628
1624
|
break;
|
|
@@ -1641,16 +1637,4 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
1641
1637
|
|
|
1642
1638
|
return { role: "user", content: parts };
|
|
1643
1639
|
}
|
|
1644
|
-
|
|
1645
|
-
private fileBlockToText(
|
|
1646
|
-
block: Extract<ContentBlock, { type: "file" }>,
|
|
1647
|
-
): string {
|
|
1648
|
-
const header = `<attached_file name="${escapeXmlAttr(
|
|
1649
|
-
block.source.filename ?? "",
|
|
1650
|
-
)}" type="${escapeXmlAttr(block.source.media_type)}" />`;
|
|
1651
|
-
if (block.extracted_text && block.extracted_text.trim().length > 0) {
|
|
1652
|
-
return `${header}\n${block.extracted_text}`;
|
|
1653
|
-
}
|
|
1654
|
-
return `${header}\nNo extracted text available.`;
|
|
1655
|
-
}
|
|
1656
1640
|
}
|
|
@@ -5,7 +5,8 @@ import { isAbortReason } from "../../util/abort-reasons.js";
|
|
|
5
5
|
import { ProviderError, type ProviderErrorReason } from "../../util/errors.js";
|
|
6
6
|
import { getLogger } from "../../util/logger.js";
|
|
7
7
|
import { extractRetryAfterMs } from "../../util/retry.js";
|
|
8
|
-
import {
|
|
8
|
+
import { clampProviderString } from "../content-block-size.js";
|
|
9
|
+
import { fileBlockToProviderText } from "../file-block-text.js";
|
|
9
10
|
import { base64Source, resolveMediaReferences } from "../media-resolve.js";
|
|
10
11
|
import { PROMPT_CACHE_BREAKPOINT_MODEL_IDS } from "../model-catalog.js";
|
|
11
12
|
import { recordProviderRequestDiagnostics } from "../request-diagnostics.js";
|
|
@@ -229,8 +230,7 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
229
230
|
const effort = configObj?.effort as string | undefined;
|
|
230
231
|
const verbosity = configObj?.verbosity as string | undefined;
|
|
231
232
|
const usageAttributionHeaders = configObj?.usageAttributionHeaders as
|
|
232
|
-
|
|
233
|
-
| undefined;
|
|
233
|
+
Record<string, string> | undefined;
|
|
234
234
|
const disableCache = configObj?.disableCache === true;
|
|
235
235
|
const disableTurnStartCache = configObj?.disableTurnStartCache === true;
|
|
236
236
|
const promptCacheKey =
|
|
@@ -1003,7 +1003,9 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
1003
1003
|
return {
|
|
1004
1004
|
type: "message",
|
|
1005
1005
|
role: "user",
|
|
1006
|
-
content: [
|
|
1006
|
+
content: [
|
|
1007
|
+
{ type: "input_text", text: clampProviderString(blocks[0].text) },
|
|
1008
|
+
],
|
|
1007
1009
|
};
|
|
1008
1010
|
}
|
|
1009
1011
|
|
|
@@ -1011,7 +1013,10 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
1011
1013
|
for (const block of blocks) {
|
|
1012
1014
|
switch (block.type) {
|
|
1013
1015
|
case "text":
|
|
1014
|
-
parts.push({
|
|
1016
|
+
parts.push({
|
|
1017
|
+
type: "input_text",
|
|
1018
|
+
text: clampProviderString(block.text),
|
|
1019
|
+
});
|
|
1015
1020
|
break;
|
|
1016
1021
|
case "image":
|
|
1017
1022
|
if (!OPENAI_SUPPORTED_IMAGE_TYPES.has(block.source.media_type)) {
|
|
@@ -1030,7 +1035,7 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
1030
1035
|
case "file":
|
|
1031
1036
|
parts.push({
|
|
1032
1037
|
type: "input_text",
|
|
1033
|
-
text:
|
|
1038
|
+
text: fileBlockToProviderText(block),
|
|
1034
1039
|
});
|
|
1035
1040
|
break;
|
|
1036
1041
|
case "server_tool_use":
|
|
@@ -1051,16 +1056,4 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
1051
1056
|
content: parts,
|
|
1052
1057
|
};
|
|
1053
1058
|
}
|
|
1054
|
-
|
|
1055
|
-
private fileBlockToText(
|
|
1056
|
-
block: Extract<ContentBlock, { type: "file" }>,
|
|
1057
|
-
): string {
|
|
1058
|
-
const header = `<attached_file name="${escapeXmlAttr(
|
|
1059
|
-
block.source.filename ?? "",
|
|
1060
|
-
)}" type="${escapeXmlAttr(block.source.media_type)}" />`;
|
|
1061
|
-
if (block.extracted_text && block.extracted_text.trim().length > 0) {
|
|
1062
|
-
return `${header}\n${block.extracted_text}`;
|
|
1063
|
-
}
|
|
1064
|
-
return `${header}\nNo extracted text available.`;
|
|
1065
|
-
}
|
|
1066
1059
|
}
|
|
@@ -313,16 +313,19 @@ export async function resolveProviderFromConnection(
|
|
|
313
313
|
// For every other connection this is `undefined` and the effective provider
|
|
314
314
|
// is the connection's own — no behavior change.
|
|
315
315
|
const effectiveProvider = opts.providerOverride ?? connection.provider;
|
|
316
|
-
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
|
|
316
|
+
const model = opts.model ?? resolveModel(config, effectiveProvider);
|
|
317
|
+
// Routing identities must already be translated to a factory id
|
|
318
|
+
// (`resolveRoutingIdentity`). `chatgpt` has no factory and must not
|
|
319
|
+
// reach adapter construction. `vellum` is a catalog factory, so it is
|
|
320
|
+
// a valid effectiveProvider after that translation.
|
|
321
|
+
if (
|
|
322
|
+
ROUTING_IDENTITY_PROVIDERS.has(effectiveProvider) &&
|
|
323
|
+
!PROVIDER_CATALOG.some((entry) => entry.id === effectiveProvider)
|
|
324
|
+
) {
|
|
321
325
|
throw new Error(
|
|
322
|
-
`resolveProviderFromConnection received unresolved routing identity "${effectiveProvider}"
|
|
326
|
+
`resolveProviderFromConnection received unresolved routing identity "${effectiveProvider}": translate to a real upstream before adapter construction`,
|
|
323
327
|
);
|
|
324
328
|
}
|
|
325
|
-
const model = opts.model ?? resolveModel(config, effectiveProvider);
|
|
326
329
|
const cacheKey = getConnectionProviderCacheKey(
|
|
327
330
|
connection,
|
|
328
331
|
model,
|
|
@@ -39,7 +39,8 @@ export class ConnectionResolutionError extends ConfigError {
|
|
|
39
39
|
| "model_incompatible"
|
|
40
40
|
| "missing_credential"
|
|
41
41
|
| "platform_unauthenticated"
|
|
42
|
-
| "unroutable_managed_model"
|
|
42
|
+
| "unroutable_managed_model"
|
|
43
|
+
| "adapter_unavailable",
|
|
43
44
|
message: string,
|
|
44
45
|
options?: { cause?: unknown; model?: string; profileName?: string },
|
|
45
46
|
) {
|
package/src/providers/types.ts
CHANGED
|
@@ -429,10 +429,7 @@ export interface SendMessageConfig {
|
|
|
429
429
|
/**
|
|
430
430
|
* When true, the TURN-STARTING user message carries content that will not
|
|
431
431
|
* recur byte-identically on the next turn, so a long-TTL breakpoint placed
|
|
432
|
-
* on it could never be read back across turns.
|
|
433
|
-
* producer: it sets the flag from the history it is about to send, when the
|
|
434
|
-
* turn-starting message carries a memory-v3 `<memory_spotlight>` block (the
|
|
435
|
-
* one injected block strip-and-replaced from every user message each turn).
|
|
432
|
+
* on it could never be read back across turns.
|
|
436
433
|
*
|
|
437
434
|
* The flag describes the turn, not the request, so it holds for every
|
|
438
435
|
* request the turn makes, including tool-loop iterations, whose trailing
|
|
@@ -37,8 +37,9 @@ export const MANAGED_ROUTABLE_PROVIDERS: ReadonlySet<string> = new Set(
|
|
|
37
37
|
* connection. Unlike the per-provider `*-managed` connections, this one does
|
|
38
38
|
* not name an upstream provider on its DB row — the upstream is determined
|
|
39
39
|
* per-request from the resolving profile. The same id is the catalog owner
|
|
40
|
-
* of Vellum-hosted GPU models. Dispatch
|
|
41
|
-
*
|
|
40
|
+
* of Vellum-hosted GPU models. Dispatch translates the identity to a
|
|
41
|
+
* factory id (including `vellum` when that is the catalog owner) before
|
|
42
|
+
* adapter lookup.
|
|
42
43
|
*/
|
|
43
44
|
export const VELLUM_MANAGED_PROVIDER = "vellum";
|
|
44
45
|
|
package/src/runtime/AGENTS.md
CHANGED
|
@@ -20,8 +20,18 @@ Channel inbound turns (Slack/Telegram/etc. — `processChannelMessageInBackgroun
|
|
|
20
20
|
|
|
21
21
|
**Invariant:** do NOT "fix" this by routing channel turns through `conversation.enqueueMessage` — the drain has no channel-callback delivery, so the reply would run but never reach the channel. A channel turn that still hits `CONVERSATION_BUSY_MESSAGE` (a non-channel turn raced in after admission) is re-scheduled for the channel-retry sweep via `deferRetryUntilIdle` — never `recordProcessingFailure` (which `classifyError` treats as fatal → dead-letter → silent drop, JARVIS-1346). The sweep is itself busy-aware: it `deferRetryUntilIdle`s a retry whose conversation is mid-turn. Busy deferral must **not** burn the retry budget: `deferRetryUntilIdle` pushes `retryAfter` forward but never increments `processingAttempts` and never dead-letters, so a conversation that stays busy across many sweeps re-defers indefinitely rather than dropping the reply at `RETRY_MAX_ATTEMPTS` (~10 min). Crash durability: while the in-memory admission waits, the inbound row sits `pending`; a crash mid-wait is recovered at startup by `recoverOrphanedChannelEvents` (`monitoring/recovery/`), which promotes boot-fenced orphan `pending` rows onto the sweep's `failed` retry path.
|
|
22
22
|
|
|
23
|
+
**A growing reply is channel-generic, and what it leaves behind is not.** `channel-reply-session.ts` owns the shared half: consuming the turn's event stream once, accumulating text across LLM calls, coalescing, tracking the plan from `ui_show`/`ui_update`, and deciding open/append/stop. Everything platform-shaped belongs to the transport. A channel declares it can carry a growing reply by implementing `streamReply` at all; whether THIS conversation can is answered by the transport's own reply to `start` (Slack refuses a turn with no thread, Telegram a chat that is not private), which arrives as an ordinary not-ok and falls back to sending the finished reply. A channel that caps how much one operation may carry declares that as `maxStreamTextChars`; the session does the splitting, because it is what tracks how much the channel has actually taken and that mark may only advance once per confirmed operation. A transport that split a wide delta into several calls of its own would leave the session unable to tell a partial delivery from a whole one, and a failure part-way through would re-send the part that already landed.
|
|
24
|
+
|
|
25
|
+
The load-bearing distinction is `streamPersists`. Slack finalizes its streamed message in place, so the stream IS the reply and durable delivery must not send it again. Telegram's draft is a 30-second preview that evaporates, so the reply is still owed and goes out the ordinary path. Two things follow, and both are easy to get wrong: a preview reports `fallback` at finish even though it really streamed, and a preview's stream id is never handed to `onStreamOpen`, because the crash-recovery breadcrumb exists to reconcile against a message the reader can still see. Recording a preview's id would point recovery at a message that never existed and lose the reply instead of posting it. Omitting `streamPersists` is the safe default: at worst the reply repeats, never disappears.
|
|
26
|
+
|
|
27
|
+
**Delivery has its own crash window, and its own recovery.** A turn is marked `processing_status = 'processed'` as soon as it persists, while its reply delivery finalizes afterwards in memory, so a crash in between strands the row `processed` + `delivery_status = 'pending'`. Neither existing path recovers that: the sweep selects `delivery_status = 'failed'`, and the orphan step above selects `processing_status = 'pending'`. `recoverStrandedDeliveryEvents` (`monitoring/recovery/`) closes it, promoting boot-fenced rows whose stored payload names both a `replyCallbackUrl` and a `replyMessageId` onto the delivery-retry arm. That payload requirement is load-bearing, not a cheap filter: it keeps the step off the intercept-settled rows (reactions, edits, admission denials) which legitimately end `processed` + `pending` with no reply owed. The corollary for new code: any path that finishes a channel turn WITHOUT delivering must settle its own `deliveryStatus` rather than leaving it `pending`, or this step will re-post a reply that was never owed. The deduplicated-ingress skip in `background-dispatch.ts` calls `markDeliveryDelivered` for exactly that reason.
|
|
28
|
+
|
|
23
29
|
**Faithful replay:** the sweep reconstructs a turn from the stored raw payload, so it must produce the SAME turn the live ingress path would have run — not an impoverished one. Two invariants, each learned from a live-path hardening change that originally skipped the sweep: (1) **content fencing** — non-guardian content is wrapped in `<external_content>` via the shared `prepareChannelInboundContent` (`routes/inbound-stages/inbound-content-prep.ts`), used by BOTH `inbound-message-handler.ts` and the sweep; replaying raw, unwrapped text would drop the untrusted-content boundary the model relies on (regression window: #30785 wrapped only the live path). (2) **idempotency key + slackMeta** — the live turn captures its `slackInbound` onto the stored payload (`storeInboundSlackMetadata`) and the sweep replays that EXACT object (`parseStoredSlackInbound`), so `deriveIngressIdempotencyKey` yields a byte-identical `client_message_id` (a replay of an already-persisted turn dedups the agent loop) and full slackMeta survives the replay. Dedup is not enough on its own: on a dedup hit the sweep gates `finalizeEventDelivery` on `isDeduplicatedDeliveryOwnedBySibling` — skipping delivery when a sibling event already owns it, so it never double-posts — and, when the deduped turn crashed before writing any reply, completes it with a fresh run rather than delivering nothing. A `buildReplaySlackInbound` fallback reconstructs the key-bearing fields for payloads stored before the capture existed (regression window: #38378 added the key to the live path only, though the sweep IS the "Slack retry" path it targeted). Any future hardening applied at channel ingress must be mirrored in the sweep, or a retried/recovered turn silently loses it.
|
|
24
30
|
|
|
31
|
+
**Reaction wake turns are the deliberate exception to the sweep's processing lane.** A reaction an admitted actor ADDS to the assistant's own post dispatches a discretion turn through `processChannelMessageInBackground` (`inbound-stages/reaction-intercept.ts`, `buildReactionWakeTurn`): the turn's persisted user row carries the reaction envelope through the ordinary ingress carriers (`channelInbound` for every channel whose lane accepts it, the transitional `slackReactionRowMeta` for Slack), so the row reads as a reaction everywhere, and its content is the same line the reload renderer produces. The reply's destination is resolved from the target row's own thread rather than the reaction event's callback, which names the reacted message as its thread.
|
|
32
|
+
|
|
33
|
+
These turns are at-most-once BY DESIGN: the sweep's processing lane rebuilds a turn from its stored payload as a plain message, which would corrupt a reaction into a fabricated user message. So the event is marked processed up front, and the payload it stores is delivery-only (callback, chat, assistant id): enough for the delivery lane to re-post a reply whose first delivery failed, and carrying no content or channel, so no processing replay can fabricate a turn from it. A post-admission busy loss degrades through the dispatch's `onTurnLostToBusy` hook to the passive transcript row instead of the sweep. Do not "fix" a lost reaction turn by widening that payload into a replayable one - teach the sweep to rebuild reaction turns first.
|
|
34
|
+
|
|
25
35
|
### SSE backpressure shedding must be observable
|
|
26
36
|
|
|
27
37
|
SSE handlers built on `ReadableStream` shed slow subscribers when `controller.desiredSize <= 0` to keep daemon memory bounded. Every shed site must emit a log line + Sentry capture so the daemon-side shed can be time-correlated with the client-side idle watchdog (otherwise stalls are invisible from both sides). See [WHATWG Streams — Backpressure](https://streams.spec.whatwg.org/#pipe-chains) and [Node `monitorEventLoopDelay`](https://nodejs.org/api/perf_hooks.html#perf_hooksmonitoreventloopdelayoptions).
|
|
@@ -160,7 +170,7 @@ All CDP-backed browser tools (`browser_navigate`, `browser_snapshot`, `browser_s
|
|
|
160
170
|
|
|
161
171
|
### Interactive requests on channels (approvals, questions)
|
|
162
172
|
|
|
163
|
-
**The guardian-request pipeline is the canonical rail for anything interactive on a channel
|
|
173
|
+
**The guardian-request pipeline is the canonical rail for anything interactive on a channel**: cards with buttons, request-code replies, typed answers. The end-to-end map (promotion → gateway `guardian_requests` row → notification broadcaster → per-channel adapters → reply router → decision primitive → per-kind resolver) lives in [docs/guardian-request-flow.md](../../docs/guardian-request-flow.md). New interactive features extend that pipeline's seams; do NOT add per-feature watchers, callback schemes, or inbound intercepts.
|
|
164
174
|
|
|
165
175
|
Identifiers and plumbing notes:
|
|
166
176
|
|
|
@@ -204,22 +214,37 @@ the gateway's Channel Identity Vocabulary, which covers the wire side.
|
|
|
204
214
|
channel, including one this repo has no code for, which is why a new
|
|
205
215
|
channel belongs here rather than in a sixth key of its own.
|
|
206
216
|
|
|
207
|
-
Every
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
itself through `mergeProviderMessageMetadata`
|
|
211
|
-
(`inbound-stages/edit-intercept.ts`, `inbound-message-handler.ts`), and an
|
|
212
|
-
outbound assistant reply is stamped with a partial envelope at reserve time
|
|
213
|
-
(`buildAssistantChannelMetadata`) whose `messageId` (and, for a reply
|
|
217
|
+
Every row the daemon authors writes it on every channel, Slack included:
|
|
218
|
+
an outbound assistant reply is stamped with a partial envelope at reserve
|
|
219
|
+
time (`buildAssistantChannelMetadata`) whose `messageId` (and, for a reply
|
|
214
220
|
split into several posts, `additionalMessageIds`) the post-send
|
|
215
221
|
reconciliation in `channel-reply-delivery.ts` back-fills from the
|
|
216
|
-
transport's delivery results,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
`
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
transport's delivery results, the assistant's own reaction rows write it
|
|
223
|
+
(`daemon/reaction-record.ts`), and bot-authored Slack backfill rows write
|
|
224
|
+
it. Every channel except Slack writes it for inbound rows too: a reaction
|
|
225
|
+
row carries the whole shape (`inbound-stages/reaction-intercept.ts`), and
|
|
226
|
+
an edit or a delete stamps `editedAt` / `deletedAt` onto whatever the row
|
|
227
|
+
already said about itself through `mergeProviderMessageMetadata`
|
|
228
|
+
(`inbound-stages/edit-intercept.ts`, `inbound-message-handler.ts`). The same reconciliation writes each id into
|
|
229
|
+
the `channel_outbound_posts` index (the outbound counterpart of
|
|
230
|
+
`channel_inbound_events`' provider-id resolution), which is what lets a
|
|
231
|
+
later reaction or delete naming the assistant's own post resolve back to
|
|
232
|
+
its row exactly; the envelope stays the row's self-description, and the
|
|
233
|
+
capped envelope scan in `findMessageByProviderMessageId` survives only as
|
|
234
|
+
the transitional fallback for rows reconciled before the table. A reply
|
|
235
|
+
still pending for the retry sweep that was reserved with Slack's own
|
|
236
|
+
pre-send envelope converges onto this envelope when that reconciliation
|
|
237
|
+
stamps it (`providerMetadataOfPreSendSlackEnvelope`, transitional in the
|
|
238
|
+
same way). Inbound Slack rows still write `slackMeta` (transitional: the
|
|
239
|
+
end state is this
|
|
240
|
+
envelope on every Slack row, with `slackMeta` as the read-compat arm for
|
|
241
|
+
historical rows). The two envelopes map onto each other on read, in both
|
|
242
|
+
directions: `readProviderMetadata` serves a `slackMeta` row as this shape
|
|
243
|
+
to the channel-agnostic readers in `persistence/delivery-crud.ts`, and
|
|
244
|
+
`readSlackMetadataFromMessageMetadata` serves a neutral row as Slack's
|
|
245
|
+
view (`slackViewOfProviderMetadata`, Slack's own fields riding the
|
|
246
|
+
schema's passthrough) to the Slack renderers and backfill readers, so no
|
|
247
|
+
reader on either side carries a per-envelope branch.
|
|
223
248
|
|
|
224
249
|
### Channel verification: gateway-owned
|
|
225
250
|
|
|
@@ -40,11 +40,6 @@ export function composeApprovalMessage(
|
|
|
40
40
|
return getFallbackMessage(context);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** @internal Exported for use by the daemon-injected generator implementation. */
|
|
44
|
-
export function escapeRegExp(input: string): string {
|
|
45
|
-
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
43
|
/** @internal Exported for use by the daemon-injected generator implementation. */
|
|
49
44
|
export function includesRequiredKeywords(
|
|
50
45
|
text: string,
|
|
@@ -54,7 +49,7 @@ export function includesRequiredKeywords(
|
|
|
54
49
|
return true;
|
|
55
50
|
}
|
|
56
51
|
return requiredKeywords.every((keyword) => {
|
|
57
|
-
const re = new RegExp(`\\b${
|
|
52
|
+
const re = new RegExp(`\\b${RegExp.escape(keyword)}\\b`, "i");
|
|
58
53
|
return re.test(text);
|
|
59
54
|
});
|
|
60
55
|
}
|
|
@@ -338,9 +338,7 @@ export class AssistantEventHub {
|
|
|
338
338
|
* it receive the event; untargeted events go to all
|
|
339
339
|
* - if `targetInterfaceId` is set, only client subscribers whose
|
|
340
340
|
* `interfaceId` matches receive the event; process subscribers and
|
|
341
|
-
* non-matching clients are skipped.
|
|
342
|
-
* broadcasts (e.g. `conversation_list_invalidated`) to a specific
|
|
343
|
-
* client surface during a migration window.
|
|
341
|
+
* non-matching clients are skipped.
|
|
344
342
|
*
|
|
345
343
|
* Fanout is isolated: a throwing or rejecting subscriber does not abort
|
|
346
344
|
* delivery to remaining subscribers.
|
|
@@ -744,13 +742,7 @@ export function broadcastMessage(
|
|
|
744
742
|
const targetClientId = options?.targetClientId;
|
|
745
743
|
const targetInterfaceId = options?.targetInterfaceId;
|
|
746
744
|
|
|
747
|
-
|
|
748
|
-
// it unscoped so every subscriber refreshes its sidebar.
|
|
749
|
-
const scopedConversationId =
|
|
750
|
-
msg.type === "conversation_list_invalidated"
|
|
751
|
-
? undefined
|
|
752
|
-
: resolvedConversationId;
|
|
753
|
-
const event = buildAssistantEvent(msg, scopedConversationId);
|
|
745
|
+
const event = buildAssistantEvent(msg, resolvedConversationId);
|
|
754
746
|
const targetCapability = capabilityForMessageType(msg.type);
|
|
755
747
|
// Self-echo suppression: a `sync_changed` carrying an `originClientId`
|
|
756
748
|
// means a specific client just mutated the resource. The hub must not
|
|
@@ -780,35 +772,6 @@ export function broadcastMessage(
|
|
|
780
772
|
stampAndBuffer(event, { targeting: publishOptions });
|
|
781
773
|
_hubChain = _hubChain
|
|
782
774
|
.then(() => assistantEventHub.publish(event, publishOptions))
|
|
783
|
-
.then(() => {
|
|
784
|
-
// When a conversation title changes, also publish a
|
|
785
|
-
// `conversation_list_invalidated` so the macOS sidebar refreshes
|
|
786
|
-
// its row ordering for the renamed conversation. Web consumes the
|
|
787
|
-
// paired `sync_changed` with `conversation:<id>:metadata` tag
|
|
788
|
-
// emitted by `publishConversationTitleChanged` and patches the
|
|
789
|
-
// single row in place, so the broadcast is scoped to macOS only.
|
|
790
|
-
//
|
|
791
|
-
// TODO(electron-cutover): remove this emission once macOS migrates
|
|
792
|
-
// to the Electron client and consumes `sync_changed` directly. At
|
|
793
|
-
// that point `conversation_list_invalidated` has no remaining
|
|
794
|
-
// consumers and the message type can be retired.
|
|
795
|
-
if (msg.type === "conversation_title_updated") {
|
|
796
|
-
return assistantEventHub
|
|
797
|
-
.publish(
|
|
798
|
-
buildAssistantEvent({
|
|
799
|
-
type: "conversation_list_invalidated",
|
|
800
|
-
reason: "renamed",
|
|
801
|
-
}),
|
|
802
|
-
{ targetInterfaceId: "macos" },
|
|
803
|
-
)
|
|
804
|
-
.catch((err: unknown) => {
|
|
805
|
-
log.warn(
|
|
806
|
-
{ err },
|
|
807
|
-
"Failed to publish conversation_list_invalidated after title update",
|
|
808
|
-
);
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
})
|
|
812
775
|
.catch((err: unknown) => {
|
|
813
776
|
log.warn({ err }, "assistant-events hub subscriber threw during publish");
|
|
814
777
|
});
|
|
@@ -166,11 +166,7 @@ export interface ChannelApprovalPrompt {
|
|
|
166
166
|
* joins (every channel's buttons ride the same `apr:` callback), and no
|
|
167
167
|
* consumer reads a channel off this field.
|
|
168
168
|
*/
|
|
169
|
-
export type ApprovalDecisionSource =
|
|
170
|
-
| "button"
|
|
171
|
-
| "reaction"
|
|
172
|
-
| "vellum_surface"
|
|
173
|
-
| "plain_text";
|
|
169
|
+
export type ApprovalDecisionSource = "button" | "vellum_surface" | "plain_text";
|
|
174
170
|
|
|
175
171
|
/** The structured result of a user's approval decision. */
|
|
176
172
|
export interface ApprovalDecisionResult {
|