@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
|
@@ -35,6 +35,7 @@ import { getDb } from "../memory/db-connection.js";
|
|
|
35
35
|
import { initializeDb } from "../memory/db-init.js";
|
|
36
36
|
import {
|
|
37
37
|
getCompactionLogsBetween,
|
|
38
|
+
getRequestLogMetaById,
|
|
38
39
|
recordRequestLog,
|
|
39
40
|
} from "../memory/llm-request-log-store.js";
|
|
40
41
|
import { llmRequestLogs } from "../memory/schema.js";
|
|
@@ -56,10 +57,11 @@ function insertLogAt(
|
|
|
56
57
|
conversationId: string,
|
|
57
58
|
createdAt: number,
|
|
58
59
|
callSite: "mainAgent" | "compactionAgent" | null,
|
|
60
|
+
requestPayload = "{}",
|
|
59
61
|
): string {
|
|
60
62
|
const id = recordRequestLog(
|
|
61
63
|
conversationId,
|
|
62
|
-
|
|
64
|
+
requestPayload,
|
|
63
65
|
"{}",
|
|
64
66
|
undefined,
|
|
65
67
|
"anthropic",
|
|
@@ -183,4 +185,75 @@ describe("getCompactionLogsBetween", () => {
|
|
|
183
185
|
const result = getCompactionLogsBetween("nonexistent-conv", null, 500);
|
|
184
186
|
expect(result).toEqual([]);
|
|
185
187
|
});
|
|
188
|
+
|
|
189
|
+
test("computes requestMessageCount in SQL without returning the request payload", () => {
|
|
190
|
+
// GIVEN compaction rows whose request payloads use each provider
|
|
191
|
+
// shape (Anthropic/OpenAI `messages`, Gemini `contents`, OpenAI
|
|
192
|
+
// Responses `input`), plus rows where the count can't be derived
|
|
193
|
+
const conv = createConversation("msg-count");
|
|
194
|
+
insertLogAt(
|
|
195
|
+
conv.id,
|
|
196
|
+
100,
|
|
197
|
+
"compactionAgent",
|
|
198
|
+
JSON.stringify({ messages: [{}, {}, {}] }),
|
|
199
|
+
);
|
|
200
|
+
insertLogAt(
|
|
201
|
+
conv.id,
|
|
202
|
+
200,
|
|
203
|
+
"compactionAgent",
|
|
204
|
+
JSON.stringify({ contents: [{}, {}] }),
|
|
205
|
+
);
|
|
206
|
+
insertLogAt(
|
|
207
|
+
conv.id,
|
|
208
|
+
300,
|
|
209
|
+
"compactionAgent",
|
|
210
|
+
JSON.stringify({ input: [{}] }),
|
|
211
|
+
);
|
|
212
|
+
insertLogAt(conv.id, 400, "compactionAgent", "not-json{{{");
|
|
213
|
+
insertLogAt(conv.id, 500, "compactionAgent", JSON.stringify({}));
|
|
214
|
+
|
|
215
|
+
// WHEN querying the trail window
|
|
216
|
+
const result = getCompactionLogsBetween(conv.id, null, 600);
|
|
217
|
+
|
|
218
|
+
// THEN each row carries the SQL-computed message count (null when
|
|
219
|
+
// the payload is malformed or has no known message array)
|
|
220
|
+
expect(result.map((r) => r.requestMessageCount)).toEqual([
|
|
221
|
+
3,
|
|
222
|
+
2,
|
|
223
|
+
1,
|
|
224
|
+
null,
|
|
225
|
+
null,
|
|
226
|
+
]);
|
|
227
|
+
// AND the rows never include the request payload column
|
|
228
|
+
for (const row of result) {
|
|
229
|
+
expect("requestPayload" in row).toBe(false);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("getRequestLogMetaById", () => {
|
|
235
|
+
beforeEach(() => {
|
|
236
|
+
resetTables();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("returns the row's metadata without either payload column", () => {
|
|
240
|
+
// GIVEN a stored log row
|
|
241
|
+
const conv = createConversation("meta-lookup");
|
|
242
|
+
const id = insertLogAt(conv.id, 100, "compactionAgent", '{"big":true}');
|
|
243
|
+
|
|
244
|
+
// WHEN looking it up by id
|
|
245
|
+
const row = getRequestLogMetaById(id);
|
|
246
|
+
|
|
247
|
+
// THEN the metadata comes back payload-free
|
|
248
|
+
expect(row).not.toBeNull();
|
|
249
|
+
expect(row!.id).toBe(id);
|
|
250
|
+
expect(row!.conversationId).toBe(conv.id);
|
|
251
|
+
expect(row!.createdAt).toBe(100);
|
|
252
|
+
expect("requestPayload" in row!).toBe(false);
|
|
253
|
+
expect("responsePayload" in row!).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
test("returns null for an unknown id", () => {
|
|
257
|
+
expect(getRequestLogMetaById("nonexistent")).toBeNull();
|
|
258
|
+
});
|
|
186
259
|
});
|
|
@@ -4,21 +4,54 @@
|
|
|
4
4
|
* Measures compaction cost with a mock provider:
|
|
5
5
|
* - compaction latency under threshold pressure
|
|
6
6
|
* - no-op fast path for below-threshold histories
|
|
7
|
-
* - token reduction below target budget
|
|
7
|
+
* - token reduction below the low-watermark target budget
|
|
8
8
|
* - single-pass summarization (exactly 1 call)
|
|
9
|
-
* - severe pressure overriding cooldown
|
|
10
9
|
*/
|
|
11
10
|
import { describe, expect, mock, test } from "bun:test";
|
|
12
11
|
|
|
12
|
+
mock.module("../util/logger.js", () => ({
|
|
13
|
+
getLogger: () =>
|
|
14
|
+
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
// The compactor reads the conversation's image attachments from the DB to
|
|
18
|
+
// build its manifest; with no images these return empty.
|
|
19
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
20
|
+
getMessages: () => [],
|
|
21
|
+
}));
|
|
22
|
+
mock.module("../memory/attachments-store.js", () => ({
|
|
23
|
+
getAttachmentMetadataForMessage: () => [],
|
|
24
|
+
getAttachmentContent: () => null,
|
|
25
|
+
}));
|
|
26
|
+
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
27
|
+
recordRequestLog: () => {},
|
|
28
|
+
}));
|
|
29
|
+
|
|
13
30
|
import { DEFAULT_CONFIG } from "../config/defaults.js";
|
|
14
31
|
import { estimatePromptTokens } from "../context/token-estimator.js";
|
|
15
32
|
import { ContextWindowManager } from "../plugins/defaults/compaction/window-manager.js";
|
|
16
33
|
import type { Message, Provider } from "../providers/types.js";
|
|
17
34
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
35
|
+
// Synthetic per-turn timestamp so the model's tail_start resolves to a message
|
|
36
|
+
// index. Turn 10 anchors a fat verbatim tail near the front, forcing the
|
|
37
|
+
// deterministic forward-cut to advance to meet the low-watermark budget.
|
|
38
|
+
function turnTimestamp(turn: number): string {
|
|
39
|
+
const hour = String(10 + Math.floor(turn / 60)).padStart(2, "0");
|
|
40
|
+
const minute = String(turn % 60).padStart(2, "0");
|
|
41
|
+
return `2026-05-21 (Thursday) ${hour}:${minute}:00 -05:00 (America/Chicago)`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function compactionResponse(callIndex: number): string {
|
|
45
|
+
return `<compaction_result>
|
|
46
|
+
<summary>
|
|
47
|
+
Compacted summary, pass ${callIndex}. Goals preserved, constraints noted.
|
|
48
|
+
</summary>
|
|
49
|
+
<key_state>
|
|
50
|
+
- Nothing critical pending.
|
|
51
|
+
</key_state>
|
|
52
|
+
<tail_start timestamp="${turnTimestamp(10)}" preview="[U10] User message with enough" />
|
|
53
|
+
</compaction_result>`;
|
|
54
|
+
}
|
|
22
55
|
|
|
23
56
|
function makeSummaryProvider(counter: { calls: number }): Provider {
|
|
24
57
|
return {
|
|
@@ -26,12 +59,7 @@ function makeSummaryProvider(counter: { calls: number }): Provider {
|
|
|
26
59
|
async sendMessage() {
|
|
27
60
|
counter.calls += 1;
|
|
28
61
|
return {
|
|
29
|
-
content: [
|
|
30
|
-
{
|
|
31
|
-
type: "text",
|
|
32
|
-
text: `## Goals\n- Preserve state\n## Constraints\n- Keep PRs small\n## Decisions\n- Call ${counter.calls}`,
|
|
33
|
-
},
|
|
34
|
-
],
|
|
62
|
+
content: [{ type: "text", text: compactionResponse(counter.calls) }],
|
|
35
63
|
model: "mock-model",
|
|
36
64
|
usage: { inputTokens: 420, outputTokens: 85 },
|
|
37
65
|
stopReason: "end_turn",
|
|
@@ -48,7 +76,9 @@ function makeLongMessages(turns: number): Message[] {
|
|
|
48
76
|
content: [
|
|
49
77
|
{
|
|
50
78
|
type: "text",
|
|
51
|
-
text:
|
|
79
|
+
text: `<turn_context>\ncurrent_time: ${turnTimestamp(
|
|
80
|
+
i,
|
|
81
|
+
)}\n</turn_context>\n[U${i}] User message with enough content to estimate tokens. Topic ${
|
|
52
82
|
i % 9
|
|
53
83
|
}.`,
|
|
54
84
|
},
|
|
@@ -73,31 +103,35 @@ function makeConfig() {
|
|
|
73
103
|
return {
|
|
74
104
|
...DEFAULT_CONFIG.llm.default.contextWindow,
|
|
75
105
|
maxInputTokens: 6000,
|
|
76
|
-
targetBudgetRatio: 0.
|
|
106
|
+
targetBudgetRatio: 0.4,
|
|
77
107
|
compactThreshold: 0.6,
|
|
78
108
|
summaryBudgetRatio: 0.05,
|
|
79
109
|
};
|
|
80
110
|
}
|
|
81
111
|
|
|
112
|
+
function makeManager(provider: Provider): ContextWindowManager {
|
|
113
|
+
return new ContextWindowManager({
|
|
114
|
+
provider,
|
|
115
|
+
systemPrompt: "system prompt",
|
|
116
|
+
config: makeConfig(),
|
|
117
|
+
conversationId: "conv-benchmark",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
describe("Compaction benchmark", () => {
|
|
83
122
|
test("compaction with mock provider completes under 500ms", async () => {
|
|
84
123
|
const counter = { calls: 0 };
|
|
85
124
|
const provider = makeSummaryProvider(counter);
|
|
86
125
|
const config = makeConfig();
|
|
87
|
-
const manager =
|
|
88
|
-
provider,
|
|
89
|
-
systemPrompt: "system prompt",
|
|
90
|
-
config,
|
|
91
|
-
});
|
|
126
|
+
const manager = makeManager(provider);
|
|
92
127
|
|
|
93
128
|
// 90 turns = 180 messages, well above 60% of 6000 = 3600 threshold
|
|
94
129
|
const messages = makeLongMessages(90);
|
|
95
130
|
const before = estimatePromptTokens(messages, "system prompt", {
|
|
96
131
|
providerName: "mock",
|
|
97
132
|
});
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
133
|
+
// Real loader's default compaction.autoThreshold is 0.7.
|
|
134
|
+
expect(before).toBeGreaterThan(config.maxInputTokens * 0.7);
|
|
101
135
|
|
|
102
136
|
const start = performance.now();
|
|
103
137
|
const result = await manager.maybeCompact(messages);
|
|
@@ -110,12 +144,7 @@ describe("Compaction benchmark", () => {
|
|
|
110
144
|
test("below-threshold check returns in under 50ms (no-op)", async () => {
|
|
111
145
|
const counter = { calls: 0 };
|
|
112
146
|
const provider = makeSummaryProvider(counter);
|
|
113
|
-
const
|
|
114
|
-
const manager = new ContextWindowManager({
|
|
115
|
-
provider,
|
|
116
|
-
systemPrompt: "system prompt",
|
|
117
|
-
config,
|
|
118
|
-
});
|
|
147
|
+
const manager = makeManager(provider);
|
|
119
148
|
|
|
120
149
|
// 3 turns = 6 messages, well below threshold
|
|
121
150
|
const messages = makeLongMessages(3);
|
|
@@ -125,7 +154,7 @@ describe("Compaction benchmark", () => {
|
|
|
125
154
|
const elapsed = performance.now() - start;
|
|
126
155
|
|
|
127
156
|
expect(result.compacted).toBe(false);
|
|
128
|
-
expect(result.reason).toBe("below
|
|
157
|
+
expect(result.reason).toBe("below auto threshold");
|
|
129
158
|
expect(elapsed).toBeLessThan(50);
|
|
130
159
|
expect(counter.calls).toBe(0);
|
|
131
160
|
});
|
|
@@ -134,11 +163,7 @@ describe("Compaction benchmark", () => {
|
|
|
134
163
|
const counter = { calls: 0 };
|
|
135
164
|
const provider = makeSummaryProvider(counter);
|
|
136
165
|
const config = makeConfig();
|
|
137
|
-
const manager =
|
|
138
|
-
provider,
|
|
139
|
-
systemPrompt: "system prompt",
|
|
140
|
-
config,
|
|
141
|
-
});
|
|
166
|
+
const manager = makeManager(provider);
|
|
142
167
|
|
|
143
168
|
const messages = makeLongMessages(90);
|
|
144
169
|
const result = await manager.maybeCompact(messages);
|
|
@@ -147,7 +172,9 @@ describe("Compaction benchmark", () => {
|
|
|
147
172
|
expect(result.estimatedInputTokens).toBeLessThan(
|
|
148
173
|
result.previousEstimatedInputTokens,
|
|
149
174
|
);
|
|
150
|
-
//
|
|
175
|
+
// The deterministic forward-cut drives the rebuilt history at or below the
|
|
176
|
+
// low-watermark budget: maxInputTokens * (targetBudgetRatio -
|
|
177
|
+
// summaryBudgetRatio).
|
|
151
178
|
const targetTokens = Math.floor(
|
|
152
179
|
config.maxInputTokens *
|
|
153
180
|
(config.targetBudgetRatio - config.summaryBudgetRatio),
|
|
@@ -158,12 +185,7 @@ describe("Compaction benchmark", () => {
|
|
|
158
185
|
test("single-pass summarization makes exactly 1 summary call", async () => {
|
|
159
186
|
const counter = { calls: 0 };
|
|
160
187
|
const provider = makeSummaryProvider(counter);
|
|
161
|
-
const
|
|
162
|
-
const manager = new ContextWindowManager({
|
|
163
|
-
provider,
|
|
164
|
-
systemPrompt: "system prompt",
|
|
165
|
-
config,
|
|
166
|
-
});
|
|
188
|
+
const manager = makeManager(provider);
|
|
167
189
|
|
|
168
190
|
const messages = makeLongMessages(90);
|
|
169
191
|
const result = await manager.maybeCompact(messages);
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic low-watermark forward-cut enforcement in
|
|
3
|
+
* `runAssistantDrivenCompaction`.
|
|
4
|
+
*
|
|
5
|
+
* When the model keeps a fat verbatim tail, the compactor advances the cut
|
|
6
|
+
* forward (dropping more leading messages into the summarized region) until the
|
|
7
|
+
* rebuilt-history estimate fits `targetTokens` — while never cutting past the
|
|
8
|
+
* most-recent-complete-exchange floor and never orphaning tool_use/tool_result
|
|
9
|
+
* pairs.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
function makeLoggerStub(): Record<string, unknown> {
|
|
14
|
+
const stub: Record<string, unknown> = {};
|
|
15
|
+
for (const m of [
|
|
16
|
+
"info",
|
|
17
|
+
"warn",
|
|
18
|
+
"error",
|
|
19
|
+
"debug",
|
|
20
|
+
"trace",
|
|
21
|
+
"fatal",
|
|
22
|
+
"silent",
|
|
23
|
+
"child",
|
|
24
|
+
]) {
|
|
25
|
+
stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
|
|
26
|
+
}
|
|
27
|
+
return stub;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
mock.module("../util/logger.js", () => ({
|
|
31
|
+
getLogger: () => makeLoggerStub(),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
mock.module("../memory/conversation-crud.js", () => ({
|
|
35
|
+
getMessages: () => [],
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
mock.module("../memory/attachments-store.js", () => ({
|
|
39
|
+
getAttachmentMetadataForMessage: () => [],
|
|
40
|
+
getAttachmentContent: () => null,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
44
|
+
recordRequestLog: () => {},
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
import { runAssistantDrivenCompaction } from "../context/compactor.js";
|
|
48
|
+
import { estimatePromptTokens } from "../context/token-estimator.js";
|
|
49
|
+
import type { Message, Provider } from "../providers/types.js";
|
|
50
|
+
|
|
51
|
+
function turnTimestamp(turn: number): string {
|
|
52
|
+
const hour = String(10 + Math.floor(turn / 60)).padStart(2, "0");
|
|
53
|
+
const minute = String(turn % 60).padStart(2, "0");
|
|
54
|
+
return `2026-05-21 (Thursday) ${hour}:${minute}:00 -05:00 (America/Chicago)`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function userTurn(turn: number, body: string): Message {
|
|
58
|
+
return {
|
|
59
|
+
role: "user",
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: `<turn_context>\ncurrent_time: ${turnTimestamp(
|
|
64
|
+
turn,
|
|
65
|
+
)}\n</turn_context>\n[U${turn}] ${body}`,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function assistantTurn(turn: number, body: string): Message {
|
|
72
|
+
return {
|
|
73
|
+
role: "assistant",
|
|
74
|
+
content: [{ type: "text", text: `[A${turn}] ${body}` }],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function makeProvider(response: string): Provider {
|
|
79
|
+
return {
|
|
80
|
+
name: "mock-provider",
|
|
81
|
+
sendMessage: async () => ({
|
|
82
|
+
content: [{ type: "text", text: response }],
|
|
83
|
+
model: "mock-model",
|
|
84
|
+
usage: { inputTokens: 100, outputTokens: 50 },
|
|
85
|
+
stopReason: "end_turn",
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const SUMMARY = "Earlier turns summarized in the assistant's own voice.";
|
|
91
|
+
|
|
92
|
+
function compactionResponse(tailTurn: number, preview: string): string {
|
|
93
|
+
return `<compaction_result>
|
|
94
|
+
<summary>
|
|
95
|
+
${SUMMARY}
|
|
96
|
+
</summary>
|
|
97
|
+
<key_state>
|
|
98
|
+
- Nothing critical pending.
|
|
99
|
+
</key_state>
|
|
100
|
+
<tail_start timestamp="${turnTimestamp(tailTurn)}" preview="${preview}" />
|
|
101
|
+
</compaction_result>`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe("runAssistantDrivenCompaction — low-watermark forward cut", () => {
|
|
105
|
+
test("advances the model's tail forward until the rebuilt history fits the target", async () => {
|
|
106
|
+
// 40 turns = 80 messages, each user body is heavy so the verbatim tail is
|
|
107
|
+
// expensive. The model anchors its tail at turn 4 (keeping ~72 messages);
|
|
108
|
+
// the forward-cut must advance far past that to meet the target.
|
|
109
|
+
const messages: Message[] = [];
|
|
110
|
+
for (let i = 0; i < 40; i++) {
|
|
111
|
+
messages.push(
|
|
112
|
+
userTurn(
|
|
113
|
+
i,
|
|
114
|
+
"User message with a fairly long body so that each turn carries real token weight in the estimate. ".repeat(
|
|
115
|
+
3,
|
|
116
|
+
),
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
messages.push(
|
|
120
|
+
assistantTurn(
|
|
121
|
+
i,
|
|
122
|
+
"Assistant reply, also reasonably long to contribute estimate weight. ".repeat(
|
|
123
|
+
3,
|
|
124
|
+
),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const targetTokens = 1500;
|
|
130
|
+
const result = await runAssistantDrivenCompaction({
|
|
131
|
+
conversationId: "conv-test",
|
|
132
|
+
messages,
|
|
133
|
+
provider: makeProvider(
|
|
134
|
+
compactionResponse(4, "User message with a fairly long body"),
|
|
135
|
+
),
|
|
136
|
+
systemPrompt: "system",
|
|
137
|
+
compaction: { enabled: true, autoThreshold: 0.7 },
|
|
138
|
+
maxInputTokens: 100_000,
|
|
139
|
+
targetTokens,
|
|
140
|
+
previousEstimatedInputTokens: 90_000,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
expect(result.compacted).toBe(true);
|
|
144
|
+
|
|
145
|
+
// The rebuilt history (summary + verbatim tail) lands at or below target.
|
|
146
|
+
const estimate = estimatePromptTokens(result.messages, "system", {
|
|
147
|
+
providerName: "mock-provider",
|
|
148
|
+
});
|
|
149
|
+
expect(estimate).toBeLessThanOrEqual(targetTokens);
|
|
150
|
+
|
|
151
|
+
// The forward-cut advanced well past the model's chosen anchor (turn 4 →
|
|
152
|
+
// index 8): far fewer tail messages survive than the model kept.
|
|
153
|
+
expect(result.preservedTailMessages).toBeLessThan(messages.length - 8);
|
|
154
|
+
expect(result.preservedTailMessages).toBeGreaterThan(0);
|
|
155
|
+
|
|
156
|
+
// The cut found a tail that fits the budget, so the floor was not the
|
|
157
|
+
// binding constraint — no futile-retry signal.
|
|
158
|
+
expect(result.tailFloorReached).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("never advances past the most-recent-complete-exchange floor", async () => {
|
|
162
|
+
// A small conversation where the target is unreachably tiny: the cut must
|
|
163
|
+
// still preserve at least the last complete user→assistant exchange.
|
|
164
|
+
const messages: Message[] = [];
|
|
165
|
+
for (let i = 0; i < 6; i++) {
|
|
166
|
+
messages.push(userTurn(i, "short user turn body here"));
|
|
167
|
+
messages.push(assistantTurn(i, "short assistant reply body here"));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result = await runAssistantDrivenCompaction({
|
|
171
|
+
conversationId: "conv-test",
|
|
172
|
+
messages,
|
|
173
|
+
provider: makeProvider(
|
|
174
|
+
compactionResponse(1, "short user turn body here"),
|
|
175
|
+
),
|
|
176
|
+
systemPrompt: "system",
|
|
177
|
+
compaction: { enabled: true, autoThreshold: 0.7 },
|
|
178
|
+
maxInputTokens: 100_000,
|
|
179
|
+
// Target far below anything achievable — forces the cut to the floor.
|
|
180
|
+
targetTokens: 1,
|
|
181
|
+
previousEstimatedInputTokens: 90_000,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(result.compacted).toBe(true);
|
|
185
|
+
// The last complete exchange (final user + final assistant) survives: the
|
|
186
|
+
// floor is the start of that exchange, so at least 2 tail messages remain.
|
|
187
|
+
expect(result.preservedTailMessages).toBeGreaterThanOrEqual(2);
|
|
188
|
+
// The tail still opens on a user turn (clean forward-cut boundary).
|
|
189
|
+
const tail = result.messages.slice(
|
|
190
|
+
result.messages.length - (result.preservedTailMessages ?? 0),
|
|
191
|
+
);
|
|
192
|
+
expect(tail[0]?.role).toBe("user");
|
|
193
|
+
// The cut advanced to the floor but the surviving tail is still over the
|
|
194
|
+
// (unreachably tiny) target — this is the floor-dominated case the
|
|
195
|
+
// window-manager uses to skip a futile retry.
|
|
196
|
+
expect(result.tailFloorReached).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("forward cut lands on a clean user boundary, never orphaning a tool_result", async () => {
|
|
200
|
+
// Build: [u0,a0, u1,a1(tool_use X), u2(tool_result X), a2, u3,a3].
|
|
201
|
+
// The model anchors at turn 0; a naive forward cut could land on the
|
|
202
|
+
// tool_result-only user message and orphan tool_use X. The boundary check
|
|
203
|
+
// must skip it.
|
|
204
|
+
const toolUse: Message = {
|
|
205
|
+
role: "assistant",
|
|
206
|
+
content: [{ type: "tool_use", id: "X", name: "Bash", input: {} }],
|
|
207
|
+
};
|
|
208
|
+
const toolResult: Message = {
|
|
209
|
+
role: "user",
|
|
210
|
+
content: [{ type: "tool_result", tool_use_id: "X", content: "ok" }],
|
|
211
|
+
};
|
|
212
|
+
const messages: Message[] = [
|
|
213
|
+
userTurn(
|
|
214
|
+
0,
|
|
215
|
+
"first user turn with a long body to weigh the estimate. ".repeat(4),
|
|
216
|
+
),
|
|
217
|
+
assistantTurn(0, "first assistant reply with a long body. ".repeat(4)),
|
|
218
|
+
userTurn(
|
|
219
|
+
1,
|
|
220
|
+
"second user turn, also long, to add estimate weight. ".repeat(4),
|
|
221
|
+
),
|
|
222
|
+
toolUse,
|
|
223
|
+
toolResult,
|
|
224
|
+
assistantTurn(2, "assistant continues after the tool result. ".repeat(4)),
|
|
225
|
+
userTurn(3, "final user turn body"),
|
|
226
|
+
assistantTurn(3, "final assistant reply body"),
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
const result = await runAssistantDrivenCompaction({
|
|
230
|
+
conversationId: "conv-test",
|
|
231
|
+
messages,
|
|
232
|
+
provider: makeProvider(
|
|
233
|
+
compactionResponse(1, "second user turn, also long"),
|
|
234
|
+
),
|
|
235
|
+
systemPrompt: "system",
|
|
236
|
+
compaction: { enabled: true, autoThreshold: 0.7 },
|
|
237
|
+
maxInputTokens: 100_000,
|
|
238
|
+
// Small target to force forward advancement past the tool cluster.
|
|
239
|
+
targetTokens: 200,
|
|
240
|
+
previousEstimatedInputTokens: 90_000,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(result.compacted).toBe(true);
|
|
244
|
+
const tail = result.messages.slice(
|
|
245
|
+
result.messages.length - (result.preservedTailMessages ?? 0),
|
|
246
|
+
);
|
|
247
|
+
// The tail must not open with an orphaned tool_result.
|
|
248
|
+
const opensWithToolResult =
|
|
249
|
+
tail[0]?.role === "user" &&
|
|
250
|
+
tail[0].content.some((b) => b.type === "tool_result");
|
|
251
|
+
expect(opensWithToolResult).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("keeps the model's tail untouched when it already fits the budget", async () => {
|
|
255
|
+
// 10 turns = 20 messages; the model anchors its tail at turn 4 (index 8)
|
|
256
|
+
// and the target is far above the rebuilt-history estimate. Enforcement
|
|
257
|
+
// must not advance the cut at all — the cut is enforcement, not
|
|
258
|
+
// optimization.
|
|
259
|
+
const messages: Message[] = [];
|
|
260
|
+
for (let i = 0; i < 10; i++) {
|
|
261
|
+
messages.push(userTurn(i, "short user turn body here"));
|
|
262
|
+
messages.push(assistantTurn(i, "short assistant reply body here"));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const result = await runAssistantDrivenCompaction({
|
|
266
|
+
conversationId: "conv-test",
|
|
267
|
+
messages,
|
|
268
|
+
provider: makeProvider(
|
|
269
|
+
compactionResponse(4, "short user turn body here"),
|
|
270
|
+
),
|
|
271
|
+
systemPrompt: "system",
|
|
272
|
+
compaction: { enabled: true, autoThreshold: 0.7 },
|
|
273
|
+
maxInputTokens: 100_000,
|
|
274
|
+
targetTokens: 50_000,
|
|
275
|
+
previousEstimatedInputTokens: 90_000,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
expect(result.compacted).toBe(true);
|
|
279
|
+
// The model's cut (turn 4 → index 8) keeps messages 8..19 = 12 messages.
|
|
280
|
+
expect(result.preservedTailMessages).toBe(12);
|
|
281
|
+
// No span was dropped, so the summary carries no truncation notice.
|
|
282
|
+
const summaryBlock = result.messages[0]?.content.find(
|
|
283
|
+
(b) => b.type === "text",
|
|
284
|
+
);
|
|
285
|
+
expect(
|
|
286
|
+
summaryBlock && "text" in summaryBlock ? summaryBlock.text : "",
|
|
287
|
+
).not.toContain("Context budget enforcement");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("acknowledges the span dropped by an advanced cut in the summary message", async () => {
|
|
291
|
+
// Same shape as the first test: a heavy conversation where the model's
|
|
292
|
+
// tail choice overshoots the budget and the cut must advance. The span
|
|
293
|
+
// between the model's cut and the enforced cut is in neither the
|
|
294
|
+
// summary's detail nor the retained tail, so the summary message must
|
|
295
|
+
// carry an explicit truncation notice.
|
|
296
|
+
const messages: Message[] = [];
|
|
297
|
+
for (let i = 0; i < 40; i++) {
|
|
298
|
+
messages.push(
|
|
299
|
+
userTurn(
|
|
300
|
+
i,
|
|
301
|
+
"User message with a fairly long body so that each turn carries real token weight in the estimate. ".repeat(
|
|
302
|
+
3,
|
|
303
|
+
),
|
|
304
|
+
),
|
|
305
|
+
);
|
|
306
|
+
messages.push(
|
|
307
|
+
assistantTurn(
|
|
308
|
+
i,
|
|
309
|
+
"Assistant reply, also reasonably long to contribute estimate weight. ".repeat(
|
|
310
|
+
3,
|
|
311
|
+
),
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const result = await runAssistantDrivenCompaction({
|
|
317
|
+
conversationId: "conv-test",
|
|
318
|
+
messages,
|
|
319
|
+
provider: makeProvider(
|
|
320
|
+
compactionResponse(4, "User message with a fairly long body"),
|
|
321
|
+
),
|
|
322
|
+
systemPrompt: "system",
|
|
323
|
+
compaction: { enabled: true, autoThreshold: 0.7 },
|
|
324
|
+
maxInputTokens: 100_000,
|
|
325
|
+
targetTokens: 1500,
|
|
326
|
+
previousEstimatedInputTokens: 90_000,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
expect(result.compacted).toBe(true);
|
|
330
|
+
const summaryBlock = result.messages[0]?.content.find(
|
|
331
|
+
(b) => b.type === "text",
|
|
332
|
+
);
|
|
333
|
+
const summaryTextOut =
|
|
334
|
+
summaryBlock && "text" in summaryBlock ? summaryBlock.text : "";
|
|
335
|
+
// The notice names the dropped span (count by role) so the loss is
|
|
336
|
+
// visible in-context rather than silent.
|
|
337
|
+
expect(summaryTextOut).toContain("Context budget enforcement");
|
|
338
|
+
expect(summaryTextOut).toMatch(
|
|
339
|
+
/\d+ message\(s\) \(\d+ user, \d+ assistant\)/,
|
|
340
|
+
);
|
|
341
|
+
// The original summary body is preserved verbatim ahead of the notice.
|
|
342
|
+
expect(summaryTextOut).toContain(SUMMARY);
|
|
343
|
+
// The notice also rides the DURABLE summary — applyCompactionResult
|
|
344
|
+
// persists and rehydrates from `result.summaryText`, so a notice that
|
|
345
|
+
// lived only on the in-memory message would vanish on reload/fork.
|
|
346
|
+
expect(result.summaryText).toContain("Context budget enforcement");
|
|
347
|
+
expect(result.summaryText).toBe(summaryTextOut);
|
|
348
|
+
});
|
|
349
|
+
});
|