@vellumai/assistant 0.8.12 → 0.9.0-staging.1
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/openapi.yaml +6935 -5545
- 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-dir-path-guard.test.ts +27 -3
- 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-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 +125 -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__/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__/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/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/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 +75 -0
- 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 +1 -1
- 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/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-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-stages/acl-enforcement.ts +26 -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 +122 -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/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 +48 -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
|
@@ -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
|
+
});
|
|
@@ -47,6 +47,7 @@ interface CompactionRunResult {
|
|
|
47
47
|
summaryText: string;
|
|
48
48
|
reason?: string;
|
|
49
49
|
summaryFailed?: boolean;
|
|
50
|
+
tailFloorReached?: boolean;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
let runCalls: Array<{
|
|
@@ -306,6 +307,69 @@ describe("ContextWindowManager.maybeCompact retry budget", () => {
|
|
|
306
307
|
expect(result.exhausted).toBe(true);
|
|
307
308
|
expect(result.estimatedInputTokens).toBe(165_000);
|
|
308
309
|
});
|
|
310
|
+
|
|
311
|
+
test("tailFloorReached after one pass → no futile retry, exhausted", async () => {
|
|
312
|
+
// Pass 1 compacted but stayed above threshold AND the forward-cut hit the
|
|
313
|
+
// tail floor — a second full-context pass would land on the same floor and
|
|
314
|
+
// free nothing. The manager must NOT run attempt 2 even though maxAttempts=3.
|
|
315
|
+
// Pre-compaction 180k → attempt-1 recompute 165k (above 140k threshold).
|
|
316
|
+
estimateReturns.push(180_000, 165_000);
|
|
317
|
+
runResults = [
|
|
318
|
+
compactResult({ estimatedInputTokens: 165_000, tailFloorReached: true }),
|
|
319
|
+
// Never consumed — proves no retry fired:
|
|
320
|
+
compactResult({ estimatedInputTokens: 50_000 }),
|
|
321
|
+
];
|
|
322
|
+
|
|
323
|
+
const manager = buildManager(3);
|
|
324
|
+
const result = await manager.maybeCompact(makeMessages(10));
|
|
325
|
+
|
|
326
|
+
expect(runCalls.length).toBe(1);
|
|
327
|
+
expect(result.exhausted).toBe(true);
|
|
328
|
+
expect(result.estimatedInputTokens).toBe(165_000);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("tailFloorReached but already under threshold → ship, not exhausted", async () => {
|
|
332
|
+
// The floor flag only forces exhaustion when still over the gate. A pass
|
|
333
|
+
// that cleared the threshold ships as a clean success regardless.
|
|
334
|
+
estimateReturns.push(180_000, 50_000);
|
|
335
|
+
runResults = [
|
|
336
|
+
compactResult({ estimatedInputTokens: 50_000, tailFloorReached: true }),
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
const manager = buildManager(3);
|
|
340
|
+
const result = await manager.maybeCompact(makeMessages(10));
|
|
341
|
+
|
|
342
|
+
expect(runCalls.length).toBe(1);
|
|
343
|
+
expect(result.compacted).toBe(true);
|
|
344
|
+
expect(result.exhausted).toBeUndefined();
|
|
345
|
+
expect(result.estimatedInputTokens).toBe(50_000);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("tailFloorReached on a retry pass → stops the retry loop", async () => {
|
|
349
|
+
// Pass 1 shrank but stayed above threshold WITHOUT hitting the floor, so a
|
|
350
|
+
// retry runs. Pass 2 shrinks further, still above threshold, and this time
|
|
351
|
+
// hits the floor — the loop must stop before attempt 3.
|
|
352
|
+
// 180k → attempt-1 165k → attempt-2 150k (both above 140k).
|
|
353
|
+
estimateReturns.push(180_000, 165_000, 150_000);
|
|
354
|
+
runResults = [
|
|
355
|
+
compactResult({ estimatedInputTokens: 165_000, compactedMessages: 3 }),
|
|
356
|
+
compactResult({
|
|
357
|
+
estimatedInputTokens: 150_000,
|
|
358
|
+
compactedMessages: 2,
|
|
359
|
+
tailFloorReached: true,
|
|
360
|
+
}),
|
|
361
|
+
// Never consumed:
|
|
362
|
+
compactResult({ estimatedInputTokens: 50_000 }),
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
const manager = buildManager(3);
|
|
366
|
+
const result = await manager.maybeCompact(makeMessages(10));
|
|
367
|
+
|
|
368
|
+
expect(runCalls.length).toBe(2);
|
|
369
|
+
expect(result.exhausted).toBe(true);
|
|
370
|
+
expect(result.estimatedInputTokens).toBe(150_000);
|
|
371
|
+
expect(result.compactedMessages).toBe(5);
|
|
372
|
+
});
|
|
309
373
|
});
|
|
310
374
|
|
|
311
375
|
describe("ContextWindowManager.emergencyCompact", () => {
|
|
@@ -13,11 +13,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
13
13
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
17
|
-
getGuardianActionRequest: () => null,
|
|
18
|
-
resolveGuardianActionRequest: () => {},
|
|
19
|
-
}));
|
|
20
|
-
|
|
21
16
|
mock.module("../providers/registry.js", () => ({
|
|
22
17
|
getProvider: () => ({ name: "mock-provider" }),
|
|
23
18
|
initializeProviders: async () => {},
|
|
@@ -41,11 +41,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
41
41
|
getLogger: () => makeLoggerStub(),
|
|
42
42
|
}));
|
|
43
43
|
|
|
44
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
45
|
-
getGuardianActionRequest: () => null,
|
|
46
|
-
resolveGuardianActionRequest: () => {},
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
44
|
mock.module("../providers/registry.js", () => ({
|
|
50
45
|
getProvider: () => ({ name: "mock-provider" }),
|
|
51
46
|
initializeProviders: async () => {},
|
|
@@ -796,6 +796,13 @@ describe("web_search_tool_result structural guard", () => {
|
|
|
796
796
|
// conservative direction (fewer nudges). Same reasoning as agent/loop.ts.
|
|
797
797
|
"plugins/defaults/exploration-drift/hooks/post-tool-use.ts",
|
|
798
798
|
|
|
799
|
+
// Detects turn boundaries by checking whether a user message carries any
|
|
800
|
+
// tool_result block (internal continuation) vs. none (genuine user prompt).
|
|
801
|
+
// A web_search_tool_result-only message is also internal, but treating one
|
|
802
|
+
// as a boundary only bounds the round count early — the conservative
|
|
803
|
+
// direction (fewer nudges). Same reasoning as exploration-drift above.
|
|
804
|
+
"plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts",
|
|
805
|
+
|
|
799
806
|
// Reconciles synthesized cancellation tool_results for locally-executed
|
|
800
807
|
// tools only. Same reasoning as agent/loop.ts above.
|
|
801
808
|
"daemon/conversation-agent-loop.ts",
|
|
@@ -36,11 +36,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
36
36
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
37
37
|
}));
|
|
38
38
|
|
|
39
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
40
|
-
getGuardianActionRequest: () => null,
|
|
41
|
-
resolveGuardianActionRequest: () => {},
|
|
42
|
-
}));
|
|
43
|
-
|
|
44
39
|
const mockProviderStub = { name: "mock-provider" };
|
|
45
40
|
mock.module("../providers/registry.js", () => ({
|
|
46
41
|
getProvider: () => mockProviderStub,
|
|
@@ -10,11 +10,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
10
10
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
11
11
|
}));
|
|
12
12
|
|
|
13
|
-
mock.module("../memory/guardian-action-store.js", () => ({
|
|
14
|
-
getGuardianActionRequest: () => null,
|
|
15
|
-
resolveGuardianActionRequest: () => {},
|
|
16
|
-
}));
|
|
17
|
-
|
|
18
13
|
mock.module("../providers/registry.js", () => ({
|
|
19
14
|
getProvider: () => ({ name: "mock-provider" }),
|
|
20
15
|
initializeProviders: async () => {},
|