@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62
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/ARCHITECTURE.md +6 -6
- package/bun.lock +2 -2
- package/docs/activation-funnel-telemetry.md +310 -0
- package/examples/plugins/echo/README.md +61 -60
- package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
- package/examples/plugins/echo/hooks/stop.ts +16 -0
- package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
- package/examples/plugins/echo/package.json +1 -2
- package/examples/plugins/echo/src/emit.ts +19 -0
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
- package/openapi.yaml +273 -121
- package/package.json +2 -2
- package/scripts/sync-llm-catalog.ts +2 -0
- package/src/__tests__/activation-early-marking.test.ts +120 -0
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
- package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
- package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
- package/src/__tests__/agent-loop-thinking.test.ts +36 -20
- package/src/__tests__/agent-loop.test.ts +441 -96
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
- package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
- package/src/__tests__/anthropic-provider.test.ts +23 -8
- package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +132 -0
- package/src/__tests__/approval-cascade.test.ts +6 -5
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/channel-approval-routes.test.ts +1 -1
- package/src/__tests__/channel-approvals.test.ts +1 -1
- package/src/__tests__/compaction-circuit.test.ts +258 -0
- package/src/__tests__/compaction-direct.test.ts +146 -0
- package/src/__tests__/compaction-events.test.ts +7 -7
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +265 -125
- package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
- package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
- package/src/__tests__/conversation-agent-loop.test.ts +342 -623
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-history-web-search.test.ts +6 -1
- package/src/__tests__/conversation-media-retry.test.ts +1 -1
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
- package/src/__tests__/conversation-process-callsite.test.ts +15 -15
- package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
- package/src/__tests__/conversation-queue.test.ts +10 -10
- package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +7 -5
- package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
- package/src/__tests__/conversation-speed-override.test.ts +11 -10
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
- package/src/__tests__/conversation-undo.test.ts +2 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
- package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +36 -0
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/empty-response-hook.test.ts +1 -1
- package/src/__tests__/file-write-tool.test.ts +63 -0
- package/src/__tests__/gateway-only-guard.test.ts +17 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/history-repair-hook.test.ts +5 -1
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/host-app-control-routes.test.ts +1 -1
- package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
- package/src/__tests__/inference-profile-reaper.test.ts +62 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
- package/src/__tests__/injector-background-turn.test.ts +13 -23
- package/src/__tests__/injector-chain.test.ts +268 -44
- package/src/__tests__/injector-disk-pressure.test.ts +210 -52
- package/src/__tests__/injector-document-comments.test.ts +97 -114
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
- package/src/__tests__/injector-v3-suppression.test.ts +4 -4
- package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
- package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver.test.ts +73 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
- package/src/__tests__/persist-unsendable-image.test.ts +97 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +91 -81
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
- package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
- package/src/__tests__/published-app-updater.test.ts +138 -0
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
- package/src/__tests__/steer-tool-repair.test.ts +1 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-detail.test.ts +25 -7
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
- package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
- package/src/__tests__/subagent-manager-notify.test.ts +1 -3
- package/src/__tests__/subagent-notify-parent.test.ts +1 -3
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +5 -1
- package/src/__tests__/tool-error-hook.test.ts +1 -1
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/__tests__/user-plugin-loader.test.ts +54 -286
- package/src/__tests__/web-fetch.test.ts +45 -0
- package/src/acp/__tests__/agent-process.test.ts +161 -0
- package/src/acp/__tests__/client-handler.test.ts +40 -0
- package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
- package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +280 -0
- package/src/acp/auto-install.ts +232 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/prepare-agent-env.ts +80 -27
- package/src/acp/resolve-agent.test.ts +188 -28
- package/src/acp/resolve-agent.ts +119 -42
- package/src/acp/resume-hint.ts +23 -0
- package/src/acp/session-manager.ts +507 -73
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +403 -251
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/responses/conversation-message.ts +72 -13
- package/src/api/responses/memory-v3-selection-log.ts +19 -10
- package/src/approvals/guardian-request-resolvers.ts +27 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/memory-v3.ts +57 -199
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
- package/src/cli/lib/install-from-github.ts +797 -60
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +416 -0
- package/src/cli/lib/plugin-marketplace.ts +215 -0
- package/src/cli/lib/search-plugins.ts +201 -29
- package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
- package/src/config/acp-defaults.test.ts +10 -0
- package/src/config/acp-defaults.ts +9 -3
- package/src/config/acp-schema.ts +1 -7
- package/src/config/bundled-skills/acp/SKILL.md +80 -41
- package/src/config/bundled-skills/acp/TOOLS.json +4 -4
- package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
- package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
- package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
- package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
- package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/call-site-defaults.ts +0 -3
- package/src/config/feature-flag-registry.json +10 -56
- package/src/config/llm-resolver.ts +39 -7
- package/src/config/loader.ts +5 -22
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
- package/src/config/schemas/call-site-catalog.ts +0 -21
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/config/schemas/llm.ts +17 -3
- package/src/config/schemas/memory-v3.ts +58 -8
- package/src/config/seed-inference-profiles.ts +18 -0
- package/src/context/compactor.ts +22 -4
- package/src/context/post-turn-tool-result-truncation.ts +39 -1
- package/src/context/strip-injections.ts +8 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
- package/src/daemon/conversation-agent-loop.ts +308 -1297
- package/src/daemon/conversation-error.ts +31 -4
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-media-retry.ts +19 -6
- package/src/daemon/conversation-messaging.ts +17 -0
- package/src/daemon/conversation-process.ts +22 -141
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +361 -393
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +165 -11
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +444 -62
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/external-plugins-bootstrap.ts +36 -46
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -7
- package/src/daemon/persist-unsendable-image.ts +62 -25
- package/src/daemon/process-message.ts +1 -1
- package/src/daemon/server.ts +2 -0
- package/src/daemon/tool-side-effects.ts +15 -0
- package/src/daemon/wake-conversation-ops.ts +269 -0
- package/src/embedded/plugin-api.ts +2 -2
- package/src/export/__tests__/transcript-formatter.test.ts +5 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
- package/src/heartbeat/heartbeat-run-store.ts +23 -1
- package/src/heartbeat/heartbeat-service.ts +26 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
- package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
- package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
- package/src/ipc/skill-routes/memory.ts +4 -2
- package/src/memory/__tests__/activation-session-store.test.ts +41 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
- package/src/memory/activation-session-store.ts +43 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +6 -0
- package/src/memory/embedding-backend.ts +5 -22
- package/src/memory/job-handlers/conversation-starters.ts +13 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
- package/src/memory/jobs-worker.ts +25 -1
- package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
- package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
- package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/onboarding-events-store.ts +66 -18
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/schema/infrastructure.ts +13 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
- package/src/memory/v2/__tests__/page-store.test.ts +22 -0
- package/src/memory/v2/consolidation-job.ts +2 -63
- package/src/memory/v2/types.ts +5 -0
- package/src/messaging/providers/telegram-bot/api.ts +14 -5
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +9 -2
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +40 -11
- package/src/plugin-api/types.ts +108 -0
- package/src/plugins/defaults/compaction/compact.ts +106 -0
- package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/plugins/defaults/compaction/manager-store.ts +57 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
- package/src/plugins/defaults/empty-response/package.json +0 -1
- package/src/plugins/defaults/history-repair/package.json +0 -1
- package/src/plugins/defaults/index.ts +156 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
- package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
- package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
- package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
- package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
- package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
- package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
- package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
- package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
- package/src/plugins/defaults/title-generate/package.json +0 -1
- package/src/plugins/defaults/tool-error/package.json +0 -1
- package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
- package/src/plugins/external-api.ts +12 -2
- package/src/plugins/pipeline.ts +6 -293
- package/src/plugins/registry.ts +9 -37
- package/src/plugins/types.ts +76 -381
- package/src/plugins/user-loader.ts +30 -127
- package/src/prompts/__tests__/system-prompt.test.ts +6 -0
- package/src/prompts/system-prompt.ts +61 -10
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
- package/src/providers/anthropic/client.ts +9 -10
- package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
- package/src/providers/inference/logit-bias.ts +55 -0
- package/src/providers/model-catalog.ts +57 -0
- package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -1
- package/src/providers/retry.ts +57 -7
- package/src/providers/types.ts +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +555 -691
- package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
- package/src/runtime/agent-wake.ts +108 -209
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/channel-approvals.ts +1 -1
- package/src/runtime/interactive-ui.ts +1 -1
- package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
- package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
- package/src/runtime/routes/acp-routes.test.ts +185 -32
- package/src/runtime/routes/acp-routes.ts +328 -30
- package/src/runtime/routes/app-management-routes.ts +3 -0
- package/src/runtime/routes/approval-routes.ts +1 -1
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/browser-tabs-routes.ts +6 -10
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/conversation-cli-routes.ts +1 -1
- package/src/runtime/routes/conversation-list-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +30 -2
- package/src/runtime/routes/conversation-starter-routes.ts +13 -7
- package/src/runtime/routes/conversations-import-routes.ts +24 -7
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-cu-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +18 -3
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
- package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
- package/src/runtime/routes/memory-v3-routes.ts +65 -305
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +337 -35
- package/src/runtime/routes/stt-routes.ts +45 -12
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/routes/workspace-routes.ts +50 -15
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/services/published-app-updater.ts +30 -8
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
- package/src/telemetry/activation-funnel.ts +167 -0
- package/src/telemetry/types.ts +13 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
- package/src/telemetry/usage-telemetry-reporter.ts +26 -1
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +9 -19
- package/src/tools/acp/list-agents.ts +3 -15
- package/src/tools/acp/spawn.test.ts +173 -202
- package/src/tools/acp/spawn.ts +37 -172
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +166 -50
- package/src/tools/browser/browser-execution.ts +12 -2
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/network/web-fetch.ts +65 -24
- package/src/tools/skills/load.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +32 -5
- package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
- package/docs/plugins.md +0 -832
- package/examples/plugins/echo/register.ts +0 -143
- package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
- package/src/__tests__/compaction-pipeline.test.ts +0 -210
- package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
- package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
- package/src/__tests__/pipeline-runner.test.ts +0 -554
- package/src/daemon/wake-target-adapter.ts +0 -253
- package/src/memory/v3/__tests__/assign.test.ts +0 -242
- package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
- package/src/memory/v3/__tests__/core.test.ts +0 -39
- package/src/memory/v3/__tests__/health.test.ts +0 -219
- package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
- package/src/memory/v3/__tests__/needle.test.ts +0 -107
- package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
- package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
- package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
- package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
- package/src/memory/v3/__tests__/router.test.ts +0 -332
- package/src/memory/v3/__tests__/selector.test.ts +0 -470
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
- package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
- package/src/memory/v3/__tests__/tree.test.ts +0 -192
- package/src/memory/v3/assign.ts +0 -268
- package/src/memory/v3/capabilities.ts +0 -124
- package/src/memory/v3/core.ts +0 -26
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/maintain-job.ts +0 -314
- package/src/memory/v3/needle.ts +0 -115
- package/src/memory/v3/orchestrate.ts +0 -126
- package/src/memory/v3/page-content.ts +0 -34
- package/src/memory/v3/provider-blocks.ts +0 -26
- package/src/memory/v3/reconcile.ts +0 -523
- package/src/memory/v3/router.ts +0 -190
- package/src/memory/v3/selection-log-store.ts +0 -84
- package/src/memory/v3/selector.ts +0 -226
- package/src/memory/v3/shadow-plugin.ts +0 -411
- package/src/memory/v3/snapshot.ts +0 -209
- package/src/memory/v3/tree.ts +0 -174
- package/src/memory/v3/types.ts +0 -59
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
- package/src/plugins/defaults/circuit-breaker/package.json +0 -15
- package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
- package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
- package/src/plugins/defaults/compaction/register.ts +0 -35
- package/src/plugins/defaults/compaction/terminal.ts +0 -73
- package/src/plugins/defaults/empty-response/register.ts +0 -23
- package/src/plugins/defaults/history-repair/register.ts +0 -24
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
- package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
- package/src/plugins/defaults/overflow-reduce/package.json +0 -15
- package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
- package/src/plugins/defaults/title-generate/register.ts +0 -35
- package/src/plugins/defaults/tool-error/register.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
- package/src/proactive-artifact/aux-message-injector.ts +0 -97
- package/src/proactive-artifact/decision.test.ts +0 -226
- package/src/proactive-artifact/decision.ts +0 -165
- package/src/proactive-artifact/index.ts +0 -7
- package/src/proactive-artifact/job.test.ts +0 -962
- package/src/proactive-artifact/job.ts +0 -372
- package/src/proactive-artifact/message-copy.ts +0 -58
- package/src/proactive-artifact/trigger-state.test.ts +0 -286
- package/src/proactive-artifact/trigger-state.ts +0 -123
- package/src/util/map-limit.ts +0 -27
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* - Skips (re)injection when the `<info>` block is already present in the
|
|
12
12
|
* turn's working messages (presence detection — the block persists in
|
|
13
13
|
* history between compactions).
|
|
14
|
+
* - Still injects when only a dynamic `<memory>` activation block is present
|
|
15
|
+
* (that wrapper must not be mistaken for the static block) — both on a
|
|
16
|
+
* normal turn and after compaction strips the prior `<info>` block.
|
|
14
17
|
*
|
|
15
18
|
* The injector sources its content itself via `readMemoryV2StaticContent()`
|
|
16
19
|
* behind the personal-memory trust gate, so each test seeds the workspace
|
|
@@ -35,7 +38,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
35
38
|
}));
|
|
36
39
|
|
|
37
40
|
const { defaultInjectors } =
|
|
38
|
-
await import("../plugins/defaults/injectors
|
|
41
|
+
await import("../plugins/defaults/memory-retrieval/injectors.js");
|
|
39
42
|
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
40
43
|
import type { Message } from "../providers/types.js";
|
|
41
44
|
import { getWorkspacePromptPath } from "../util/platform.js";
|
|
@@ -54,7 +57,6 @@ function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
|
54
57
|
conversationId: "conv-test",
|
|
55
58
|
turnIndex: 0,
|
|
56
59
|
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
57
|
-
injectionInputs: {},
|
|
58
60
|
...overrides,
|
|
59
61
|
};
|
|
60
62
|
}
|
|
@@ -96,7 +98,7 @@ describe("memory-v2-static injector", () => {
|
|
|
96
98
|
|
|
97
99
|
test("returns null in minimal mode even with content", async () => {
|
|
98
100
|
seedEssentials("Alice prefers VS Code.");
|
|
99
|
-
const ctx = makeContext({
|
|
101
|
+
const ctx = makeContext({ mode: "minimal" });
|
|
100
102
|
expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
|
|
101
103
|
});
|
|
102
104
|
|
|
@@ -140,10 +142,12 @@ describe("memory-v2-static injector", () => {
|
|
|
140
142
|
expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
|
|
141
143
|
});
|
|
142
144
|
|
|
143
|
-
test("
|
|
144
|
-
//
|
|
145
|
-
// `<memory>…</memory
|
|
146
|
-
//
|
|
145
|
+
test("injects the <info> block even when a dynamic <memory> activation block is present", async () => {
|
|
146
|
+
// Regression (#33612): the v2 *dynamic* activation block uses the
|
|
147
|
+
// `<memory>…</memory>` wrapper and `prepareMemory` prepends it to the tail
|
|
148
|
+
// user message every turn, before this injector runs. The static injector
|
|
149
|
+
// must NOT treat that as its own `<info>` block — doing so suppressed the
|
|
150
|
+
// static block on essentially every turn.
|
|
147
151
|
seedEssentials("Alice prefers VS Code.");
|
|
148
152
|
const ctx = makeContext();
|
|
149
153
|
const runMessages: Message[] = [
|
|
@@ -152,12 +156,86 @@ describe("memory-v2-static injector", () => {
|
|
|
152
156
|
content: [
|
|
153
157
|
{
|
|
154
158
|
type: "text",
|
|
155
|
-
text: "<memory>\n
|
|
159
|
+
text: "<memory>\n# memory/concepts/foo.md\nactivated page summary\n</memory>",
|
|
156
160
|
},
|
|
157
161
|
{ type: "text", text: "What next?" },
|
|
158
162
|
],
|
|
159
163
|
},
|
|
160
164
|
];
|
|
165
|
+
const block = await memoryV2StaticInjector.produce(ctx, runMessages);
|
|
166
|
+
expect(block).not.toBeNull();
|
|
167
|
+
expect(block!.id).toBe("memory-v2-static");
|
|
168
|
+
expect(block!.text).toBe(
|
|
169
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("reinjects the <info> block after compaction strips it, alongside a fresh <memory> block", async () => {
|
|
174
|
+
// Post-compaction state: `stripInjectionsForCompaction` removed the prior
|
|
175
|
+
// `<info>` block, and the next turn's `prepareMemory` re-added a fresh
|
|
176
|
+
// dynamic `<memory>` block ahead of the chain. With no `<info>` present, the
|
|
177
|
+
// injector must reinject it rather than skip on the `<memory>` block.
|
|
178
|
+
seedEssentials("Alice prefers VS Code.");
|
|
179
|
+
const ctx = makeContext();
|
|
180
|
+
const postCompactMessages: Message[] = [
|
|
181
|
+
{
|
|
182
|
+
role: "user",
|
|
183
|
+
content: [
|
|
184
|
+
{ type: "text", text: "<memory>\nactivated page summary\n</memory>" },
|
|
185
|
+
{ type: "text", text: "Continue please." },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
];
|
|
189
|
+
const block = await memoryV2StaticInjector.produce(
|
|
190
|
+
ctx,
|
|
191
|
+
postCompactMessages,
|
|
192
|
+
);
|
|
193
|
+
expect(block).not.toBeNull();
|
|
194
|
+
expect(block!.id).toBe("memory-v2-static");
|
|
195
|
+
expect(block!.text).toContain("## Essentials");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("skips (re)injection when the <info> block is carried in a flattened history block", async () => {
|
|
199
|
+
// Regression: when a historical user message's separate injection content
|
|
200
|
+
// blocks get collapsed into one text block (observed after a daemon restart
|
|
201
|
+
// / plugin hot-reload rebuilt the rendered history mid-conversation — the
|
|
202
|
+
// blocks are joined with "\n"), the `<info>` wrapper is no longer its own
|
|
203
|
+
// block but a newline-delimited section inside a larger block. Presence
|
|
204
|
+
// detection must still recognize it; otherwise the injector re-injects a
|
|
205
|
+
// duplicate `<info>` onto the new tail (the double-injection bug).
|
|
206
|
+
seedEssentials("Alice prefers VS Code.");
|
|
207
|
+
const ctx = makeContext();
|
|
208
|
+
const flattenedFirstUser = [
|
|
209
|
+
"<workspace>\nRoot: /ws\nFiles: a.md\n</workspace>",
|
|
210
|
+
"<turn_context>\ncurrent_time: now\n</turn_context>",
|
|
211
|
+
"<memory>\nactivated page summary\n</memory>",
|
|
212
|
+
"<info>\n## Essentials\n\nAlice prefers VS Code.\n</info>",
|
|
213
|
+
"what should we do next?",
|
|
214
|
+
].join("\n");
|
|
215
|
+
const runMessages: Message[] = [
|
|
216
|
+
{ role: "user", content: [{ type: "text", text: flattenedFirstUser }] },
|
|
217
|
+
{ role: "assistant", content: [{ type: "text", text: "On it." }] },
|
|
218
|
+
{ role: "user", content: [{ type: "text", text: "great, continue" }] },
|
|
219
|
+
];
|
|
161
220
|
expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
|
|
162
221
|
});
|
|
222
|
+
|
|
223
|
+
test("still injects when a user message opens an <info> tag without closing it", async () => {
|
|
224
|
+
// False-positive guard: the wrapper matcher requires BOTH the opening and
|
|
225
|
+
// closing tags, so user prose that merely opens an injection-like tag must
|
|
226
|
+
// not be mistaken for a carried block and suppress injection.
|
|
227
|
+
seedEssentials("Alice prefers VS Code.");
|
|
228
|
+
const ctx = makeContext();
|
|
229
|
+
const runMessages: Message[] = [
|
|
230
|
+
{
|
|
231
|
+
role: "user",
|
|
232
|
+
content: [
|
|
233
|
+
{ type: "text", text: "<info>\n is this how the tag works?" },
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
];
|
|
237
|
+
const block = await memoryV2StaticInjector.produce(ctx, runMessages);
|
|
238
|
+
expect(block).not.toBeNull();
|
|
239
|
+
expect(block!.id).toBe("memory-v2-static");
|
|
240
|
+
});
|
|
163
241
|
});
|
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, test } from "bun:test";
|
|
11
11
|
|
|
12
|
-
import type { ConversationCandidateSet } from "../notifications/conversation-candidates.js";
|
|
13
12
|
import {
|
|
14
13
|
buildAccessRequestContractText,
|
|
15
14
|
buildAccessRequestIdentityLine,
|
|
16
|
-
composeFallbackCopy,
|
|
17
15
|
hasAccessRequestInstructions,
|
|
18
16
|
hasInviteFlowDirective,
|
|
19
17
|
normalizeForDirectiveMatching,
|
|
20
18
|
sanitizeIdentityField,
|
|
21
19
|
sanitizeMessagePreview,
|
|
22
|
-
} from "../notifications/copy
|
|
20
|
+
} from "../notifications/access-request-copy.js";
|
|
21
|
+
import type { ConversationCandidateSet } from "../notifications/conversation-candidates.js";
|
|
22
|
+
import { composeFallbackCopy } from "../notifications/copy-composer.js";
|
|
23
23
|
import {
|
|
24
24
|
enforceGuardianCallConversationAffinity,
|
|
25
25
|
validateConversationActions,
|
|
@@ -128,12 +128,19 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
128
128
|
return { content: "ok", isError: false };
|
|
129
129
|
};
|
|
130
130
|
|
|
131
|
-
const loop = new AgentLoop(
|
|
131
|
+
const loop = new AgentLoop({
|
|
132
|
+
provider: provider,
|
|
133
|
+
systemPrompt: "system",
|
|
134
|
+
conversationId: "test-conversation",
|
|
132
135
|
tools: dummyTools,
|
|
133
136
|
toolExecutor: toolExecutor,
|
|
134
137
|
});
|
|
135
138
|
const start = Date.now();
|
|
136
|
-
await loop.run(
|
|
139
|
+
await loop.run({
|
|
140
|
+
messages: [userMessage],
|
|
141
|
+
onEvent: () => {},
|
|
142
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
143
|
+
});
|
|
137
144
|
const elapsed = Date.now() - start;
|
|
138
145
|
|
|
139
146
|
// Parallel: ~50ms + overhead. Sequential would be ~250ms.
|
|
@@ -169,12 +176,19 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
169
176
|
return { content: "ok", isError: false };
|
|
170
177
|
};
|
|
171
178
|
|
|
172
|
-
const loop = new AgentLoop(
|
|
179
|
+
const loop = new AgentLoop({
|
|
180
|
+
provider: provider,
|
|
181
|
+
systemPrompt: "system",
|
|
182
|
+
conversationId: "test-conversation",
|
|
173
183
|
tools: dummyTools,
|
|
174
184
|
toolExecutor: toolExecutor,
|
|
175
185
|
});
|
|
176
186
|
const start = Date.now();
|
|
177
|
-
await loop.run(
|
|
187
|
+
await loop.run({
|
|
188
|
+
messages: [userMessage],
|
|
189
|
+
onEvent: () => {},
|
|
190
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
191
|
+
});
|
|
178
192
|
const elapsed = Date.now() - start;
|
|
179
193
|
|
|
180
194
|
// All 10 tools should have executed
|
|
@@ -222,13 +236,20 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
222
236
|
return { content: "ok", isError: false };
|
|
223
237
|
};
|
|
224
238
|
|
|
225
|
-
const loop = new AgentLoop(
|
|
239
|
+
const loop = new AgentLoop({
|
|
240
|
+
provider: provider,
|
|
241
|
+
systemPrompt: "system",
|
|
242
|
+
conversationId: "test-conversation",
|
|
226
243
|
tools: dummyTools,
|
|
227
244
|
toolExecutor: toolExecutor,
|
|
228
245
|
});
|
|
229
246
|
const events: AgentEvent[] = [];
|
|
230
247
|
const start = Date.now();
|
|
231
|
-
await loop.run(
|
|
248
|
+
await loop.run({
|
|
249
|
+
messages: [userMessage],
|
|
250
|
+
onEvent: collectEvents(events),
|
|
251
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
252
|
+
});
|
|
232
253
|
const elapsed = Date.now() - start;
|
|
233
254
|
|
|
234
255
|
// Parallel: ~2000ms (dominated by slow tool). Sequential: ~2400ms (2000 + 4*100).
|
|
@@ -289,12 +310,18 @@ describe("Parallel tool execution benchmarks", () => {
|
|
|
289
310
|
return { content: "should not return", isError: false };
|
|
290
311
|
};
|
|
291
312
|
|
|
292
|
-
const loop = new AgentLoop(
|
|
313
|
+
const loop = new AgentLoop({
|
|
314
|
+
provider: provider,
|
|
315
|
+
systemPrompt: "system",
|
|
316
|
+
conversationId: "test-conversation",
|
|
293
317
|
tools: dummyTools,
|
|
294
318
|
toolExecutor: toolExecutor,
|
|
295
319
|
});
|
|
296
320
|
const start = Date.now();
|
|
297
|
-
const { history } = await loop.run(
|
|
321
|
+
const { history } = await loop.run({
|
|
322
|
+
messages: [userMessage],
|
|
323
|
+
onEvent: () => {},
|
|
324
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
298
325
|
signal: controller.signal,
|
|
299
326
|
});
|
|
300
327
|
const elapsed = Date.now() - start;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for the durable-downscale path of the image-too-large
|
|
3
|
+
* recovery (JARVIS-1041 review follow-up).
|
|
4
|
+
*
|
|
5
|
+
* When an oversized stored image *can* be shrunk on this host (the common macOS
|
|
6
|
+
* path where `sips` is available), `persistUnsendableImageDowngrades` must write
|
|
7
|
+
* the downscaled bytes back to the DB — not leave the original in place. The
|
|
8
|
+
* latest tool-result media is intentionally kept in context, so leaving the
|
|
9
|
+
* full-size block would rehydrate and re-reject on every later turn instead of
|
|
10
|
+
* durably self-healing the conversation.
|
|
11
|
+
*
|
|
12
|
+
* `optimizeImageForTransport` needs `sips` and a decodable image to actually
|
|
13
|
+
* downscale, which is not portable to CI, so it is mocked here to simulate a
|
|
14
|
+
* successful shrink. The mock is process-global, so this case lives in its own
|
|
15
|
+
* file (the test runner isolates each file in its own process) to avoid
|
|
16
|
+
* disturbing the no-op-resize cases in persist-unsendable-image.test.ts.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
20
|
+
|
|
21
|
+
// ── Module mock (must precede the import of the module under test) ────
|
|
22
|
+
// Simulate a host where resizing succeeds: any oversized image is shrunk to a
|
|
23
|
+
// small, distinct JPEG payload. durableImageReplacement checks the provider
|
|
24
|
+
// caps before calling this, so in-limit images never reach the mock.
|
|
25
|
+
const SHRUNK_DATA = "c2hydW5r"; // base64 for "shrunk"
|
|
26
|
+
mock.module("../agent/image-optimize.js", () => ({
|
|
27
|
+
optimizeImageForTransport: () => ({
|
|
28
|
+
data: SHRUNK_DATA,
|
|
29
|
+
mediaType: "image/jpeg",
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
import { persistUnsendableImageDowngrades } from "../daemon/persist-unsendable-image.js";
|
|
34
|
+
import {
|
|
35
|
+
addMessage,
|
|
36
|
+
createConversation,
|
|
37
|
+
getMessages,
|
|
38
|
+
} from "../memory/conversation-crud.js";
|
|
39
|
+
import { getDb } from "../memory/db-connection.js";
|
|
40
|
+
import { initializeDb } from "../memory/db-init.js";
|
|
41
|
+
import type { ContentBlock } from "../providers/types.js";
|
|
42
|
+
|
|
43
|
+
initializeDb();
|
|
44
|
+
|
|
45
|
+
function resetTables(): void {
|
|
46
|
+
const db = getDb();
|
|
47
|
+
db.run("DELETE FROM message_attachments");
|
|
48
|
+
db.run("DELETE FROM attachments");
|
|
49
|
+
db.run("DELETE FROM memory_segments");
|
|
50
|
+
db.run("DELETE FROM memory_embeddings");
|
|
51
|
+
db.run("DELETE FROM messages");
|
|
52
|
+
db.run("DELETE FROM conversations");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Minimal PNG whose IHDR declares dimensions past the 8000px provider cap. */
|
|
56
|
+
function oversizedPngBase64(): string {
|
|
57
|
+
const width = 12000;
|
|
58
|
+
const height = 9000;
|
|
59
|
+
return Buffer.from(
|
|
60
|
+
Uint8Array.from([
|
|
61
|
+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, // PNG signature
|
|
62
|
+
0x00, 0x00, 0x00, 0x0d, // IHDR length (13)
|
|
63
|
+
0x49, 0x48, 0x44, 0x52, // "IHDR"
|
|
64
|
+
(width >>> 24) & 0xff,
|
|
65
|
+
(width >>> 16) & 0xff,
|
|
66
|
+
(width >>> 8) & 0xff,
|
|
67
|
+
width & 0xff,
|
|
68
|
+
(height >>> 24) & 0xff,
|
|
69
|
+
(height >>> 16) & 0xff,
|
|
70
|
+
(height >>> 8) & 0xff,
|
|
71
|
+
height & 0xff,
|
|
72
|
+
0x08, 0x06, 0x00, 0x00, 0x00,
|
|
73
|
+
]),
|
|
74
|
+
).toString("base64");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function toolResultWithImage(data: string): ContentBlock {
|
|
78
|
+
return {
|
|
79
|
+
type: "tool_result",
|
|
80
|
+
tool_use_id: "toolu_123",
|
|
81
|
+
content: "Screenshot captured",
|
|
82
|
+
contentBlocks: [
|
|
83
|
+
{ type: "image", source: { type: "base64", media_type: "image/png", data } },
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function storedContent(conversationId: string): ContentBlock[][] {
|
|
89
|
+
return getMessages(conversationId).map(
|
|
90
|
+
(row) => JSON.parse(row.content) as ContentBlock[],
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
describe("persistUnsendableImageDowngrades (downscalable host)", () => {
|
|
95
|
+
beforeEach(() => {
|
|
96
|
+
resetTables();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
/** JARVIS-1041: an oversized screenshot that CAN be shrunk must persist the
|
|
100
|
+
* downscaled bytes, not the note and not the original. */
|
|
101
|
+
test("persists the downscaled image for a shrinkable tool_result screenshot", async () => {
|
|
102
|
+
// GIVEN a tool_result holding an oversized but shrinkable screenshot
|
|
103
|
+
const conv = createConversation();
|
|
104
|
+
await addMessage(
|
|
105
|
+
conv.id,
|
|
106
|
+
"user",
|
|
107
|
+
JSON.stringify([toolResultWithImage(oversizedPngBase64())]),
|
|
108
|
+
{ skipIndexing: true },
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// WHEN the downgrade is persisted
|
|
112
|
+
const rewritten = persistUnsendableImageDowngrades(conv.id);
|
|
113
|
+
|
|
114
|
+
// THEN the nested block stays an image, rewritten to the downscaled payload
|
|
115
|
+
expect(rewritten).toBe(1);
|
|
116
|
+
const [content] = storedContent(conv.id);
|
|
117
|
+
const toolResult = content.find((b) => b.type === "tool_result") as {
|
|
118
|
+
contentBlocks?: ContentBlock[];
|
|
119
|
+
};
|
|
120
|
+
const nested = toolResult.contentBlocks?.[0];
|
|
121
|
+
expect(nested?.type).toBe("image");
|
|
122
|
+
expect((nested as Extract<ContentBlock, { type: "image" }>).source.data).toBe(
|
|
123
|
+
SHRUNK_DATA,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/** Re-running is a no-op: the downscaled payload is within limits. */
|
|
128
|
+
test("is idempotent after a downscale rewrite", async () => {
|
|
129
|
+
// GIVEN a conversation whose oversized screenshot was already downscaled
|
|
130
|
+
const conv = createConversation();
|
|
131
|
+
await addMessage(
|
|
132
|
+
conv.id,
|
|
133
|
+
"user",
|
|
134
|
+
JSON.stringify([toolResultWithImage(oversizedPngBase64())]),
|
|
135
|
+
{ skipIndexing: true },
|
|
136
|
+
);
|
|
137
|
+
expect(persistUnsendableImageDowngrades(conv.id)).toBe(1);
|
|
138
|
+
|
|
139
|
+
// WHEN the downgrade runs again
|
|
140
|
+
const secondRun = persistUnsendableImageDowngrades(conv.id);
|
|
141
|
+
|
|
142
|
+
// THEN nothing further is rewritten
|
|
143
|
+
expect(secondRun).toBe(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
oversizedImageReplacement,
|
|
19
|
+
persistUnsendableImageDowngrades,
|
|
20
|
+
} from "../daemon/persist-unsendable-image.js";
|
|
18
21
|
import {
|
|
19
22
|
addMessage,
|
|
20
23
|
createConversation,
|
|
@@ -87,6 +90,20 @@ function imageBlock(data: string): ContentBlock {
|
|
|
87
90
|
};
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
/**
|
|
94
|
+
* A tool_result carrying a nested image in its contentBlocks, mirroring what a
|
|
95
|
+
* browser screenshot produces. This is the JARVIS-1041 shape: the oversized
|
|
96
|
+
* image lives at tool_result.contentBlocks, never as a top-level block.
|
|
97
|
+
*/
|
|
98
|
+
function toolResultWithImage(data: string): ContentBlock {
|
|
99
|
+
return {
|
|
100
|
+
type: "tool_result",
|
|
101
|
+
tool_use_id: "toolu_123",
|
|
102
|
+
content: "Screenshot captured",
|
|
103
|
+
contentBlocks: [imageBlock(data)],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
90
107
|
function storedContent(conversationId: string): ContentBlock[][] {
|
|
91
108
|
return getMessages(conversationId).map(
|
|
92
109
|
(row) => JSON.parse(row.content) as ContentBlock[],
|
|
@@ -194,6 +211,61 @@ describe("persistUnsendableImageDowngrades", () => {
|
|
|
194
211
|
expect(content.some((b) => b.type === "image")).toBe(false);
|
|
195
212
|
});
|
|
196
213
|
|
|
214
|
+
/** JARVIS-1041: the oversized image is nested inside a tool_result (e.g. a
|
|
215
|
+
* browser screenshot), not a top-level block. The downgrade must descend
|
|
216
|
+
* into tool_result.contentBlocks and swap the nested image for a note, while
|
|
217
|
+
* keeping the tool_result itself intact so tool_use/tool_result pairing
|
|
218
|
+
* survives. */
|
|
219
|
+
test("downgrades an oversized image nested in tool_result.contentBlocks", async () => {
|
|
220
|
+
// GIVEN an assistant turn whose tool_result holds an oversized screenshot
|
|
221
|
+
const conv = createConversation();
|
|
222
|
+
await addMessage(
|
|
223
|
+
conv.id,
|
|
224
|
+
"user",
|
|
225
|
+
JSON.stringify([toolResultWithImage(makePngBase64(12000, 9000))]),
|
|
226
|
+
{ skipIndexing: true },
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
// WHEN the downgrade is persisted
|
|
230
|
+
const rewritten = persistUnsendableImageDowngrades(conv.id);
|
|
231
|
+
|
|
232
|
+
// THEN the message is rewritten with the nested image swapped for a note
|
|
233
|
+
expect(rewritten).toBe(1);
|
|
234
|
+
const [content] = storedContent(conv.id);
|
|
235
|
+
const toolResult = content.find((b) => b.type === "tool_result");
|
|
236
|
+
expect(toolResult).toBeDefined();
|
|
237
|
+
// AND the tool_result is preserved (pairing intact) with no image left
|
|
238
|
+
const nested = (toolResult as { contentBlocks?: ContentBlock[] })
|
|
239
|
+
.contentBlocks;
|
|
240
|
+
expect(nested?.some((b) => b.type === "image")).toBe(false);
|
|
241
|
+
expect(nested?.some((b) => b.type === "text")).toBe(true);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
/** A sendable nested screenshot is never disturbed. */
|
|
245
|
+
test("leaves a normally-sized tool_result image untouched", async () => {
|
|
246
|
+
// GIVEN a tool_result with an image well within provider limits
|
|
247
|
+
const conv = createConversation();
|
|
248
|
+
await addMessage(
|
|
249
|
+
conv.id,
|
|
250
|
+
"user",
|
|
251
|
+
JSON.stringify([toolResultWithImage(makePngBase64(1024, 768))]),
|
|
252
|
+
{ skipIndexing: true },
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
// WHEN the downgrade is persisted
|
|
256
|
+
const rewritten = persistUnsendableImageDowngrades(conv.id);
|
|
257
|
+
|
|
258
|
+
// THEN nothing is rewritten and the nested image remains
|
|
259
|
+
expect(rewritten).toBe(0);
|
|
260
|
+
const [content] = storedContent(conv.id);
|
|
261
|
+
const toolResult = content.find((b) => b.type === "tool_result") as {
|
|
262
|
+
contentBlocks?: ContentBlock[];
|
|
263
|
+
};
|
|
264
|
+
expect(toolResult.contentBlocks?.some((b) => b.type === "image")).toBe(
|
|
265
|
+
true,
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
197
269
|
/** Re-running after a rewrite is a safe no-op (no image blocks remain). */
|
|
198
270
|
test("is idempotent — a second run rewrites nothing", async () => {
|
|
199
271
|
// GIVEN a conversation whose oversized image has already been downgraded
|
|
@@ -213,3 +285,27 @@ describe("persistUnsendableImageDowngrades", () => {
|
|
|
213
285
|
expect(secondRun).toBe(0);
|
|
214
286
|
});
|
|
215
287
|
});
|
|
288
|
+
|
|
289
|
+
describe("oversizedImageReplacement", () => {
|
|
290
|
+
/** A still-sendable image must be left alone — never replaced with a note.
|
|
291
|
+
* This is the gate that keeps the in-memory recovery from discarding valid
|
|
292
|
+
* screenshots when only one image in the turn was actually oversized. */
|
|
293
|
+
test("returns null for an image within the provider caps", () => {
|
|
294
|
+
const sendable = imageBlock(makePngBase64(1024, 768)) as Extract<
|
|
295
|
+
ContentBlock,
|
|
296
|
+
{ type: "image" }
|
|
297
|
+
>;
|
|
298
|
+
expect(oversizedImageReplacement(sendable)).toBeNull();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
/** An image past the provider caps that cannot be shrunk on this host (fake
|
|
302
|
+
* PNG that sips cannot decode) collapses to the unsendable note. */
|
|
303
|
+
test("returns the unsendable note when an oversized image cannot be shrunk", () => {
|
|
304
|
+
const oversized = imageBlock(makePngBase64(12000, 9000)) as Extract<
|
|
305
|
+
ContentBlock,
|
|
306
|
+
{ type: "image" }
|
|
307
|
+
>;
|
|
308
|
+
const replacement = oversizedImageReplacement(oversized);
|
|
309
|
+
expect(replacement?.type).toBe("text");
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -370,3 +370,81 @@ describe("persistence-layer secret redaction", () => {
|
|
|
370
370
|
expect(textBlock?.text).toBe(text);
|
|
371
371
|
});
|
|
372
372
|
});
|
|
373
|
+
|
|
374
|
+
describe("thinking timing persistence", () => {
|
|
375
|
+
let state: EventHandlerState;
|
|
376
|
+
|
|
377
|
+
beforeEach(() => {
|
|
378
|
+
addMessageCalls.length = 0;
|
|
379
|
+
state = createEventHandlerState();
|
|
380
|
+
state.turnStartedAt = 1_700_000_000_000;
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
afterEach(() => {
|
|
384
|
+
addMessageCalls.length = 0;
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
function makeThinkingCompleteEvent(): Extract<
|
|
388
|
+
AgentEvent,
|
|
389
|
+
{ type: "message_complete" }
|
|
390
|
+
> {
|
|
391
|
+
return {
|
|
392
|
+
type: "message_complete",
|
|
393
|
+
message: {
|
|
394
|
+
role: "assistant",
|
|
395
|
+
content: [
|
|
396
|
+
{ type: "thinking", thinking: "let me reason", signature: "sig" },
|
|
397
|
+
{ type: "text", text: "the answer" },
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
test("stamps per-block thinking timing onto the persisted thinking block", async () => {
|
|
404
|
+
// GIVEN a turn whose row was reserved at llm_call_started
|
|
405
|
+
await handleLlmCallStarted(state, makeDeps());
|
|
406
|
+
|
|
407
|
+
// AND streaming then captured timing for one thinking block (startedAt when
|
|
408
|
+
// it opened, completedAt at its last reasoning delta). The reserve resets
|
|
409
|
+
// the accumulator, so deltas — and thus the timing — land after it.
|
|
410
|
+
state.currentThinkingTimestamps = [{ startedAt: 1000, completedAt: 1750 }];
|
|
411
|
+
|
|
412
|
+
// WHEN the message completes and content is persisted
|
|
413
|
+
await handleMessageComplete(state, makeDeps(), makeThinkingCompleteEvent());
|
|
414
|
+
|
|
415
|
+
// THEN the persisted thinking block carries the internal `_`-prefixed
|
|
416
|
+
// timing so a history reload can surface the duration + "Started at" hover
|
|
417
|
+
const persisted = lastPersisted("assistant");
|
|
418
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
419
|
+
type: string;
|
|
420
|
+
_startedAt?: number;
|
|
421
|
+
_completedAt?: number;
|
|
422
|
+
}>;
|
|
423
|
+
const thinkingBlock = blocks.find((b) => b.type === "thinking");
|
|
424
|
+
expect(thinkingBlock?._startedAt).toBe(1000);
|
|
425
|
+
expect(thinkingBlock?._completedAt).toBe(1750);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("persists no thinking timing when none was captured this turn", async () => {
|
|
429
|
+
// GIVEN a turn that produced a thinking block but captured no timing
|
|
430
|
+
// (thinking streaming disabled, so the timing list stays empty)
|
|
431
|
+
expect(state.currentThinkingTimestamps).toEqual([]);
|
|
432
|
+
|
|
433
|
+
// WHEN the message completes and content is persisted
|
|
434
|
+
await handleLlmCallStarted(state, makeDeps());
|
|
435
|
+
await handleMessageComplete(state, makeDeps(), makeThinkingCompleteEvent());
|
|
436
|
+
|
|
437
|
+
// THEN the thinking block is persisted without timing, so the client hides
|
|
438
|
+
// the duration exactly as a tool call with no timing
|
|
439
|
+
const persisted = lastPersisted("assistant");
|
|
440
|
+
const blocks = JSON.parse(persisted.content) as Array<{
|
|
441
|
+
type: string;
|
|
442
|
+
_startedAt?: number;
|
|
443
|
+
_completedAt?: number;
|
|
444
|
+
}>;
|
|
445
|
+
const thinkingBlock = blocks.find((b) => b.type === "thinking");
|
|
446
|
+
expect(thinkingBlock).toBeDefined();
|
|
447
|
+
expect(thinkingBlock?._startedAt).toBeUndefined();
|
|
448
|
+
expect(thinkingBlock?._completedAt).toBeUndefined();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
@@ -34,22 +34,19 @@ describe("buildShimSource", () => {
|
|
|
34
34
|
test("emits a globalThis trampoline + one binding per export", () => {
|
|
35
35
|
const source = buildShimSource(
|
|
36
36
|
["foo", "bar"],
|
|
37
|
-
Symbol.for("vellum.plugin-api
|
|
37
|
+
Symbol.for("vellum.plugin-api"),
|
|
38
38
|
);
|
|
39
39
|
expect(source).toBe(
|
|
40
|
-
`const api = globalThis[Symbol.for("vellum.plugin-api
|
|
40
|
+
`const api = globalThis[Symbol.for("vellum.plugin-api")];\n` +
|
|
41
41
|
`export const foo = api.foo;\n` +
|
|
42
42
|
`export const bar = api.bar;\n`,
|
|
43
43
|
);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
test("handles an empty export list (today's types-only surface)", () => {
|
|
47
|
-
const source = buildShimSource(
|
|
48
|
-
[],
|
|
49
|
-
Symbol.for("vellum.plugin-api.v1"),
|
|
50
|
-
);
|
|
47
|
+
const source = buildShimSource([], Symbol.for("vellum.plugin-api"));
|
|
51
48
|
expect(source).toBe(
|
|
52
|
-
`const api = globalThis[Symbol.for("vellum.plugin-api
|
|
49
|
+
`const api = globalThis[Symbol.for("vellum.plugin-api")];\n`,
|
|
53
50
|
);
|
|
54
51
|
});
|
|
55
52
|
});
|
|
@@ -109,7 +106,7 @@ describe("ensurePluginApiShim", () => {
|
|
|
109
106
|
const pluginDir = join(workspaceDir, "plugins", "fake-plugin");
|
|
110
107
|
await mkdir(pluginDir, { recursive: true });
|
|
111
108
|
await writeFile(
|
|
112
|
-
join(pluginDir, "
|
|
109
|
+
join(pluginDir, "probe.js"),
|
|
113
110
|
`import * as api from "@vellumai/plugin-api";\nexport { api };\n`,
|
|
114
111
|
);
|
|
115
112
|
|
|
@@ -118,7 +115,7 @@ describe("ensurePluginApiShim", () => {
|
|
|
118
115
|
// → plugin-api namespace. If any link in that chain is broken, this
|
|
119
116
|
// import throws.
|
|
120
117
|
const mod: { api: Record<string, unknown> } = await import(
|
|
121
|
-
join(pluginDir, "
|
|
118
|
+
join(pluginDir, "probe.js")
|
|
122
119
|
);
|
|
123
120
|
expect(mod.api).toBeDefined();
|
|
124
121
|
});
|