@vellumai/assistant 0.8.12 → 0.9.0-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/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +219 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/mock-gateway-ipc.ts +23 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/resolve-app-id.test.ts +56 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/bundler/package-resolver.ts +0 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +167 -3
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +28 -11
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +152 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +91 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for the compaction
|
|
2
|
+
* Tests for the compaction route + projection.
|
|
3
3
|
*
|
|
4
4
|
* The handler is exercised against a fake `LlmRequestLogSource` so we
|
|
5
5
|
* can pin (a) the BadRequestError / NotFoundError branches that don't
|
|
6
|
-
* involve any logs, and (b) the happy path that returns
|
|
7
|
-
*
|
|
8
|
-
* directly so
|
|
9
|
-
* to a different log source.
|
|
6
|
+
* involve any logs, and (b) the happy path that returns the projected
|
|
7
|
+
* compaction(s) with the expected shape. The projection functions are
|
|
8
|
+
* unit-tested directly so their branches stay covered even if the
|
|
9
|
+
* handler ever swaps to a different log source.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -35,13 +35,17 @@ mock.module("../../../config/loader.js", () => ({
|
|
|
35
35
|
// ---------------------------------------------------------------------
|
|
36
36
|
|
|
37
37
|
import type { CompactionLogEvent } from "../../../memory/compaction-log-store-clickhouse.js";
|
|
38
|
-
import type {
|
|
38
|
+
import type {
|
|
39
|
+
CompactionAgentLogRow,
|
|
40
|
+
LogMetaRow,
|
|
41
|
+
} from "../../../memory/llm-request-log-store.js";
|
|
39
42
|
|
|
40
43
|
interface FakeSourceState {
|
|
41
44
|
conversation: { id: string } | null;
|
|
42
|
-
selectedCall:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
selectedCall: LogMetaRow | null;
|
|
46
|
+
/** `createdAt` of the previous real call, or null = no earlier call. */
|
|
47
|
+
previousNonCompactionCallCreatedAt: number | null;
|
|
48
|
+
compactionLogs: CompactionAgentLogRow[];
|
|
45
49
|
/** null = compactionLogs destination not configured (legacy-only). */
|
|
46
50
|
compactionStoreEvents: CompactionLogEvent[] | null;
|
|
47
51
|
compactionStoreError: Error | null;
|
|
@@ -50,7 +54,7 @@ interface FakeSourceState {
|
|
|
50
54
|
const state: FakeSourceState = {
|
|
51
55
|
conversation: null,
|
|
52
56
|
selectedCall: null,
|
|
53
|
-
|
|
57
|
+
previousNonCompactionCallCreatedAt: null,
|
|
54
58
|
compactionLogs: [],
|
|
55
59
|
compactionStoreEvents: null,
|
|
56
60
|
compactionStoreError: null,
|
|
@@ -59,10 +63,10 @@ const state: FakeSourceState = {
|
|
|
59
63
|
// Records the inputs the handler passed to its collaborators so tests
|
|
60
64
|
// can pin the windowing plumbing without relying on the projection.
|
|
61
65
|
const sourceCalls = {
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
getRequestLogMetaByIdArgs: [] as string[],
|
|
67
|
+
getPreviousNonCompactionCallCreatedAtArgs: [] as Array<{
|
|
64
68
|
conversationId: string;
|
|
65
|
-
|
|
69
|
+
beforeCreatedAt: number;
|
|
66
70
|
}>,
|
|
67
71
|
getCompactionLogsBetweenArgs: [] as Array<{
|
|
68
72
|
conversationId: string;
|
|
@@ -102,24 +106,28 @@ mock.module("../../../memory/conversation-crud.js", () => ({
|
|
|
102
106
|
state.conversation && state.conversation.id === id
|
|
103
107
|
? state.conversation
|
|
104
108
|
: null,
|
|
105
|
-
getTurnTimeBounds: (conversationId: string, messageCreatedAt: number) => {
|
|
106
|
-
sourceCalls.getTurnTimeBoundsArgs.push({
|
|
107
|
-
conversationId,
|
|
108
|
-
messageCreatedAt,
|
|
109
|
-
});
|
|
110
|
-
return state.turnBounds;
|
|
111
|
-
},
|
|
112
109
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
113
110
|
}));
|
|
114
111
|
|
|
115
112
|
mock.module("../../../memory/llm-request-log-source.js", () => ({
|
|
116
113
|
getLlmRequestLogSource: async () => ({
|
|
117
|
-
getRequestLogById: async (
|
|
118
|
-
|
|
114
|
+
getRequestLogById: async () => null,
|
|
115
|
+
getRequestLogMetaById: async (id: string) => {
|
|
116
|
+
sourceCalls.getRequestLogMetaByIdArgs.push(id);
|
|
119
117
|
return state.selectedCall;
|
|
120
118
|
},
|
|
121
119
|
getRequestLogsByMessageId: async () => [],
|
|
122
120
|
getRequestLogsByConversationId: async () => [],
|
|
121
|
+
getPreviousNonCompactionCallCreatedAt: async (
|
|
122
|
+
conversationId: string,
|
|
123
|
+
beforeCreatedAt: number,
|
|
124
|
+
) => {
|
|
125
|
+
sourceCalls.getPreviousNonCompactionCallCreatedAtArgs.push({
|
|
126
|
+
conversationId,
|
|
127
|
+
beforeCreatedAt,
|
|
128
|
+
});
|
|
129
|
+
return state.previousNonCompactionCallCreatedAt;
|
|
130
|
+
},
|
|
123
131
|
getCompactionLogsBetween: async (
|
|
124
132
|
conversationId: string,
|
|
125
133
|
afterCreatedAt: number | null,
|
|
@@ -150,14 +158,12 @@ const handler = route.handler as (
|
|
|
150
158
|
args: Record<string, unknown>,
|
|
151
159
|
) => Promise<{ conversationId: string; events: unknown[] }>;
|
|
152
160
|
|
|
153
|
-
function
|
|
161
|
+
function fakeLogMetaRow(overrides: Partial<LogMetaRow> = {}): LogMetaRow {
|
|
154
162
|
return {
|
|
155
163
|
id: "log-default",
|
|
156
164
|
conversationId: "conv-default",
|
|
157
165
|
messageId: null,
|
|
158
166
|
provider: "anthropic",
|
|
159
|
-
requestPayload: "{}",
|
|
160
|
-
responsePayload: "{}",
|
|
161
167
|
createdAt: 1000,
|
|
162
168
|
agentLoopExitReason: null,
|
|
163
169
|
callSite: null,
|
|
@@ -165,6 +171,17 @@ function fakeLogRow(overrides: Partial<LogRow> = {}): LogRow {
|
|
|
165
171
|
};
|
|
166
172
|
}
|
|
167
173
|
|
|
174
|
+
function fakeCompactionRow(
|
|
175
|
+
overrides: Partial<CompactionAgentLogRow> = {},
|
|
176
|
+
): CompactionAgentLogRow {
|
|
177
|
+
return {
|
|
178
|
+
...fakeLogMetaRow(),
|
|
179
|
+
responsePayload: "{}",
|
|
180
|
+
requestMessageCount: null,
|
|
181
|
+
...overrides,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
168
185
|
function fakeCompactionLogEvent(
|
|
169
186
|
overrides: Partial<CompactionLogEvent> = {},
|
|
170
187
|
): CompactionLogEvent {
|
|
@@ -203,12 +220,12 @@ function fakeCompactionLogEvent(
|
|
|
203
220
|
beforeEach(() => {
|
|
204
221
|
state.conversation = null;
|
|
205
222
|
state.selectedCall = null;
|
|
206
|
-
state.
|
|
223
|
+
state.previousNonCompactionCallCreatedAt = null;
|
|
207
224
|
state.compactionLogs = [];
|
|
208
225
|
state.compactionStoreEvents = null;
|
|
209
226
|
state.compactionStoreError = null;
|
|
210
|
-
sourceCalls.
|
|
211
|
-
sourceCalls.
|
|
227
|
+
sourceCalls.getRequestLogMetaByIdArgs.length = 0;
|
|
228
|
+
sourceCalls.getPreviousNonCompactionCallCreatedAtArgs.length = 0;
|
|
212
229
|
sourceCalls.getCompactionLogsBetweenArgs.length = 0;
|
|
213
230
|
sourceCalls.getEventsBetweenArgs.length = 0;
|
|
214
231
|
});
|
|
@@ -267,7 +284,7 @@ describe("handleGetCompactionTrail — request-shape errors", () => {
|
|
|
267
284
|
|
|
268
285
|
test("throws BadRequestError when callId belongs to a different conversation", async () => {
|
|
269
286
|
state.conversation = { id: "conv-1" };
|
|
270
|
-
state.selectedCall =
|
|
287
|
+
state.selectedCall = fakeLogMetaRow({
|
|
271
288
|
id: "call-x",
|
|
272
289
|
conversationId: "conv-other",
|
|
273
290
|
createdAt: 1234,
|
|
@@ -286,140 +303,129 @@ describe("handleGetCompactionTrail — request-shape errors", () => {
|
|
|
286
303
|
// ---------------------------------------------------------------------
|
|
287
304
|
|
|
288
305
|
describe("handleGetCompactionTrail — happy path", () => {
|
|
289
|
-
test("
|
|
306
|
+
test("scopes the window to the previous real call (floor) and the selected call (ceiling)", async () => {
|
|
307
|
+
// GIVEN a selected call whose previous real (non-compactionAgent)
|
|
308
|
+
// call ran at 2000
|
|
290
309
|
state.conversation = { id: "conv-1" };
|
|
291
|
-
state.selectedCall =
|
|
310
|
+
state.selectedCall = fakeLogMetaRow({
|
|
292
311
|
id: "call-selected",
|
|
293
312
|
conversationId: "conv-1",
|
|
294
313
|
createdAt: 5000,
|
|
295
314
|
});
|
|
296
|
-
|
|
297
|
-
state.turnBounds = { startTime: 2000, endTime: 9000 };
|
|
315
|
+
state.previousNonCompactionCallCreatedAt = 2000;
|
|
298
316
|
state.compactionLogs = [];
|
|
299
317
|
|
|
318
|
+
// WHEN the handler resolves the compactions for the call
|
|
300
319
|
await handler({
|
|
301
320
|
pathParams: { id: "conv-1" },
|
|
302
321
|
queryParams: { callId: "call-selected" },
|
|
303
322
|
});
|
|
304
323
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
324
|
+
// THEN it anchors the floor on the previous real call and the
|
|
325
|
+
// ceiling on the selected call's own createdAt — strictly, with no
|
|
326
|
+
// boundary fudging.
|
|
327
|
+
expect(sourceCalls.getRequestLogMetaByIdArgs).toEqual(["call-selected"]);
|
|
328
|
+
expect(sourceCalls.getPreviousNonCompactionCallCreatedAtArgs).toEqual([
|
|
329
|
+
{ conversationId: "conv-1", beforeCreatedAt: 5000 },
|
|
308
330
|
]);
|
|
309
|
-
// 1ms-shift around the exclusive `(>, <)` predicate so rows that
|
|
310
|
-
// land on the bounds themselves come back.
|
|
311
|
-
expect(sourceCalls.getCompactionLogsBetweenArgs).toEqual([
|
|
312
|
-
{ conversationId: "conv-1", afterCreatedAt: 1999, beforeCreatedAt: 9001 },
|
|
313
|
-
]);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
test("scopes the trail to the whole turn — compactions after the selected call are in scope", async () => {
|
|
317
|
-
// Selecting an early call in a turn that contains compactions
|
|
318
|
-
// *after* the selected call must still surface those later events.
|
|
319
|
-
// This is the core promise of turn-scoping: position within the
|
|
320
|
-
// turn is irrelevant.
|
|
321
|
-
state.conversation = { id: "conv-1" };
|
|
322
|
-
state.selectedCall = fakeLogRow({
|
|
323
|
-
id: "call-early",
|
|
324
|
-
conversationId: "conv-1",
|
|
325
|
-
createdAt: 2500,
|
|
326
|
-
});
|
|
327
|
-
state.turnBounds = { startTime: 2000, endTime: 9000 };
|
|
328
|
-
state.compactionLogs = [];
|
|
329
|
-
|
|
330
|
-
await handler({
|
|
331
|
-
pathParams: { id: "conv-1" },
|
|
332
|
-
queryParams: { callId: "call-early" },
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// Ceiling is the *turn end*, not the selected call's createdAt.
|
|
336
331
|
expect(sourceCalls.getCompactionLogsBetweenArgs).toEqual([
|
|
337
|
-
{ conversationId: "conv-1", afterCreatedAt:
|
|
332
|
+
{ conversationId: "conv-1", afterCreatedAt: 2000, beforeCreatedAt: 5000 },
|
|
338
333
|
]);
|
|
339
334
|
});
|
|
340
335
|
|
|
341
|
-
test("
|
|
342
|
-
//
|
|
343
|
-
// pre-turn-scoping behavior for this degenerate input.
|
|
336
|
+
test("uses an open (null) floor when the selected call is the first real call", async () => {
|
|
337
|
+
// GIVEN a selected call with no earlier real call in the conversation
|
|
344
338
|
state.conversation = { id: "conv-1" };
|
|
345
|
-
state.selectedCall =
|
|
346
|
-
id: "call-
|
|
339
|
+
state.selectedCall = fakeLogMetaRow({
|
|
340
|
+
id: "call-first",
|
|
347
341
|
conversationId: "conv-1",
|
|
348
342
|
createdAt: 5000,
|
|
349
343
|
});
|
|
350
|
-
state.
|
|
344
|
+
state.previousNonCompactionCallCreatedAt = null;
|
|
351
345
|
state.compactionLogs = [];
|
|
352
346
|
|
|
347
|
+
// WHEN the handler resolves the compactions for the call
|
|
353
348
|
await handler({
|
|
354
349
|
pathParams: { id: "conv-1" },
|
|
355
|
-
queryParams: { callId: "call-
|
|
350
|
+
queryParams: { callId: "call-first" },
|
|
356
351
|
});
|
|
357
352
|
|
|
353
|
+
// THEN the floor is dropped so every preceding compaction is in scope
|
|
358
354
|
expect(sourceCalls.getCompactionLogsBetweenArgs).toEqual([
|
|
359
355
|
{ conversationId: "conv-1", afterCreatedAt: null, beforeCreatedAt: 5000 },
|
|
360
356
|
]);
|
|
361
357
|
});
|
|
362
358
|
|
|
363
359
|
test("returns an empty events list when no compactions ran in the window", async () => {
|
|
360
|
+
// GIVEN a selected call with no compactions attributed to it
|
|
364
361
|
state.conversation = { id: "conv-1" };
|
|
365
|
-
state.selectedCall =
|
|
362
|
+
state.selectedCall = fakeLogMetaRow({
|
|
366
363
|
id: "call-1",
|
|
367
364
|
conversationId: "conv-1",
|
|
368
365
|
createdAt: 5000,
|
|
369
366
|
});
|
|
370
|
-
state.
|
|
367
|
+
state.previousNonCompactionCallCreatedAt = 2000;
|
|
371
368
|
state.compactionLogs = [];
|
|
372
369
|
|
|
370
|
+
// WHEN the handler resolves the compactions for the call
|
|
373
371
|
const result = await handler({
|
|
374
372
|
pathParams: { id: "conv-1" },
|
|
375
373
|
queryParams: { callId: "call-1" },
|
|
376
374
|
});
|
|
375
|
+
|
|
376
|
+
// THEN it returns an empty list, not an error
|
|
377
377
|
expect(result).toEqual({ conversationId: "conv-1", events: [] });
|
|
378
378
|
});
|
|
379
379
|
|
|
380
|
-
test("projects each compaction log to the wire shape", async () => {
|
|
380
|
+
test("projects each compaction log row to the wire shape", async () => {
|
|
381
|
+
// GIVEN two legacy compaction rows attributed to the selected call
|
|
381
382
|
state.conversation = { id: "conv-1" };
|
|
382
|
-
state.selectedCall =
|
|
383
|
+
state.selectedCall = fakeLogMetaRow({
|
|
383
384
|
id: "call-1",
|
|
384
385
|
conversationId: "conv-1",
|
|
385
386
|
createdAt: 9000,
|
|
386
387
|
});
|
|
387
|
-
state.
|
|
388
|
+
state.previousNonCompactionCallCreatedAt = 500;
|
|
388
389
|
state.compactionLogs = [
|
|
389
|
-
|
|
390
|
+
fakeCompactionRow({
|
|
390
391
|
id: "compaction-1",
|
|
391
392
|
conversationId: "conv-1",
|
|
392
393
|
createdAt: 1000,
|
|
393
394
|
}),
|
|
394
|
-
|
|
395
|
+
fakeCompactionRow({
|
|
395
396
|
id: "compaction-2",
|
|
396
397
|
conversationId: "conv-1",
|
|
397
398
|
createdAt: 2000,
|
|
398
399
|
}),
|
|
399
400
|
];
|
|
400
401
|
|
|
402
|
+
// WHEN the handler resolves the compactions for the call
|
|
401
403
|
const result = await handler({
|
|
402
404
|
pathParams: { id: "conv-1" },
|
|
403
405
|
queryParams: { callId: "call-1" },
|
|
404
406
|
});
|
|
405
407
|
|
|
408
|
+
// THEN it emits one event per row, each carrying the full wire shape
|
|
406
409
|
expect(result.conversationId).toBe("conv-1");
|
|
407
410
|
expect(result.events).toHaveLength(2);
|
|
408
|
-
// Field-set check on the first event — values are validated by the
|
|
409
|
-
// projection tests below; here we just confirm the route emits the
|
|
410
|
-
// full wire shape.
|
|
411
411
|
expect(Object.keys(result.events[0] as object).sort()).toEqual([
|
|
412
|
+
"compacted",
|
|
413
|
+
"compactedMessages",
|
|
414
|
+
"contextTokensAfter",
|
|
415
|
+
"contextTokensBefore",
|
|
412
416
|
"createdAt",
|
|
413
417
|
"durationMs",
|
|
414
|
-
"estimatedCostUsd",
|
|
415
418
|
"id",
|
|
416
|
-
"
|
|
417
|
-
"
|
|
418
|
-
"
|
|
419
|
-
"
|
|
420
|
-
"
|
|
421
|
-
"
|
|
422
|
-
"
|
|
419
|
+
"messagesAfter",
|
|
420
|
+
"messagesBefore",
|
|
421
|
+
"preservedTailMessages",
|
|
422
|
+
"skipReason",
|
|
423
|
+
"summaryFailed",
|
|
424
|
+
"summaryInputTokens",
|
|
425
|
+
"summaryModel",
|
|
426
|
+
"summaryOutputTokens",
|
|
427
|
+
"summaryText",
|
|
428
|
+
"trigger",
|
|
423
429
|
]);
|
|
424
430
|
});
|
|
425
431
|
});
|
|
@@ -429,27 +435,30 @@ describe("handleGetCompactionTrail — happy path", () => {
|
|
|
429
435
|
// ---------------------------------------------------------------------
|
|
430
436
|
|
|
431
437
|
describe("handleGetCompactionTrail — compaction log store", () => {
|
|
432
|
-
test("serves the
|
|
438
|
+
test("serves the compactions from the store and skips the legacy projection", async () => {
|
|
439
|
+
// GIVEN the compaction-log store is configured and has a completed event
|
|
433
440
|
state.conversation = { id: "conv-1" };
|
|
434
|
-
state.selectedCall =
|
|
441
|
+
state.selectedCall = fakeLogMetaRow({
|
|
435
442
|
id: "call-1",
|
|
436
443
|
conversationId: "conv-1",
|
|
437
444
|
createdAt: 5000,
|
|
438
445
|
});
|
|
439
|
-
state.
|
|
446
|
+
state.previousNonCompactionCallCreatedAt = 2000;
|
|
440
447
|
state.compactionStoreEvents = [fakeCompactionLogEvent()];
|
|
441
448
|
|
|
449
|
+
// WHEN the handler resolves the compactions for the call
|
|
442
450
|
const result = await handler({
|
|
443
451
|
pathParams: { id: "conv-1" },
|
|
444
452
|
queryParams: { callId: "call-1" },
|
|
445
453
|
});
|
|
446
454
|
|
|
447
|
-
//
|
|
455
|
+
// THEN it queries the store with the same call window and never
|
|
456
|
+
// touches the legacy projection
|
|
448
457
|
expect(sourceCalls.getEventsBetweenArgs).toEqual([
|
|
449
458
|
{
|
|
450
459
|
conversationId: "conv-1",
|
|
451
|
-
afterStartedAt:
|
|
452
|
-
beforeStartedAt:
|
|
460
|
+
afterStartedAt: 2000,
|
|
461
|
+
beforeStartedAt: 5000,
|
|
453
462
|
},
|
|
454
463
|
]);
|
|
455
464
|
expect(sourceCalls.getCompactionLogsBetweenArgs).toEqual([]);
|
|
@@ -457,37 +466,46 @@ describe("handleGetCompactionTrail — compaction log store", () => {
|
|
|
457
466
|
{
|
|
458
467
|
id: "comp-1",
|
|
459
468
|
createdAt: 3000,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
469
|
+
trigger: "budget",
|
|
470
|
+
compacted: true,
|
|
471
|
+
summaryFailed: false,
|
|
472
|
+
skipReason: "auto",
|
|
473
|
+
contextTokensBefore: 900,
|
|
474
|
+
contextTokensAfter: 300,
|
|
475
|
+
messagesBefore: 12,
|
|
476
|
+
messagesAfter: 4,
|
|
477
|
+
compactedMessages: 10,
|
|
478
|
+
preservedTailMessages: 2,
|
|
464
479
|
durationMs: 500,
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
480
|
+
summaryModel: "test-model",
|
|
481
|
+
summaryInputTokens: 880,
|
|
482
|
+
summaryOutputTokens: 120,
|
|
483
|
+
summaryText: "summary text",
|
|
469
484
|
},
|
|
470
485
|
]);
|
|
471
486
|
});
|
|
472
487
|
|
|
473
488
|
test("falls back to the legacy projection when the store has no rows for the window", async () => {
|
|
489
|
+
// GIVEN the store is configured but returns no events for this call
|
|
474
490
|
state.conversation = { id: "conv-1" };
|
|
475
|
-
state.selectedCall =
|
|
491
|
+
state.selectedCall = fakeLogMetaRow({
|
|
476
492
|
id: "call-1",
|
|
477
493
|
conversationId: "conv-1",
|
|
478
494
|
createdAt: 5000,
|
|
479
495
|
});
|
|
480
|
-
state.
|
|
496
|
+
state.previousNonCompactionCallCreatedAt = 2000;
|
|
481
497
|
state.compactionStoreEvents = [];
|
|
482
498
|
state.compactionLogs = [
|
|
483
|
-
|
|
499
|
+
fakeCompactionRow({ id: "compaction-legacy", conversationId: "conv-1" }),
|
|
484
500
|
];
|
|
485
501
|
|
|
502
|
+
// WHEN the handler resolves the compactions for the call
|
|
486
503
|
const result = await handler({
|
|
487
504
|
pathParams: { id: "conv-1" },
|
|
488
505
|
queryParams: { callId: "call-1" },
|
|
489
506
|
});
|
|
490
507
|
|
|
508
|
+
// THEN it falls through to the legacy projection
|
|
491
509
|
expect(sourceCalls.getEventsBetweenArgs).toHaveLength(1);
|
|
492
510
|
expect(sourceCalls.getCompactionLogsBetweenArgs).toHaveLength(1);
|
|
493
511
|
expect(result.events).toHaveLength(1);
|
|
@@ -495,36 +513,40 @@ describe("handleGetCompactionTrail — compaction log store", () => {
|
|
|
495
513
|
});
|
|
496
514
|
|
|
497
515
|
test("falls back to the legacy projection when the store read throws", async () => {
|
|
516
|
+
// GIVEN the store read fails (e.g. ClickHouse unreachable)
|
|
498
517
|
state.conversation = { id: "conv-1" };
|
|
499
|
-
state.selectedCall =
|
|
518
|
+
state.selectedCall = fakeLogMetaRow({
|
|
500
519
|
id: "call-1",
|
|
501
520
|
conversationId: "conv-1",
|
|
502
521
|
createdAt: 5000,
|
|
503
522
|
});
|
|
504
|
-
state.
|
|
523
|
+
state.previousNonCompactionCallCreatedAt = 2000;
|
|
505
524
|
state.compactionStoreError = new Error("clickhouse unreachable");
|
|
506
525
|
state.compactionLogs = [
|
|
507
|
-
|
|
526
|
+
fakeCompactionRow({ id: "compaction-legacy", conversationId: "conv-1" }),
|
|
508
527
|
];
|
|
509
528
|
|
|
529
|
+
// WHEN the handler resolves the compactions for the call
|
|
510
530
|
const result = await handler({
|
|
511
531
|
pathParams: { id: "conv-1" },
|
|
512
532
|
queryParams: { callId: "call-1" },
|
|
513
533
|
});
|
|
514
534
|
|
|
535
|
+
// THEN it falls through to the legacy projection
|
|
515
536
|
expect(sourceCalls.getCompactionLogsBetweenArgs).toHaveLength(1);
|
|
516
537
|
expect(result.events).toHaveLength(1);
|
|
517
538
|
expect((result.events[0] as { id: string }).id).toBe("compaction-legacy");
|
|
518
539
|
});
|
|
519
540
|
|
|
520
541
|
test("falls back to the legacy projection when any event is missing its end row", async () => {
|
|
542
|
+
// GIVEN one of the store events never wrote its end row (no finishedAt)
|
|
521
543
|
state.conversation = { id: "conv-1" };
|
|
522
|
-
state.selectedCall =
|
|
544
|
+
state.selectedCall = fakeLogMetaRow({
|
|
523
545
|
id: "call-1",
|
|
524
546
|
conversationId: "conv-1",
|
|
525
547
|
createdAt: 5000,
|
|
526
548
|
});
|
|
527
|
-
state.
|
|
549
|
+
state.previousNonCompactionCallCreatedAt = 2000;
|
|
528
550
|
state.compactionStoreEvents = [
|
|
529
551
|
fakeCompactionLogEvent(),
|
|
530
552
|
fakeCompactionLogEvent({
|
|
@@ -535,14 +557,17 @@ describe("handleGetCompactionTrail — compaction log store", () => {
|
|
|
535
557
|
}),
|
|
536
558
|
];
|
|
537
559
|
state.compactionLogs = [
|
|
538
|
-
|
|
560
|
+
fakeCompactionRow({ id: "compaction-legacy", conversationId: "conv-1" }),
|
|
539
561
|
];
|
|
540
562
|
|
|
563
|
+
// WHEN the handler resolves the compactions for the call
|
|
541
564
|
const result = await handler({
|
|
542
565
|
pathParams: { id: "conv-1" },
|
|
543
566
|
queryParams: { callId: "call-1" },
|
|
544
567
|
});
|
|
545
568
|
|
|
569
|
+
// THEN it falls through to the legacy projection rather than serving
|
|
570
|
+
// an event with null counts/duration
|
|
546
571
|
expect(sourceCalls.getEventsBetweenArgs).toHaveLength(1);
|
|
547
572
|
expect(sourceCalls.getCompactionLogsBetweenArgs).toHaveLength(1);
|
|
548
573
|
expect(result.events).toHaveLength(1);
|
|
@@ -556,30 +581,58 @@ describe("handleGetCompactionTrail — compaction log store", () => {
|
|
|
556
581
|
|
|
557
582
|
describe("projectCompactionLogEventToTrailEvent", () => {
|
|
558
583
|
test("maps a completed event onto the wire shape", () => {
|
|
584
|
+
// GIVEN a completed compaction-log event
|
|
585
|
+
// WHEN it is projected to the wire shape
|
|
559
586
|
const event = projectCompactionLogEventToTrailEvent(
|
|
560
587
|
fakeCompactionLogEvent(),
|
|
561
588
|
);
|
|
589
|
+
|
|
590
|
+
// THEN the headline figures are the context reduction and the
|
|
591
|
+
// summarizer's own usage is carried separately
|
|
562
592
|
expect(event).toEqual({
|
|
563
593
|
id: "comp-1",
|
|
564
594
|
createdAt: 3000,
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
595
|
+
trigger: "budget",
|
|
596
|
+
compacted: true,
|
|
597
|
+
summaryFailed: false,
|
|
598
|
+
skipReason: "auto",
|
|
599
|
+
contextTokensBefore: 900,
|
|
600
|
+
contextTokensAfter: 300,
|
|
601
|
+
messagesBefore: 12,
|
|
602
|
+
messagesAfter: 4,
|
|
603
|
+
compactedMessages: 10,
|
|
604
|
+
preservedTailMessages: 2,
|
|
569
605
|
durationMs: 500,
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
606
|
+
summaryModel: "test-model",
|
|
607
|
+
summaryInputTokens: 880,
|
|
608
|
+
summaryOutputTokens: 120,
|
|
609
|
+
summaryText: "summary text",
|
|
574
610
|
});
|
|
575
611
|
});
|
|
576
612
|
|
|
577
|
-
test("maps an
|
|
613
|
+
test("maps an empty trigger to null", () => {
|
|
614
|
+
// GIVEN an event whose trigger column is the empty-string default
|
|
615
|
+
// WHEN it is projected
|
|
616
|
+
const event = projectCompactionLogEventToTrailEvent(
|
|
617
|
+
fakeCompactionLogEvent({ trigger: "" }),
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
// THEN the empty string is normalized to the "not known" sentinel
|
|
621
|
+
expect(event.trigger).toBeNull();
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test("carries through null end-phase fields on an incomplete event", () => {
|
|
625
|
+
// GIVEN a start-only event (no end row written yet)
|
|
626
|
+
// WHEN it is projected
|
|
578
627
|
const event = projectCompactionLogEventToTrailEvent(
|
|
579
628
|
fakeCompactionLogEvent({
|
|
580
629
|
completed: false,
|
|
581
630
|
finishedAt: null,
|
|
582
631
|
durationMs: null,
|
|
632
|
+
compacted: null,
|
|
633
|
+
summaryFailed: null,
|
|
634
|
+
resultMessageCount: null,
|
|
635
|
+
estimatedInputTokens: null,
|
|
583
636
|
summaryInputTokens: null,
|
|
584
637
|
summaryOutputTokens: null,
|
|
585
638
|
summaryModel: null,
|
|
@@ -587,65 +640,66 @@ describe("projectCompactionLogEventToTrailEvent", () => {
|
|
|
587
640
|
summaryText: null,
|
|
588
641
|
}),
|
|
589
642
|
);
|
|
643
|
+
|
|
644
|
+
// THEN the end-phase fields are null while the start-phase
|
|
645
|
+
// pre-compaction figures survive
|
|
590
646
|
expect(event.durationMs).toBeNull();
|
|
591
|
-
expect(event.
|
|
592
|
-
expect(event.
|
|
593
|
-
expect(event.
|
|
594
|
-
expect(event.
|
|
595
|
-
expect(event.
|
|
596
|
-
|
|
597
|
-
expect(event.
|
|
647
|
+
expect(event.compacted).toBeNull();
|
|
648
|
+
expect(event.contextTokensAfter).toBeNull();
|
|
649
|
+
expect(event.messagesAfter).toBeNull();
|
|
650
|
+
expect(event.summaryModel).toBeNull();
|
|
651
|
+
expect(event.summaryText).toBeNull();
|
|
652
|
+
expect(event.contextTokensBefore).toBe(900);
|
|
653
|
+
expect(event.messagesBefore).toBe(12);
|
|
598
654
|
});
|
|
599
655
|
});
|
|
600
656
|
|
|
601
657
|
describe("projectLogRowToCompactionTrailEvent", () => {
|
|
602
|
-
test("returns null for every
|
|
603
|
-
|
|
658
|
+
test("returns null for every field the legacy row can't recover", () => {
|
|
659
|
+
// GIVEN a compaction-agent row with empty payloads
|
|
660
|
+
// WHEN it is projected
|
|
661
|
+
const event = projectLogRowToCompactionTrailEvent(fakeCompactionRow());
|
|
662
|
+
|
|
663
|
+
// THEN only id/createdAt survive; everything the row doesn't carry
|
|
664
|
+
// lands as null
|
|
604
665
|
expect(event.id).toBe("log-default");
|
|
605
666
|
expect(event.createdAt).toBe(1000);
|
|
606
|
-
expect(event.
|
|
607
|
-
expect(event.
|
|
608
|
-
expect(event.
|
|
609
|
-
expect(event.
|
|
610
|
-
expect(event.
|
|
611
|
-
expect(event.
|
|
612
|
-
expect(event.
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
// Even if a future payload shape carries duration info, the
|
|
617
|
-
// projection deliberately drops it — the gap is what surfaces to
|
|
618
|
-
// the UI as "Unavailable" and informs the data-model decision.
|
|
619
|
-
const event = projectLogRowToCompactionTrailEvent(
|
|
620
|
-
fakeLogRow({
|
|
621
|
-
responsePayload: JSON.stringify({
|
|
622
|
-
// A made-up shape with a duration value to confirm the
|
|
623
|
-
// projection ignores it.
|
|
624
|
-
durationMs: 1234,
|
|
625
|
-
duration_ms: 1234,
|
|
626
|
-
}),
|
|
627
|
-
}),
|
|
628
|
-
);
|
|
667
|
+
expect(event.trigger).toBeNull();
|
|
668
|
+
expect(event.compacted).toBeNull();
|
|
669
|
+
expect(event.summaryFailed).toBeNull();
|
|
670
|
+
expect(event.skipReason).toBeNull();
|
|
671
|
+
expect(event.contextTokensBefore).toBeNull();
|
|
672
|
+
expect(event.contextTokensAfter).toBeNull();
|
|
673
|
+
expect(event.messagesBefore).toBeNull();
|
|
674
|
+
expect(event.messagesAfter).toBeNull();
|
|
675
|
+
expect(event.compactedMessages).toBeNull();
|
|
676
|
+
expect(event.preservedTailMessages).toBeNull();
|
|
629
677
|
expect(event.durationMs).toBeNull();
|
|
678
|
+
expect(event.summaryModel).toBeNull();
|
|
679
|
+
expect(event.summaryInputTokens).toBeNull();
|
|
680
|
+
expect(event.summaryOutputTokens).toBeNull();
|
|
681
|
+
expect(event.summaryText).toBeNull();
|
|
630
682
|
});
|
|
631
683
|
|
|
632
|
-
test("
|
|
633
|
-
//
|
|
634
|
-
// the
|
|
684
|
+
test("maps the SQL-computed request message count to compactedMessages", () => {
|
|
685
|
+
// GIVEN a row whose request payload held 7 messages (fed to the
|
|
686
|
+
// summarizer, i.e. the messages that were compacted)
|
|
687
|
+
// WHEN it is projected
|
|
635
688
|
const event = projectLogRowToCompactionTrailEvent(
|
|
636
|
-
|
|
689
|
+
fakeCompactionRow({ requestMessageCount: 7 }),
|
|
637
690
|
);
|
|
638
|
-
|
|
691
|
+
|
|
692
|
+
// THEN that count surfaces as compactedMessages
|
|
693
|
+
expect(event.compactedMessages).toBe(7);
|
|
639
694
|
});
|
|
640
695
|
|
|
641
|
-
test("extracts model +
|
|
696
|
+
test("extracts the summary model + token usage + text from an Anthropic payload", () => {
|
|
697
|
+
// GIVEN a row carrying a real Anthropic summarizer response
|
|
698
|
+
// WHEN it is projected
|
|
642
699
|
const event = projectLogRowToCompactionTrailEvent(
|
|
643
|
-
|
|
700
|
+
fakeCompactionRow({
|
|
644
701
|
provider: "anthropic",
|
|
645
|
-
|
|
646
|
-
model: "claude-sonnet-4-5",
|
|
647
|
-
messages: [{ role: "user", content: "Summarize the prior context." }],
|
|
648
|
-
}),
|
|
702
|
+
requestMessageCount: 1,
|
|
649
703
|
responsePayload: JSON.stringify({
|
|
650
704
|
type: "message",
|
|
651
705
|
model: "claude-sonnet-4-5",
|
|
@@ -656,22 +710,27 @@ describe("projectLogRowToCompactionTrailEvent", () => {
|
|
|
656
710
|
}),
|
|
657
711
|
}),
|
|
658
712
|
);
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
expect(event.
|
|
662
|
-
expect(event.
|
|
663
|
-
expect(event.
|
|
713
|
+
|
|
714
|
+
// THEN the summarizer's own model + usage + text are recovered
|
|
715
|
+
expect(event.summaryModel).toBe("claude-sonnet-4-5");
|
|
716
|
+
expect(event.summaryInputTokens).toBe(12_000);
|
|
717
|
+
expect(event.summaryOutputTokens).toBe(800);
|
|
718
|
+
expect(event.summaryText).toBe("Summary text…");
|
|
719
|
+
expect(event.compactedMessages).toBe(1);
|
|
664
720
|
});
|
|
665
721
|
|
|
666
722
|
test("tolerates non-JSON payloads without throwing (falls back to nulls)", () => {
|
|
723
|
+
// GIVEN a row with a malformed (non-JSON) response payload
|
|
724
|
+
// WHEN it is projected
|
|
667
725
|
const event = projectLogRowToCompactionTrailEvent(
|
|
668
|
-
|
|
669
|
-
requestPayload: "not-json{{{",
|
|
726
|
+
fakeCompactionRow({
|
|
670
727
|
responsePayload: "<html>nope</html>",
|
|
671
728
|
}),
|
|
672
729
|
);
|
|
730
|
+
|
|
731
|
+
// THEN it degrades to nulls instead of throwing
|
|
673
732
|
expect(event.id).toBe("log-default");
|
|
674
|
-
expect(event.
|
|
675
|
-
expect(event.
|
|
733
|
+
expect(event.summaryModel).toBeNull();
|
|
734
|
+
expect(event.summaryInputTokens).toBeNull();
|
|
676
735
|
});
|
|
677
736
|
});
|