@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
|
@@ -134,7 +134,9 @@ describe("ClickHouseLlmRequestLogSource", () => {
|
|
|
134
134
|
const call = recorder[0]!;
|
|
135
135
|
const parsed = new URL(call.url);
|
|
136
136
|
expect(parsed.searchParams.get("database")).toBe("default");
|
|
137
|
-
expect(parsed.searchParams.get("param_assistant_id")).toBe(
|
|
137
|
+
expect(parsed.searchParams.get("param_assistant_id")).toBe(
|
|
138
|
+
"asst-fixture-001",
|
|
139
|
+
);
|
|
138
140
|
expect(parsed.searchParams.get("param_log_id")).toBe("log-1");
|
|
139
141
|
expect(call.init?.method).toBe("POST");
|
|
140
142
|
const auth = (call.init?.headers as Record<string, string>).Authorization;
|
|
@@ -224,6 +226,90 @@ describe("ClickHouseLlmRequestLogSource", () => {
|
|
|
224
226
|
expect(body).not.toContain(`'msg-b'`);
|
|
225
227
|
});
|
|
226
228
|
|
|
229
|
+
test("getRequestLogMetaById returns metadata without selecting either payload column", async () => {
|
|
230
|
+
// GIVEN a stored row in ClickHouse
|
|
231
|
+
const recorder: FakeFetchCall[] = [];
|
|
232
|
+
const metaRow = {
|
|
233
|
+
id: "log-1",
|
|
234
|
+
conversation_id: "conv-1",
|
|
235
|
+
message_id: "msg-1",
|
|
236
|
+
provider: "anthropic",
|
|
237
|
+
created_at: "1778465138786",
|
|
238
|
+
agent_loop_exit_reason: "no_tool_calls",
|
|
239
|
+
call_site: "compactionAgent",
|
|
240
|
+
};
|
|
241
|
+
const src = makeSource({
|
|
242
|
+
body: JSON.stringify(metaRow) + "\n",
|
|
243
|
+
recorder,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// WHEN looking it up by id
|
|
247
|
+
const row = await src.getRequestLogMetaById("log-1");
|
|
248
|
+
|
|
249
|
+
// THEN the metadata comes back without payload fields
|
|
250
|
+
expect(row).toEqual({
|
|
251
|
+
id: "log-1",
|
|
252
|
+
conversationId: "conv-1",
|
|
253
|
+
messageId: "msg-1",
|
|
254
|
+
provider: "anthropic",
|
|
255
|
+
createdAt: 1778465138786,
|
|
256
|
+
agentLoopExitReason: "no_tool_calls",
|
|
257
|
+
callSite: "compactionAgent",
|
|
258
|
+
});
|
|
259
|
+
// AND the query never selects the payload columns (a single request
|
|
260
|
+
// payload can be a full context window)
|
|
261
|
+
const body = String(recorder[0]!.init?.body ?? "");
|
|
262
|
+
expect(body).not.toContain("request_payload");
|
|
263
|
+
expect(body).not.toContain("response_payload");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("getCompactionLogsBetween computes message count in SQL and skips the request payload", async () => {
|
|
267
|
+
// GIVEN compaction rows whose message count ClickHouse computed via
|
|
268
|
+
// JSONLength (Int64 arrives as a quoted string under JSONEachRow)
|
|
269
|
+
const recorder: FakeFetchCall[] = [];
|
|
270
|
+
const compactionRow = {
|
|
271
|
+
id: "log-1",
|
|
272
|
+
conversation_id: "conv-1",
|
|
273
|
+
message_id: "",
|
|
274
|
+
provider: "anthropic",
|
|
275
|
+
response_payload: '{"bar":2}',
|
|
276
|
+
request_message_count: "42",
|
|
277
|
+
created_at: "1778465138786",
|
|
278
|
+
agent_loop_exit_reason: "",
|
|
279
|
+
call_site: "compactionAgent",
|
|
280
|
+
};
|
|
281
|
+
const src = makeSource({
|
|
282
|
+
body:
|
|
283
|
+
JSON.stringify(compactionRow) +
|
|
284
|
+
"\n" +
|
|
285
|
+
JSON.stringify({
|
|
286
|
+
...compactionRow,
|
|
287
|
+
id: "log-2",
|
|
288
|
+
request_message_count: null,
|
|
289
|
+
}) +
|
|
290
|
+
"\n",
|
|
291
|
+
recorder,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// WHEN querying the trail window
|
|
295
|
+
const rows = await src.getCompactionLogsBetween("conv-1", 100, 500);
|
|
296
|
+
|
|
297
|
+
// THEN each row carries the numeric message count (null when the
|
|
298
|
+
// payload had no known message array) and no request payload
|
|
299
|
+
expect(rows).toHaveLength(2);
|
|
300
|
+
expect(rows[0]!.requestMessageCount).toBe(42);
|
|
301
|
+
expect(rows[1]!.requestMessageCount).toBeNull();
|
|
302
|
+
expect(rows[0]!.responsePayload).toBe('{"bar":2}');
|
|
303
|
+
expect("requestPayload" in rows[0]!).toBe(false);
|
|
304
|
+
// AND the SQL only touches request_payload inside JSONLength —
|
|
305
|
+
// it is never selected as a column
|
|
306
|
+
const body = String(recorder[0]!.init?.body ?? "");
|
|
307
|
+
expect(body).toContain("JSONLength(request_payload");
|
|
308
|
+
expect(body.match(/request_payload/g)).toHaveLength(
|
|
309
|
+
body.match(/JSONLength\(request_payload/g)!.length,
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
227
313
|
test("missing clickhouse:url credential surfaces a clear error", async () => {
|
|
228
314
|
const src = new ClickHouseLlmRequestLogSource(DEFAULT_CONFIG, {
|
|
229
315
|
resolveUrl: async () => null,
|
|
@@ -341,6 +341,97 @@ describe("resolveCallSiteConfig", () => {
|
|
|
341
341
|
expect(resolved.verbosity).toBe("low");
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
+
test("forceOverrideProfile floats the override profile above site profile and callsite fields for non-main call sites", () => {
|
|
345
|
+
const llm = LLMSchema.parse({
|
|
346
|
+
default: fullDefault,
|
|
347
|
+
profiles: {
|
|
348
|
+
active: { verbosity: "low" },
|
|
349
|
+
sitep: { effort: "none", speed: "fast" },
|
|
350
|
+
forced: {
|
|
351
|
+
model: "claude-haiku-4-5",
|
|
352
|
+
effort: "high",
|
|
353
|
+
thinking: { enabled: false },
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
callSites: {
|
|
357
|
+
memoryExtraction: { profile: "sitep", maxTokens: 1000 },
|
|
358
|
+
},
|
|
359
|
+
activeProfile: "active",
|
|
360
|
+
});
|
|
361
|
+
const resolved = resolveCallSiteConfig("memoryExtraction", llm, {
|
|
362
|
+
overrideProfile: "forced",
|
|
363
|
+
forceOverrideProfile: true,
|
|
364
|
+
});
|
|
365
|
+
// Forced override wins over both the site profile and the call-site
|
|
366
|
+
// override fields it touches.
|
|
367
|
+
expect(resolved.model).toBe("claude-haiku-4-5");
|
|
368
|
+
expect(resolved.effort).toBe("high");
|
|
369
|
+
expect(resolved.thinking.enabled).toBe(false);
|
|
370
|
+
// Call-site layers still win where the forced profile is silent.
|
|
371
|
+
expect(resolved.maxTokens).toBe(1000);
|
|
372
|
+
expect(resolved.speed).toBe("fast");
|
|
373
|
+
// Active profile still applies under everything.
|
|
374
|
+
expect(resolved.verbosity).toBe("low");
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("forceOverrideProfile absent leaves the override below callsite fields (unchanged precedence)", () => {
|
|
378
|
+
const llm = LLMSchema.parse({
|
|
379
|
+
default: fullDefault,
|
|
380
|
+
profiles: {
|
|
381
|
+
forced: { effort: "high" },
|
|
382
|
+
},
|
|
383
|
+
callSites: {
|
|
384
|
+
memoryExtraction: { effort: "none" },
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
const resolved = resolveCallSiteConfig("memoryExtraction", llm, {
|
|
388
|
+
overrideProfile: "forced",
|
|
389
|
+
});
|
|
390
|
+
expect(resolved.effort).toBe("none");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("forceOverrideProfile with a missing profile reference falls through to unchanged precedence", () => {
|
|
394
|
+
const llm = LLMSchema.parse({
|
|
395
|
+
default: fullDefault,
|
|
396
|
+
profiles: {
|
|
397
|
+
sitep: { effort: "low", model: "claude-haiku-4-5" },
|
|
398
|
+
},
|
|
399
|
+
callSites: {
|
|
400
|
+
memoryExtraction: { profile: "sitep" },
|
|
401
|
+
},
|
|
402
|
+
});
|
|
403
|
+
const resolved = resolveCallSiteConfig("memoryExtraction", llm, {
|
|
404
|
+
overrideProfile: "nonexistent",
|
|
405
|
+
forceOverrideProfile: true,
|
|
406
|
+
});
|
|
407
|
+
// The missing reference is inert: site profile still wins.
|
|
408
|
+
expect(resolved.effort).toBe("low");
|
|
409
|
+
expect(resolved.model).toBe("claude-haiku-4-5");
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("forceOverrideProfile is a no-op for mainAgent (override already resolves on top)", () => {
|
|
413
|
+
const llm = LLMSchema.parse({
|
|
414
|
+
default: fullDefault,
|
|
415
|
+
profiles: {
|
|
416
|
+
active: { effort: "low" },
|
|
417
|
+
override: { effort: "high" },
|
|
418
|
+
},
|
|
419
|
+
callSites: {
|
|
420
|
+
mainAgent: { effort: "none" },
|
|
421
|
+
},
|
|
422
|
+
activeProfile: "active",
|
|
423
|
+
});
|
|
424
|
+
const withForce = resolveCallSiteConfig("mainAgent", llm, {
|
|
425
|
+
overrideProfile: "override",
|
|
426
|
+
forceOverrideProfile: true,
|
|
427
|
+
});
|
|
428
|
+
const withoutForce = resolveCallSiteConfig("mainAgent", llm, {
|
|
429
|
+
overrideProfile: "override",
|
|
430
|
+
});
|
|
431
|
+
expect(withForce).toEqual(withoutForce);
|
|
432
|
+
expect(withForce.effort).toBe("high");
|
|
433
|
+
});
|
|
434
|
+
|
|
344
435
|
test("overrideProfile absent leaves prior behavior intact", () => {
|
|
345
436
|
// No `opts` argument at all — the resolver must behave exactly as it did
|
|
346
437
|
// before this PR for configs without activeProfile/overrideProfile.
|
|
@@ -579,6 +670,30 @@ describe("resolveCallSiteConfig", () => {
|
|
|
579
670
|
expect(resolved.effort).toBe("medium");
|
|
580
671
|
});
|
|
581
672
|
|
|
673
|
+
test("workflowLeaf defaults to the cost-optimized profile", () => {
|
|
674
|
+
const llm = LLMSchema.parse({
|
|
675
|
+
default: fullDefault,
|
|
676
|
+
profiles: {
|
|
677
|
+
balanced: {
|
|
678
|
+
model: "claude-sonnet-4-7",
|
|
679
|
+
effort: "medium",
|
|
680
|
+
},
|
|
681
|
+
"cost-optimized": {
|
|
682
|
+
model: "claude-haiku-4-5-20251001",
|
|
683
|
+
effort: "low",
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
expect(resolveDefaultProfileKey("workflowLeaf", llm)).toBe(
|
|
689
|
+
"cost-optimized",
|
|
690
|
+
);
|
|
691
|
+
const resolved = resolveCallSiteConfig("workflowLeaf", llm);
|
|
692
|
+
expect(resolved.model).toBe("claude-haiku-4-5-20251001");
|
|
693
|
+
expect(resolved.effort).toBe("low");
|
|
694
|
+
expect(resolved.thinking.enabled).toBe(false);
|
|
695
|
+
});
|
|
696
|
+
|
|
582
697
|
test("explicit callSites config overrides CALL_SITE_DEFAULTS", () => {
|
|
583
698
|
const llm = LLMSchema.parse({
|
|
584
699
|
default: fullDefault,
|
|
@@ -66,6 +66,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
66
66
|
deepMergeForTest(target, overrides);
|
|
67
67
|
},
|
|
68
68
|
getConfig: () => rawConfig,
|
|
69
|
+
getDeploymentContextDefaults: () => ({}),
|
|
69
70
|
invalidateConfigCache: () => {},
|
|
70
71
|
withSuppressedConfigDiskWrites: async (fn: () => unknown) => fn(),
|
|
71
72
|
withSuppressedConfigDiskWritesSync: (fn: () => unknown) => fn(),
|
|
@@ -321,7 +322,7 @@ describe("PATCH /v1/config — managed profile deletion guard", () => {
|
|
|
321
322
|
const result = await patchRoute.handler({
|
|
322
323
|
body: { llm: { profiles: { "custom-balanced": null } } },
|
|
323
324
|
});
|
|
324
|
-
expect(result).
|
|
325
|
+
expect(result).toHaveProperty("llm");
|
|
325
326
|
});
|
|
326
327
|
|
|
327
328
|
test("allows deletion of a user-defined profile via null", async () => {
|
|
@@ -329,14 +330,14 @@ describe("PATCH /v1/config — managed profile deletion guard", () => {
|
|
|
329
330
|
const result = await patchRoute.handler({
|
|
330
331
|
body: { llm: { profiles: { "my-custom": null } } },
|
|
331
332
|
});
|
|
332
|
-
expect(result).
|
|
333
|
+
expect(result).toHaveProperty("llm");
|
|
333
334
|
});
|
|
334
335
|
|
|
335
336
|
test("allows non-profile config patches", async () => {
|
|
336
337
|
const result = await patchRoute.handler({
|
|
337
338
|
body: { someOtherKey: "value" },
|
|
338
339
|
});
|
|
339
|
-
expect(result).
|
|
340
|
+
expect(result).toHaveProperty("llm");
|
|
340
341
|
});
|
|
341
342
|
|
|
342
343
|
test("clears stale Velay ownership when manually patching public base URL", async () => {
|
|
@@ -353,7 +354,7 @@ describe("PATCH /v1/config — managed profile deletion guard", () => {
|
|
|
353
354
|
},
|
|
354
355
|
});
|
|
355
356
|
|
|
356
|
-
expect(result).
|
|
357
|
+
expect(result).toHaveProperty("ingress");
|
|
357
358
|
expect(savedRaw).toEqual({
|
|
358
359
|
ingress: {
|
|
359
360
|
publicBaseUrl: "https://manual.example.test",
|
|
@@ -371,7 +372,7 @@ describe("PATCH /v1/config — managed profile deletion guard", () => {
|
|
|
371
372
|
},
|
|
372
373
|
},
|
|
373
374
|
});
|
|
374
|
-
expect(result).
|
|
375
|
+
expect(result).toHaveProperty("llm");
|
|
375
376
|
});
|
|
376
377
|
|
|
377
378
|
test("rejects nulling the entire profiles map", async () => {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the default `max-tokens-continue` plugin's hooks.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - The `post-model-call` hook's decision for the canonical cases: a
|
|
6
|
+
* main-agent turn truncated at the output token limit → continue, with the
|
|
7
|
+
* truncated turn and the canonical continuation nudge appended to
|
|
8
|
+
* `messages`; any other stop reason → stop; non-main-agent call sites →
|
|
9
|
+
* stop; a provider rejection → stop; an entirely-stripped (empty) truncated
|
|
10
|
+
* turn → stop.
|
|
11
|
+
* - The per-run budget is split across the two hooks: `post-model-call`
|
|
12
|
+
* consumes one unit per continue (up to `MAX_TOKENS_AUTO_CONTINUES`) and
|
|
13
|
+
* the `stop` hook clears the counter on the definitive terminal so the next
|
|
14
|
+
* run starts with a full budget.
|
|
15
|
+
*
|
|
16
|
+
* The loop's actual side-effects (keeping the partial turn, per-run backstop,
|
|
17
|
+
* the continuation card on a terminal stop) live in `agent/loop.ts`. This
|
|
18
|
+
* file isolates the hook.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
22
|
+
|
|
23
|
+
import type {
|
|
24
|
+
PluginLogger,
|
|
25
|
+
PostModelCallContext,
|
|
26
|
+
StopContext,
|
|
27
|
+
} from "../plugin-api/types.js";
|
|
28
|
+
import {
|
|
29
|
+
hasMaxTokensContinueBudget,
|
|
30
|
+
MAX_TOKENS_AUTO_CONTINUES,
|
|
31
|
+
resetMaxTokensContinueStoreForTests,
|
|
32
|
+
} from "../plugins/defaults/max-tokens-continue/continue-state-store.js";
|
|
33
|
+
import postModelCall, {
|
|
34
|
+
MAX_TOKENS_CONTINUE_NUDGE_TEXT,
|
|
35
|
+
} from "../plugins/defaults/max-tokens-continue/hooks/post-model-call.js";
|
|
36
|
+
import stop from "../plugins/defaults/max-tokens-continue/hooks/stop.js";
|
|
37
|
+
import type { ContentBlock } from "../providers/types.js";
|
|
38
|
+
|
|
39
|
+
// ─── Fixtures ────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const noopLogger: PluginLogger = {
|
|
42
|
+
info: () => {},
|
|
43
|
+
warn: () => {},
|
|
44
|
+
error: () => {},
|
|
45
|
+
debug: () => {},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const truncatedText: ContentBlock = {
|
|
49
|
+
type: "text",
|
|
50
|
+
text: "Here is the start of a very long answer that got cut o",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function makeCtx(
|
|
54
|
+
overrides: Partial<PostModelCallContext> = {},
|
|
55
|
+
): PostModelCallContext {
|
|
56
|
+
return {
|
|
57
|
+
conversationId: "conv-mtc",
|
|
58
|
+
callSite: "mainAgent",
|
|
59
|
+
content: [truncatedText],
|
|
60
|
+
messages: [{ role: "user", content: [{ type: "text", text: "build it" }] }],
|
|
61
|
+
stopReason: "max_tokens",
|
|
62
|
+
decision: "stop",
|
|
63
|
+
logger: noopLogger,
|
|
64
|
+
...overrides,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
resetMaxTokensContinueStoreForTests();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ─── Decisions ───────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
describe("max-tokens-continue post-model-call hook", () => {
|
|
75
|
+
test("truncated main-agent turn → continue with the turn and nudge appended", async () => {
|
|
76
|
+
/**
|
|
77
|
+
* Tests that a max_tokens stop on a main-agent turn auto-continues.
|
|
78
|
+
*/
|
|
79
|
+
// GIVEN a main-agent turn that stopped at the output token limit.
|
|
80
|
+
const ctx = makeCtx();
|
|
81
|
+
const priorLength = ctx.messages.length;
|
|
82
|
+
|
|
83
|
+
// WHEN the hook runs.
|
|
84
|
+
await postModelCall(ctx);
|
|
85
|
+
|
|
86
|
+
// THEN it asks the loop to continue.
|
|
87
|
+
expect(ctx.decision).toBe("continue");
|
|
88
|
+
// AND it appends the truncated assistant turn followed by the nudge.
|
|
89
|
+
expect(ctx.messages).toHaveLength(priorLength + 2);
|
|
90
|
+
expect(ctx.messages[priorLength]).toEqual({
|
|
91
|
+
role: "assistant",
|
|
92
|
+
content: [truncatedText],
|
|
93
|
+
});
|
|
94
|
+
expect(ctx.messages[priorLength + 1]).toEqual({
|
|
95
|
+
role: "user",
|
|
96
|
+
content: [{ type: "text", text: MAX_TOKENS_CONTINUE_NUDGE_TEXT }],
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("non-max-tokens stop reason → stop", async () => {
|
|
101
|
+
/**
|
|
102
|
+
* Tests that ordinary stop reasons are left alone.
|
|
103
|
+
*/
|
|
104
|
+
// GIVEN a turn that ended normally.
|
|
105
|
+
const ctx = makeCtx({ stopReason: "end_turn" });
|
|
106
|
+
|
|
107
|
+
// WHEN the hook runs.
|
|
108
|
+
await postModelCall(ctx);
|
|
109
|
+
|
|
110
|
+
// THEN the decision stays stop and messages are untouched.
|
|
111
|
+
expect(ctx.decision).toBe("stop");
|
|
112
|
+
expect(ctx.messages).toHaveLength(1);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("non-mainAgent call site → stop", async () => {
|
|
116
|
+
/**
|
|
117
|
+
* Tests that background/subagent calls are never auto-continued.
|
|
118
|
+
*/
|
|
119
|
+
// GIVEN a truncated turn on a non-main-agent call site.
|
|
120
|
+
const ctx = makeCtx({ callSite: "recall" });
|
|
121
|
+
|
|
122
|
+
// WHEN the hook runs.
|
|
123
|
+
await postModelCall(ctx);
|
|
124
|
+
|
|
125
|
+
// THEN the decision stays stop.
|
|
126
|
+
expect(ctx.decision).toBe("stop");
|
|
127
|
+
expect(ctx.messages).toHaveLength(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("provider rejection → stop", async () => {
|
|
131
|
+
/**
|
|
132
|
+
* Tests that a rejection outcome (no reply content) is ignored.
|
|
133
|
+
*/
|
|
134
|
+
// GIVEN a provider rejection carrying an error.
|
|
135
|
+
const ctx = makeCtx({ error: new Error("boom"), content: [] });
|
|
136
|
+
|
|
137
|
+
// WHEN the hook runs.
|
|
138
|
+
await postModelCall(ctx);
|
|
139
|
+
|
|
140
|
+
// THEN the decision stays stop.
|
|
141
|
+
expect(ctx.decision).toBe("stop");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("empty truncated content → stop", async () => {
|
|
145
|
+
/**
|
|
146
|
+
* Tests that a turn with nothing left after truncation-block stripping
|
|
147
|
+
* ends terminally rather than pushing an empty assistant message.
|
|
148
|
+
*/
|
|
149
|
+
// GIVEN a max_tokens stop whose safe content is empty.
|
|
150
|
+
const ctx = makeCtx({ content: [] });
|
|
151
|
+
|
|
152
|
+
// WHEN the hook runs.
|
|
153
|
+
await postModelCall(ctx);
|
|
154
|
+
|
|
155
|
+
// THEN the decision stays stop and nothing is appended.
|
|
156
|
+
expect(ctx.decision).toBe("stop");
|
|
157
|
+
expect(ctx.messages).toHaveLength(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("budget exhausts after MAX_TOKENS_AUTO_CONTINUES and stop hook resets it", async () => {
|
|
161
|
+
/**
|
|
162
|
+
* Tests the per-run continue budget and its reset on the terminal stop.
|
|
163
|
+
*/
|
|
164
|
+
// GIVEN the budget is consumed by repeated truncated turns.
|
|
165
|
+
for (let i = 0; i < MAX_TOKENS_AUTO_CONTINUES; i++) {
|
|
166
|
+
const ctx = makeCtx();
|
|
167
|
+
await postModelCall(ctx);
|
|
168
|
+
expect(ctx.decision).toBe("continue");
|
|
169
|
+
}
|
|
170
|
+
expect(hasMaxTokensContinueBudget("conv-mtc")).toBe(false);
|
|
171
|
+
|
|
172
|
+
// WHEN one more truncated turn arrives.
|
|
173
|
+
const exhausted = makeCtx();
|
|
174
|
+
await postModelCall(exhausted);
|
|
175
|
+
|
|
176
|
+
// THEN the hook lets the turn end.
|
|
177
|
+
expect(exhausted.decision).toBe("stop");
|
|
178
|
+
expect(exhausted.messages).toHaveLength(1);
|
|
179
|
+
|
|
180
|
+
// AND the stop hook clears the budget so the next run continues afresh.
|
|
181
|
+
await stop({ conversationId: "conv-mtc" } as StopContext);
|
|
182
|
+
expect(hasMaxTokensContinueBudget("conv-mtc")).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -322,7 +322,13 @@ describe("image-studio skill script wrapper", () => {
|
|
|
322
322
|
expect(result.isError).toBe(true);
|
|
323
323
|
expect(result.content).toContain("Mock Gemini error: API failure");
|
|
324
324
|
expect(result.content).toContain(
|
|
325
|
-
"
|
|
325
|
+
"Failed model: gemini-3.1-flash-image-preview",
|
|
326
|
+
);
|
|
327
|
+
expect(result.content).toContain(
|
|
328
|
+
"Do not change service configuration (managed/your-own mode or default provider/model settings)",
|
|
329
|
+
);
|
|
330
|
+
expect(result.content).toContain(
|
|
331
|
+
"Retrying this call once with a different model parameter is allowed",
|
|
326
332
|
);
|
|
327
333
|
});
|
|
328
334
|
|
|
@@ -335,7 +341,10 @@ describe("image-studio skill script wrapper", () => {
|
|
|
335
341
|
expect(result.isError).toBe(true);
|
|
336
342
|
expect(result.content).toContain("Mock OpenAI error: openai failure");
|
|
337
343
|
expect(result.content).toContain(
|
|
338
|
-
"Do not change service configuration (mode
|
|
344
|
+
"Do not change service configuration (managed/your-own mode or default provider/model settings)",
|
|
345
|
+
);
|
|
346
|
+
expect(result.content).toContain(
|
|
347
|
+
"Retrying this call once with a different model parameter is allowed",
|
|
339
348
|
);
|
|
340
349
|
});
|
|
341
350
|
|
|
@@ -346,8 +355,9 @@ describe("image-studio skill script wrapper", () => {
|
|
|
346
355
|
|
|
347
356
|
expect(result.isError).toBe(true);
|
|
348
357
|
expect(result.content).toContain("No Gemini API key");
|
|
358
|
+
expect(result.content).toContain("Report this error to the user as-is");
|
|
349
359
|
expect(result.content).toContain(
|
|
350
|
-
"Do not change service configuration (mode
|
|
360
|
+
"Do not change service configuration (managed/your-own mode or default provider/model settings)",
|
|
351
361
|
);
|
|
352
362
|
});
|
|
353
363
|
|
|
@@ -359,8 +369,9 @@ describe("image-studio skill script wrapper", () => {
|
|
|
359
369
|
|
|
360
370
|
expect(result.isError).toBe(true);
|
|
361
371
|
expect(result.content).toContain("Managed proxy is not available");
|
|
372
|
+
expect(result.content).toContain("Report this error to the user as-is");
|
|
362
373
|
expect(result.content).toContain(
|
|
363
|
-
"Do not change service configuration (mode
|
|
374
|
+
"Do not change service configuration (managed/your-own mode or default provider/model settings)",
|
|
364
375
|
);
|
|
365
376
|
});
|
|
366
377
|
|
|
@@ -463,11 +474,11 @@ describe("image-studio TOOLS.json manifest", () => {
|
|
|
463
474
|
expect(props.mode.enum).toEqual(["generate", "edit"]);
|
|
464
475
|
expect(props.source_paths.type).toBe("array");
|
|
465
476
|
expect(props.attachment_ids).toBeUndefined();
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
477
|
+
// No enum by design: model accepts tier aliases or concrete IDs, and is
|
|
478
|
+
// validated at runtime against the registry so the schema never goes
|
|
479
|
+
// stale when models change.
|
|
480
|
+
expect(props.model.type).toBe("string");
|
|
481
|
+
expect(props.model.enum).toBeUndefined();
|
|
471
482
|
expect(props.variants.type).toBe("number");
|
|
472
483
|
});
|
|
473
484
|
});
|
|
@@ -82,6 +82,28 @@ const GET_FEATURE_FLAGS_DEFAULT: Record<string, boolean> = {
|
|
|
82
82
|
__test_default__: false,
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
class FakeIpcCallError extends Error {
|
|
86
|
+
readonly statusCode?: number;
|
|
87
|
+
readonly errorCode?: string;
|
|
88
|
+
readonly errorDetails?: unknown;
|
|
89
|
+
|
|
90
|
+
constructor(
|
|
91
|
+
message: string,
|
|
92
|
+
fields: {
|
|
93
|
+
statusCode?: number;
|
|
94
|
+
errorCode?: string;
|
|
95
|
+
errorDetails?: unknown;
|
|
96
|
+
} = {},
|
|
97
|
+
) {
|
|
98
|
+
super(message);
|
|
99
|
+
this.name = "IpcCallError";
|
|
100
|
+
if (fields.statusCode !== undefined) this.statusCode = fields.statusCode;
|
|
101
|
+
if (fields.errorCode !== undefined) this.errorCode = fields.errorCode;
|
|
102
|
+
if (fields.errorDetails !== undefined)
|
|
103
|
+
this.errorDetails = fields.errorDetails;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
85
107
|
export function installGatewayIpcMock(): void {
|
|
86
108
|
mock.module("@vellumai/gateway-client/ipc-client", () => ({
|
|
87
109
|
ipcCall: async (
|
|
@@ -97,6 +119,7 @@ export function installGatewayIpcMock(): void {
|
|
|
97
119
|
if (method === "get_feature_flags") return GET_FEATURE_FLAGS_DEFAULT;
|
|
98
120
|
return undefined;
|
|
99
121
|
},
|
|
122
|
+
IpcCallError: FakeIpcCallError,
|
|
100
123
|
PersistentIpcClient: FakePersistentIpcClient,
|
|
101
124
|
}));
|
|
102
125
|
}
|
|
@@ -20,7 +20,7 @@ describe("model intents", () => {
|
|
|
20
20
|
"claude-haiku-4-5-20251001",
|
|
21
21
|
);
|
|
22
22
|
expect(resolveModelIntent("anthropic", "quality-optimized")).toBe(
|
|
23
|
-
"claude-
|
|
23
|
+
"claude-opus-4-8",
|
|
24
24
|
);
|
|
25
25
|
expect(resolveModelIntent("anthropic", "vision-optimized")).toBe(
|
|
26
26
|
"claude-opus-4-6",
|
|
@@ -139,6 +139,32 @@ describe("normalizeOnboardingContext", () => {
|
|
|
139
139
|
expect(result.preferredName).toBeUndefined();
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
test("maps occupation through, trimmed", () => {
|
|
143
|
+
const ctx: OnboardingContext = {
|
|
144
|
+
tools: [],
|
|
145
|
+
tasks: [],
|
|
146
|
+
tone: "friendly",
|
|
147
|
+
occupation: " Software Engineer ",
|
|
148
|
+
};
|
|
149
|
+
const result = normalizeOnboardingContext(ctx);
|
|
150
|
+
expect(result.occupation).toBe("Software Engineer");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("absent or blank occupation yields undefined", () => {
|
|
154
|
+
expect(
|
|
155
|
+
normalizeOnboardingContext({ tools: [], tasks: [], tone: "friendly" })
|
|
156
|
+
.occupation,
|
|
157
|
+
).toBeUndefined();
|
|
158
|
+
expect(
|
|
159
|
+
normalizeOnboardingContext({
|
|
160
|
+
tools: [],
|
|
161
|
+
tasks: [],
|
|
162
|
+
tone: "friendly",
|
|
163
|
+
occupation: " ",
|
|
164
|
+
}).occupation,
|
|
165
|
+
).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
142
168
|
test("tone passes through", () => {
|
|
143
169
|
const ctx: OnboardingContext = {
|
|
144
170
|
tools: [],
|
|
@@ -15,8 +15,6 @@ import {
|
|
|
15
15
|
hasAccessRequestInstructions,
|
|
16
16
|
hasInviteFlowDirective,
|
|
17
17
|
normalizeForDirectiveMatching,
|
|
18
|
-
sanitizeIdentityField,
|
|
19
|
-
sanitizeMessagePreview,
|
|
20
18
|
} from "../notifications/access-request-copy.js";
|
|
21
19
|
import type { ConversationCandidateSet } from "../notifications/conversation-candidates.js";
|
|
22
20
|
import { composeFallbackCopy } from "../notifications/copy-composer.js";
|
|
@@ -24,6 +22,10 @@ import {
|
|
|
24
22
|
enforceGuardianCallConversationAffinity,
|
|
25
23
|
validateConversationActions,
|
|
26
24
|
} from "../notifications/decision-engine.js";
|
|
25
|
+
import {
|
|
26
|
+
sanitizeIdentityField,
|
|
27
|
+
sanitizeMessagePreview,
|
|
28
|
+
} from "../notifications/notification-utils.js";
|
|
27
29
|
import type { NotificationSignal } from "../notifications/signal.js";
|
|
28
30
|
import type {
|
|
29
31
|
NotificationChannel,
|
|
@@ -186,9 +186,18 @@ describe("TelegramAdapter", () => {
|
|
|
186
186
|
contextPayload: {
|
|
187
187
|
requestId: "req-abc-123",
|
|
188
188
|
requestCode: "XYZW",
|
|
189
|
-
senderIdentifier: "
|
|
189
|
+
senderIdentifier: "TestUser",
|
|
190
190
|
sourceChannel: "telegram",
|
|
191
191
|
},
|
|
192
|
+
approvalContext: {
|
|
193
|
+
requestId: "req-abc-123",
|
|
194
|
+
actions: [
|
|
195
|
+
{ id: "approve_once", label: "Approve once" },
|
|
196
|
+
{ id: "reject", label: "Reject" },
|
|
197
|
+
],
|
|
198
|
+
plainTextFallback:
|
|
199
|
+
'TestUser is requesting access to the assistant.\nReply "XYZW approve" to grant access or "XYZW reject" to deny.\nReply "open invite flow" to start Trusted Contacts invite flow.',
|
|
200
|
+
},
|
|
192
201
|
});
|
|
193
202
|
|
|
194
203
|
const result = await adapter.send(payload, makeDestination());
|
|
@@ -237,7 +246,7 @@ describe("TelegramAdapter", () => {
|
|
|
237
246
|
body: "Someone is requesting access.",
|
|
238
247
|
},
|
|
239
248
|
contextPayload: {
|
|
240
|
-
senderIdentifier: "
|
|
249
|
+
senderIdentifier: "TestUser",
|
|
241
250
|
sourceChannel: "telegram",
|
|
242
251
|
// no requestId
|
|
243
252
|
},
|
|
@@ -264,9 +273,18 @@ describe("TelegramAdapter", () => {
|
|
|
264
273
|
contextPayload: {
|
|
265
274
|
requestId: "req-abc-123",
|
|
266
275
|
requestCode: "XYZW",
|
|
267
|
-
senderIdentifier: "
|
|
276
|
+
senderIdentifier: "TestUser",
|
|
268
277
|
sourceChannel: "telegram",
|
|
269
278
|
},
|
|
279
|
+
approvalContext: {
|
|
280
|
+
requestId: "req-abc-123",
|
|
281
|
+
actions: [
|
|
282
|
+
{ id: "approve_once", label: "Approve once" },
|
|
283
|
+
{ id: "reject", label: "Reject" },
|
|
284
|
+
],
|
|
285
|
+
plainTextFallback:
|
|
286
|
+
'TestUser is requesting access to the assistant.\nReply "XYZW approve" to grant access or "XYZW reject" to deny.\nReply "open invite flow" to start Trusted Contacts invite flow.',
|
|
287
|
+
},
|
|
270
288
|
});
|
|
271
289
|
|
|
272
290
|
const result = await adapter.send(payload, makeDestination());
|