@vellumai/assistant 0.8.12 → 0.9.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +0 -14
- package/ARCHITECTURE.md +45 -45
- package/README.md +1 -1
- package/bun.lock +200 -154
- package/docs/architecture/integrations.md +3 -3
- package/docs/architecture/memory.md +2 -2
- package/docs/architecture/security.md +10 -10
- package/docs/runbook-trusted-contacts.md +12 -12
- package/docs/skills.md +6 -6
- package/docs/workflows-testing.md +221 -0
- package/docs/workflows.md +510 -0
- package/examples/plugins/echo/README.md +5 -5
- package/knip.json +2 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
- package/openapi.yaml +7197 -5708
- package/package.json +8 -4
- package/scripts/generate-openapi.ts +66 -114
- package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
- package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
- package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
- package/src/__tests__/app-compiler.test.ts +7 -1
- package/src/__tests__/app-dir-path-guard.test.ts +27 -3
- package/src/__tests__/app-executors.test.ts +43 -0
- package/src/__tests__/approval-cascade.test.ts +0 -5
- package/src/__tests__/approval-routes-http.test.ts +91 -0
- package/src/__tests__/assistant-stream-state.test.ts +107 -0
- package/src/__tests__/browser-fill-credential.test.ts +3 -3
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
- package/src/__tests__/compaction-events.test.ts +63 -7
- package/src/__tests__/compaction-trail-store.test.ts +74 -1
- package/src/__tests__/compaction.benchmark.test.ts +63 -41
- package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
- package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
- package/src/__tests__/conversation-history-web-search.test.ts +7 -0
- package/src/__tests__/conversation-process-callsite.test.ts +0 -5
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
- package/src/__tests__/conversation-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-queue.test.ts +0 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
- package/src/__tests__/conversation-speed-override.test.ts +0 -5
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-usage.test.ts +2 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
- package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
- package/src/__tests__/credential-broker-server-use.test.ts +2 -2
- package/src/__tests__/credential-broker.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +417 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault.test.ts +37 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
- package/src/__tests__/dynamic-page-surface.test.ts +219 -0
- package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
- package/src/__tests__/gateway-flag-listener.test.ts +24 -7
- package/src/__tests__/guardian-action-sweep.test.ts +56 -219
- package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
- package/src/__tests__/llm-resolver.test.ts +115 -0
- package/src/__tests__/managed-profile-guard.test.ts +6 -5
- package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
- package/src/__tests__/media-generate-image.test.ts +20 -9
- package/src/__tests__/mock-gateway-ipc.ts +23 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/normalize-onboarding.test.ts +26 -0
- package/src/__tests__/notification-decision-strategy.test.ts +4 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
- package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
- package/src/__tests__/require-fresh-approval.test.ts +425 -1
- package/src/__tests__/resolve-app-id.test.ts +56 -0
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
- package/src/__tests__/schedule-routes.test.ts +257 -4
- package/src/__tests__/schedule-store.test.ts +60 -0
- package/src/__tests__/schedule-tools.test.ts +247 -2
- package/src/__tests__/skill-execute-input.test.ts +85 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
- package/src/__tests__/task-scheduler.test.ts +299 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
- package/src/__tests__/tool-result-spool.test.ts +3 -1
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
- package/src/agent/compaction-circuit.ts +11 -0
- package/src/agent/loop.ts +181 -12
- package/src/api/constants/call-sites.ts +12 -0
- package/src/api/events/assistant-thinking-delta.ts +10 -0
- package/src/api/events/usage-update.ts +7 -0
- package/src/api/index.ts +4 -1
- package/src/api/responses/memory-v3-selection-log.ts +18 -11
- package/src/approvals/approval-primitive.ts +2 -2
- package/src/background-wake/background-wake-routes.test.ts +5 -2
- package/src/bundler/compiler-tools.ts +1 -1
- package/src/bundler/package-resolver.ts +0 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/guardian-action-sweep.ts +16 -93
- package/src/cli/AGENTS.md +4 -0
- package/src/cli/commands/__tests__/schedules.test.ts +430 -1
- package/src/cli/commands/credentials.ts +28 -24
- package/src/cli/commands/image-generation.ts +23 -9
- package/src/cli/commands/notifications.ts +1 -1
- package/src/cli/commands/plugins.ts +89 -46
- package/src/cli/commands/schedules.ts +384 -11
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
- package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
- package/src/cli/lib/inspect-plugin.ts +62 -1
- package/src/cli/lib/install-from-github.ts +52 -5
- package/src/cli/lib/upgrade-plugin.ts +18 -0
- package/src/config/__tests__/workflows-schema.test.ts +60 -0
- package/src/config/bundled-skills/acp/SKILL.md +2 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
- package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
- package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
- package/src/config/bundled-skills/schedule/SKILL.md +7 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
- package/src/config/bundled-skills/workflows/SKILL.md +214 -0
- package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
- package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
- package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
- package/src/config/bundled-tool-registry.ts +14 -2
- package/src/config/call-site-defaults.ts +5 -0
- package/src/config/feature-flag-registry.json +12 -4
- package/src/config/llm-context-resolution.ts +8 -0
- package/src/config/llm-resolver.ts +30 -0
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
- package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
- package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
- package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/heartbeat.ts +4 -1
- package/src/config/schemas/llm.ts +33 -26
- package/src/config/schemas/memory-retrospective.ts +19 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/schemas/services.ts +5 -2
- package/src/config/schemas/workflows.ts +42 -0
- package/src/config/skills.ts +3 -3
- package/src/context/compactor.ts +273 -39
- package/src/context/post-turn-tool-result-truncation.ts +23 -6
- package/src/context/tool-result-spool.ts +12 -17
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/process-manager.ts +37 -3
- package/src/credential-execution/prompted-credential.ts +205 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
- package/src/daemon/conversation-process.ts +11 -2
- package/src/daemon/conversation-surfaces.ts +167 -3
- package/src/daemon/conversation-tool-setup.ts +103 -26
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +115 -11
- package/src/daemon/handlers/shared.ts +26 -14
- package/src/daemon/host-cu-proxy.ts +15 -12
- package/src/daemon/host-file-proxy.ts +15 -12
- package/src/daemon/host-transfer-proxy.ts +30 -24
- package/src/daemon/lifecycle.ts +40 -3
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/messages.ts +2 -10
- package/src/daemon/message-types/workflows.ts +49 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
- package/src/daemon/process-message.ts +6 -0
- package/src/daemon/tool-setup-types.ts +57 -0
- package/src/daemon/wake-conversation-ops.ts +18 -0
- package/src/heartbeat/heartbeat-run-store.ts +8 -2
- package/src/home/feed-types.ts +1 -1
- package/src/ipc/gateway-flag-listener.ts +28 -6
- package/src/mcp/mcp-auth-state.ts +8 -20
- package/src/media/__tests__/image-models.test.ts +57 -0
- package/src/media/image-models.ts +66 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
- package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
- package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
- package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
- package/src/memory/auto-analysis-enqueue.ts +14 -1
- package/src/memory/compaction-log-store-clickhouse.ts +6 -4
- package/src/memory/conversation-crud.ts +9 -2
- package/src/memory/conversation-disk-view.ts +1 -1
- package/src/memory/conversation-queries.ts +22 -7
- package/src/memory/db-init.ts +20 -0
- package/src/memory/db-maintenance.ts +16 -0
- package/src/memory/embedding-runtime-manager.ts +1 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
- package/src/memory/llm-request-log-source-local.ts +19 -1
- package/src/memory/llm-request-log-source.ts +35 -6
- package/src/memory/llm-request-log-store.ts +90 -2
- package/src/memory/memory-retrospective-constants.ts +9 -0
- package/src/memory/memory-retrospective-enqueue.ts +3 -6
- package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
- package/src/memory/memory-retrospective-job.ts +500 -208
- package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
- package/src/memory/memory-retrospective-state.ts +85 -2
- package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
- package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
- package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
- package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
- package/src/memory/migrations/284-workflow-runs.ts +51 -0
- package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
- package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
- package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
- package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
- package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
- package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
- package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
- package/src/memory/migrations/index.ts +10 -0
- package/src/memory/preloaded-apps.ts +116 -0
- package/src/memory/schema/infrastructure.ts +4 -0
- package/src/memory/schema/memory-core.ts +4 -0
- package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
- package/src/memory/v2/__tests__/page-store.test.ts +13 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -6
- package/src/memory/v2/page-store.ts +4 -3
- package/src/memory/v2/qdrant.ts +42 -3
- package/src/memory/v2/types.ts +16 -10
- package/src/messaging/draft-store.ts +1 -1
- package/src/notifications/access-request-copy.ts +200 -113
- package/src/notifications/adapters/slack.ts +250 -111
- package/src/notifications/adapters/telegram.ts +7 -44
- package/src/notifications/approval-card-builder.ts +93 -0
- package/src/notifications/broadcaster.ts +74 -0
- package/src/notifications/conversation-pairing.ts +8 -6
- package/src/notifications/copy-composer.ts +32 -26
- package/src/notifications/decision-engine.ts +59 -7
- package/src/notifications/guardian-question-mode.ts +145 -155
- package/src/notifications/home-feed-side-effect.ts +28 -11
- package/src/notifications/notification-utils.ts +66 -0
- package/src/notifications/signal.ts +6 -0
- package/src/notifications/tool-approval-copy.ts +142 -0
- package/src/notifications/types.ts +19 -0
- package/src/permissions/threshold.ts +11 -0
- package/src/plugin-api/types.ts +16 -4
- package/src/plugins/defaults/compaction/window-manager.ts +44 -0
- package/src/plugins/defaults/index.ts +46 -0
- package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
- package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
- package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
- package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
- package/src/prompts/__tests__/system-prompt.test.ts +100 -1
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
- package/src/prompts/normalize-onboarding.ts +2 -0
- package/src/prompts/persona-resolver.ts +3 -0
- package/src/prompts/system-prompt.ts +51 -2
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
- package/src/prompts/templates/system-sections.ts +8 -3
- package/src/providers/call-site-routing.ts +6 -3
- package/src/providers/fireworks/client.ts +3 -0
- package/src/providers/inference/auth.ts +52 -46
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
- package/src/providers/openai/chat-completions-provider.ts +47 -9
- package/src/providers/openai/coerce-object-args.ts +104 -0
- package/src/providers/retry.ts +8 -5
- package/src/providers/types.ts +10 -0
- package/src/runtime/__tests__/agent-wake.test.ts +629 -7
- package/src/runtime/access-request-helper.ts +16 -9
- package/src/runtime/agent-wake.ts +302 -51
- package/src/runtime/assistant-stream-state.ts +141 -8
- package/src/runtime/background-job-runner.ts +9 -0
- package/src/runtime/channel-approval-types.ts +1 -0
- package/src/runtime/channel-invite-transports/telegram.ts +6 -5
- package/src/runtime/channel-invite-transports/voice.ts +2 -2
- package/src/runtime/channel-invite-types.ts +4 -2
- package/src/runtime/channel-retry-sweep.ts +19 -41
- package/src/runtime/finalize-event-delivery.ts +72 -0
- package/src/runtime/guardian-action-message-composer.ts +0 -54
- package/src/runtime/http-server.ts +6 -14
- package/src/runtime/http-types.ts +0 -1
- package/src/runtime/message-composer-types.ts +0 -9
- package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
- package/src/runtime/middleware/auth.ts +27 -3
- package/src/runtime/middleware/rate-limiter.ts +28 -1
- package/src/runtime/migrations/vbundle-builder.ts +6 -5
- package/src/runtime/pending-interactions.ts +20 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
- package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
- package/src/runtime/routes/approval-routes.ts +35 -8
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
- package/src/runtime/routes/btw-routes.ts +37 -1
- package/src/runtime/routes/channel-delivery-routes.ts +11 -7
- package/src/runtime/routes/channel-route-definitions.ts +3 -0
- package/src/runtime/routes/channel-route-shared.ts +3 -1
- package/src/runtime/routes/consolidation-routes.ts +17 -13
- package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
- package/src/runtime/routes/conversation-list-routes.ts +41 -4
- package/src/runtime/routes/conversation-query-routes.ts +196 -11
- package/src/runtime/routes/conversation-routes.ts +15 -1
- package/src/runtime/routes/credential-prompt-routes.ts +39 -17
- package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
- package/src/runtime/routes/heartbeat-routes.ts +18 -13
- package/src/runtime/routes/image-generation-routes.ts +20 -2
- package/src/runtime/routes/inbound-message-handler.ts +13 -12
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/log-export/AGENTS.md +1 -1
- package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +5 -9
- package/src/runtime/routes/plugins-routes.ts +26 -0
- package/src/runtime/routes/ps-routes.ts +10 -8
- package/src/runtime/routes/retrospective-routes.ts +235 -0
- package/src/runtime/routes/runs-pagination.ts +75 -0
- package/src/runtime/routes/schedule-routes.ts +367 -59
- package/src/runtime/routes/sounds-config-routes.ts +239 -0
- package/src/runtime/routes/surface-action-routes.ts +84 -4
- package/src/runtime/routes/workflow-routes.test.ts +372 -0
- package/src/runtime/routes/workflow-routes.ts +363 -0
- package/src/runtime/routes/workspace-routes.test.ts +61 -1
- package/src/runtime/routes/workspace-routes.ts +26 -1
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
- package/src/runtime/services/analyze-conversation.ts +26 -13
- package/src/schedule/inference-profile.ts +28 -0
- package/src/schedule/schedule-store.ts +152 -4
- package/src/schedule/scheduler-types.ts +6 -0
- package/src/schedule/scheduler.ts +96 -0
- package/src/security/secret-allowlist.ts +1 -1
- package/src/skills/path-classifier.ts +1 -1
- package/src/tools/apps/executors.ts +23 -0
- package/src/tools/apps/resolve-app-id.ts +42 -0
- package/src/tools/browser/browser-execution.ts +9 -11
- package/src/tools/credentials/broker.ts +4 -4
- package/src/tools/credentials/vault.ts +26 -137
- package/src/tools/executor.ts +69 -0
- package/src/tools/flag-gated-tools.test.ts +76 -0
- package/src/tools/permission-checker.ts +8 -1
- package/src/tools/registry.ts +51 -0
- package/src/tools/schedule/create.ts +77 -1
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +73 -1
- package/src/tools/skills/execute.ts +56 -0
- package/src/tools/terminal/shell.ts +1 -1
- package/src/tools/ui-surface/definitions.ts +91 -2
- package/src/tools/workflows/manage-workflows.ts +183 -0
- package/src/tools/workflows/run-workflow.test.ts +442 -0
- package/src/tools/workflows/run-workflow.ts +88 -0
- package/src/types/onboarding-context.ts +2 -0
- package/src/usage/attribution.ts +24 -0
- package/src/util/canonicalize-identity.ts +12 -3
- package/src/util/platform.ts +17 -17
- package/src/watcher/__tests__/engine.test.ts +24 -0
- package/src/watcher/__tests__/telemetry.test.ts +135 -0
- package/src/watcher/engine.ts +7 -0
- package/src/watcher/telemetry.ts +74 -0
- package/src/workflows/capabilities.test.ts +365 -0
- package/src/workflows/capabilities.ts +359 -0
- package/src/workflows/deterministic-stringify.ts +27 -0
- package/src/workflows/engine-integration.test.ts +656 -0
- package/src/workflows/engine.test.ts +1144 -0
- package/src/workflows/engine.ts +1078 -0
- package/src/workflows/fanout-load.test.ts +168 -0
- package/src/workflows/journal-store.test.ts +369 -0
- package/src/workflows/journal-store.ts +470 -0
- package/src/workflows/leaf-runner.test.ts +704 -0
- package/src/workflows/leaf-runner.ts +589 -0
- package/src/workflows/library.test.ts +134 -0
- package/src/workflows/library.ts +124 -0
- package/src/workflows/run-manager.test.ts +711 -0
- package/src/workflows/run-manager.ts +593 -0
- package/src/workflows/sandbox-escape.test.ts +339 -0
- package/src/workflows/sandbox.test.ts +251 -0
- package/src/workflows/sandbox.ts +447 -0
- package/src/workspace/adaptive-thinking-repair.ts +33 -11
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
- package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
- package/src/workspace/migrations/031-drop-user-md.ts +1 -4
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
- package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
- package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
- package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/migrations/runner.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
- package/src/__tests__/guardian-action-store.test.ts +0 -106
- package/src/daemon/guardian-action-generators.ts +0 -71
- package/src/memory/guardian-action-store.ts +0 -484
- package/src/runtime/guardian-action-grant-minter.ts +0 -150
package/src/config/schema.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
TimeoutConfigSchema,
|
|
57
57
|
} from "./schemas/timeouts.js";
|
|
58
58
|
import { ToolsConfigSchema } from "./schemas/tools.js";
|
|
59
|
+
import { WorkflowsConfigSchema } from "./schemas/workflows.js";
|
|
59
60
|
import { WorkspaceGitConfigSchema } from "./schemas/workspace-git.js";
|
|
60
61
|
|
|
61
62
|
export const AssistantConfigSchema = z
|
|
@@ -121,6 +122,7 @@ export const AssistantConfigSchema = z
|
|
|
121
122
|
),
|
|
122
123
|
ui: UiConfigSchema.default(UiConfigSchema.parse({})),
|
|
123
124
|
tools: ToolsConfigSchema.default(ToolsConfigSchema.parse({})),
|
|
125
|
+
workflows: WorkflowsConfigSchema.default(WorkflowsConfigSchema.parse({})),
|
|
124
126
|
// Per-plugin config blocks keyed by plugin name. The schema is intentionally
|
|
125
127
|
// permissive — each plugin's manifest supplies its own validator which the
|
|
126
128
|
// plugin bootstrap (`external-plugins-bootstrap.ts`) runs against the raw
|
|
@@ -82,6 +82,13 @@ const CATALOG_RECORD: CatalogRecord = {
|
|
|
82
82
|
description: "Handles voice call conversations.",
|
|
83
83
|
domain: "agentLoop",
|
|
84
84
|
},
|
|
85
|
+
workflowLeaf: {
|
|
86
|
+
id: "workflowLeaf",
|
|
87
|
+
displayName: "Workflow Leaf",
|
|
88
|
+
description:
|
|
89
|
+
"Runs an ephemeral leaf agent fanned out by the workflow orchestration engine.",
|
|
90
|
+
domain: "agentLoop",
|
|
91
|
+
},
|
|
85
92
|
|
|
86
93
|
// memory
|
|
87
94
|
memoryExtraction: {
|
|
@@ -5,7 +5,10 @@ export const HeartbeatConfigSchema = z
|
|
|
5
5
|
.object({
|
|
6
6
|
enabled: z
|
|
7
7
|
.boolean({ error: "heartbeat.enabled must be a boolean" })
|
|
8
|
-
.
|
|
8
|
+
// Heartbeats are opt-in for new workspaces. Workspaces created while
|
|
9
|
+
// the default was true keep their behavior via workspace migration
|
|
10
|
+
// 102-preserve-heartbeat-enabled-for-existing-workspaces.
|
|
11
|
+
.default(false)
|
|
9
12
|
.describe("Whether periodic heartbeat checks are enabled"),
|
|
10
13
|
intervalMs: z
|
|
11
14
|
.number({ error: "heartbeat.intervalMs must be a number" })
|
|
@@ -13,16 +13,18 @@ import { z } from "zod";
|
|
|
13
13
|
// Provider enum
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
|
|
16
|
-
const LLMProvider = z
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
export const LLMProvider = z
|
|
17
|
+
.enum([
|
|
18
|
+
"anthropic",
|
|
19
|
+
"openai",
|
|
20
|
+
"gemini",
|
|
21
|
+
"ollama",
|
|
22
|
+
"fireworks",
|
|
23
|
+
"openrouter",
|
|
24
|
+
"openai-compatible",
|
|
25
|
+
"minimax",
|
|
26
|
+
])
|
|
27
|
+
.meta({ id: "LLMProvider" });
|
|
26
28
|
type LLMProvider = z.infer<typeof LLMProvider>;
|
|
27
29
|
|
|
28
30
|
// ---------------------------------------------------------------------------
|
|
@@ -78,6 +80,7 @@ export const LLMCallSiteEnum = z.enum([
|
|
|
78
80
|
"trustRuleSuggestion",
|
|
79
81
|
"homeGreeting",
|
|
80
82
|
"homeSuggestedPrompts",
|
|
83
|
+
"workflowLeaf",
|
|
81
84
|
]);
|
|
82
85
|
export type LLMCallSite = z.infer<typeof LLMCallSiteEnum>;
|
|
83
86
|
|
|
@@ -349,23 +352,27 @@ export type LLMConfigBase = z.infer<typeof LLMConfigBase>;
|
|
|
349
352
|
* objects so callers can override individual leaves (e.g. `{ thinking:
|
|
350
353
|
* { enabled: false } }`).
|
|
351
354
|
*/
|
|
352
|
-
const LLMConfigFragment = z
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
355
|
+
export const LLMConfigFragment = z
|
|
356
|
+
.object({
|
|
357
|
+
provider: LLMProvider.optional(),
|
|
358
|
+
model: ModelSchema.optional(),
|
|
359
|
+
maxTokens: MaxTokensSchema.optional(),
|
|
360
|
+
effort: EffortEnum.optional(),
|
|
361
|
+
speed: SpeedEnum.optional(),
|
|
362
|
+
verbosity: VerbosityEnum.optional(),
|
|
363
|
+
temperature: TemperatureSchema.optional(),
|
|
364
|
+
thinking: ThinkingFragmentSchema.optional(),
|
|
365
|
+
contextWindow: ContextWindowDeepPartialSchema.optional(),
|
|
366
|
+
openrouter: OpenRouterDeepPartialSchema.optional(),
|
|
367
|
+
logitBias: LogitBiasPresetSchema.optional(),
|
|
368
|
+
disableCache: z.boolean().optional(),
|
|
369
|
+
})
|
|
370
|
+
.meta({ id: "LLMConfigFragment" });
|
|
371
|
+
export type LLMConfigFragment = z.infer<typeof LLMConfigFragment>;
|
|
367
372
|
|
|
368
|
-
export const ProfileStatusSchema = z
|
|
373
|
+
export const ProfileStatusSchema = z
|
|
374
|
+
.enum(["active", "disabled"])
|
|
375
|
+
.meta({ id: "ProfileStatus" });
|
|
369
376
|
export type ProfileStatus = z.infer<typeof ProfileStatusSchema>;
|
|
370
377
|
|
|
371
378
|
// ---------------------------------------------------------------------------
|
|
@@ -38,6 +38,25 @@ export const MemoryRetrospectiveConfigSchema = z
|
|
|
38
38
|
.describe(
|
|
39
39
|
"Minimum milliseconds between attempts (success or failure). Prevents tight retry loops across trigger types. Pre-compaction bypasses this gate.",
|
|
40
40
|
),
|
|
41
|
+
|
|
42
|
+
keepSupersededRuns: z
|
|
43
|
+
.boolean({
|
|
44
|
+
error: "memory.retrospective.keepSupersededRuns must be a boolean",
|
|
45
|
+
})
|
|
46
|
+
.default(false)
|
|
47
|
+
.describe(
|
|
48
|
+
"When false (default), superseded retrospective conversations are deleted once a newer run succeeds — the persisted remembered_log on memory_retrospective_state is the dedup baseline (the most recent run is scanned only as a fallback for state rows that predate the log column), so older runs are dead weight (fork-based runs each carry a full copy of the source conversation's messages). Operators who want to retain the full run history set this to true; retained runs also skip the startup orphan sweep so they survive restarts.",
|
|
49
|
+
),
|
|
50
|
+
|
|
51
|
+
matchConversationProfile: z
|
|
52
|
+
.boolean({
|
|
53
|
+
error:
|
|
54
|
+
"memory.retrospective.matchConversationProfile must be a boolean",
|
|
55
|
+
})
|
|
56
|
+
.default(false)
|
|
57
|
+
.describe(
|
|
58
|
+
"When true, fork-based retrospectives run under the source conversation's inference profile (which forkConversation copies onto the fork) instead of the call site's default. Provider prompt caches are byte-exact prefix matches scoped per model, and a thinking enable/disable mismatch invalidates the messages cache tier — so reusing the source's cached prefix requires the retrospective to resolve the SAME model/thinking/effort as the conversation's own turns. Falls back to the call site's default when the conversation has no profile or the referenced profile no longer exists.",
|
|
59
|
+
),
|
|
41
60
|
})
|
|
42
61
|
.describe(
|
|
43
62
|
"Controls the memory-retrospective background pass. Model selection lives under llm.callSites.memoryRetrospective.",
|
|
@@ -110,6 +110,14 @@ export const UiConfigSchema = z
|
|
|
110
110
|
.describe(
|
|
111
111
|
"IANA timezone identifier detected from the client environment for assistant temporal grounding when no manual override is configured (e.g. 'America/New_York'). Use an empty string to clear the setting.",
|
|
112
112
|
),
|
|
113
|
+
emptyStateGreetingCacheTtlMs: z
|
|
114
|
+
.number({ error: "ui.emptyStateGreetingCacheTtlMs must be a number" })
|
|
115
|
+
.int("ui.emptyStateGreetingCacheTtlMs must be an integer")
|
|
116
|
+
.min(0, "ui.emptyStateGreetingCacheTtlMs must be >= 0")
|
|
117
|
+
.default(4 * 60 * 60 * 1000)
|
|
118
|
+
.describe(
|
|
119
|
+
"Server-side cache TTL (ms) for the generated empty-state (new-chat) greeting. 0 disables caching, so a fresh greeting is generated on every request.",
|
|
120
|
+
),
|
|
113
121
|
})
|
|
114
122
|
.describe(
|
|
115
123
|
"User interface display settings. Empty-state greeting model selection lives under llm.callSites.emptyStateGreeting.",
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
import { DEFAULT_IMAGE_MODEL } from "../../media/image-models.js";
|
|
3
4
|
import { SEARCH_PROVIDER_IDS } from "../../providers/search-provider-catalog.js";
|
|
4
5
|
import { SttServiceSchema } from "./stt.js";
|
|
5
6
|
import { TtsServiceSchema } from "./tts.js";
|
|
6
7
|
|
|
7
|
-
const ServiceModeSchema = z
|
|
8
|
+
export const ServiceModeSchema = z
|
|
9
|
+
.enum(["managed", "your-own"])
|
|
10
|
+
.meta({ id: "ServiceMode" });
|
|
8
11
|
type ServiceMode = z.infer<typeof ServiceModeSchema>;
|
|
9
12
|
|
|
10
13
|
export const VALID_INFERENCE_PROVIDERS = [
|
|
@@ -42,7 +45,7 @@ const InferenceServiceSchema = z.object({});
|
|
|
42
45
|
|
|
43
46
|
const ImageGenerationServiceSchema = BaseServiceSchema.extend({
|
|
44
47
|
provider: z.enum(VALID_IMAGE_GEN_PROVIDERS).default("gemini"),
|
|
45
|
-
model: z.string().default(
|
|
48
|
+
model: z.string().default(DEFAULT_IMAGE_MODEL),
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
const WebSearchServiceSchema = BaseServiceSchema.extend({
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const WorkflowsConfigSchema = z
|
|
4
|
+
.object({
|
|
5
|
+
maxAgentsPerRun: z
|
|
6
|
+
.number({ error: "workflows.maxAgentsPerRun must be a number" })
|
|
7
|
+
.int()
|
|
8
|
+
.positive()
|
|
9
|
+
.default(500)
|
|
10
|
+
.describe(
|
|
11
|
+
"Maximum total leaf agents a single workflow run may spawn across all steps",
|
|
12
|
+
),
|
|
13
|
+
maxConcurrentLeaves: z
|
|
14
|
+
.number({ error: "workflows.maxConcurrentLeaves must be a number" })
|
|
15
|
+
.int()
|
|
16
|
+
.positive()
|
|
17
|
+
.default(6)
|
|
18
|
+
.describe(
|
|
19
|
+
"Maximum number of leaf agents that may run concurrently within a single workflow run",
|
|
20
|
+
),
|
|
21
|
+
maxConcurrentRuns: z
|
|
22
|
+
.number({ error: "workflows.maxConcurrentRuns must be a number" })
|
|
23
|
+
.int()
|
|
24
|
+
.positive()
|
|
25
|
+
.default(3)
|
|
26
|
+
.describe(
|
|
27
|
+
"Maximum number of workflow runs that may execute concurrently",
|
|
28
|
+
),
|
|
29
|
+
journalRetentionDays: z
|
|
30
|
+
.number({ error: "workflows.journalRetentionDays must be a number" })
|
|
31
|
+
.int()
|
|
32
|
+
.positive()
|
|
33
|
+
.default(30)
|
|
34
|
+
.describe(
|
|
35
|
+
"Number of days to retain workflow run journals before pruning",
|
|
36
|
+
),
|
|
37
|
+
})
|
|
38
|
+
.describe(
|
|
39
|
+
"Workflow orchestration engine configuration — caps and concurrency knobs",
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export type WorkflowsConfig = z.infer<typeof WorkflowsConfigSchema>;
|
package/src/config/skills.ts
CHANGED
|
@@ -62,7 +62,7 @@ const SkillMetadataSchema = z
|
|
|
62
62
|
* Origin of a skill in the merged catalog.
|
|
63
63
|
*
|
|
64
64
|
* - `bundled`: ships inside the assistant binary under `bundled-skills/`.
|
|
65
|
-
* - `managed`: installed into
|
|
65
|
+
* - `managed`: installed into `$VELLUM_WORKSPACE_DIR/skills/` from our catalog.
|
|
66
66
|
* - `workspace`: user-authored skill living in a conversation's working dir.
|
|
67
67
|
* - `extra`: third-party directory roots passed via `loadSkillCatalog`'s
|
|
68
68
|
* `extraDirs` argument (primarily for tests).
|
|
@@ -479,7 +479,7 @@ function readSkillFromDirectory(
|
|
|
479
479
|
if (isOutsideSkillsRoot(skillsDir, directoryPath)) {
|
|
480
480
|
log.warn(
|
|
481
481
|
{ directoryPath },
|
|
482
|
-
"Skipping skill directory that resolves outside
|
|
482
|
+
"Skipping skill directory that resolves outside $VELLUM_WORKSPACE_DIR/skills",
|
|
483
483
|
);
|
|
484
484
|
return null;
|
|
485
485
|
}
|
|
@@ -496,7 +496,7 @@ function readSkillFromDirectory(
|
|
|
496
496
|
if (isOutsideSkillsRoot(skillsDir, skillFilePath)) {
|
|
497
497
|
log.warn(
|
|
498
498
|
{ skillFilePath },
|
|
499
|
-
"Skipping SKILL.md that resolves outside
|
|
499
|
+
"Skipping SKILL.md that resolves outside $VELLUM_WORKSPACE_DIR/skills",
|
|
500
500
|
);
|
|
501
501
|
return null;
|
|
502
502
|
}
|
package/src/context/compactor.ts
CHANGED
|
@@ -43,7 +43,10 @@ import {
|
|
|
43
43
|
} from "../runtime/actor-trust-resolver.js";
|
|
44
44
|
import { getLogger } from "../util/logger.js";
|
|
45
45
|
import { stripInjectionsForCompaction } from "./strip-injections.js";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
estimatePromptTokens,
|
|
48
|
+
estimateToolsTokens,
|
|
49
|
+
} from "./token-estimator.js";
|
|
47
50
|
|
|
48
51
|
const log = getLogger("compactor");
|
|
49
52
|
|
|
@@ -134,7 +137,8 @@ Compress aggressively:
|
|
|
134
137
|
For picking where to cut between summary and preserved tail:
|
|
135
138
|
- Find the last major topic shift or energy change
|
|
136
139
|
- Keep the active thread of conversation fully intact
|
|
137
|
-
-
|
|
140
|
+
- Keep the verbatim tail within roughly {tail_budget} tokens so the
|
|
141
|
+
compacted conversation has room to breathe before the next pass
|
|
138
142
|
- Never cut in the middle of an ongoing discussion
|
|
139
143
|
|
|
140
144
|
IMAGE MANIFEST (images in this conversation):
|
|
@@ -181,6 +185,14 @@ export interface CompactionRunArgs {
|
|
|
181
185
|
compaction: CompactionConfig;
|
|
182
186
|
/** Effective context window for the conversation (in tokens). */
|
|
183
187
|
maxInputTokens: number;
|
|
188
|
+
/**
|
|
189
|
+
* Low-watermark token budget the rebuilt history (summary + verbatim tail)
|
|
190
|
+
* should land at or below after a successful pass. Drives the deterministic
|
|
191
|
+
* forward-cut that advances the model's tail choice until the estimate fits,
|
|
192
|
+
* so one pass buys a long quiet period instead of landing a hair under the
|
|
193
|
+
* trigger. When omitted the forward-cut is skipped (legacy/emergency paths).
|
|
194
|
+
*/
|
|
195
|
+
targetTokens?: number;
|
|
184
196
|
/** Pre-computed estimated input tokens for the live history. */
|
|
185
197
|
previousEstimatedInputTokens: number;
|
|
186
198
|
/** Skip the autoThreshold check — fire compaction unconditionally. */
|
|
@@ -232,6 +244,19 @@ export interface CompactionRunResult {
|
|
|
232
244
|
reason?: string;
|
|
233
245
|
/** True when the provider call threw and no compaction was applied. */
|
|
234
246
|
summaryFailed?: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* Set on a successful pass when the deterministic forward-cut advanced to the
|
|
249
|
+
* tail floor (the start of the most recent complete exchange) but the rebuilt
|
|
250
|
+
* history still exceeds `targetTokens`. The verbatim tail alone — the
|
|
251
|
+
* in-flight turn during a tool-heavy round — is over budget, and no boundary
|
|
252
|
+
* the cut can reach fits it. The window-manager's retry loop reads this to
|
|
253
|
+
* stop retrying immediately: a second full-context pass would land on the same
|
|
254
|
+
* floor and free nothing, just paying another full cache write. Omitted (and
|
|
255
|
+
* treated as `false`) on no-op / skipped / legacy (`targetTokens` absent)
|
|
256
|
+
* results. Internal-result-only — not persisted or emitted on any external
|
|
257
|
+
* wire payload.
|
|
258
|
+
*/
|
|
259
|
+
tailFloorReached?: boolean;
|
|
235
260
|
}
|
|
236
261
|
|
|
237
262
|
export interface ParsedCompactionResult {
|
|
@@ -551,6 +576,113 @@ export function adjustTailIndexForToolPairing(
|
|
|
551
576
|
return 0;
|
|
552
577
|
}
|
|
553
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Whether keeping `messages[index..]` as the verbatim tail lands on a clean
|
|
581
|
+
* boundary: the tail must open on a user turn that does not lead with a
|
|
582
|
+
* client-side `tool_result` (which would orphan its matching `tool_use` in the
|
|
583
|
+
* summarized prefix). This is the forward-walk dual of
|
|
584
|
+
* {@link adjustTailIndexForToolPairing}'s backward walk — the deterministic
|
|
585
|
+
* budget enforcement only advances the cut to indices that satisfy it.
|
|
586
|
+
*/
|
|
587
|
+
function isForwardCutBoundary(messages: Message[], index: number): boolean {
|
|
588
|
+
const m = messages[index];
|
|
589
|
+
if (m == null || m.role !== "user") return false;
|
|
590
|
+
// guard:allow-tool-result-only — server-side web_search_tool_result is
|
|
591
|
+
// self-paired inside its assistant message and never spans user turns.
|
|
592
|
+
return !m.content.some((block) => block.type === "tool_result");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** Outcome of the deterministic forward-cut budget enforcement. */
|
|
596
|
+
interface ForwardCutOutcome {
|
|
597
|
+
/** The chosen tail index (≥ `startIndex`, ≤ `floorIndex`). */
|
|
598
|
+
index: number;
|
|
599
|
+
/**
|
|
600
|
+
* True when the cut ran out of forward boundaries — it advanced to (or could
|
|
601
|
+
* not advance past) the tail floor — yet the rebuilt-history estimate still
|
|
602
|
+
* exceeds `targetTokens`. Signals to the retry loop that a second pass cannot
|
|
603
|
+
* do better: the floor is the same and another full-context LLM pass would
|
|
604
|
+
* land on the same over-budget tail. See {@link CompactionRunResult.tailFloorReached}.
|
|
605
|
+
*/
|
|
606
|
+
tailFloorReached: boolean;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Deterministic low-watermark enforcement. Given the model's tail choice
|
|
611
|
+
* (already resolved + back-walked for tool pairing), advance the cut FORWARD —
|
|
612
|
+
* dropping more leading messages into the summarized region, keeping a smaller
|
|
613
|
+
* verbatim tail — until the rebuilt-history estimate fits `targetTokens` or a
|
|
614
|
+
* minimum-tail floor is hit.
|
|
615
|
+
*
|
|
616
|
+
* The floor preserves conversational integrity: it never advances past
|
|
617
|
+
* `floorIndex`, which the caller anchors to the start of the most recent
|
|
618
|
+
* complete user→assistant exchange so the current in-flight turn is never cut.
|
|
619
|
+
* Every candidate cut must pass {@link isForwardCutBoundary} so tool pairs are
|
|
620
|
+
* never orphaned. Returns the original `startIndex` unchanged when the model's
|
|
621
|
+
* own tail already fits the budget (the cut is enforcement, not optimization)
|
|
622
|
+
* or when no forward boundary improves on it.
|
|
623
|
+
*
|
|
624
|
+
* Reports `tailFloorReached` when the cut exhausts its forward boundaries (the
|
|
625
|
+
* loop never broke early on a fit) while the best tail it found is still over
|
|
626
|
+
* `targetTokens` — the floor-dominated case where the verbatim tail (the
|
|
627
|
+
* in-flight turn during a tool-heavy round) alone exceeds the budget. The
|
|
628
|
+
* window-manager reads this to skip a futile second pass.
|
|
629
|
+
*/
|
|
630
|
+
function advanceTailForBudget(args: {
|
|
631
|
+
messages: Message[];
|
|
632
|
+
startIndex: number;
|
|
633
|
+
floorIndex: number;
|
|
634
|
+
targetTokens: number;
|
|
635
|
+
estimateTail: (tail: Message[]) => number;
|
|
636
|
+
}): ForwardCutOutcome {
|
|
637
|
+
const { messages, startIndex, floorIndex, targetTokens, estimateTail } = args;
|
|
638
|
+
// The model's own tail choice already fits — keep it untouched. Without this
|
|
639
|
+
// early return the loop below would advance to the first forward boundary
|
|
640
|
+
// regardless (smaller tails also fit), needlessly dropping verbatim messages
|
|
641
|
+
// the budget never required us to drop.
|
|
642
|
+
if (estimateTail(messages.slice(startIndex)) <= targetTokens) {
|
|
643
|
+
return { index: startIndex, tailFloorReached: false };
|
|
644
|
+
}
|
|
645
|
+
let chosen = startIndex;
|
|
646
|
+
let fits = false;
|
|
647
|
+
for (let i = startIndex + 1; i <= floorIndex; i++) {
|
|
648
|
+
if (!isForwardCutBoundary(messages, i)) continue;
|
|
649
|
+
chosen = i;
|
|
650
|
+
const estimate = estimateTail(messages.slice(i));
|
|
651
|
+
if (estimate <= targetTokens) {
|
|
652
|
+
fits = true;
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return { index: chosen, tailFloorReached: !fits };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Anchor index for the forward-cut floor: the start of the most recent
|
|
661
|
+
* complete user→assistant exchange. The deterministic budget enforcement never
|
|
662
|
+
* advances the cut past this point, so a single pass always preserves at least
|
|
663
|
+
* the latest finished exchange and never bites into the current in-flight turn.
|
|
664
|
+
*
|
|
665
|
+
* Walks back from the end to the last assistant message, then back to the user
|
|
666
|
+
* message that opens its exchange (the nearest preceding clean user boundary).
|
|
667
|
+
* Falls back to the model's chosen `tailIndex` when no such exchange exists
|
|
668
|
+
* (e.g. the tail is a single in-flight turn), which makes the enforcement a
|
|
669
|
+
* no-op rather than cutting too aggressively.
|
|
670
|
+
*/
|
|
671
|
+
function resolveTailFloorIndex(messages: Message[], tailIndex: number): number {
|
|
672
|
+
let lastAssistant = -1;
|
|
673
|
+
for (let i = messages.length - 1; i > tailIndex; i--) {
|
|
674
|
+
if (messages[i].role === "assistant") {
|
|
675
|
+
lastAssistant = i;
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (lastAssistant < 0) return tailIndex;
|
|
680
|
+
for (let i = lastAssistant - 1; i > tailIndex; i--) {
|
|
681
|
+
if (isForwardCutBoundary(messages, i)) return i;
|
|
682
|
+
}
|
|
683
|
+
return tailIndex;
|
|
684
|
+
}
|
|
685
|
+
|
|
554
686
|
// ---------------------------------------------------------------------------
|
|
555
687
|
// Retained-image hydration
|
|
556
688
|
// ---------------------------------------------------------------------------
|
|
@@ -618,12 +750,22 @@ function guessMimeFromFilename(filename: string): string {
|
|
|
618
750
|
export function buildInstructionMessage(
|
|
619
751
|
customPrompt: string | null | undefined,
|
|
620
752
|
imageManifest: string,
|
|
753
|
+
tailBudgetTokens?: number,
|
|
621
754
|
): Message {
|
|
622
755
|
const template =
|
|
623
756
|
customPrompt && customPrompt.trim().length > 0
|
|
624
757
|
? customPrompt
|
|
625
758
|
: DEFAULT_COMPACTION_PROMPT;
|
|
626
|
-
|
|
759
|
+
// `{tail_budget}` is a soft nudge — the deterministic forward-cut downstream
|
|
760
|
+
// is what actually enforces the budget. Custom prompts without the
|
|
761
|
+
// placeholder render unchanged; the default prompt always carries it.
|
|
762
|
+
const tailBudgetText =
|
|
763
|
+
tailBudgetTokens != null && tailBudgetTokens > 0
|
|
764
|
+
? String(tailBudgetTokens)
|
|
765
|
+
: "as few as needed";
|
|
766
|
+
const text = template
|
|
767
|
+
.replace("{image_manifest}", imageManifest)
|
|
768
|
+
.replace("{tail_budget}", tailBudgetText);
|
|
627
769
|
return {
|
|
628
770
|
role: "user",
|
|
629
771
|
content: [{ type: "text", text }],
|
|
@@ -736,6 +878,7 @@ export async function runAssistantDrivenCompaction(
|
|
|
736
878
|
const instruction = buildInstructionMessage(
|
|
737
879
|
args.compaction.prompt ?? null,
|
|
738
880
|
manifestText,
|
|
881
|
+
args.targetTokens,
|
|
739
882
|
);
|
|
740
883
|
|
|
741
884
|
const requestMessages = buildCompactionRequest(args.messages, instruction);
|
|
@@ -826,22 +969,141 @@ export async function runAssistantDrivenCompaction(
|
|
|
826
969
|
};
|
|
827
970
|
}
|
|
828
971
|
|
|
829
|
-
const
|
|
972
|
+
const pairedTailIndex = adjustTailIndexForToolPairing(
|
|
830
973
|
args.messages,
|
|
831
974
|
resolvedTailIndex,
|
|
832
975
|
);
|
|
833
|
-
if (
|
|
976
|
+
if (pairedTailIndex !== resolvedTailIndex) {
|
|
834
977
|
log.info(
|
|
835
978
|
{
|
|
836
979
|
conversationId: args.conversationId,
|
|
837
980
|
originalTailIndex: resolvedTailIndex,
|
|
838
|
-
tailIndex,
|
|
839
|
-
walkedBy: resolvedTailIndex -
|
|
981
|
+
tailIndex: pairedTailIndex,
|
|
982
|
+
walkedBy: resolvedTailIndex - pairedTailIndex,
|
|
840
983
|
},
|
|
841
984
|
"Adjusted compaction tail backward to preserve tool_use/tool_result pairing",
|
|
842
985
|
);
|
|
843
986
|
}
|
|
844
987
|
|
|
988
|
+
const summaryText = buildSummaryMemoryText(parsed.summary, parsed.keyState);
|
|
989
|
+
// The durable summary. When the forward-cut below advances the tail, a
|
|
990
|
+
// truncation notice is appended HERE — not just on the in-memory message —
|
|
991
|
+
// because `applyCompactionResult` persists and rehydrates the summary from
|
|
992
|
+
// the result's `summaryText`: a notice only on the message would vanish on
|
|
993
|
+
// reload/fork, silently hiding that the dropped span was never summarized.
|
|
994
|
+
let finalSummaryText = summaryText;
|
|
995
|
+
let summaryMessage: Message = {
|
|
996
|
+
role: "assistant",
|
|
997
|
+
content: [{ type: "text", text: finalSummaryText }],
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
const {
|
|
1001
|
+
blocks: retainedImageBlocks,
|
|
1002
|
+
resolved,
|
|
1003
|
+
missing,
|
|
1004
|
+
} = buildRetainedImageBlocks(parsed.retainedImageFilenames, manifest);
|
|
1005
|
+
if (missing.length > 0) {
|
|
1006
|
+
log.warn(
|
|
1007
|
+
{ missing },
|
|
1008
|
+
"Compaction referenced images that could not be resolved against attachments — dropping",
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const retainedImageMessage: Message | null =
|
|
1013
|
+
retainedImageBlocks.length > 0
|
|
1014
|
+
? {
|
|
1015
|
+
role: "user",
|
|
1016
|
+
content: [
|
|
1017
|
+
{
|
|
1018
|
+
type: "text" as const,
|
|
1019
|
+
text: "Images retained from the compacted portion of the conversation:",
|
|
1020
|
+
},
|
|
1021
|
+
...retainedImageBlocks,
|
|
1022
|
+
],
|
|
1023
|
+
}
|
|
1024
|
+
: null;
|
|
1025
|
+
|
|
1026
|
+
// Deterministic low-watermark enforcement. The model was asked to keep the
|
|
1027
|
+
// tail within the budget, but it routinely keeps a fat tail in repetitive
|
|
1028
|
+
// conversations, so each pass would otherwise free almost nothing and the
|
|
1029
|
+
// history bounces back over the trigger within a tick or two. When the
|
|
1030
|
+
// rebuilt history (summary + retained images + verbatim tail) still exceeds
|
|
1031
|
+
// `targetTokens`, advance the cut forward — onto clean user-turn boundaries
|
|
1032
|
+
// only — until it fits or the most-recent-complete-exchange floor is hit.
|
|
1033
|
+
// No second LLM call: the summary stays as written, and the span dropped
|
|
1034
|
+
// between the model's cut and the enforced cut is acknowledged with an
|
|
1035
|
+
// explicit truncation notice appended to the summary message (see below),
|
|
1036
|
+
// so the loss is visible in-context rather than silent.
|
|
1037
|
+
let tailIndex = pairedTailIndex;
|
|
1038
|
+
// Whether the deterministic forward-cut hit the tail floor while still over
|
|
1039
|
+
// `targetTokens` — propagated onto the success result so the window-manager's
|
|
1040
|
+
// retry loop can skip a futile second full-context pass (the floor is the same
|
|
1041
|
+
// next time, so it cannot do better). Only meaningful when a `targetTokens`
|
|
1042
|
+
// budget drove the forward-cut.
|
|
1043
|
+
let tailFloorReached = false;
|
|
1044
|
+
if (args.targetTokens != null && pairedTailIndex > 0) {
|
|
1045
|
+
const providerName =
|
|
1046
|
+
args.provider.tokenEstimationProvider ?? args.provider.name;
|
|
1047
|
+
// Mirror the window-manager's post-compaction estimate (system prompt +
|
|
1048
|
+
// tools + messages) so the forward-cut targets the same number the manager
|
|
1049
|
+
// recomputes on return — otherwise the cut would under-count by the tool
|
|
1050
|
+
// budget and land short of the real low-watermark.
|
|
1051
|
+
const toolTokenBudget = args.tools ? estimateToolsTokens(args.tools) : 0;
|
|
1052
|
+
const fixedPrefix: Message[] = [summaryMessage];
|
|
1053
|
+
if (retainedImageMessage) fixedPrefix.push(retainedImageMessage);
|
|
1054
|
+
const estimateRebuilt = (tail: Message[]): number =>
|
|
1055
|
+
estimatePromptTokens(
|
|
1056
|
+
[...fixedPrefix, ...stripInjectionsForCompaction(tail)],
|
|
1057
|
+
args.systemPrompt,
|
|
1058
|
+
{ providerName, toolTokenBudget },
|
|
1059
|
+
);
|
|
1060
|
+
const floorIndex = resolveTailFloorIndex(args.messages, pairedTailIndex);
|
|
1061
|
+
const advanced = advanceTailForBudget({
|
|
1062
|
+
messages: args.messages,
|
|
1063
|
+
startIndex: pairedTailIndex,
|
|
1064
|
+
floorIndex,
|
|
1065
|
+
targetTokens: args.targetTokens,
|
|
1066
|
+
estimateTail: estimateRebuilt,
|
|
1067
|
+
});
|
|
1068
|
+
tailFloorReached = advanced.tailFloorReached;
|
|
1069
|
+
if (advanced.index !== pairedTailIndex) {
|
|
1070
|
+
tailIndex = advanced.index;
|
|
1071
|
+
// The LLM wrote its summary believing the verbatim tail would start at
|
|
1072
|
+
// `pairedTailIndex`, so the messages in [pairedTailIndex, tailIndex) are
|
|
1073
|
+
// covered by neither the summary's detail nor the retained tail. Append
|
|
1074
|
+
// a deterministic truncation notice so the loss is visible in-context
|
|
1075
|
+
// instead of silent — the conversation can recompute or recall what it
|
|
1076
|
+
// needs rather than acting on a gap it doesn't know exists. The notice
|
|
1077
|
+
// is a few dozen tokens against a multi-thousand-token target, so the
|
|
1078
|
+
// budget estimate above remains effectively accurate.
|
|
1079
|
+
const dropped = args.messages.slice(pairedTailIndex, tailIndex);
|
|
1080
|
+
const droppedUser = dropped.filter((m) => m.role === "user").length;
|
|
1081
|
+
const droppedAssistant = dropped.length - droppedUser;
|
|
1082
|
+
const truncationNote =
|
|
1083
|
+
`\n\n[Context budget enforcement: ${dropped.length} message(s) ` +
|
|
1084
|
+
`(${droppedUser} user, ${droppedAssistant} assistant) between this ` +
|
|
1085
|
+
`summary and the retained tail were truncated to fit the context ` +
|
|
1086
|
+
`budget and are not covered in detail above.]`;
|
|
1087
|
+
finalSummaryText = summaryText + truncationNote;
|
|
1088
|
+
summaryMessage = {
|
|
1089
|
+
role: "assistant",
|
|
1090
|
+
content: [{ type: "text", text: finalSummaryText }],
|
|
1091
|
+
};
|
|
1092
|
+
log.info(
|
|
1093
|
+
{
|
|
1094
|
+
conversationId: args.conversationId,
|
|
1095
|
+
modelTailIndex: pairedTailIndex,
|
|
1096
|
+
tailIndex,
|
|
1097
|
+
floorIndex,
|
|
1098
|
+
droppedFromTail: dropped.length,
|
|
1099
|
+
targetTokens: args.targetTokens,
|
|
1100
|
+
tailFloorReached,
|
|
1101
|
+
},
|
|
1102
|
+
"Advanced compaction tail forward to meet low-watermark token budget",
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
845
1107
|
if (tailIndex === 0) {
|
|
846
1108
|
return {
|
|
847
1109
|
...emptyResult(
|
|
@@ -876,37 +1138,8 @@ export async function runAssistantDrivenCompaction(
|
|
|
876
1138
|
args.messages.slice(tailIndex),
|
|
877
1139
|
);
|
|
878
1140
|
|
|
879
|
-
const summaryText = buildSummaryMemoryText(parsed.summary, parsed.keyState);
|
|
880
|
-
const summaryMessage: Message = {
|
|
881
|
-
role: "assistant",
|
|
882
|
-
content: [{ type: "text", text: summaryText }],
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
const {
|
|
886
|
-
blocks: retainedImageBlocks,
|
|
887
|
-
resolved,
|
|
888
|
-
missing,
|
|
889
|
-
} = buildRetainedImageBlocks(parsed.retainedImageFilenames, manifest);
|
|
890
|
-
if (missing.length > 0) {
|
|
891
|
-
log.warn(
|
|
892
|
-
{ missing },
|
|
893
|
-
"Compaction referenced images that could not be resolved against attachments — dropping",
|
|
894
|
-
);
|
|
895
|
-
}
|
|
896
|
-
|
|
897
1141
|
const compactedMessages: Message[] = [summaryMessage];
|
|
898
|
-
if (
|
|
899
|
-
compactedMessages.push({
|
|
900
|
-
role: "user",
|
|
901
|
-
content: [
|
|
902
|
-
{
|
|
903
|
-
type: "text" as const,
|
|
904
|
-
text: "Images retained from the compacted portion of the conversation:",
|
|
905
|
-
},
|
|
906
|
-
...retainedImageBlocks,
|
|
907
|
-
],
|
|
908
|
-
});
|
|
909
|
-
}
|
|
1142
|
+
if (retainedImageMessage) compactedMessages.push(retainedImageMessage);
|
|
910
1143
|
compactedMessages.push(...tailMessages);
|
|
911
1144
|
|
|
912
1145
|
const nonPersistedCompactedAway = Math.min(
|
|
@@ -928,7 +1161,7 @@ export async function runAssistantDrivenCompaction(
|
|
|
928
1161
|
? { originalTailIndex: resolvedTailIndex }
|
|
929
1162
|
: {}),
|
|
930
1163
|
retainedImages: resolved.length,
|
|
931
|
-
summaryChars:
|
|
1164
|
+
summaryChars: finalSummaryText.length,
|
|
932
1165
|
},
|
|
933
1166
|
"Applied assistant-driven compaction",
|
|
934
1167
|
);
|
|
@@ -956,9 +1189,10 @@ export async function runAssistantDrivenCompaction(
|
|
|
956
1189
|
response.usage.cacheCreationInputTokens ?? 0,
|
|
957
1190
|
summaryCacheReadInputTokens: response.usage.cacheReadInputTokens ?? 0,
|
|
958
1191
|
summaryRawResponses: response.rawResponse ? [response.rawResponse] : [],
|
|
959
|
-
summaryText,
|
|
1192
|
+
summaryText: finalSummaryText,
|
|
960
1193
|
keyState: parsed.keyState,
|
|
961
1194
|
summaryFailed: false,
|
|
1195
|
+
tailFloorReached,
|
|
962
1196
|
};
|
|
963
1197
|
}
|
|
964
1198
|
|