@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
package/src/plugin-api/types.ts
CHANGED
|
@@ -493,8 +493,11 @@ export type PostModelCallDecision = "continue" | "stop";
|
|
|
493
493
|
* the hook's result as the persisted and streamed message), and
|
|
494
494
|
* {@link stopReason} carries the provider's stop reason. Fires once per model
|
|
495
495
|
* call, including tool-bearing turns (a reply can carry both text and
|
|
496
|
-
* `tool_use`)
|
|
497
|
-
*
|
|
496
|
+
* `tool_use`). A hook should leave blocks it does not own untouched, but it
|
|
497
|
+
* may **append a `tool_use` block** to invoke a tool as if the model had
|
|
498
|
+
* called it — the loop executes whatever the finalized content carries (see
|
|
499
|
+
* {@link content}). This is the supported way for a plugin to drive a tool
|
|
500
|
+
* (e.g. render a surface via `ui_show`) deterministically after a turn.
|
|
498
501
|
* - **Provider rejection.** The call threw before any reply existed.
|
|
499
502
|
* {@link error} holds the rejection, {@link content} is empty, and
|
|
500
503
|
* {@link stopReason} is `null`. A hook that recognizes the rejection may
|
|
@@ -518,8 +521,17 @@ export interface PostModelCallContext {
|
|
|
518
521
|
/** The call site this message serves — `"mainAgent"` for the user-facing reply; `null` when untagged. */
|
|
519
522
|
readonly callSite: LLMCallSite | null;
|
|
520
523
|
/**
|
|
521
|
-
* The finalized message content. Mutable
|
|
522
|
-
*
|
|
524
|
+
* The finalized message content. Mutable, and the source of truth for both
|
|
525
|
+
* persistence and execution: the loop derives the turn's executable tool
|
|
526
|
+
* calls from this array *after* the hook chain runs. A hook may transform the
|
|
527
|
+
* text blocks, **append a `tool_use` block** to invoke a tool as if the model
|
|
528
|
+
* had called it (executed through the normal tool path — trust rules apply,
|
|
529
|
+
* and its result/surface is appended after any already-streamed text without
|
|
530
|
+
* discarding it), or drop a `tool_use` block to suppress a call. The host
|
|
531
|
+
* assigns an id to any appended `tool_use` block whose `id` is empty or
|
|
532
|
+
* collides. Empty on a provider rejection. Appended `tool_use` blocks are
|
|
533
|
+
* dropped on a truncated (max-tokens) turn, which short-circuits before the
|
|
534
|
+
* executor runs and so cannot pair a tool call with a result.
|
|
523
535
|
*/
|
|
524
536
|
content: ContentBlock[];
|
|
525
537
|
/**
|
|
@@ -115,6 +115,13 @@ export interface ContextWindowResult {
|
|
|
115
115
|
* `context_too_large`. Omitted on the ordinary compaction path.
|
|
116
116
|
*/
|
|
117
117
|
autoCompressApplied?: boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Propagated from the compactor: the deterministic forward-cut hit the tail
|
|
120
|
+
* floor while still over the low-watermark budget. {@link _maybeCompact} reads
|
|
121
|
+
* it to stop retrying — a second pass lands on the same floor and frees
|
|
122
|
+
* nothing. See {@link CompactionRunResult.tailFloorReached}.
|
|
123
|
+
*/
|
|
124
|
+
tailFloorReached?: boolean;
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
export interface ShouldCompactResult {
|
|
@@ -627,6 +634,28 @@ export class ContextWindowManager {
|
|
|
627
634
|
return Math.floor(this.config.maxInputTokens * (1 - safetyMargin));
|
|
628
635
|
}
|
|
629
636
|
|
|
637
|
+
/**
|
|
638
|
+
* Low-watermark token budget a compaction pass aims to land the rebuilt
|
|
639
|
+
* history at or below. Derived from `contextWindow.targetBudgetRatio` (the
|
|
640
|
+
* fraction of the window to retain after compaction) minus the summary's own
|
|
641
|
+
* reserve (`summaryBudgetRatio`), so the post-compaction total — summary plus
|
|
642
|
+
* verbatim tail — fits the target. Clamped meaningfully below the
|
|
643
|
+
* auto-threshold success gate: a target at or above the gate would defeat the
|
|
644
|
+
* purpose (a pass could "succeed" while landing a hair under the trigger and
|
|
645
|
+
* thrash on the next tick), so it is pulled down to at most 80% of the gate.
|
|
646
|
+
*/
|
|
647
|
+
private resolveCompactionTargetTokens(thresholdTokens: number): number {
|
|
648
|
+
const { maxInputTokens, targetBudgetRatio, summaryBudgetRatio } =
|
|
649
|
+
this.config;
|
|
650
|
+
const verbatimRatio = Math.max(
|
|
651
|
+
0,
|
|
652
|
+
targetBudgetRatio - (summaryBudgetRatio ?? 0),
|
|
653
|
+
);
|
|
654
|
+
const raw = Math.floor(maxInputTokens * verbatimRatio);
|
|
655
|
+
const ceiling = Math.floor(thresholdTokens * 0.8);
|
|
656
|
+
return Math.max(1, Math.min(raw, ceiling));
|
|
657
|
+
}
|
|
658
|
+
|
|
630
659
|
private async _maybeCompact(
|
|
631
660
|
messages: Message[],
|
|
632
661
|
signal?: AbortSignal,
|
|
@@ -642,6 +671,7 @@ export class ContextWindowManager {
|
|
|
642
671
|
const thresholdTokens = Math.floor(
|
|
643
672
|
this.config.maxInputTokens * compaction.autoThreshold,
|
|
644
673
|
);
|
|
674
|
+
const targetTokens = this.resolveCompactionTargetTokens(thresholdTokens);
|
|
645
675
|
|
|
646
676
|
if (!compaction.enabled) {
|
|
647
677
|
return noopResult(messages, previousEstimatedInputTokens, {
|
|
@@ -681,6 +711,7 @@ export class ContextWindowManager {
|
|
|
681
711
|
tools: this.resolveTools?.(),
|
|
682
712
|
compaction,
|
|
683
713
|
maxInputTokens: this.config.maxInputTokens,
|
|
714
|
+
targetTokens,
|
|
684
715
|
previousEstimatedInputTokens,
|
|
685
716
|
force: options?.force,
|
|
686
717
|
signal,
|
|
@@ -713,6 +744,16 @@ export class ContextWindowManager {
|
|
|
713
744
|
return { ...result, estimatedInputTokens };
|
|
714
745
|
}
|
|
715
746
|
|
|
747
|
+
// The deterministic forward-cut already advanced to the tail floor (the
|
|
748
|
+
// most recent complete exchange) and still couldn't fit the budget — the
|
|
749
|
+
// verbatim tail alone is over budget (a tool-heavy in-flight turn). A
|
|
750
|
+
// second full-context pass would re-derive the same floor and free
|
|
751
|
+
// nothing, just paying another full cache write. Stop now and surface
|
|
752
|
+
// `exhausted` so reducers escalate instead of thrashing the compactor.
|
|
753
|
+
if (result.tailFloorReached) {
|
|
754
|
+
return { ...result, estimatedInputTokens, exhausted: true };
|
|
755
|
+
}
|
|
756
|
+
|
|
716
757
|
// Still above the threshold after one pass — retry on the compacted
|
|
717
758
|
// history, up to the remaining budget. Each retry runs against the
|
|
718
759
|
// PREVIOUS attempt's output, building a tighter summary each time.
|
|
@@ -737,6 +778,9 @@ export class ContextWindowManager {
|
|
|
737
778
|
if (estimatedInputTokens < thresholdTokens) {
|
|
738
779
|
return { ...result, estimatedInputTokens };
|
|
739
780
|
}
|
|
781
|
+
// Forward-cut hit the floor and still over budget — same stop condition
|
|
782
|
+
// as the first pass: another retry lands on the same floor.
|
|
783
|
+
if (nextResult.tailFloorReached) break;
|
|
740
784
|
// Non-productive (compacted but didn't shrink) — stuck compactor.
|
|
741
785
|
if (estimatedInputTokens >= previousEstimate) break;
|
|
742
786
|
previousEstimate = estimatedInputTokens;
|
|
@@ -44,12 +44,20 @@ import imageRecoveryPostModelCall from "./image-recovery/hooks/post-model-call.j
|
|
|
44
44
|
import imageRecoveryStop from "./image-recovery/hooks/stop.js";
|
|
45
45
|
import { resetImageRecoveryStoreForTests } from "./image-recovery/image-recovery-state-store.js";
|
|
46
46
|
import imageRecoveryPkg from "./image-recovery/package.json" with { type: "json" };
|
|
47
|
+
import { resetMaxTokensContinueStoreForTests } from "./max-tokens-continue/continue-state-store.js";
|
|
48
|
+
import maxTokensContinuePostModelCall from "./max-tokens-continue/hooks/post-model-call.js";
|
|
49
|
+
import maxTokensContinueStop from "./max-tokens-continue/hooks/stop.js";
|
|
50
|
+
import maxTokensContinuePkg from "./max-tokens-continue/package.json" with { type: "json" };
|
|
47
51
|
import memoryRetrievalPostCompact from "./memory-retrieval/hooks/post-compact.js";
|
|
48
52
|
import memoryRetrievalUserPromptSubmit from "./memory-retrieval/hooks/user-prompt-submit.js";
|
|
49
53
|
import memoryRetrievalPkg from "./memory-retrieval/package.json" with { type: "json" };
|
|
50
54
|
import memoryV3PostCompact from "./memory-v3-shadow/hooks/post-compact.js";
|
|
51
55
|
import memoryV3UserPromptSubmit from "./memory-v3-shadow/hooks/user-prompt-submit.js";
|
|
52
56
|
import memoryV3Pkg from "./memory-v3-shadow/package.json" with { type: "json" };
|
|
57
|
+
import taskProgressNudgePostToolUse, {
|
|
58
|
+
resetTaskProgressNudgeStateForTests,
|
|
59
|
+
} from "./task-progress-nudge/hooks/post-tool-use.js";
|
|
60
|
+
import taskProgressNudgePkg from "./task-progress-nudge/package.json" with { type: "json" };
|
|
53
61
|
import titleGenerateStop from "./title-generate/hooks/stop.js";
|
|
54
62
|
import titleGenerateUserPromptSubmit from "./title-generate/hooks/user-prompt-submit.js";
|
|
55
63
|
import titleGeneratePkg from "./title-generate/package.json" with { type: "json" };
|
|
@@ -150,6 +158,24 @@ export const defaultImageRecoveryPlugin: Plugin = {
|
|
|
150
158
|
},
|
|
151
159
|
};
|
|
152
160
|
|
|
161
|
+
/**
|
|
162
|
+
* `max-tokens-continue` — a `post-model-call` hook that auto-resumes a
|
|
163
|
+
* user-facing turn the provider truncated at its output token limit, keeping
|
|
164
|
+
* the partial output and re-querying with a continuation nudge so long
|
|
165
|
+
* generations can finish without the user clicking the continuation card.
|
|
166
|
+
* Bounded per run; the `stop` hook clears the budget on a terminal stop.
|
|
167
|
+
*/
|
|
168
|
+
export const defaultMaxTokensContinuePlugin: Plugin = {
|
|
169
|
+
manifest: {
|
|
170
|
+
name: maxTokensContinuePkg.name,
|
|
171
|
+
version: maxTokensContinuePkg.version,
|
|
172
|
+
},
|
|
173
|
+
hooks: {
|
|
174
|
+
"post-model-call": maxTokensContinuePostModelCall,
|
|
175
|
+
stop: maxTokensContinueStop,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
153
179
|
/**
|
|
154
180
|
* `memory-v3-shadow` — houses the memory-v3 shadow/live orchestration engine
|
|
155
181
|
* (`memory-v3-shadow/`) and its injector. The `user-prompt-submit` /
|
|
@@ -217,6 +243,22 @@ export const defaultExplorationDriftPlugin: Plugin = {
|
|
|
217
243
|
},
|
|
218
244
|
};
|
|
219
245
|
|
|
246
|
+
/**
|
|
247
|
+
* `task-progress-nudge` — a `post-tool-use` hook that nudges the model to show
|
|
248
|
+
* a `task_progress` card once an interactive turn has accumulated several
|
|
249
|
+
* tool-call rounds without one. Best-effort and once-per-turn; capable models
|
|
250
|
+
* that already show a card are never nudged.
|
|
251
|
+
*/
|
|
252
|
+
export const defaultTaskProgressNudgePlugin: Plugin = {
|
|
253
|
+
manifest: {
|
|
254
|
+
name: taskProgressNudgePkg.name,
|
|
255
|
+
version: taskProgressNudgePkg.version,
|
|
256
|
+
},
|
|
257
|
+
hooks: {
|
|
258
|
+
"post-tool-use": taskProgressNudgePostToolUse,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
|
|
220
262
|
/**
|
|
221
263
|
* `tool-result-truncate` — a `post-tool-use` hook that tail-drops an oversized
|
|
222
264
|
* tool result down to a character budget derived from the model's context
|
|
@@ -243,8 +285,10 @@ function getAllDefaultPlugins(): readonly Plugin[] {
|
|
|
243
285
|
defaultMemoryRetrievalPlugin,
|
|
244
286
|
defaultToolResultTruncatePlugin,
|
|
245
287
|
defaultEmptyResponsePlugin,
|
|
288
|
+
defaultMaxTokensContinuePlugin,
|
|
246
289
|
defaultToolErrorPlugin,
|
|
247
290
|
defaultExplorationDriftPlugin,
|
|
291
|
+
defaultTaskProgressNudgePlugin,
|
|
248
292
|
defaultHistoryRepairPlugin,
|
|
249
293
|
defaultImageRecoveryPlugin,
|
|
250
294
|
defaultCompactionPlugin,
|
|
@@ -290,8 +334,10 @@ export function registerDefaultPlugins(): void {
|
|
|
290
334
|
export function resetPluginRegistryAndRegisterDefaults(): void {
|
|
291
335
|
resetPluginRegistryForTests();
|
|
292
336
|
resetEmptyResponseNudgeStoreForTests();
|
|
337
|
+
resetMaxTokensContinueStoreForTests();
|
|
293
338
|
resetRepairStateStoreForTests();
|
|
294
339
|
resetImageRecoveryStoreForTests();
|
|
295
340
|
resetExplorationDriftStateForTests();
|
|
341
|
+
resetTaskProgressNudgeStateForTests();
|
|
296
342
|
registerDefaultPlugins();
|
|
297
343
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-conversation auto-continue budget for the default max-tokens-continue
|
|
3
|
+
* module.
|
|
4
|
+
*
|
|
5
|
+
* The `post-model-call` hook resumes a turn the provider truncated at its
|
|
6
|
+
* output token limit by appending a continuation nudge and asking the loop to
|
|
7
|
+
* re-query. The recovery is bounded per run: a long output legitimately spans
|
|
8
|
+
* a few continuations (each model call gets a fresh output budget), but a turn
|
|
9
|
+
* that keeps hitting the limit after several resumes is burning tokens without
|
|
10
|
+
* converging, so the hook lets it end and the continuation card surfaces for
|
|
11
|
+
* the user to drive.
|
|
12
|
+
*
|
|
13
|
+
* The two hooks split this state's lifecycle: `post-model-call` consumes a
|
|
14
|
+
* unit of budget each time it issues a continue, and the sibling `stop` hook
|
|
15
|
+
* clears the counter when the turn terminates. A conversation therefore only
|
|
16
|
+
* holds an entry while a run is in flight, and the next run starts with a
|
|
17
|
+
* full budget.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Maximum automatic continuations per run before the turn ends terminally. */
|
|
21
|
+
export const MAX_TOKENS_AUTO_CONTINUES = 3;
|
|
22
|
+
|
|
23
|
+
/** Continues consumed this run, keyed by conversation. */
|
|
24
|
+
const continuesUsed = new Map<string, number>();
|
|
25
|
+
|
|
26
|
+
/** Whether the conversation still has auto-continue budget this run. */
|
|
27
|
+
export function hasMaxTokensContinueBudget(conversationId: string): boolean {
|
|
28
|
+
return (continuesUsed.get(conversationId) ?? 0) < MAX_TOKENS_AUTO_CONTINUES;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Consume one unit of the conversation's auto-continue budget. */
|
|
32
|
+
export function consumeMaxTokensContinueBudget(conversationId: string): void {
|
|
33
|
+
continuesUsed.set(
|
|
34
|
+
conversationId,
|
|
35
|
+
(continuesUsed.get(conversationId) ?? 0) + 1,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Clear the conversation's budget counter so the next run starts afresh. The
|
|
41
|
+
* sibling `stop` hook calls this when the turn terminates.
|
|
42
|
+
*/
|
|
43
|
+
export function clearMaxTokensContinueBudget(conversationId: string): void {
|
|
44
|
+
continuesUsed.delete(conversationId);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Test-only: drop every conversation's budget state so a suite that drives
|
|
49
|
+
* the hook directly starts each case from an empty store.
|
|
50
|
+
*/
|
|
51
|
+
export function resetMaxTokensContinueStoreForTests(): void {
|
|
52
|
+
continuesUsed.clear();
|
|
53
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `post-model-call` hook: when the provider truncates a user-facing
|
|
3
|
+
* turn at its output token limit, resume it automatically instead of ending
|
|
4
|
+
* the turn.
|
|
5
|
+
*
|
|
6
|
+
* A `max_tokens` stop means the model had more to say — the reply (often a
|
|
7
|
+
* long generation such as a skill building an entire app) simply exceeded the
|
|
8
|
+
* profile's output budget. Without intervention the loop ends the turn and
|
|
9
|
+
* surfaces a continuation card the user must click, and a single click often
|
|
10
|
+
* just truncates again — the turn can never finish. This hook keeps the
|
|
11
|
+
* truncated turn in history, appends a continuation nudge, and sets
|
|
12
|
+
* `decision: "continue"` so the next model call resumes with a fresh output
|
|
13
|
+
* budget.
|
|
14
|
+
*
|
|
15
|
+
* Scope and bounds:
|
|
16
|
+
*
|
|
17
|
+
* - **Main-agent turns only.** Background, subagent, and compaction calls
|
|
18
|
+
* self-manage their budgets; per the post-model-call contract the hook
|
|
19
|
+
* gates on {@link PostModelCallContext.callSite}.
|
|
20
|
+
* - **Bounded per run.** A long output legitimately spans a few
|
|
21
|
+
* continuations, but a turn that keeps truncating after
|
|
22
|
+
* `MAX_TOKENS_AUTO_CONTINUES` resumes is not converging; the hook lets it
|
|
23
|
+
* end so the continuation card surfaces for the user to drive. The sibling
|
|
24
|
+
* `stop` hook (see `./stop.ts`) clears the budget when the turn
|
|
25
|
+
* terminates.
|
|
26
|
+
* - **Truncation-safe content only.** The agent loop strips unsafe blocks
|
|
27
|
+
* (truncated `tool_use` and friends) before running this hook; an entirely
|
|
28
|
+
* empty remainder leaves nothing to resume from, so the hook lets the turn
|
|
29
|
+
* end rather than pushing an empty assistant message the provider would
|
|
30
|
+
* reject.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
|
|
34
|
+
|
|
35
|
+
import { isMaxTokensStopReason } from "../../../../agent/loop.js";
|
|
36
|
+
import {
|
|
37
|
+
consumeMaxTokensContinueBudget,
|
|
38
|
+
hasMaxTokensContinueBudget,
|
|
39
|
+
} from "../continue-state-store.js";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Continuation nudge appended after the truncated assistant turn. Shown to
|
|
43
|
+
* the LLM, not the user — edits here affect model behavior, not end-user UX.
|
|
44
|
+
*/
|
|
45
|
+
export const MAX_TOKENS_CONTINUE_NUDGE_TEXT =
|
|
46
|
+
"<system_notice>Your previous response was cut off because it reached the maximum output length. Continue exactly where you stopped — do not repeat content you already sent and do not start over.</system_notice>";
|
|
47
|
+
|
|
48
|
+
const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
|
|
49
|
+
if (ctx.error) return;
|
|
50
|
+
if (!isMaxTokensStopReason(ctx.stopReason)) return;
|
|
51
|
+
if (ctx.callSite !== "mainAgent") return;
|
|
52
|
+
// Nothing survived truncation-block stripping — there is no partial output
|
|
53
|
+
// to resume from, so let the turn end terminally.
|
|
54
|
+
if (ctx.content.length === 0) return;
|
|
55
|
+
|
|
56
|
+
if (!hasMaxTokensContinueBudget(ctx.conversationId)) {
|
|
57
|
+
ctx.logger.warn(
|
|
58
|
+
{ plugin: "max-tokens-continue", conversationId: ctx.conversationId },
|
|
59
|
+
"Turn kept hitting the output token limit — auto-continue budget exhausted, ending the turn",
|
|
60
|
+
);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
consumeMaxTokensContinueBudget(ctx.conversationId);
|
|
65
|
+
ctx.messages.push({
|
|
66
|
+
role: "assistant",
|
|
67
|
+
content: structuredClone(ctx.content),
|
|
68
|
+
});
|
|
69
|
+
ctx.messages.push({
|
|
70
|
+
role: "user",
|
|
71
|
+
content: [{ type: "text", text: MAX_TOKENS_CONTINUE_NUDGE_TEXT }],
|
|
72
|
+
});
|
|
73
|
+
ctx.decision = "continue";
|
|
74
|
+
ctx.logger.warn(
|
|
75
|
+
{ plugin: "max-tokens-continue", conversationId: ctx.conversationId },
|
|
76
|
+
"Turn truncated at the output token limit — auto-continuing",
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default postModelCall;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default `stop` hook: clears the per-conversation max-tokens auto-continue
|
|
3
|
+
* budget when a turn terminates.
|
|
4
|
+
*
|
|
5
|
+
* The `post-model-call` hook (see `./post-model-call.ts`) consumes budget each
|
|
6
|
+
* time it resumes a truncated turn. `stop` is the definitive terminal hook —
|
|
7
|
+
* it fires exactly once when the turn is truly ending — so clearing the
|
|
8
|
+
* counter here unconditionally guarantees the next run starts with a full
|
|
9
|
+
* budget, no matter how the turn ended.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
|
|
13
|
+
|
|
14
|
+
import { clearMaxTokensContinueBudget } from "../continue-state-store.js";
|
|
15
|
+
|
|
16
|
+
const stop: PluginHookFn<StopContext> = async (ctx) => {
|
|
17
|
+
clearMaxTokensContinueBudget(ctx.conversationId);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default stop;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "default-max-tokens-continue",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "First-party default plugin that auto-resumes a user-facing turn truncated by the provider's output token limit.",
|
|
5
|
+
"private": true,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.12.0"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@vellumai/plugin-api": ">=0.8.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { shouldRunV2Retrieval } from "../user-prompt-submit.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* v2 graph-memory retrieval is the deprecated path. It is gated off under
|
|
7
|
+
* `memory-v3-live` — v3 owns the injected-memory layer and runtime assembly
|
|
8
|
+
* strips any v2 `<memory>` block, so running v2's per-turn retrieval (embedding
|
|
9
|
+
* + hybrid search + the `memoryRetrieval` LLM router) would only have its
|
|
10
|
+
* result discarded. It is also skipped for untrusted actors. The
|
|
11
|
+
* conversation/abort-signal presence checks stay inline at the call site (for
|
|
12
|
+
* type narrowing) and are deliberately NOT part of this policy decision.
|
|
13
|
+
*/
|
|
14
|
+
describe("shouldRunV2Retrieval", () => {
|
|
15
|
+
test("runs for a trusted actor when memory-v3-live is off (v2/shadow path)", () => {
|
|
16
|
+
expect(
|
|
17
|
+
shouldRunV2Retrieval({ isTrustedActor: true, memoryV3Live: false }),
|
|
18
|
+
).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("is skipped under memory-v3-live even for a trusted actor", () => {
|
|
22
|
+
// The cutover: v3 owns memory, so v2 retrieval (and its LLM router) must
|
|
23
|
+
// not fire — this is the per-turn cost the gate removes.
|
|
24
|
+
expect(
|
|
25
|
+
shouldRunV2Retrieval({ isTrustedActor: true, memoryV3Live: true }),
|
|
26
|
+
).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("is skipped for an untrusted actor regardless of the flag", () => {
|
|
30
|
+
expect(
|
|
31
|
+
shouldRunV2Retrieval({ isTrustedActor: false, memoryV3Live: false }),
|
|
32
|
+
).toBe(false);
|
|
33
|
+
expect(
|
|
34
|
+
shouldRunV2Retrieval({ isTrustedActor: false, memoryV3Live: true }),
|
|
35
|
+
).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
UserPromptSubmitContext,
|
|
33
33
|
} from "@vellumai/plugin-api";
|
|
34
34
|
|
|
35
|
+
import { isAssistantFeatureFlagEnabled } from "../../../../config/assistant-feature-flags.js";
|
|
35
36
|
import { getConfig } from "../../../../config/loader.js";
|
|
36
37
|
import { findConversationOrSubagent } from "../../../../daemon/conversation-registry.js";
|
|
37
38
|
import {
|
|
@@ -48,6 +49,22 @@ import { broadcastMessage } from "../../../../runtime/assistant-event-hub.js";
|
|
|
48
49
|
import type { GraphMemoryResult } from "../../../types.js";
|
|
49
50
|
import { MEMORY_V3_INJECTED_BLOCK_METADATA_KEY } from "../../memory-v3-shadow/ever-injected-store.js";
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Whether to run v2 graph-memory retrieval this turn. v2 retrieval is the
|
|
54
|
+
* deprecated path: under `memory-v3-live`, v3 is the injected-memory source and
|
|
55
|
+
* runtime assembly strips any v2 `<memory>` block, so running v2's retrieval
|
|
56
|
+
* (embedding + hybrid search + the `memoryRetrieval` LLM router) only to
|
|
57
|
+
* discard the result is pure per-turn waste. Untrusted actors never run it
|
|
58
|
+
* either. The caller additionally requires the live conversation and its abort
|
|
59
|
+
* signal to be present (kept inline at the call site for type narrowing).
|
|
60
|
+
*/
|
|
61
|
+
export function shouldRunV2Retrieval(params: {
|
|
62
|
+
isTrustedActor: boolean;
|
|
63
|
+
memoryV3Live: boolean;
|
|
64
|
+
}): boolean {
|
|
65
|
+
return params.isTrustedActor && !params.memoryV3Live;
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
/**
|
|
52
69
|
* Persist and broadcast the retrieval's side effects: the injected block on
|
|
53
70
|
* the user message's metadata (so it survives reloads), a recall-log row, and
|
|
@@ -250,8 +267,19 @@ const userPromptSubmitMemoryRetrieval: PluginHookFn<
|
|
|
250
267
|
conversation?.assistantId,
|
|
251
268
|
);
|
|
252
269
|
|
|
270
|
+
// v2 graph retrieval is the deprecated path: `shouldRunV2Retrieval` skips it
|
|
271
|
+
// under memory-v3-live (v3 owns the `<memory>` layer and assembly strips any
|
|
272
|
+
// v2 block) and for untrusted actors. The `conversation && abortSignal`
|
|
273
|
+
// presence checks stay inline so the block below narrows. NOTE: this removes
|
|
274
|
+
// the v2 fallback — under v3-live, a v3 empty/failed selection yields no NEW
|
|
275
|
+
// injected memory that turn (prior turns' frozen v3 cards still ride history).
|
|
276
|
+
const memoryV3Live = isAssistantFeatureFlagEnabled("memory-v3-live", config);
|
|
253
277
|
let v2BlockPersisted = false;
|
|
254
|
-
if (
|
|
278
|
+
if (
|
|
279
|
+
shouldRunV2Retrieval({ isTrustedActor, memoryV3Live }) &&
|
|
280
|
+
conversation &&
|
|
281
|
+
abortSignal
|
|
282
|
+
) {
|
|
255
283
|
// Retrieval progress (`memory_status`) and the `memory_recalled` summary
|
|
256
284
|
// publish to the shared `broadcastMessage` hub — the sink every turn
|
|
257
285
|
// publisher converges to — rather than a threaded event callback. This
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
} from "../../../../memory/memory-marker.js";
|
|
58
58
|
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
59
59
|
import { migrateAddMemoryV3EverInjected } from "../../../../memory/migrations/277-add-memory-v3-ever-injected.js";
|
|
60
|
+
import { migrateMemoryV3SelectionsMessageIdAndSections } from "../../../../memory/migrations/283-memory-v3-selections-message-id-and-sections.js";
|
|
60
61
|
import * as schema from "../../../../memory/schema.js";
|
|
61
62
|
import type { PageIndexEntry } from "../../../../memory/v2/page-index.js";
|
|
62
63
|
import type {
|
|
@@ -131,6 +132,7 @@ function makeDb() {
|
|
|
131
132
|
const db = drizzle(testSqlite, { schema });
|
|
132
133
|
migrateAddMemoryV3EverInjected(db);
|
|
133
134
|
migrateAddMemoryV3Selections(db);
|
|
135
|
+
migrateMemoryV3SelectionsMessageIdAndSections(db);
|
|
134
136
|
// Minimal `messages` shape — metadata persistence, the prune valve's
|
|
135
137
|
// v3-ownership scan, and the restart rehydration read only these columns.
|
|
136
138
|
testSqlite.run(/*sql*/ `
|
|
@@ -631,7 +633,10 @@ async function runTurn(
|
|
|
631
633
|
}
|
|
632
634
|
|
|
633
635
|
// Spotlight: scoped strip of the stale block (real assembly helper), then
|
|
634
|
-
//
|
|
636
|
+
// re-attach the fresh one. Real assembly splices it after the memory cards
|
|
637
|
+
// (after-memory-prefix); this sim appends to the tail because only the
|
|
638
|
+
// block's presence, content, placement value, and strip-and-replace are
|
|
639
|
+
// asserted here — not its exact position within the message.
|
|
635
640
|
const spotlight = await memoryV3SpotlightInjector.produce(ctx);
|
|
636
641
|
const stripped = stripSpotlightInjections(history);
|
|
637
642
|
history.splice(0, history.length, ...stripped);
|
|
@@ -976,13 +981,13 @@ describe("memory-v3 carry integration — cache contract", () => {
|
|
|
976
981
|
});
|
|
977
982
|
|
|
978
983
|
describe("memory-v3 carry integration — spotlight contract", () => {
|
|
979
|
-
test("spotlight is present every turn,
|
|
984
|
+
test("spotlight is present every turn, after the memory cards, bounded by n × (window + 1)", () => {
|
|
980
985
|
for (const record of records) {
|
|
981
986
|
expect(record.spotlightText.startsWith("<memory_spotlight>\n")).toBe(
|
|
982
987
|
true,
|
|
983
988
|
);
|
|
984
989
|
expect(record.spotlightText.endsWith("\n</memory_spotlight>")).toBe(true);
|
|
985
|
-
expect(record.spotlightPlacement).toBe("
|
|
990
|
+
expect(record.spotlightPlacement).toBe("after-memory-prefix");
|
|
986
991
|
expect(record.spotlightEntries).toBeGreaterThanOrEqual(1);
|
|
987
992
|
expect(record.spotlightEntries).toBeLessThanOrEqual(
|
|
988
993
|
SPOTLIGHT_N * (SPOTLIGHT_WINDOW_TURNS + 1),
|
|
@@ -33,6 +33,7 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
|
33
33
|
import { unwrapMemoryBlock } from "../../../../memory/memory-marker.js";
|
|
34
34
|
import { migrateAddMemoryV3Selections } from "../../../../memory/migrations/268-add-memory-v3-selections.js";
|
|
35
35
|
import { migrateAddMemoryV3EverInjected } from "../../../../memory/migrations/277-add-memory-v3-ever-injected.js";
|
|
36
|
+
import { migrateMemoryV3SelectionsMessageIdAndSections } from "../../../../memory/migrations/283-memory-v3-selections-message-id-and-sections.js";
|
|
36
37
|
import * as schema from "../../../../memory/schema.js";
|
|
37
38
|
import type { InjectionBlock } from "../../../types.js";
|
|
38
39
|
import type { OrchestrateResult } from "../orchestrate.js";
|
|
@@ -93,6 +94,7 @@ function makeDb() {
|
|
|
93
94
|
migrateAddMemoryV3EverInjected(db);
|
|
94
95
|
// The prune valve's recency ranking reads `memory_v3_selections`.
|
|
95
96
|
migrateAddMemoryV3Selections(db);
|
|
97
|
+
migrateMemoryV3SelectionsMessageIdAndSections(db);
|
|
96
98
|
// The prune valve plans only against slugs whose card sections are
|
|
97
99
|
// locatable in persisted `memoryV3InjectedBlock` rows
|
|
98
100
|
// (`collectPersistedV3Cards`) — minimal `messages` shape it reads.
|
|
@@ -573,13 +575,13 @@ describe("memoryV3SpotlightInjector — ephemeral section spotlight", () => {
|
|
|
573
575
|
const sectionB = section("page-b", "Beta", "beta section text");
|
|
574
576
|
const sectionC = section("page-c", "Gamma", "gamma section text");
|
|
575
577
|
|
|
576
|
-
test("renders selected finder hits' matched sections
|
|
578
|
+
test("renders selected finder hits' matched sections right after the memory cards", async () => {
|
|
577
579
|
liveEnabled = true;
|
|
578
580
|
turnResults.set(0, result(["page-a", "page-b"], [["page-a", sectionA]]));
|
|
579
581
|
|
|
580
582
|
const block = await produceSpotlight("conv-1", 0);
|
|
581
583
|
expect(block).not.toBeNull();
|
|
582
|
-
expect(block!.placement).toBe("
|
|
584
|
+
expect(block!.placement).toBe("after-memory-prefix");
|
|
583
585
|
expect(block!.text.startsWith("<memory_spotlight>\n")).toBe(true);
|
|
584
586
|
expect(block!.text.endsWith("\n</memory_spotlight>")).toBe(true);
|
|
585
587
|
expect(block!.text).toContain(
|
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
* by `pinned_ids`; out-of-range IDs dropped; selections deduped by slug
|
|
14
14
|
* (a page can appear as both a card and a finder line; pinned flags OR).
|
|
15
15
|
* - Omitted `ids` → keep ALL candidates (recall-safe, slug-deduped).
|
|
16
|
-
* - Explicit `ids: []` → keep none (deliberate abstention).
|
|
16
|
+
* - Explicit `ids: []` → keep none (deliberate abstention) — a normal result.
|
|
17
|
+
* - Empty candidate pool → keep none (nothing to select).
|
|
17
18
|
* - Finder snippets are whitespace-collapsed and truncated (~300 chars).
|
|
18
|
-
* - No provider / missing tool_use / schema mismatch / throw →
|
|
19
|
-
* (
|
|
19
|
+
* - No provider / missing tool_use / schema mismatch / provider throw → throw
|
|
20
|
+
* MemoryV3RetrievalUnavailableError (an INFRA failure, deliberately DISTINCT
|
|
21
|
+
* from a deliberate empty selection), the last three after a re-prompt retry.
|
|
20
22
|
* - One forced-tool `select_pages` call on the v3 L2 call site with
|
|
21
23
|
* `disableTurnStartCache` (the tail varies per turn — the provider's
|
|
22
24
|
* auto-anchor would never hit).
|
|
@@ -61,7 +63,8 @@ mock.module("../../../../util/logger.js", () => ({
|
|
|
61
63
|
}),
|
|
62
64
|
}));
|
|
63
65
|
|
|
64
|
-
const { selectPool } =
|
|
66
|
+
const { selectPool, MemoryV3RetrievalUnavailableError } =
|
|
67
|
+
await import("../pool-select.js");
|
|
65
68
|
type SelectorPool = Parameters<typeof selectPool>[0];
|
|
66
69
|
|
|
67
70
|
// ---------------------------------------------------------------------------
|
|
@@ -226,35 +229,42 @@ describe("selectPool — id mapping", () => {
|
|
|
226
229
|
});
|
|
227
230
|
|
|
228
231
|
// ---------------------------------------------------------------------------
|
|
229
|
-
// selectPool —
|
|
232
|
+
// selectPool — infrastructure failures THROW (no silent degradation). A
|
|
233
|
+
// deliberate empty selection and an empty pool (covered above) still return
|
|
234
|
+
// normally; only a genuine infra failure throws so the LIVE injector can
|
|
235
|
+
// hard-fail the turn instead of shipping it with no memory.
|
|
230
236
|
// ---------------------------------------------------------------------------
|
|
231
237
|
|
|
232
|
-
describe("selectPool —
|
|
233
|
-
test("no provider →
|
|
238
|
+
describe("selectPool — infrastructure failures throw", () => {
|
|
239
|
+
test("no provider → throws without calling the provider", async () => {
|
|
234
240
|
providerStub = null;
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
242
|
+
MemoryV3RetrievalUnavailableError,
|
|
243
|
+
);
|
|
237
244
|
expect(providerCalls).toHaveLength(0);
|
|
238
245
|
});
|
|
239
246
|
|
|
240
|
-
test("missing tool_use →
|
|
247
|
+
test("missing tool_use → throws after retrying", async () => {
|
|
241
248
|
providerStub = makeProvider(noToolResponse());
|
|
242
|
-
|
|
243
|
-
|
|
249
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
250
|
+
MemoryV3RetrievalUnavailableError,
|
|
251
|
+
);
|
|
244
252
|
expect(providerCalls).toHaveLength(3);
|
|
245
253
|
});
|
|
246
254
|
|
|
247
|
-
test("schema mismatch →
|
|
255
|
+
test("schema mismatch → throws after retrying", async () => {
|
|
248
256
|
providerStub = makeProvider(toolUseResponse({ ids: "not-an-array" }));
|
|
249
|
-
|
|
250
|
-
|
|
257
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
258
|
+
MemoryV3RetrievalUnavailableError,
|
|
259
|
+
);
|
|
251
260
|
expect(providerCalls).toHaveLength(3);
|
|
252
261
|
});
|
|
253
262
|
|
|
254
|
-
test("provider throw →
|
|
263
|
+
test("provider throw → throws after retrying", async () => {
|
|
255
264
|
providerStub = makeThrowingProvider();
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
await expect(selectPool(makePool(), makeTurn("x"))).rejects.toThrow(
|
|
266
|
+
MemoryV3RetrievalUnavailableError,
|
|
267
|
+
);
|
|
258
268
|
expect(providerCalls).toHaveLength(3);
|
|
259
269
|
});
|
|
260
270
|
|