@vellumai/assistant 0.8.8 → 0.8.9-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +2 -2
- 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 +236 -6
- package/package.json +2 -2
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
- package/src/__tests__/agent-loop-exit-reason.test.ts +185 -140
- 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 +1 -1
- package/src/__tests__/app-control-flow.test.ts +1 -1
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +5 -4
- package/src/__tests__/approval-routes-http.test.ts +4 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +1 -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 +132 -0
- package/src/__tests__/compaction-events.test.ts +5 -5
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +59 -103
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +94 -189
- package/src/__tests__/conversation-agent-loop.test.ts +249 -490
- package/src/__tests__/conversation-clean-command.test.ts +5 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
- package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
- package/src/__tests__/conversation-process-callsite.test.ts +14 -14
- package/src/__tests__/conversation-provider-retry-repair.test.ts +18 -16
- package/src/__tests__/conversation-queue.test.ts +9 -9
- package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
- package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
- package/src/__tests__/conversation-slash-queue.test.ts +6 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
- package/src/__tests__/conversation-speed-override.test.ts +10 -9
- package/src/__tests__/conversation-starter-routes.test.ts +14 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
- package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
- package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
- package/src/__tests__/credential-security-invariants.test.ts +0 -1
- package/src/__tests__/db-acp-history.test.ts +101 -0
- package/src/__tests__/dynamic-page-surface.test.ts +31 -0
- 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 +12 -2
- package/src/__tests__/guardian-grant-minting.test.ts +1 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +2 -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 +1 -1
- 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__/memory-retrieval-hook.test.ts +73 -6
- package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
- package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
- package/src/__tests__/plugin-api-shim.test.ts +6 -9
- package/src/__tests__/plugin-bootstrap.test.ts +12 -23
- package/src/__tests__/plugin-registry.test.ts +3 -49
- package/src/__tests__/plugin-types.test.ts +0 -70
- package/src/__tests__/reaction-persistence.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +33 -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 +1 -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__/user-plugin-loader.test.ts +54 -286
- 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-history-db.ts +82 -0
- package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
- package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
- package/src/acp/agent-process.ts +61 -1
- package/src/acp/auto-install.test.ts +196 -0
- package/src/acp/auto-install.ts +177 -0
- package/src/acp/client-handler.ts +31 -0
- package/src/acp/feature-gate.test.ts +48 -0
- package/src/acp/feature-gate.ts +34 -0
- package/src/acp/prepare-agent-env.ts +83 -29
- package/src/acp/resolve-agent.test.ts +320 -7
- package/src/acp/resolve-agent.ts +182 -18
- package/src/acp/resume-hint.ts +25 -0
- package/src/acp/session-manager.ts +495 -73
- package/src/acp/types.ts +8 -0
- package/src/agent/compaction-circuit.ts +60 -102
- package/src/agent/loop.ts +390 -240
- package/src/api/responses/conversation-message.ts +14 -1
- package/src/approvals/guardian-request-resolvers.ts +1 -1
- package/src/background-wake/next-wake.ts +1 -0
- package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
- package/src/cli/commands/plugins.ts +43 -37
- package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
- package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
- package/src/cli/lib/install-from-github.ts +464 -55
- package/src/cli/lib/plugin-catalog-cache.ts +84 -0
- package/src/cli/lib/plugin-details.ts +409 -0
- package/src/cli/lib/plugin-marketplace.ts +197 -0
- package/src/cli/lib/search-plugins.ts +195 -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 +6 -0
- package/src/config/bundled-skills/acp/SKILL.md +83 -31
- 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 +29 -0
- 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/document-editor/SKILL.md +28 -23
- package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
- package/src/config/bundled-tool-registry.ts +2 -0
- package/src/config/feature-flag-registry.json +14 -5
- package/src/config/schemas/heartbeat.ts +9 -0
- package/src/context/strip-injections.ts +8 -2
- package/src/context/window-manager.ts +27 -13
- package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
- package/src/daemon/conversation-agent-loop.ts +167 -1005
- package/src/daemon/conversation-lifecycle.ts +11 -255
- package/src/daemon/conversation-process.ts +8 -136
- package/src/daemon/conversation-registry.ts +159 -0
- package/src/daemon/conversation-runtime-assembly.ts +293 -392
- package/src/daemon/conversation-store.ts +9 -90
- package/src/daemon/conversation-surfaces.ts +24 -8
- package/src/daemon/conversation-workspace.ts +17 -0
- package/src/daemon/conversation.ts +383 -56
- package/src/daemon/external-plugins-bootstrap.ts +14 -19
- package/src/daemon/handlers/conversations.ts +3 -1
- package/src/daemon/handlers/skills.ts +4 -1
- package/src/daemon/host-proxy-preactivation.ts +1 -3
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/server.ts +2 -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__/jobs-worker-v2-schedule.test.ts +87 -0
- package/src/memory/conversation-crud.ts +29 -19
- package/src/memory/conversation-starter-checkpoints.ts +1 -0
- package/src/memory/db-init.ts +2 -0
- 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/index.ts +1 -0
- package/src/memory/schema/acp.ts +4 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
- package/src/memory/v2/consolidation-job.ts +13 -4
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +6 -5
- package/src/plugin-api/types.ts +75 -0
- package/src/plugins/defaults/compaction/compact.ts +59 -0
- package/src/plugins/defaults/compaction/manager-store.ts +44 -0
- package/src/plugins/defaults/compaction/package.json +1 -2
- 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 +135 -26
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +103 -38
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +167 -56
- 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/unified-turn-context.ts +223 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
- 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 +75 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
- package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
- package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -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/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/proactive-artifact/aux-message-injector.ts +1 -1
- package/src/proactive-artifact/job.test.ts +1 -1
- package/src/prompts/__tests__/system-prompt.test.ts +6 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
- 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 +283 -55
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +398 -73
- 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 +89 -25
- package/src/runtime/routes/acp-routes.ts +81 -29
- 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/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 +28 -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/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 +16 -6
- package/src/runtime/routes/playground/helpers.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +336 -35
- package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
- package/src/runtime/routes/work-items-routes.ts +2 -4
- package/src/runtime/services/conversation-serializer.ts +1 -1
- package/src/signals/cancel.ts +2 -4
- package/src/subagent/manager.ts +21 -5
- package/src/tools/acp/context.ts +20 -0
- package/src/tools/acp/list-agents.test.ts +7 -1
- package/src/tools/acp/spawn.test.ts +158 -55
- package/src/tools/acp/spawn.ts +47 -72
- package/src/tools/acp/steer.test.ts +105 -8
- package/src/tools/acp/steer.ts +48 -17
- package/src/tools/apps/executors.ts +13 -8
- package/src/tools/filesystem/write.ts +34 -0
- package/src/tools/subagent/spawn.ts +2 -4
- package/src/tools/ui-surface/definitions.ts +25 -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/__tests__/plugin-external-api.test.ts +0 -68
- package/src/daemon/wake-target-adapter.ts +0 -253
- 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/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/plugins/external-api.ts +0 -104
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
- /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__/health.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +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}/core.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}/health.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
- /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `pre-model-call` and `assistant-message` plugin hooks: a plugin
|
|
3
|
+
* can edit the outbound request, transform the finalized assistant message
|
|
4
|
+
* (persisted + streamed), and defer the live stream so the transformed text is
|
|
5
|
+
* emitted once. All hooks here use neutral transforms (redaction / uppercasing).
|
|
6
|
+
*/
|
|
7
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
import type { AgentEvent } from "../agent/loop.js";
|
|
10
|
+
import { AgentLoop } from "../agent/loop.js";
|
|
11
|
+
import type {
|
|
12
|
+
AssistantMessageContext,
|
|
13
|
+
PreModelCallContext,
|
|
14
|
+
} from "../plugin-api/types.js";
|
|
15
|
+
import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
|
|
16
|
+
import { registerPlugin } from "../plugins/registry.js";
|
|
17
|
+
import type {
|
|
18
|
+
ContentBlock,
|
|
19
|
+
Message,
|
|
20
|
+
ProviderResponse,
|
|
21
|
+
} from "../providers/types.js";
|
|
22
|
+
import { createMockProvider, textResponse } from "./helpers/mock-provider.js";
|
|
23
|
+
|
|
24
|
+
const userMessage: Message = {
|
|
25
|
+
role: "user",
|
|
26
|
+
content: [{ type: "text", text: "Hello" }],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function collect(events: AgentEvent[]): (event: AgentEvent) => void {
|
|
30
|
+
return (event) => events.push(event);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function streamedText(events: AgentEvent[]): string {
|
|
34
|
+
return events
|
|
35
|
+
.filter(
|
|
36
|
+
(e): e is Extract<AgentEvent, { type: "text_delta" }> =>
|
|
37
|
+
e.type === "text_delta",
|
|
38
|
+
)
|
|
39
|
+
.map((e) => e.text)
|
|
40
|
+
.join("");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function textOf(content: ReadonlyArray<ContentBlock>): string {
|
|
44
|
+
let out = "";
|
|
45
|
+
for (const block of content) if (block.type === "text") out += block.text;
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function lastAssistant(history: Message[]): Message {
|
|
50
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
51
|
+
if (history[i].role === "assistant") return history[i];
|
|
52
|
+
}
|
|
53
|
+
throw new Error("no assistant message in history");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function registerOutputHookPlugin(hooks: {
|
|
57
|
+
preModelCall?: (ctx: PreModelCallContext) => void;
|
|
58
|
+
assistantMessage?: (ctx: AssistantMessageContext) => void;
|
|
59
|
+
}): void {
|
|
60
|
+
registerPlugin({
|
|
61
|
+
manifest: { name: "test-output-hooks", version: "0.0.0" },
|
|
62
|
+
hooks: {
|
|
63
|
+
...(hooks.preModelCall
|
|
64
|
+
? {
|
|
65
|
+
"pre-model-call": async (ctx: PreModelCallContext) => {
|
|
66
|
+
hooks.preModelCall!(ctx);
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
: {}),
|
|
70
|
+
...(hooks.assistantMessage
|
|
71
|
+
? {
|
|
72
|
+
"assistant-message": async (ctx: AssistantMessageContext) => {
|
|
73
|
+
hooks.assistantMessage!(ctx);
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
: {}),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe("agent loop output hooks", () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
resetPluginRegistryAndRegisterDefaults();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("assistant-message transforms the persisted message content", async () => {
|
|
87
|
+
registerOutputHookPlugin({
|
|
88
|
+
assistantMessage: (ctx) => {
|
|
89
|
+
ctx.content = ctx.content.map((b) =>
|
|
90
|
+
b.type === "text"
|
|
91
|
+
? { type: "text", text: b.text.replace("secret", "[redacted]") }
|
|
92
|
+
: b,
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
const { provider } = createMockProvider([textResponse("my secret value")]);
|
|
97
|
+
const loop = new AgentLoop({
|
|
98
|
+
provider: provider,
|
|
99
|
+
systemPrompt: "system",
|
|
100
|
+
conversationId: "test-conversation",
|
|
101
|
+
});
|
|
102
|
+
const events: AgentEvent[] = [];
|
|
103
|
+
const { history } = await loop.run({
|
|
104
|
+
messages: [userMessage],
|
|
105
|
+
onEvent: collect(events),
|
|
106
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
107
|
+
});
|
|
108
|
+
expect(textOf(lastAssistant(history).content)).toBe("my [redacted] value");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("deferred output: the real stream is suppressed and the transformed text is emitted once", async () => {
|
|
112
|
+
registerOutputHookPlugin({
|
|
113
|
+
preModelCall: (ctx) => {
|
|
114
|
+
ctx.deferAssistantOutput = true;
|
|
115
|
+
},
|
|
116
|
+
assistantMessage: (ctx) => {
|
|
117
|
+
ctx.content = [{ type: "text", text: "[filtered]" }];
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
const { provider } = createMockProvider([textResponse("my secret value")]);
|
|
121
|
+
const loop = new AgentLoop({
|
|
122
|
+
provider: provider,
|
|
123
|
+
systemPrompt: "system",
|
|
124
|
+
conversationId: "test-conversation",
|
|
125
|
+
});
|
|
126
|
+
const events: AgentEvent[] = [];
|
|
127
|
+
const { history } = await loop.run({
|
|
128
|
+
messages: [userMessage],
|
|
129
|
+
onEvent: collect(events),
|
|
130
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
131
|
+
});
|
|
132
|
+
// Her real words never streamed; only the transformed text did, once.
|
|
133
|
+
expect(streamedText(events)).not.toContain("secret");
|
|
134
|
+
expect(streamedText(events)).toBe("[filtered]");
|
|
135
|
+
expect(textOf(lastAssistant(history).content)).toBe("[filtered]");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("without defer, the real text still streams while storage is transformed", async () => {
|
|
139
|
+
registerOutputHookPlugin({
|
|
140
|
+
assistantMessage: (ctx) => {
|
|
141
|
+
ctx.content = [{ type: "text", text: "[stored]" }];
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
const { provider } = createMockProvider([textResponse("live text")]);
|
|
145
|
+
const loop = new AgentLoop({
|
|
146
|
+
provider: provider,
|
|
147
|
+
systemPrompt: "system",
|
|
148
|
+
conversationId: "test-conversation",
|
|
149
|
+
});
|
|
150
|
+
const events: AgentEvent[] = [];
|
|
151
|
+
const { history } = await loop.run({
|
|
152
|
+
messages: [userMessage],
|
|
153
|
+
onEvent: collect(events),
|
|
154
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
155
|
+
});
|
|
156
|
+
expect(streamedText(events)).toBe("live text"); // streamed live, untransformed
|
|
157
|
+
expect(textOf(lastAssistant(history).content)).toBe("[stored]"); // storage transformed
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("transforms text but leaves tool_use blocks intact", async () => {
|
|
161
|
+
const toolAndText: ProviderResponse = {
|
|
162
|
+
content: [
|
|
163
|
+
{ type: "text", text: "calling" },
|
|
164
|
+
{ type: "tool_use", id: "t1", name: "noop", input: {} },
|
|
165
|
+
],
|
|
166
|
+
model: "mock-model",
|
|
167
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
168
|
+
stopReason: "tool_use",
|
|
169
|
+
};
|
|
170
|
+
registerOutputHookPlugin({
|
|
171
|
+
assistantMessage: (ctx) => {
|
|
172
|
+
ctx.content = ctx.content.map((b) =>
|
|
173
|
+
b.type === "text" ? { type: "text", text: b.text.toUpperCase() } : b,
|
|
174
|
+
);
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
const { provider } = createMockProvider([
|
|
178
|
+
toolAndText,
|
|
179
|
+
textResponse("done"),
|
|
180
|
+
]);
|
|
181
|
+
const loop = new AgentLoop({
|
|
182
|
+
provider: provider,
|
|
183
|
+
systemPrompt: "system",
|
|
184
|
+
conversationId: "test-conversation",
|
|
185
|
+
tools: [
|
|
186
|
+
{
|
|
187
|
+
name: "noop",
|
|
188
|
+
description: "",
|
|
189
|
+
input_schema: { type: "object", properties: {} },
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
toolExecutor: async () => ({ content: "ok", isError: false }),
|
|
193
|
+
});
|
|
194
|
+
const { history } = await loop.run({
|
|
195
|
+
messages: [userMessage],
|
|
196
|
+
onEvent: collect([]),
|
|
197
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
198
|
+
});
|
|
199
|
+
const toolTurn = history.find(
|
|
200
|
+
(m) =>
|
|
201
|
+
m.role === "assistant" && m.content.some((b) => b.type === "tool_use"),
|
|
202
|
+
)!;
|
|
203
|
+
expect(toolTurn.content.find((b) => b.type === "text")).toEqual({
|
|
204
|
+
type: "text",
|
|
205
|
+
text: "CALLING",
|
|
206
|
+
});
|
|
207
|
+
expect(toolTurn.content.find((b) => b.type === "tool_use")).toMatchObject({
|
|
208
|
+
type: "tool_use",
|
|
209
|
+
id: "t1",
|
|
210
|
+
name: "noop",
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("pre-model-call can edit the outbound system prompt", async () => {
|
|
215
|
+
registerOutputHookPlugin({
|
|
216
|
+
preModelCall: (ctx) => {
|
|
217
|
+
ctx.systemPrompt = `${ctx.systemPrompt ?? ""} [EDITED]`;
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
const { provider, calls } = createMockProvider([textResponse("hi")]);
|
|
221
|
+
const loop = new AgentLoop({
|
|
222
|
+
provider: provider,
|
|
223
|
+
systemPrompt: "base prompt",
|
|
224
|
+
conversationId: "test-conversation",
|
|
225
|
+
});
|
|
226
|
+
await loop.run({
|
|
227
|
+
messages: [userMessage],
|
|
228
|
+
onEvent: collect([]),
|
|
229
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
230
|
+
});
|
|
231
|
+
expect(calls[0].systemPrompt).toContain("[EDITED]");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("fail-open: a hook that mutates in place AND then throws cannot corrupt the persisted content", async () => {
|
|
235
|
+
// The hook mutates the array it receives before throwing. If the loop
|
|
236
|
+
// handed the hook the real `assistantMessage.content` array, the
|
|
237
|
+
// mid-mutation would survive into history. The loop must clone the
|
|
238
|
+
// content before invoking the hook.
|
|
239
|
+
registerOutputHookPlugin({
|
|
240
|
+
assistantMessage: (ctx) => {
|
|
241
|
+
ctx.content.push({ type: "text", text: "[INJECTED]" });
|
|
242
|
+
throw new Error("boom");
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
const { provider } = createMockProvider([textResponse("untouched")]);
|
|
246
|
+
const loop = new AgentLoop({
|
|
247
|
+
provider: provider,
|
|
248
|
+
systemPrompt: "system",
|
|
249
|
+
conversationId: "test-conversation",
|
|
250
|
+
});
|
|
251
|
+
const { history } = await loop.run({
|
|
252
|
+
messages: [userMessage],
|
|
253
|
+
onEvent: collect([]),
|
|
254
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
255
|
+
});
|
|
256
|
+
const finalContent = lastAssistant(history).content;
|
|
257
|
+
expect(textOf(finalContent)).toBe("untouched");
|
|
258
|
+
expect(
|
|
259
|
+
finalContent.some((b) => b.type === "text" && b.text === "[INJECTED]"),
|
|
260
|
+
).toBe(false);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("max_tokens turn: the hook fires and the deferred final text is emitted", async () => {
|
|
264
|
+
// Without this, an output-filter plugin misses the truncated reply; with
|
|
265
|
+
// defer set, the live stream was suppressed and the client would see
|
|
266
|
+
// nothing at all.
|
|
267
|
+
const seen: { calls: number } = { calls: 0 };
|
|
268
|
+
registerOutputHookPlugin({
|
|
269
|
+
preModelCall: (ctx) => {
|
|
270
|
+
ctx.deferAssistantOutput = true;
|
|
271
|
+
},
|
|
272
|
+
assistantMessage: (ctx) => {
|
|
273
|
+
seen.calls += 1;
|
|
274
|
+
ctx.content = ctx.content.map((b) =>
|
|
275
|
+
b.type === "text" ? { type: "text", text: b.text.toUpperCase() } : b,
|
|
276
|
+
);
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
const truncated: ProviderResponse = {
|
|
280
|
+
content: [{ type: "text", text: "partial answer" }],
|
|
281
|
+
model: "mock-model",
|
|
282
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
283
|
+
stopReason: "max_tokens",
|
|
284
|
+
};
|
|
285
|
+
const { provider } = createMockProvider([truncated]);
|
|
286
|
+
const loop = new AgentLoop({
|
|
287
|
+
provider: provider,
|
|
288
|
+
systemPrompt: "system",
|
|
289
|
+
conversationId: "test-conversation",
|
|
290
|
+
});
|
|
291
|
+
const events: AgentEvent[] = [];
|
|
292
|
+
const { history } = await loop.run({
|
|
293
|
+
messages: [userMessage],
|
|
294
|
+
onEvent: collect(events),
|
|
295
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
296
|
+
});
|
|
297
|
+
expect(seen.calls).toBe(1);
|
|
298
|
+
expect(textOf(lastAssistant(history).content)).toBe("PARTIAL ANSWER");
|
|
299
|
+
// Real stream suppressed; transformed final text emitted once.
|
|
300
|
+
expect(streamedText(events)).toBe("PARTIAL ANSWER");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("deferred final text passes through sensitive-output substitution", async () => {
|
|
304
|
+
// A tool returns a sensitive binding; the next assistant turn uses the
|
|
305
|
+
// placeholder (the persisted message must keep it — model never sees real
|
|
306
|
+
// values on reload), but the deferred final stream must show the real
|
|
307
|
+
// value, just like the normal live stream would.
|
|
308
|
+
registerOutputHookPlugin({
|
|
309
|
+
preModelCall: (ctx) => {
|
|
310
|
+
ctx.deferAssistantOutput = true;
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
const placeholder = "VELLUM_ASSISTANT_INVITE_CODE_TESTXXXX";
|
|
314
|
+
const real = "real-invite-code-xyz";
|
|
315
|
+
const toolThenText = [
|
|
316
|
+
{
|
|
317
|
+
content: [{ type: "tool_use", id: "t1", name: "issue", input: {} }],
|
|
318
|
+
model: "mock-model",
|
|
319
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
320
|
+
stopReason: "tool_use",
|
|
321
|
+
} as ProviderResponse,
|
|
322
|
+
textResponse(`Your code is ${placeholder}.`),
|
|
323
|
+
];
|
|
324
|
+
const { provider } = createMockProvider(toolThenText);
|
|
325
|
+
const loop = new AgentLoop({
|
|
326
|
+
provider: provider,
|
|
327
|
+
systemPrompt: "system",
|
|
328
|
+
conversationId: "test-conversation",
|
|
329
|
+
tools: [
|
|
330
|
+
{
|
|
331
|
+
name: "issue",
|
|
332
|
+
description: "",
|
|
333
|
+
input_schema: { type: "object", properties: {} },
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
toolExecutor: async () => ({
|
|
337
|
+
content: `code=${placeholder}`,
|
|
338
|
+
isError: false,
|
|
339
|
+
sensitiveBindings: [{ kind: "invite_code", placeholder, value: real }],
|
|
340
|
+
}),
|
|
341
|
+
});
|
|
342
|
+
const events: AgentEvent[] = [];
|
|
343
|
+
const { history } = await loop.run({
|
|
344
|
+
messages: [userMessage],
|
|
345
|
+
onEvent: collect(events),
|
|
346
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
347
|
+
});
|
|
348
|
+
// Persisted message keeps the placeholder — model must never see real
|
|
349
|
+
// values on reload.
|
|
350
|
+
expect(textOf(lastAssistant(history).content)).toContain(placeholder);
|
|
351
|
+
expect(textOf(lastAssistant(history).content)).not.toContain(real);
|
|
352
|
+
// Streamed final text shows the substituted real value, matching the
|
|
353
|
+
// behavior of the normal live stream.
|
|
354
|
+
expect(streamedText(events)).toContain(real);
|
|
355
|
+
expect(streamedText(events)).not.toContain(placeholder);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
@@ -110,13 +110,19 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
|
|
|
110
110
|
_input: Record<string, unknown>,
|
|
111
111
|
) => ({ content: "ok", isError: false });
|
|
112
112
|
|
|
113
|
-
const loop = new AgentLoop(
|
|
113
|
+
const loop = new AgentLoop({
|
|
114
|
+
provider: provider,
|
|
115
|
+
systemPrompt: "system",
|
|
116
|
+
conversationId: "test-conversation",
|
|
114
117
|
config: { maxTokens: 1024 },
|
|
115
118
|
tools: dummyTools,
|
|
116
119
|
toolExecutor: toolExecutor,
|
|
117
120
|
});
|
|
118
121
|
|
|
119
|
-
await loop.run(
|
|
122
|
+
await loop.run({
|
|
123
|
+
messages: [userMessage],
|
|
124
|
+
onEvent: () => {},
|
|
125
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
120
126
|
callSite: "mainAgent",
|
|
121
127
|
overrideProfile: "fast",
|
|
122
128
|
});
|
|
@@ -130,11 +136,18 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
|
|
|
130
136
|
|
|
131
137
|
test("omits overrideProfile from providerConfig when unset (default behavior unchanged)", async () => {
|
|
132
138
|
const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
|
|
133
|
-
const loop = new AgentLoop(
|
|
139
|
+
const loop = new AgentLoop({
|
|
140
|
+
provider: provider,
|
|
141
|
+
systemPrompt: "system",
|
|
142
|
+
conversationId: "test-conversation",
|
|
134
143
|
config: { maxTokens: 1024 },
|
|
135
144
|
});
|
|
136
145
|
|
|
137
|
-
await loop.run(
|
|
146
|
+
await loop.run({
|
|
147
|
+
messages: [userMessage],
|
|
148
|
+
onEvent: () => {},
|
|
149
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
150
|
+
});
|
|
138
151
|
|
|
139
152
|
// Single send, no overrideProfile field at all.
|
|
140
153
|
expect(configs()).toHaveLength(1);
|
|
@@ -148,11 +161,17 @@ describe("AgentLoop.run — overrideProfile plumbing", () => {
|
|
|
148
161
|
// receives so a non-existent profile silently falls back at the
|
|
149
162
|
// provider layer (covered by provider-send-message-override-profile.test.ts).
|
|
150
163
|
const { provider, configs } = makeRecordingProvider([textResponse("hi")]);
|
|
151
|
-
const loop = new AgentLoop(
|
|
164
|
+
const loop = new AgentLoop({
|
|
165
|
+
provider: provider,
|
|
166
|
+
systemPrompt: "system",
|
|
167
|
+
conversationId: "test-conversation",
|
|
152
168
|
config: { maxTokens: 1024 },
|
|
153
169
|
});
|
|
154
170
|
|
|
155
|
-
await loop.run(
|
|
171
|
+
await loop.run({
|
|
172
|
+
messages: [userMessage],
|
|
173
|
+
onEvent: () => {},
|
|
174
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
156
175
|
callSite: "mainAgent",
|
|
157
176
|
overrideProfile: "does-not-exist",
|
|
158
177
|
});
|
|
@@ -83,14 +83,19 @@ describe("AgentLoop provider_error event emission", () => {
|
|
|
83
83
|
const { provider, calls } = makeThrowingProvider("anthropic", () => thrown);
|
|
84
84
|
|
|
85
85
|
const events: AgentEvent[] = [];
|
|
86
|
-
const loop = new AgentLoop(
|
|
86
|
+
const loop = new AgentLoop({
|
|
87
|
+
provider: provider,
|
|
88
|
+
systemPrompt: "you are a helpful assistant",
|
|
89
|
+
conversationId: "test-conversation",
|
|
90
|
+
});
|
|
87
91
|
|
|
88
|
-
await loop.run(
|
|
89
|
-
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
90
|
-
(e) => {
|
|
92
|
+
await loop.run({
|
|
93
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
94
|
+
onEvent: (e) => {
|
|
91
95
|
events.push(e);
|
|
92
96
|
},
|
|
93
|
-
|
|
97
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
98
|
+
});
|
|
94
99
|
|
|
95
100
|
expect(calls).toHaveLength(1);
|
|
96
101
|
|
|
@@ -122,14 +127,19 @@ describe("AgentLoop provider_error event emission", () => {
|
|
|
122
127
|
const { provider } = makeThrowingProvider("gemini", () => thrown);
|
|
123
128
|
|
|
124
129
|
const events: AgentEvent[] = [];
|
|
125
|
-
const loop = new AgentLoop(
|
|
130
|
+
const loop = new AgentLoop({
|
|
131
|
+
provider: provider,
|
|
132
|
+
systemPrompt: "system",
|
|
133
|
+
conversationId: "test-conversation",
|
|
134
|
+
});
|
|
126
135
|
|
|
127
|
-
await loop.run(
|
|
128
|
-
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
129
|
-
(e) => {
|
|
136
|
+
await loop.run({
|
|
137
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
138
|
+
onEvent: (e) => {
|
|
130
139
|
events.push(e);
|
|
131
140
|
},
|
|
132
|
-
|
|
141
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
142
|
+
});
|
|
133
143
|
|
|
134
144
|
const providerErrorIdx = events.findIndex(
|
|
135
145
|
(e) => e.type === "provider_error",
|
|
@@ -148,14 +158,19 @@ describe("AgentLoop provider_error event emission", () => {
|
|
|
148
158
|
const { provider } = makeThrowingProvider("openai", () => thrown);
|
|
149
159
|
|
|
150
160
|
const events: AgentEvent[] = [];
|
|
151
|
-
const loop = new AgentLoop(
|
|
161
|
+
const loop = new AgentLoop({
|
|
162
|
+
provider: provider,
|
|
163
|
+
systemPrompt: "system",
|
|
164
|
+
conversationId: "test-conversation",
|
|
165
|
+
});
|
|
152
166
|
|
|
153
|
-
await loop.run(
|
|
154
|
-
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
155
|
-
(e) => {
|
|
167
|
+
await loop.run({
|
|
168
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
169
|
+
onEvent: (e) => {
|
|
156
170
|
events.push(e);
|
|
157
171
|
},
|
|
158
|
-
|
|
172
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
173
|
+
});
|
|
159
174
|
|
|
160
175
|
const providerErrorEvent = events.find((e) => e.type === "provider_error");
|
|
161
176
|
expect(providerErrorEvent).toBeDefined();
|
|
@@ -179,15 +194,20 @@ describe("AgentLoop provider_error event emission", () => {
|
|
|
179
194
|
});
|
|
180
195
|
|
|
181
196
|
const events: AgentEvent[] = [];
|
|
182
|
-
const loop = new AgentLoop(
|
|
197
|
+
const loop = new AgentLoop({
|
|
198
|
+
provider: provider,
|
|
199
|
+
systemPrompt: "system",
|
|
200
|
+
conversationId: "test-conversation",
|
|
201
|
+
});
|
|
183
202
|
|
|
184
|
-
await loop.run(
|
|
185
|
-
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
186
|
-
(e) => {
|
|
203
|
+
await loop.run({
|
|
204
|
+
messages: [{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
205
|
+
onEvent: (e) => {
|
|
187
206
|
events.push(e);
|
|
188
207
|
},
|
|
189
|
-
{
|
|
190
|
-
|
|
208
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
209
|
+
signal: controller.signal,
|
|
210
|
+
});
|
|
191
211
|
|
|
192
212
|
const providerErrorEvent = events.find((e) => e.type === "provider_error");
|
|
193
213
|
// Cancellation should never produce a recording row — there's no
|
|
@@ -37,17 +37,21 @@ function createMockProvider(): {
|
|
|
37
37
|
describe("AgentLoop thinking and effort", () => {
|
|
38
38
|
test("sends adaptive thinking when thinking is enabled", async () => {
|
|
39
39
|
const { provider, lastConfig } = createMockProvider();
|
|
40
|
-
const loop = new AgentLoop(
|
|
40
|
+
const loop = new AgentLoop({
|
|
41
|
+
provider: provider,
|
|
42
|
+
systemPrompt: "test",
|
|
43
|
+
conversationId: "test-conversation",
|
|
41
44
|
config: {
|
|
42
45
|
maxTokens: 64000,
|
|
43
46
|
thinking: { enabled: true },
|
|
44
47
|
},
|
|
45
48
|
});
|
|
46
49
|
|
|
47
|
-
await loop.run(
|
|
48
|
-
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
49
|
-
() => {},
|
|
50
|
-
|
|
50
|
+
await loop.run({
|
|
51
|
+
messages: [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
52
|
+
onEvent: () => {},
|
|
53
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
54
|
+
});
|
|
51
55
|
|
|
52
56
|
const config = lastConfig()!;
|
|
53
57
|
const thinking = config.thinking as { type: string };
|
|
@@ -56,17 +60,21 @@ describe("AgentLoop thinking and effort", () => {
|
|
|
56
60
|
|
|
57
61
|
test("sends disabled thinking when thinking is disabled", async () => {
|
|
58
62
|
const { provider, lastConfig } = createMockProvider();
|
|
59
|
-
const loop = new AgentLoop(
|
|
63
|
+
const loop = new AgentLoop({
|
|
64
|
+
provider: provider,
|
|
65
|
+
systemPrompt: "test",
|
|
66
|
+
conversationId: "test-conversation",
|
|
60
67
|
config: {
|
|
61
68
|
maxTokens: 64000,
|
|
62
69
|
thinking: { enabled: false },
|
|
63
70
|
},
|
|
64
71
|
});
|
|
65
72
|
|
|
66
|
-
await loop.run(
|
|
67
|
-
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
68
|
-
() => {},
|
|
69
|
-
|
|
73
|
+
await loop.run({
|
|
74
|
+
messages: [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
75
|
+
onEvent: () => {},
|
|
76
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
77
|
+
});
|
|
70
78
|
|
|
71
79
|
const config = lastConfig()!;
|
|
72
80
|
expect(config.thinking).toEqual({ type: "disabled" });
|
|
@@ -74,17 +82,21 @@ describe("AgentLoop thinking and effort", () => {
|
|
|
74
82
|
|
|
75
83
|
test("sends effort in provider config", async () => {
|
|
76
84
|
const { provider, lastConfig } = createMockProvider();
|
|
77
|
-
const loop = new AgentLoop(
|
|
85
|
+
const loop = new AgentLoop({
|
|
86
|
+
provider: provider,
|
|
87
|
+
systemPrompt: "test",
|
|
88
|
+
conversationId: "test-conversation",
|
|
78
89
|
config: {
|
|
79
90
|
maxTokens: 64000,
|
|
80
91
|
effort: "high",
|
|
81
92
|
},
|
|
82
93
|
});
|
|
83
94
|
|
|
84
|
-
await loop.run(
|
|
85
|
-
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
86
|
-
() => {},
|
|
87
|
-
|
|
95
|
+
await loop.run({
|
|
96
|
+
messages: [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
97
|
+
onEvent: () => {},
|
|
98
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
99
|
+
});
|
|
88
100
|
|
|
89
101
|
const config = lastConfig()!;
|
|
90
102
|
expect(config.effort).toBe("high");
|
|
@@ -92,7 +104,10 @@ describe("AgentLoop thinking and effort", () => {
|
|
|
92
104
|
|
|
93
105
|
test("sends effort with disabled thinking when thinking is disabled", async () => {
|
|
94
106
|
const { provider, lastConfig } = createMockProvider();
|
|
95
|
-
const loop = new AgentLoop(
|
|
107
|
+
const loop = new AgentLoop({
|
|
108
|
+
provider: provider,
|
|
109
|
+
systemPrompt: "test",
|
|
110
|
+
conversationId: "test-conversation",
|
|
96
111
|
config: {
|
|
97
112
|
maxTokens: 64000,
|
|
98
113
|
effort: "medium",
|
|
@@ -100,10 +115,11 @@ describe("AgentLoop thinking and effort", () => {
|
|
|
100
115
|
},
|
|
101
116
|
});
|
|
102
117
|
|
|
103
|
-
await loop.run(
|
|
104
|
-
[{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
105
|
-
() => {},
|
|
106
|
-
|
|
118
|
+
await loop.run({
|
|
119
|
+
messages: [{ role: "user", content: [{ type: "text", text: "Hi" }] }],
|
|
120
|
+
onEvent: () => {},
|
|
121
|
+
trust: { sourceChannel: "vellum", trustClass: "unknown" },
|
|
122
|
+
});
|
|
107
123
|
|
|
108
124
|
const config = lastConfig()!;
|
|
109
125
|
expect(config.effort).toBe("medium");
|