@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
|
@@ -186,15 +186,29 @@ export function seedInferenceProfiles(
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
// Profile ordering
|
|
189
|
+
// Profile ordering: ensure all seeded profiles appear in the order array.
|
|
190
|
+
// A managed key absent from a workspace's order is placed next to the
|
|
191
|
+
// sibling that precedes it in the catalog rather than appended, so the
|
|
192
|
+
// managed profiles stay grouped in catalog order even on a workspace whose
|
|
193
|
+
// order already lists custom profiles. Entries already in the order keep
|
|
194
|
+
// their relative positions, so a user arrangement survives.
|
|
190
195
|
const profileOrder = Array.isArray(llm.profileOrder)
|
|
191
196
|
? (llm.profileOrder as string[])
|
|
192
197
|
: [];
|
|
193
|
-
const
|
|
194
|
-
for (const name of
|
|
195
|
-
if (
|
|
198
|
+
const managedNames = Object.keys(MANAGED_PROFILE_TEMPLATES);
|
|
199
|
+
for (const [index, name] of managedNames.entries()) {
|
|
200
|
+
if (profileOrder.includes(name)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const anchor = managedNames
|
|
204
|
+
.slice(0, index)
|
|
205
|
+
.reverse()
|
|
206
|
+
.map((sibling) => profileOrder.indexOf(sibling))
|
|
207
|
+
.find((position) => position >= 0);
|
|
208
|
+
if (anchor === undefined) {
|
|
196
209
|
profileOrder.push(name);
|
|
197
|
-
|
|
210
|
+
} else {
|
|
211
|
+
profileOrder.splice(anchor + 1, 0, name);
|
|
198
212
|
}
|
|
199
213
|
}
|
|
200
214
|
llm.profileOrder = profileOrder;
|
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
} from "../security/untrusted-content.js";
|
|
15
15
|
import { safeStringSlice } from "../util/unicode.js";
|
|
16
16
|
|
|
17
|
-
/** Minimum content length (chars) before a tool result is eligible for truncation. ~
|
|
18
|
-
export const THRESHOLD_CHARS =
|
|
17
|
+
/** Minimum content length (chars) before a tool result is eligible for truncation. ~6250 tokens at 4 chars/token. */
|
|
18
|
+
export const THRESHOLD_CHARS = 25_000;
|
|
19
19
|
|
|
20
20
|
/** Target size (chars) for the truncated stub. ~300 tokens at 4 chars/token. */
|
|
21
21
|
export const TARGET_CHARS = 1_200;
|
|
@@ -381,3 +381,57 @@ describe("system-card boundaries", () => {
|
|
|
381
381
|
expect(findDisplayTurnEndIndex(rows, 0)).toBe(0);
|
|
382
382
|
});
|
|
383
383
|
});
|
|
384
|
+
|
|
385
|
+
describe("provider-error boundaries", () => {
|
|
386
|
+
const errorMeta = JSON.stringify({
|
|
387
|
+
messageKind: "provider_error",
|
|
388
|
+
providerErrorCode: "PROVIDER_BILLING",
|
|
389
|
+
providerErrorCategory: "credits_exhausted",
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("a provider-error row never merges into the preceding assistant run", () => {
|
|
393
|
+
const { messages } = mergeConsecutiveAssistantMessages([
|
|
394
|
+
makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
|
|
395
|
+
makeMsg(
|
|
396
|
+
"assistant",
|
|
397
|
+
JSON.stringify([{ type: "text", text: "You're out of credits." }]),
|
|
398
|
+
{ metadata: errorMeta },
|
|
399
|
+
),
|
|
400
|
+
]);
|
|
401
|
+
expect(messages).toHaveLength(2);
|
|
402
|
+
expect(messages[1]!.metadata).toBe(errorMeta);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("an assistant row never merges into a preceding provider-error row", () => {
|
|
406
|
+
const { messages } = mergeConsecutiveAssistantMessages([
|
|
407
|
+
makeMsg(
|
|
408
|
+
"assistant",
|
|
409
|
+
JSON.stringify([{ type: "text", text: "You're out of credits." }]),
|
|
410
|
+
{ metadata: errorMeta },
|
|
411
|
+
),
|
|
412
|
+
makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
|
|
413
|
+
]);
|
|
414
|
+
expect(messages).toHaveLength(2);
|
|
415
|
+
expect(messages[0]!.metadata).toBe(errorMeta);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
test("findDisplayTurnEndIndex treats a provider-error row as a single-row turn", () => {
|
|
419
|
+
const rows = [
|
|
420
|
+
makeMsg("assistant", JSON.stringify([{ type: "text", text: "error" }]), {
|
|
421
|
+
metadata: errorMeta,
|
|
422
|
+
}),
|
|
423
|
+
makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
|
|
424
|
+
];
|
|
425
|
+
expect(findDisplayTurnEndIndex(rows, 0)).toBe(0);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("findDisplayTurnEndIndex stops an assistant run before a provider-error row", () => {
|
|
429
|
+
const rows = [
|
|
430
|
+
makeMsg("assistant", JSON.stringify([{ type: "text", text: "reply" }])),
|
|
431
|
+
makeMsg("assistant", JSON.stringify([{ type: "text", text: "error" }]), {
|
|
432
|
+
metadata: errorMeta,
|
|
433
|
+
}),
|
|
434
|
+
];
|
|
435
|
+
expect(findDisplayTurnEndIndex(rows, 0)).toBe(0);
|
|
436
|
+
});
|
|
437
|
+
});
|
|
@@ -32,20 +32,21 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import type { MessageRow } from "../persistence/conversation-crud.js";
|
|
35
|
-
import {
|
|
35
|
+
import { isStandaloneAssistantMessage } from "../persistence/conversation-crud.js";
|
|
36
36
|
import type { ContentBlock } from "../providers/types.js";
|
|
37
37
|
import { getLogger } from "../util/logger.js";
|
|
38
38
|
|
|
39
39
|
const log = getLogger("message-consolidation");
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* True when
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* True when an assistant row is a standalone display turn (system card or
|
|
43
|
+
* provider-error notice) that never merges with adjacent assistant rows in
|
|
44
|
+
* either direction. Merging one into an adjacent run would let the anchor's
|
|
45
|
+
* metadata win and drop the `messageKind` marker from the wire (or stamp it
|
|
46
|
+
* onto a bubble containing real assistant text).
|
|
46
47
|
*/
|
|
47
|
-
|
|
48
|
-
return
|
|
48
|
+
function isStandaloneAssistantRow(msg: MessageRow): boolean {
|
|
49
|
+
return isStandaloneAssistantMessage(msg.role, msg.metadata);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
// ── Block predicates ────────────────────────────────────────────────
|
|
@@ -120,8 +121,9 @@ export function findDisplayTurnEndIndex(
|
|
|
120
121
|
if (messages[startIdx]?.role !== "assistant") {
|
|
121
122
|
return startIdx;
|
|
122
123
|
}
|
|
123
|
-
//
|
|
124
|
-
|
|
124
|
+
// System cards and provider-error rows are single-row display turns that
|
|
125
|
+
// never span neighbours.
|
|
126
|
+
if (isStandaloneAssistantRow(messages[startIdx]!)) {
|
|
125
127
|
return startIdx;
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -131,7 +133,7 @@ export function findDisplayTurnEndIndex(
|
|
|
131
133
|
if (!next) {
|
|
132
134
|
break;
|
|
133
135
|
}
|
|
134
|
-
if (next.role === "assistant" && !
|
|
136
|
+
if (next.role === "assistant" && !isStandaloneAssistantRow(next)) {
|
|
135
137
|
endIdx += 1;
|
|
136
138
|
continue;
|
|
137
139
|
}
|
|
@@ -297,15 +299,15 @@ export function mergeConsecutiveAssistantMessages(messages: MessageRow[]): {
|
|
|
297
299
|
|
|
298
300
|
for (const msg of messages) {
|
|
299
301
|
const lastIdx = result.length - 1;
|
|
300
|
-
// System cards stay standalone in both
|
|
301
|
-
// into the preceding assistant run, and the
|
|
302
|
-
// never folds into
|
|
302
|
+
// System cards and provider-error rows stay standalone in both
|
|
303
|
+
// directions: neither folds into the preceding assistant run, and the
|
|
304
|
+
// following assistant row never folds into them.
|
|
303
305
|
const isConsecutiveAssistant =
|
|
304
306
|
msg.role === "assistant" &&
|
|
305
|
-
!
|
|
307
|
+
!isStandaloneAssistantRow(msg) &&
|
|
306
308
|
lastIdx >= 0 &&
|
|
307
309
|
result[lastIdx].role === "assistant" &&
|
|
308
|
-
!
|
|
310
|
+
!isStandaloneAssistantRow(result[lastIdx]);
|
|
309
311
|
|
|
310
312
|
if (!isConsecutiveAssistant) {
|
|
311
313
|
result.push(msg);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiring tests for the `chat.assistant_reply` emit in `runDeferredTurnTail`:
|
|
3
|
+
* which turn outcomes reach the producer, what the tail hands it, and that the
|
|
4
|
+
* emit can neither block nor break turn finalization.
|
|
5
|
+
*/
|
|
6
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
|
|
9
|
+
import { setConfig } from "../../__tests__/helpers/set-config.js";
|
|
10
|
+
import type { InflightContentWriter } from "../inflight-message-content.js";
|
|
11
|
+
|
|
12
|
+
// The finalize module's import graph reaches the memory indexer; keep it inert
|
|
13
|
+
// so no embedding backend is touched.
|
|
14
|
+
setConfig("memory", { enabled: false, v2: { enabled: false } });
|
|
15
|
+
|
|
16
|
+
const CONVERSATION_ID = "conv-tail-1";
|
|
17
|
+
const ASSISTANT_MESSAGE_ID = "msg-assistant-1";
|
|
18
|
+
const USER_MESSAGE_ID = "msg-user-1";
|
|
19
|
+
|
|
20
|
+
interface EmitCall {
|
|
21
|
+
conversationId: string;
|
|
22
|
+
assistantMessageId: string;
|
|
23
|
+
userMessageId: string | undefined;
|
|
24
|
+
replyDeliveredInAppOnly?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const emitCalls: EmitCall[] = [];
|
|
28
|
+
/** Ordered trace of the tail's deferred work, for the ordering assertion. */
|
|
29
|
+
const trace: string[] = [];
|
|
30
|
+
let producerBehavior: "resolve" | "reject" | "hang" = "resolve";
|
|
31
|
+
|
|
32
|
+
mock.module("../../notifications/assistant-reply-producer.js", () => ({
|
|
33
|
+
emitAssistantReplyNotification: (params: EmitCall): Promise<void> => {
|
|
34
|
+
emitCalls.push({
|
|
35
|
+
conversationId: params.conversationId,
|
|
36
|
+
assistantMessageId: params.assistantMessageId,
|
|
37
|
+
userMessageId: params.userMessageId,
|
|
38
|
+
...("replyDeliveredInAppOnly" in params
|
|
39
|
+
? { replyDeliveredInAppOnly: params.replyDeliveredInAppOnly }
|
|
40
|
+
: {}),
|
|
41
|
+
});
|
|
42
|
+
trace.push("emit");
|
|
43
|
+
if (producerBehavior === "reject") {
|
|
44
|
+
const rejected = Promise.reject(new Error("simulated producer failure"));
|
|
45
|
+
// The call site fires the producer without awaiting it, so nothing in
|
|
46
|
+
// the tail observes this rejection. Settle it here so the failure stays
|
|
47
|
+
// a test fixture rather than a process-level unhandled rejection.
|
|
48
|
+
rejected.catch(() => {});
|
|
49
|
+
return rejected;
|
|
50
|
+
}
|
|
51
|
+
if (producerBehavior === "hang") {
|
|
52
|
+
return new Promise<void>(() => {});
|
|
53
|
+
}
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
},
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const realCrud = await import("../../persistence/conversation-crud.js");
|
|
59
|
+
mock.module("../../persistence/conversation-crud.js", () => ({
|
|
60
|
+
...realCrud,
|
|
61
|
+
// No conversation row: the tail's truncation and disk-sync steps
|
|
62
|
+
// short-circuit, keeping these tests on the notify wiring.
|
|
63
|
+
getConversation: () => null,
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
const { runDeferredTurnTail } =
|
|
67
|
+
await import("../conversation-turn-finalize.js");
|
|
68
|
+
|
|
69
|
+
const rlog = makeMockLogger() as Parameters<
|
|
70
|
+
typeof runDeferredTurnTail
|
|
71
|
+
>[0]["rlog"];
|
|
72
|
+
|
|
73
|
+
async function runTail(overrides: {
|
|
74
|
+
turnCompleted: boolean;
|
|
75
|
+
lastAssistantMessageId?: string | undefined;
|
|
76
|
+
deferredFinalizeEffects?: ReadonlyArray<() => Promise<void>>;
|
|
77
|
+
userMessageId?: string | undefined;
|
|
78
|
+
replyDeliveredInAppOnly?: boolean;
|
|
79
|
+
}): Promise<void> {
|
|
80
|
+
await runDeferredTurnTail({
|
|
81
|
+
ctx: { conversationId: CONVERSATION_ID, messages: [] },
|
|
82
|
+
state: {
|
|
83
|
+
deferredFinalizeEffects: overrides.deferredFinalizeEffects ?? [],
|
|
84
|
+
lastAssistantMessageId:
|
|
85
|
+
"lastAssistantMessageId" in overrides
|
|
86
|
+
? overrides.lastAssistantMessageId
|
|
87
|
+
: ASSISTANT_MESSAGE_ID,
|
|
88
|
+
inflightWriters: new Map<string, InflightContentWriter>(),
|
|
89
|
+
},
|
|
90
|
+
rlog,
|
|
91
|
+
generationCompletedAt: Date.now(),
|
|
92
|
+
turnCompleted: overrides.turnCompleted,
|
|
93
|
+
userMessageId:
|
|
94
|
+
"userMessageId" in overrides ? overrides.userMessageId : USER_MESSAGE_ID,
|
|
95
|
+
...(overrides.replyDeliveredInAppOnly
|
|
96
|
+
? { replyDeliveredInAppOnly: true }
|
|
97
|
+
: {}),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
beforeEach(() => {
|
|
102
|
+
emitCalls.length = 0;
|
|
103
|
+
trace.length = 0;
|
|
104
|
+
producerBehavior = "resolve";
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("runDeferredTurnTail assistant-reply notification", () => {
|
|
108
|
+
test("emits once for a completed turn", async () => {
|
|
109
|
+
await runTail({ turnCompleted: true });
|
|
110
|
+
|
|
111
|
+
expect(emitCalls).toEqual([
|
|
112
|
+
{
|
|
113
|
+
conversationId: CONVERSATION_ID,
|
|
114
|
+
assistantMessageId: ASSISTANT_MESSAGE_ID,
|
|
115
|
+
userMessageId: USER_MESSAGE_ID,
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("forwards the app-only delivery marker to the producer", async () => {
|
|
121
|
+
await runTail({ turnCompleted: true, replyDeliveredInAppOnly: true });
|
|
122
|
+
|
|
123
|
+
expect(emitCalls).toEqual([
|
|
124
|
+
{
|
|
125
|
+
conversationId: CONVERSATION_ID,
|
|
126
|
+
assistantMessageId: ASSISTANT_MESSAGE_ID,
|
|
127
|
+
userMessageId: USER_MESSAGE_ID,
|
|
128
|
+
replyDeliveredInAppOnly: true,
|
|
129
|
+
},
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("omits the app-only delivery marker for an ordinary turn", async () => {
|
|
134
|
+
await runTail({ turnCompleted: true });
|
|
135
|
+
|
|
136
|
+
expect(emitCalls[0]).not.toHaveProperty("replyDeliveredInAppOnly");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("stays silent for a handed-off or cancelled turn", async () => {
|
|
140
|
+
await runTail({ turnCompleted: false });
|
|
141
|
+
|
|
142
|
+
expect(emitCalls).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("stays silent when the turn produced no assistant row", async () => {
|
|
146
|
+
await runTail({ turnCompleted: true, lastAssistantMessageId: undefined });
|
|
147
|
+
|
|
148
|
+
expect(emitCalls).toEqual([]);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("emits after the deferred finalize effects have run", async () => {
|
|
152
|
+
await runTail({
|
|
153
|
+
turnCompleted: true,
|
|
154
|
+
deferredFinalizeEffects: [
|
|
155
|
+
async () => {
|
|
156
|
+
trace.push("effect-1");
|
|
157
|
+
},
|
|
158
|
+
async () => {
|
|
159
|
+
trace.push("effect-2");
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(trace).toEqual(["effect-1", "effect-2", "emit"]);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("completes without awaiting the producer, even when it never settles", async () => {
|
|
168
|
+
producerBehavior = "hang";
|
|
169
|
+
|
|
170
|
+
await runTail({ turnCompleted: true });
|
|
171
|
+
|
|
172
|
+
expect(emitCalls).toHaveLength(1);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("survives a failing producer", async () => {
|
|
176
|
+
producerBehavior = "reject";
|
|
177
|
+
|
|
178
|
+
await runTail({ turnCompleted: true });
|
|
179
|
+
|
|
180
|
+
expect(emitCalls).toHaveLength(1);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runDeferredTurnTail` runs after the terminal SSE, so the user can delete the
|
|
3
|
+
* conversation while its awaited bookkeeping is still draining. These tests pin
|
|
4
|
+
* that the tail's two filesystem steps (tool-result spooling and the JSONL disk
|
|
5
|
+
* mirror) read the conversation fresh and skip once it is gone, rather than
|
|
6
|
+
* rebuilding a deleted conversation's directory from in-memory history.
|
|
7
|
+
*/
|
|
8
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
+
|
|
10
|
+
import { makeMockLogger } from "../../__tests__/helpers/mock-logger.js";
|
|
11
|
+
import { setConfig } from "../../__tests__/helpers/set-config.js";
|
|
12
|
+
import type { ConversationRow } from "../../persistence/conversation-crud.js";
|
|
13
|
+
import type { Message } from "../../providers/types.js";
|
|
14
|
+
import type { InflightContentWriter } from "../inflight-message-content.js";
|
|
15
|
+
|
|
16
|
+
// The finalize module's import graph reaches the memory indexer; keep it inert
|
|
17
|
+
// so no embedding backend is touched.
|
|
18
|
+
setConfig("memory", { enabled: false, v2: { enabled: false } });
|
|
19
|
+
|
|
20
|
+
const CONVERSATION_ID = "conv-tail-delete-1";
|
|
21
|
+
const ASSISTANT_MESSAGE_ID = "msg-assistant-1";
|
|
22
|
+
const USER_MESSAGE_ID = "msg-user-1";
|
|
23
|
+
const CONVERSATION_DIR = "/tmp/vellum-test/conv-tail-delete-1";
|
|
24
|
+
|
|
25
|
+
/** Flipped by a deferred finalize effect to model a mid-tail deletion. */
|
|
26
|
+
let conversationDeleted = false;
|
|
27
|
+
let getConversationCalls = 0;
|
|
28
|
+
const resolvedDirCalls: string[] = [];
|
|
29
|
+
const truncateCalls: string[] = [];
|
|
30
|
+
const diskSyncCalls: string[] = [];
|
|
31
|
+
|
|
32
|
+
const TRUNCATED_MESSAGES: Message[] = [{ role: "user", content: [] }];
|
|
33
|
+
|
|
34
|
+
mock.module("../../notifications/assistant-reply-producer.js", () => ({
|
|
35
|
+
emitAssistantReplyNotification: (): Promise<void> => Promise.resolve(),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
function makeConversation(): ConversationRow {
|
|
39
|
+
return {
|
|
40
|
+
id: CONVERSATION_ID,
|
|
41
|
+
title: null,
|
|
42
|
+
createdAt: 1700000000000,
|
|
43
|
+
updatedAt: 1700000000000,
|
|
44
|
+
totalInputTokens: 0,
|
|
45
|
+
totalOutputTokens: 0,
|
|
46
|
+
totalEstimatedCost: 0,
|
|
47
|
+
contextSummary: null,
|
|
48
|
+
contextCompactedMessageCount: 0,
|
|
49
|
+
contextCompactedAt: null,
|
|
50
|
+
historyStrippedAt: null,
|
|
51
|
+
slackContextCompactionWatermarkTs: null,
|
|
52
|
+
slackContextCompactionWatermarkAt: null,
|
|
53
|
+
conversationType: "chat",
|
|
54
|
+
source: "user",
|
|
55
|
+
originChannel: null,
|
|
56
|
+
originInterface: null,
|
|
57
|
+
forkParentConversationId: null,
|
|
58
|
+
forkParentMessageId: null,
|
|
59
|
+
isAutoTitle: 0,
|
|
60
|
+
scheduleJobId: null,
|
|
61
|
+
lastMessageAt: null,
|
|
62
|
+
archivedAt: null,
|
|
63
|
+
surfacedAt: null,
|
|
64
|
+
inferenceProfile: null,
|
|
65
|
+
enabledPlugins: null,
|
|
66
|
+
inferenceProfileSessionId: null,
|
|
67
|
+
inferenceProfileExpiresAt: null,
|
|
68
|
+
lastNotifiedInferenceProfile: null,
|
|
69
|
+
processingStartedAt: null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const realCrud = await import("../../persistence/conversation-crud.js");
|
|
74
|
+
mock.module("../../persistence/conversation-crud.js", () => ({
|
|
75
|
+
...realCrud,
|
|
76
|
+
getConversation: (): ConversationRow | null => {
|
|
77
|
+
getConversationCalls += 1;
|
|
78
|
+
return conversationDeleted ? null : makeConversation();
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
const realDirectories =
|
|
83
|
+
await import("../../persistence/conversation-directories.js");
|
|
84
|
+
mock.module("../../persistence/conversation-directories.js", () => ({
|
|
85
|
+
...realDirectories,
|
|
86
|
+
getResolvedConversationDirPath: (id: string): string => {
|
|
87
|
+
resolvedDirCalls.push(id);
|
|
88
|
+
return CONVERSATION_DIR;
|
|
89
|
+
},
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
const realTruncation =
|
|
93
|
+
await import("../../context/post-turn-tool-result-truncation.js");
|
|
94
|
+
mock.module("../../context/post-turn-tool-result-truncation.js", () => ({
|
|
95
|
+
...realTruncation,
|
|
96
|
+
derefToolResultReReads: (messages: Message[]) => ({
|
|
97
|
+
messages,
|
|
98
|
+
dereferencedCount: 0,
|
|
99
|
+
}),
|
|
100
|
+
postTurnTruncateToolResults: (
|
|
101
|
+
_messages: Message[],
|
|
102
|
+
options: { conversationDir: string },
|
|
103
|
+
) => {
|
|
104
|
+
truncateCalls.push(options.conversationDir);
|
|
105
|
+
return { messages: TRUNCATED_MESSAGES, truncatedCount: 0 };
|
|
106
|
+
},
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const realDiskView =
|
|
110
|
+
await import("../../persistence/conversation-disk-view.js");
|
|
111
|
+
mock.module("../../persistence/conversation-disk-view.js", () => ({
|
|
112
|
+
...realDiskView,
|
|
113
|
+
syncMessageToDisk: (_conversationId: string, messageId: string): void => {
|
|
114
|
+
diskSyncCalls.push(messageId);
|
|
115
|
+
},
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
const { runDeferredTurnTail } =
|
|
119
|
+
await import("../conversation-turn-finalize.js");
|
|
120
|
+
|
|
121
|
+
const rlog = makeMockLogger() as Parameters<
|
|
122
|
+
typeof runDeferredTurnTail
|
|
123
|
+
>[0]["rlog"];
|
|
124
|
+
|
|
125
|
+
/** Runs the tail with one deferred effect standing in for the turn's bookkeeping. */
|
|
126
|
+
async function runTail(
|
|
127
|
+
deferredEffect: () => Promise<void>,
|
|
128
|
+
): Promise<Message[]> {
|
|
129
|
+
const ctx = { conversationId: CONVERSATION_ID, messages: [] as Message[] };
|
|
130
|
+
await runDeferredTurnTail({
|
|
131
|
+
ctx,
|
|
132
|
+
state: {
|
|
133
|
+
deferredFinalizeEffects: [deferredEffect],
|
|
134
|
+
lastAssistantMessageId: ASSISTANT_MESSAGE_ID,
|
|
135
|
+
inflightWriters: new Map<string, InflightContentWriter>(),
|
|
136
|
+
},
|
|
137
|
+
rlog,
|
|
138
|
+
generationCompletedAt: Date.now(),
|
|
139
|
+
turnCompleted: true,
|
|
140
|
+
userMessageId: USER_MESSAGE_ID,
|
|
141
|
+
});
|
|
142
|
+
return ctx.messages;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
beforeEach(() => {
|
|
146
|
+
conversationDeleted = false;
|
|
147
|
+
getConversationCalls = 0;
|
|
148
|
+
resolvedDirCalls.length = 0;
|
|
149
|
+
truncateCalls.length = 0;
|
|
150
|
+
diskSyncCalls.length = 0;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("runDeferredTurnTail conversation deleted mid-tail", () => {
|
|
154
|
+
test("skips the filesystem steps when the conversation is gone", async () => {
|
|
155
|
+
const messages = await runTail(async () => {
|
|
156
|
+
conversationDeleted = true;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(resolvedDirCalls).toEqual([]);
|
|
160
|
+
expect(truncateCalls).toEqual([]);
|
|
161
|
+
expect(diskSyncCalls).toEqual([]);
|
|
162
|
+
expect(messages).toEqual([]);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("reads the conversation once more before the filesystem steps", async () => {
|
|
166
|
+
await runTail(async () => {
|
|
167
|
+
conversationDeleted = true;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(getConversationCalls).toBe(2);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("runs the filesystem steps when the conversation survives", async () => {
|
|
174
|
+
const messages = await runTail(async () => {});
|
|
175
|
+
|
|
176
|
+
expect(resolvedDirCalls).toEqual([CONVERSATION_ID]);
|
|
177
|
+
expect(truncateCalls).toEqual([CONVERSATION_DIR]);
|
|
178
|
+
expect(diskSyncCalls).toEqual([ASSISTANT_MESSAGE_ID]);
|
|
179
|
+
expect(messages).toEqual(TRUNCATED_MESSAGES);
|
|
180
|
+
});
|
|
181
|
+
});
|