@vellumai/assistant 0.7.2 → 0.7.3
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 +16 -1
- package/docs/architecture/memory.md +5 -2
- package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
- package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
- package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
- package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
- package/openapi.yaml +449 -22
- package/package.json +1 -1
- package/src/__tests__/app-control-flow.test.ts +21 -11
- package/src/__tests__/assistant-event-hub.test.ts +48 -0
- package/src/__tests__/assistant-event.test.ts +0 -10
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
- package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
- package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
- package/src/__tests__/call-conversation-messages.test.ts +8 -2
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
- package/src/__tests__/channel-readiness-service.test.ts +4 -2
- package/src/__tests__/config-loader-backfill.test.ts +379 -0
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
- package/src/__tests__/config-watcher.test.ts +140 -69
- package/src/__tests__/context-search-agent-runner.test.ts +61 -3
- package/src/__tests__/context-search-conversations-source.test.ts +0 -24
- package/src/__tests__/context-search-fanout.test.ts +0 -1
- package/src/__tests__/context-search-memory-source.test.ts +3 -7
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
- package/src/__tests__/context-search-pkb-source.test.ts +0 -1
- package/src/__tests__/context-search-workspace-source.test.ts +0 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
- package/src/__tests__/conversation-agent-loop.test.ts +454 -5
- package/src/__tests__/conversation-error.test.ts +150 -3
- package/src/__tests__/conversation-process-callsite.test.ts +43 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
- package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
- package/src/__tests__/conversation-speed-override.test.ts +0 -3
- package/src/__tests__/conversation-store.test.ts +0 -18
- package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/credentials-cli.test.ts +7 -0
- package/src/__tests__/cu-unified-flow.test.ts +176 -10
- package/src/__tests__/date-context.test.ts +164 -2
- package/src/__tests__/disk-pressure-guard.test.ts +262 -0
- package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
- package/src/__tests__/disk-pressure-policy.test.ts +241 -0
- package/src/__tests__/disk-pressure-routes.test.ts +379 -0
- package/src/__tests__/disk-pressure-tools.test.ts +277 -0
- package/src/__tests__/disk-usage.test.ts +150 -0
- package/src/__tests__/events-client-registration.test.ts +52 -0
- package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
- package/src/__tests__/file-write-tool.test.ts +4 -10
- package/src/__tests__/filing-service.test.ts +3 -4
- package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
- package/src/__tests__/heartbeat-service.test.ts +260 -11
- package/src/__tests__/host-app-control-proxy.test.ts +195 -25
- package/src/__tests__/host-bash-proxy.test.ts +227 -34
- package/src/__tests__/host-bash-routes.test.ts +178 -13
- package/src/__tests__/host-cu-proxy.test.ts +210 -3
- package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
- package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
- package/src/__tests__/host-file-proxy.test.ts +268 -6
- package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
- package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
- package/src/__tests__/http-user-message-parity.test.ts +107 -1
- package/src/__tests__/injector-chain.test.ts +18 -6
- package/src/__tests__/injector-disk-pressure.test.ts +224 -0
- package/src/__tests__/managed-profile-guard.test.ts +18 -0
- package/src/__tests__/mcp-abort-signal.test.ts +130 -0
- package/src/__tests__/memory-admin-recall.test.ts +3 -11
- package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
- package/src/__tests__/normalize-onboarding.test.ts +180 -0
- package/src/__tests__/oauth-connect-routes.test.ts +316 -0
- package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
- package/src/__tests__/onboarding-persona-write.test.ts +308 -0
- package/src/__tests__/openai-provider.test.ts +45 -8
- package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
- package/src/__tests__/platform-callback-registration.test.ts +21 -4
- package/src/__tests__/platform.test.ts +2 -1
- package/src/__tests__/playbook-execution.test.ts +0 -43
- package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
- package/src/__tests__/provider-tool-name.test.ts +23 -0
- package/src/__tests__/relay-server.test.ts +15 -4
- package/src/__tests__/runtime-events-sse.test.ts +4 -8
- package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -1
- package/src/__tests__/suggestion-routes.test.ts +46 -0
- package/src/__tests__/twilio-validation.test.ts +2 -2
- package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
- package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
- package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
- package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
- package/src/approvals/guardian-decision-primitive.ts +13 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -17
- package/src/backup/snapshot-lock.ts +2 -27
- package/src/bundler/compiler-tools.ts +3 -2
- package/src/calls/call-conversation-messages.ts +46 -10
- package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
- package/src/cli/commands/bash.ts +35 -108
- package/src/cli/commands/contacts.ts +64 -25
- package/src/cli/commands/credentials.ts +56 -0
- package/src/cli/commands/memory-v2.ts +7 -6
- package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
- package/src/cli/commands/oauth/connect.ts +127 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
- package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
- package/src/cli/commands/platform/index.ts +16 -7
- package/src/cli/commands/status.ts +57 -0
- package/src/cli/program.ts +4 -2
- package/src/config/assistant-feature-flags.ts +13 -3
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
- package/src/config/env.ts +0 -8
- package/src/config/feature-flag-registry.json +27 -3
- package/src/config/loader.ts +127 -8
- package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -5
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +2 -0
- package/src/config/schemas/memory-lifecycle.ts +13 -0
- package/src/config/schemas/memory-v2.ts +75 -11
- package/src/config/schemas/platform.ts +43 -3
- package/src/config/schemas/services.ts +28 -0
- package/src/config/seed-inference-profiles.ts +230 -33
- package/src/contacts/contact-store.ts +0 -25
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
- package/src/daemon/assistant-attachments.ts +4 -4
- package/src/daemon/config-watcher.ts +85 -57
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +170 -33
- package/src/daemon/conversation-error.ts +87 -15
- package/src/daemon/conversation-lifecycle.ts +1 -3
- package/src/daemon/conversation-process.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +26 -0
- package/src/daemon/conversation-store.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +195 -15
- package/src/daemon/conversation-tool-setup.ts +57 -14
- package/src/daemon/conversation.ts +17 -22
- package/src/daemon/date-context.ts +71 -22
- package/src/daemon/disk-pressure-background-gate.ts +73 -0
- package/src/daemon/disk-pressure-guard.ts +343 -0
- package/src/daemon/disk-pressure-policy.ts +163 -0
- package/src/daemon/handlers/shared.ts +0 -1
- package/src/daemon/handlers/skills.ts +3 -4
- package/src/daemon/host-app-control-proxy.ts +137 -41
- package/src/daemon/host-bash-proxy.ts +46 -21
- package/src/daemon/host-cu-proxy.ts +49 -3
- package/src/daemon/host-file-proxy.ts +43 -7
- package/src/daemon/host-transfer-proxy.ts +95 -4
- package/src/daemon/lifecycle.ts +79 -28
- package/src/daemon/meet-host-supervisor.ts +4 -4
- package/src/daemon/meet-manifest-loader.ts +0 -1
- package/src/daemon/memory-v2-startup.ts +14 -4
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +4 -0
- package/src/daemon/message-types/disk-pressure.ts +9 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/profiler-run-store.ts +5 -5
- package/src/daemon/tool-setup-types.ts +2 -2
- package/src/documents/document-store.ts +85 -0
- package/src/filing/filing-service.ts +30 -5
- package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
- package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
- package/src/heartbeat/heartbeat-run-store.ts +13 -0
- package/src/heartbeat/heartbeat-service.ts +205 -31
- package/src/home/feed-scheduler.ts +18 -0
- package/src/inbound/platform-callback-registration.ts +8 -15
- package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
- package/src/ipc/assistant-server.ts +56 -2
- package/src/ipc/gateway-client.ts +37 -3
- package/src/live-voice/live-voice-archive.ts +4 -4
- package/src/live-voice/protocol.ts +5 -7
- package/src/media/image-service.ts +1 -7
- package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
- package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
- package/src/memory/admin.ts +5 -9
- package/src/memory/context-search/agent-runner.ts +19 -2
- package/src/memory/context-search/sources/conversations.ts +2 -11
- package/src/memory/context-search/sources/memory-v2.ts +5 -4
- package/src/memory/context-search/sources/memory.ts +0 -1
- package/src/memory/context-search/types.ts +0 -1
- package/src/memory/conversation-crud.ts +4 -12
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-runtime-manager.ts +119 -5
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
- package/src/memory/graph/conversation-graph-memory.ts +42 -54
- package/src/memory/graph/extraction.ts +1 -3
- package/src/memory/graph/graph-search.test.ts +10 -67
- package/src/memory/graph/graph-search.ts +1 -20
- package/src/memory/graph/retriever.test.ts +6 -0
- package/src/memory/graph/retriever.ts +6 -10
- package/src/memory/indexer.ts +54 -45
- package/src/memory/job-handlers/backfill.ts +2 -11
- package/src/memory/job-handlers/cleanup.ts +43 -0
- package/src/memory/job-handlers/embedding.ts +6 -8
- package/src/memory/job-handlers/summarization.ts +2 -7
- package/src/memory/jobs-store.ts +48 -0
- package/src/memory/jobs-worker.ts +81 -43
- package/src/memory/memory-v2-activation-log-store.ts +32 -14
- package/src/memory/memory-v2-concept-frequency.ts +169 -0
- package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-search.test.ts +6 -0
- package/src/memory/qdrant-client.ts +0 -13
- package/src/memory/rerank-local.ts +374 -0
- package/src/memory/search/semantic.ts +6 -67
- package/src/memory/trace-event-store.ts +1 -17
- package/src/memory/v2/__tests__/activation.test.ts +311 -250
- package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
- package/src/memory/v2/__tests__/injection.test.ts +157 -167
- package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
- package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
- package/src/memory/v2/__tests__/reranker.test.ts +338 -0
- package/src/memory/v2/__tests__/sim.test.ts +5 -199
- package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
- package/src/memory/v2/__tests__/static-context.test.ts +76 -1
- package/src/memory/v2/activation.ts +149 -156
- package/src/memory/v2/consolidation-job.ts +62 -12
- package/src/memory/v2/injection.ts +47 -60
- package/src/memory/v2/prompts/consolidation.ts +36 -1
- package/src/memory/v2/qdrant.ts +99 -0
- package/src/memory/v2/reranker.ts +177 -0
- package/src/memory/v2/sim.ts +10 -84
- package/src/memory/v2/skill-content.ts +4 -3
- package/src/memory/v2/skill-store.ts +82 -59
- package/src/memory/v2/static-context.ts +22 -0
- package/src/memory/v2/types.ts +10 -10
- package/src/notifications/copy-composer.ts +13 -0
- package/src/notifications/signal.ts +4 -0
- package/src/oauth/AGENTS.md +3 -1
- package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.test.ts +66 -1
- package/src/oauth/connection-resolver.ts +55 -1
- package/src/oauth/oauth-connect-state.ts +77 -0
- package/src/oauth/seed-providers.ts +58 -1
- package/src/plugins/defaults/injectors.ts +35 -2
- package/src/plugins/defaults/memory-retrieval.ts +5 -6
- package/src/plugins/types.ts +7 -0
- package/src/proactive-artifact/aux-message-injector.ts +74 -0
- package/src/proactive-artifact/decision.test.ts +226 -0
- package/src/proactive-artifact/decision.ts +165 -0
- package/src/proactive-artifact/index.ts +7 -0
- package/src/proactive-artifact/job.test.ts +867 -0
- package/src/proactive-artifact/job.ts +352 -0
- package/src/proactive-artifact/message-copy.ts +41 -0
- package/src/proactive-artifact/trigger-state.test.ts +277 -0
- package/src/proactive-artifact/trigger-state.ts +119 -0
- package/src/prompts/normalize-onboarding.ts +80 -0
- package/src/prompts/persona-resolver.ts +101 -9
- package/src/prompts/system-prompt.ts +21 -7
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/providers/__tests__/retry-callsite.test.ts +222 -1
- package/src/providers/model-intents.ts +7 -0
- package/src/providers/openrouter/client.ts +8 -0
- package/src/providers/retry.ts +50 -0
- package/src/providers/types.ts +1 -0
- package/src/runtime/__tests__/agent-wake.test.ts +456 -3
- package/src/runtime/agent-wake.ts +238 -100
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/assistant-event.ts +0 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/auth/same-actor.ts +216 -0
- package/src/runtime/channel-retry-sweep.ts +65 -1
- package/src/runtime/guardian-reply-router.ts +10 -0
- package/src/runtime/local-actor-identity.ts +52 -11
- package/src/runtime/pending-interactions.ts +8 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/client-routes.ts +20 -2
- package/src/runtime/routes/contact-routes.ts +0 -25
- package/src/runtime/routes/conversation-routes.ts +35 -26
- package/src/runtime/routes/debug-bash-routes.ts +163 -0
- package/src/runtime/routes/disk-pressure-routes.ts +121 -0
- package/src/runtime/routes/document-pdf-renderer.ts +6 -2
- package/src/runtime/routes/documents-routes.ts +2 -75
- package/src/runtime/routes/events-routes.ts +41 -9
- package/src/runtime/routes/host-bash-routes.ts +23 -3
- package/src/runtime/routes/host-cu-routes.ts +33 -6
- package/src/runtime/routes/host-file-routes.ts +32 -6
- package/src/runtime/routes/host-transfer-routes.ts +79 -16
- package/src/runtime/routes/identity-routes.ts +7 -138
- package/src/runtime/routes/inbound-message-handler.ts +77 -12
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
- package/src/runtime/routes/index.ts +6 -0
- package/src/runtime/routes/memory-item-routes.test.ts +41 -15
- package/src/runtime/routes/memory-v2-routes.ts +33 -0
- package/src/runtime/routes/oauth-connect-routes.ts +153 -0
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/run-script.ts +37 -5
- package/src/schedule/scheduler.ts +20 -1
- package/src/security/encrypted-store.ts +2 -0
- package/src/security/secure-keys.ts +55 -0
- package/src/skills/remote-skill-policy.ts +4 -10
- package/src/subagent/index.ts +1 -7
- package/src/subagent/manager.ts +1 -15
- package/src/tasks/task-runner.ts +0 -1
- package/src/tasks/task-store.ts +0 -3
- package/src/tools/background-tool-registry.ts +17 -3
- package/src/tools/host-filesystem/edit.test.ts +151 -0
- package/src/tools/host-filesystem/edit.ts +43 -1
- package/src/tools/host-filesystem/read.test.ts +129 -0
- package/src/tools/host-filesystem/read.ts +43 -1
- package/src/tools/host-filesystem/transfer.test.ts +127 -2
- package/src/tools/host-filesystem/transfer.ts +56 -11
- package/src/tools/host-filesystem/write.test.ts +134 -0
- package/src/tools/host-filesystem/write.ts +43 -1
- package/src/tools/host-terminal/host-shell.ts +13 -6
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/memory/register.test.ts +12 -9
- package/src/tools/memory/register.ts +1 -2
- package/src/tools/provider-tool-name.ts +28 -0
- package/src/tools/registry.ts +30 -9
- package/src/tools/terminal/shell.ts +9 -1
- package/src/tools/tool-approval-handler.ts +31 -6
- package/src/tools/types.ts +24 -2
- package/src/tts/provider-catalog.ts +3 -5
- package/src/util/disk-usage.ts +138 -0
- package/src/util/platform.ts +21 -11
- package/src/util/process-liveness.ts +26 -0
- package/src/workspace/heartbeat-service.ts +19 -0
- package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
- package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
- package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
- package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
- package/src/memory/v2/skill-qdrant.ts +0 -404
- package/src/signals/bash.ts +0 -198
|
@@ -11,31 +11,37 @@ const PROVIDER_DEFAULT_MODELS: Record<string, string> = Object.fromEntries(
|
|
|
11
11
|
|
|
12
12
|
const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
13
13
|
anthropic: {
|
|
14
|
+
balanced: "claude-sonnet-4-6",
|
|
14
15
|
"latency-optimized": "claude-haiku-4-5-20251001",
|
|
15
16
|
"quality-optimized": "claude-opus-4-7",
|
|
16
17
|
"vision-optimized": "claude-opus-4-6",
|
|
17
18
|
},
|
|
18
19
|
openai: {
|
|
20
|
+
balanced: "gpt-5.4-mini",
|
|
19
21
|
"latency-optimized": "gpt-5.4-nano",
|
|
20
22
|
"quality-optimized": "gpt-5.4",
|
|
21
23
|
"vision-optimized": "gpt-5.4",
|
|
22
24
|
},
|
|
23
25
|
gemini: {
|
|
26
|
+
balanced: "gemini-3-flash-preview",
|
|
24
27
|
"latency-optimized": "gemini-3.1-flash-lite-preview",
|
|
25
28
|
"quality-optimized": "gemini-3.1-pro-preview",
|
|
26
29
|
"vision-optimized": "gemini-3-flash-preview",
|
|
27
30
|
},
|
|
28
31
|
ollama: {
|
|
32
|
+
balanced: "llama3.2",
|
|
29
33
|
"latency-optimized": "llama3.2",
|
|
30
34
|
"quality-optimized": "llama3.2",
|
|
31
35
|
"vision-optimized": "llama3.2",
|
|
32
36
|
},
|
|
33
37
|
fireworks: {
|
|
38
|
+
balanced: "accounts/fireworks/models/kimi-k2p5",
|
|
34
39
|
"latency-optimized": "accounts/fireworks/models/kimi-k2p5",
|
|
35
40
|
"quality-optimized": "accounts/fireworks/models/kimi-k2p5",
|
|
36
41
|
"vision-optimized": "accounts/fireworks/models/kimi-k2p5",
|
|
37
42
|
},
|
|
38
43
|
openrouter: {
|
|
44
|
+
balanced: "anthropic/claude-sonnet-4.6",
|
|
39
45
|
"latency-optimized": "anthropic/claude-haiku-4.5",
|
|
40
46
|
"quality-optimized": "anthropic/claude-opus-4.7",
|
|
41
47
|
"vision-optimized": "anthropic/claude-opus-4.6",
|
|
@@ -45,6 +51,7 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
|
45
51
|
const FALLBACK_DEFAULT_MODEL = "claude-opus-4-7";
|
|
46
52
|
|
|
47
53
|
const MODEL_INTENTS = new Set<ModelIntent>([
|
|
54
|
+
"balanced",
|
|
48
55
|
"latency-optimized",
|
|
49
56
|
"quality-optimized",
|
|
50
57
|
"vision-optimized",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProviderError } from "../../util/errors.js";
|
|
1
2
|
import { AnthropicProvider } from "../anthropic/client.js";
|
|
2
3
|
import { OpenAIChatCompletionsProvider } from "../openai/chat-completions-provider.js";
|
|
3
4
|
import { isThinkingConfigEnabled } from "../thinking-config.js";
|
|
@@ -139,6 +140,13 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
139
140
|
cause: error,
|
|
140
141
|
});
|
|
141
142
|
}
|
|
143
|
+
if (error instanceof ProviderError && error.provider !== this.name) {
|
|
144
|
+
throw new ProviderError(error.message, this.name, error.statusCode, {
|
|
145
|
+
cause: error.cause ?? error,
|
|
146
|
+
retryAfterMs: error.retryAfterMs,
|
|
147
|
+
abortReason: error.abortReason,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
142
150
|
throw error;
|
|
143
151
|
}
|
|
144
152
|
}
|
package/src/providers/retry.ts
CHANGED
|
@@ -273,6 +273,56 @@ function normalizeSendMessageOptions(
|
|
|
273
273
|
delete nextConfig.thinking;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
+
// Anthropic (and OpenRouter fronting Anthropic) rejects requests that
|
|
277
|
+
// combine extended thinking with `temperature` ≠ 1. From the API:
|
|
278
|
+
// "`temperature` may only be set to 1 when thinking is enabled or in
|
|
279
|
+
// adaptive mode."
|
|
280
|
+
//
|
|
281
|
+
// Defense-in-depth: callers that hardcode a non-default temperature in
|
|
282
|
+
// their per-call config are easy to miss when reviewing — we already had
|
|
283
|
+
// this bug ship in three places (reply suggestions, recall agent
|
|
284
|
+
// round, recall fallback finalize). Drop the offending temperature with
|
|
285
|
+
// a warn log so the request goes through with Anthropic's default
|
|
286
|
+
// (which is 1 in thinking mode anyway). We keep `thinking` rather than
|
|
287
|
+
// `temperature` because thinking is the more deliberate, profile-level
|
|
288
|
+
// choice — silently downgrading reasoning capacity for an unrelated
|
|
289
|
+
// per-call hint would be the worse failure mode.
|
|
290
|
+
//
|
|
291
|
+
// Scope:
|
|
292
|
+
// - Anthropic: always.
|
|
293
|
+
// - OpenRouter fronting `anthropic/*`: same wire constraint applies.
|
|
294
|
+
// - Other providers: not our problem here (e.g. OpenAI reasoning models
|
|
295
|
+
// strip `temperature` upstream; non-Anthropic OpenRouter reasoning
|
|
296
|
+
// models don't have this exact constraint).
|
|
297
|
+
const isThinkingTemperatureConflict = (() => {
|
|
298
|
+
if (nextConfig.thinking == null) return false;
|
|
299
|
+
if (isThinkingConfigDisabled(nextConfig.thinking)) return false;
|
|
300
|
+
const temp = nextConfig.temperature;
|
|
301
|
+
if (typeof temp !== "number") return false;
|
|
302
|
+
if (temp === 1) return false;
|
|
303
|
+
if (providerName === "anthropic") return true;
|
|
304
|
+
if (providerName === "openrouter") {
|
|
305
|
+
const model =
|
|
306
|
+
typeof nextConfig.model === "string" ? nextConfig.model : "";
|
|
307
|
+
return model.startsWith("anthropic/");
|
|
308
|
+
}
|
|
309
|
+
return false;
|
|
310
|
+
})();
|
|
311
|
+
if (isThinkingTemperatureConflict) {
|
|
312
|
+
log.warn(
|
|
313
|
+
{
|
|
314
|
+
providerName,
|
|
315
|
+
callSite: config.callSite,
|
|
316
|
+
droppedTemperature: nextConfig.temperature,
|
|
317
|
+
},
|
|
318
|
+
"Dropping `temperature` because thinking is enabled — Anthropic only " +
|
|
319
|
+
"accepts `temperature: 1` (or unset) when thinking/adaptive mode is " +
|
|
320
|
+
"on. Set `thinking: { type: 'disabled' }` on the call site if you " +
|
|
321
|
+
"need a specific temperature.",
|
|
322
|
+
);
|
|
323
|
+
delete nextConfig.temperature;
|
|
324
|
+
}
|
|
325
|
+
|
|
276
326
|
// effort is supported by Anthropic, OpenAI, and OpenAI-compatible providers; strip for others
|
|
277
327
|
if (
|
|
278
328
|
!EFFORT_SUPPORTED_PROVIDERS.has(providerName) &&
|
package/src/providers/types.ts
CHANGED
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
19
|
|
|
20
|
+
import type { DiskPressureStatus } from "../../daemon/disk-pressure-guard.js";
|
|
21
|
+
|
|
20
22
|
// Stub the DB-backed override-profile read so unit tests don't need a
|
|
21
23
|
// real SQLite database. The wake helper calls this on every invocation
|
|
22
24
|
// to honor the conversation's pinned inference profile.
|
|
@@ -24,6 +26,48 @@ mock.module("../../memory/conversation-crud.js", () => ({
|
|
|
24
26
|
getConversationOverrideProfile: () => undefined,
|
|
25
27
|
}));
|
|
26
28
|
|
|
29
|
+
mock.module("../../config/loader.js", () => ({
|
|
30
|
+
getConfig: () => ({ llm: {} }),
|
|
31
|
+
loadConfig: () => ({ llm: {} }),
|
|
32
|
+
loadRawConfig: () => ({}),
|
|
33
|
+
saveRawConfig: () => {},
|
|
34
|
+
getConfigReadOnly: () => ({ llm: {} }),
|
|
35
|
+
applyNestedDefaults: (config: unknown) => config,
|
|
36
|
+
deepMergeOverwrite: (base: unknown) => base,
|
|
37
|
+
mergeDefaultWorkspaceConfig: () => {},
|
|
38
|
+
getNestedValue: () => undefined,
|
|
39
|
+
setNestedValue: () => {},
|
|
40
|
+
API_KEY_PROVIDERS: [],
|
|
41
|
+
_appendQuarantineBulletin: () => {},
|
|
42
|
+
invalidateConfigCache: () => {},
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
mock.module("../../config/llm-context-resolution.js", () => ({
|
|
46
|
+
resolveEffectiveContextWindow: () => ({
|
|
47
|
+
maxInputTokens: 200_000,
|
|
48
|
+
}),
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
let mockDiskPressureStatus: DiskPressureStatus = {
|
|
52
|
+
enabled: false,
|
|
53
|
+
state: "disabled",
|
|
54
|
+
locked: false,
|
|
55
|
+
acknowledged: false,
|
|
56
|
+
overrideActive: false,
|
|
57
|
+
effectivelyLocked: false,
|
|
58
|
+
lockId: null,
|
|
59
|
+
usagePercent: null,
|
|
60
|
+
thresholdPercent: 95,
|
|
61
|
+
path: null,
|
|
62
|
+
lastCheckedAt: null,
|
|
63
|
+
blockedCapabilities: [],
|
|
64
|
+
error: null,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
mock.module("../../daemon/disk-pressure-guard.js", () => ({
|
|
68
|
+
getDiskPressureStatus: () => mockDiskPressureStatus,
|
|
69
|
+
}));
|
|
70
|
+
|
|
27
71
|
import type { AgentEvent } from "../../agent/loop.js";
|
|
28
72
|
import type { Message } from "../../providers/types.js";
|
|
29
73
|
import {
|
|
@@ -37,7 +81,11 @@ import {
|
|
|
37
81
|
interface MockTarget extends WakeTarget {
|
|
38
82
|
emittedEvents: AgentEvent[];
|
|
39
83
|
pushedMessages: Message[];
|
|
40
|
-
runCalls: Array<{
|
|
84
|
+
runCalls: Array<{
|
|
85
|
+
input: Message[];
|
|
86
|
+
requestId?: string;
|
|
87
|
+
turnContext?: unknown;
|
|
88
|
+
}>;
|
|
41
89
|
processingToggles: boolean[];
|
|
42
90
|
/** Tail messages handed to `persistTailMessage`, in call order. */
|
|
43
91
|
persistedTailCalls: Message[];
|
|
@@ -70,7 +118,11 @@ function makeTarget(options: {
|
|
|
70
118
|
}): MockTarget {
|
|
71
119
|
const emittedEvents: AgentEvent[] = [];
|
|
72
120
|
const pushedMessages: Message[] = [];
|
|
73
|
-
const runCalls: Array<{
|
|
121
|
+
const runCalls: Array<{
|
|
122
|
+
input: Message[];
|
|
123
|
+
requestId?: string;
|
|
124
|
+
turnContext?: unknown;
|
|
125
|
+
}> = [];
|
|
74
126
|
const processingToggles: boolean[] = [];
|
|
75
127
|
const persistedTailCalls: Message[] = [];
|
|
76
128
|
const callSequence: string[] = [];
|
|
@@ -97,8 +149,11 @@ function makeTarget(options: {
|
|
|
97
149
|
onEvent: (event: AgentEvent) => void | Promise<void>,
|
|
98
150
|
_signal?: AbortSignal,
|
|
99
151
|
requestId?: string,
|
|
152
|
+
_onCheckpoint?: unknown,
|
|
153
|
+
_callSite?: unknown,
|
|
154
|
+
turnContext?: unknown,
|
|
100
155
|
) => {
|
|
101
|
-
runCalls.push({ input: [...input], requestId });
|
|
156
|
+
runCalls.push({ input: [...input], requestId, turnContext });
|
|
102
157
|
// Emit any scripted events the test wanted us to produce.
|
|
103
158
|
for (const ev of options.scriptedEvents ?? []) {
|
|
104
159
|
await onEvent(ev);
|
|
@@ -165,11 +220,163 @@ function makeTarget(options: {
|
|
|
165
220
|
|
|
166
221
|
beforeEach(() => {
|
|
167
222
|
__resetWakeChainForTests();
|
|
223
|
+
mockDiskPressureStatus = {
|
|
224
|
+
enabled: false,
|
|
225
|
+
state: "disabled",
|
|
226
|
+
locked: false,
|
|
227
|
+
acknowledged: false,
|
|
228
|
+
overrideActive: false,
|
|
229
|
+
effectivelyLocked: false,
|
|
230
|
+
lockId: null,
|
|
231
|
+
usagePercent: null,
|
|
232
|
+
thresholdPercent: 95,
|
|
233
|
+
path: null,
|
|
234
|
+
lastCheckedAt: null,
|
|
235
|
+
blockedCapabilities: [],
|
|
236
|
+
error: null,
|
|
237
|
+
};
|
|
168
238
|
});
|
|
169
239
|
|
|
170
240
|
// ── Tests ────────────────────────────────────────────────────────────
|
|
171
241
|
|
|
172
242
|
describe("wakeAgentForOpportunity", () => {
|
|
243
|
+
test("disabled disk pressure flag allows background wakes to pass through", async () => {
|
|
244
|
+
const target = makeTarget({
|
|
245
|
+
scriptedAssistant: null,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const result = await wakeAgentForOpportunity(
|
|
249
|
+
{
|
|
250
|
+
conversationId: target.conversationId,
|
|
251
|
+
hint: "background completion",
|
|
252
|
+
source: "background-tool",
|
|
253
|
+
},
|
|
254
|
+
{ resolveTarget: async () => target },
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
expect(result).toEqual({ invoked: true, producedToolCalls: false });
|
|
258
|
+
expect(target.runCalls).toHaveLength(1);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("blocks background wakes during disk pressure before marking processing", async () => {
|
|
262
|
+
mockDiskPressureStatus = {
|
|
263
|
+
enabled: true,
|
|
264
|
+
state: "critical",
|
|
265
|
+
locked: true,
|
|
266
|
+
acknowledged: true,
|
|
267
|
+
overrideActive: false,
|
|
268
|
+
effectivelyLocked: true,
|
|
269
|
+
lockId: "disk-pressure-test",
|
|
270
|
+
usagePercent: 98,
|
|
271
|
+
thresholdPercent: 95,
|
|
272
|
+
path: "/",
|
|
273
|
+
lastCheckedAt: "2026-05-05T00:00:00.000Z",
|
|
274
|
+
blockedCapabilities: ["agent-turns", "background-work", "remote-ingress"],
|
|
275
|
+
error: null,
|
|
276
|
+
};
|
|
277
|
+
const target = makeTarget({
|
|
278
|
+
isProcessing: true,
|
|
279
|
+
scriptedAssistant: {
|
|
280
|
+
role: "assistant",
|
|
281
|
+
content: [{ type: "text", text: "should not run" }],
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const result = await wakeAgentForOpportunity(
|
|
286
|
+
{
|
|
287
|
+
conversationId: target.conversationId,
|
|
288
|
+
hint: "background shell completed",
|
|
289
|
+
source: "background-tool",
|
|
290
|
+
trustContext: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
291
|
+
},
|
|
292
|
+
{ resolveTarget: async () => target },
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
expect(result).toEqual({
|
|
296
|
+
invoked: false,
|
|
297
|
+
producedToolCalls: false,
|
|
298
|
+
reason: "disk_pressure",
|
|
299
|
+
});
|
|
300
|
+
expect(target.runCalls).toHaveLength(0);
|
|
301
|
+
expect(target.processingToggles).toEqual([]);
|
|
302
|
+
expect(target.drainQueueCalls).toBe(0);
|
|
303
|
+
expect(target.isProcessing()).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("blocks trusted-contact direct wakes during disk pressure", async () => {
|
|
307
|
+
mockDiskPressureStatus = {
|
|
308
|
+
enabled: true,
|
|
309
|
+
state: "critical",
|
|
310
|
+
locked: true,
|
|
311
|
+
acknowledged: true,
|
|
312
|
+
overrideActive: false,
|
|
313
|
+
effectivelyLocked: true,
|
|
314
|
+
lockId: "disk-pressure-test",
|
|
315
|
+
usagePercent: 98,
|
|
316
|
+
thresholdPercent: 95,
|
|
317
|
+
path: "/",
|
|
318
|
+
lastCheckedAt: "2026-05-05T00:00:00.000Z",
|
|
319
|
+
blockedCapabilities: ["agent-turns", "background-work", "remote-ingress"],
|
|
320
|
+
error: null,
|
|
321
|
+
};
|
|
322
|
+
const target = makeTarget({ scriptedAssistant: null });
|
|
323
|
+
|
|
324
|
+
const result = await wakeAgentForOpportunity(
|
|
325
|
+
{
|
|
326
|
+
conversationId: target.conversationId,
|
|
327
|
+
hint: "notify the guardian",
|
|
328
|
+
source: "notification",
|
|
329
|
+
trustContext: {
|
|
330
|
+
sourceChannel: "slack",
|
|
331
|
+
trustClass: "trusted_contact",
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{ resolveTarget: async () => target },
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
expect(result.reason).toBe("disk_pressure");
|
|
338
|
+
expect(target.runCalls).toHaveLength(0);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("threads cleanup-mode injection context for explicit local-owner wakes", async () => {
|
|
342
|
+
mockDiskPressureStatus = {
|
|
343
|
+
enabled: true,
|
|
344
|
+
state: "critical",
|
|
345
|
+
locked: true,
|
|
346
|
+
acknowledged: true,
|
|
347
|
+
overrideActive: false,
|
|
348
|
+
effectivelyLocked: true,
|
|
349
|
+
lockId: "disk-pressure-test",
|
|
350
|
+
usagePercent: 98,
|
|
351
|
+
thresholdPercent: 95,
|
|
352
|
+
path: "/",
|
|
353
|
+
lastCheckedAt: "2026-05-05T00:00:00.000Z",
|
|
354
|
+
blockedCapabilities: ["agent-turns", "background-work", "remote-ingress"],
|
|
355
|
+
error: null,
|
|
356
|
+
};
|
|
357
|
+
const target = makeTarget({ scriptedAssistant: null });
|
|
358
|
+
|
|
359
|
+
const result = await wakeAgentForOpportunity(
|
|
360
|
+
{
|
|
361
|
+
conversationId: target.conversationId,
|
|
362
|
+
hint: "clean storage",
|
|
363
|
+
source: "local-cleanup",
|
|
364
|
+
sourceChannel: "vellum",
|
|
365
|
+
sourceInterface: "macos",
|
|
366
|
+
},
|
|
367
|
+
{ resolveTarget: async () => target },
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
expect(result).toEqual({ invoked: true, producedToolCalls: false });
|
|
371
|
+
expect(target.runCalls).toHaveLength(1);
|
|
372
|
+
expect(target.runCalls[0]!.turnContext).toMatchObject({
|
|
373
|
+
conversationId: target.conversationId,
|
|
374
|
+
injectionInputs: {
|
|
375
|
+
diskPressureContext: { cleanupModeActive: true },
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
173
380
|
test("silent no-op when agent produces no tool calls and no text", async () => {
|
|
174
381
|
const target = makeTarget({
|
|
175
382
|
baseline: [
|
|
@@ -982,4 +1189,250 @@ describe("wakeAgentForOpportunity", () => {
|
|
|
982
1189
|
expect(target.processingDuringDrain).toEqual([false]);
|
|
983
1190
|
},
|
|
984
1191
|
);
|
|
1192
|
+
|
|
1193
|
+
test(
|
|
1194
|
+
"checkpoint fires mid-run: events stream live and tail is persisted " +
|
|
1195
|
+
"incrementally so a long-running wake is observable",
|
|
1196
|
+
async () => {
|
|
1197
|
+
// Locks in the streaming-during-run fix. A long-running wake (e.g.
|
|
1198
|
+
// memory consolidation, often 5-30 minutes and 30+ turns) must
|
|
1199
|
+
// emit events and persist tail messages as each turn finalizes —
|
|
1200
|
+
// otherwise opening the conversation mid-flight returns 0 messages
|
|
1201
|
+
// from fetchHistory and the client renders the empty welcome
|
|
1202
|
+
// state instead of the in-progress turns.
|
|
1203
|
+
const turn1Assistant: Message = {
|
|
1204
|
+
role: "assistant",
|
|
1205
|
+
content: [
|
|
1206
|
+
{ type: "tool_use", id: "tu-1", name: "file_write", input: {} },
|
|
1207
|
+
],
|
|
1208
|
+
};
|
|
1209
|
+
const turn1ToolResult: Message = {
|
|
1210
|
+
role: "user",
|
|
1211
|
+
content: [{ type: "tool_result", tool_use_id: "tu-1", content: "ok" }],
|
|
1212
|
+
};
|
|
1213
|
+
const turn2Assistant: Message = {
|
|
1214
|
+
role: "assistant",
|
|
1215
|
+
content: [
|
|
1216
|
+
{ type: "tool_use", id: "tu-2", name: "remember", input: {} },
|
|
1217
|
+
],
|
|
1218
|
+
};
|
|
1219
|
+
const turn2ToolResult: Message = {
|
|
1220
|
+
role: "user",
|
|
1221
|
+
content: [{ type: "tool_result", tool_use_id: "tu-2", content: "ok" }],
|
|
1222
|
+
};
|
|
1223
|
+
const finalAssistant: Message = {
|
|
1224
|
+
role: "assistant",
|
|
1225
|
+
content: [{ type: "text", text: "All done." }],
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
const emittedEvents: AgentEvent[] = [];
|
|
1229
|
+
const pushedMessages: Message[] = [];
|
|
1230
|
+
const persistedTailCalls: Message[] = [];
|
|
1231
|
+
// Snapshot of how many tail messages had been persisted at each
|
|
1232
|
+
// point a streaming event reached the target. This is the actual
|
|
1233
|
+
// observability invariant: when a turn-2 streaming event arrives,
|
|
1234
|
+
// turn-1's messages must already be persisted so a fetchHistory
|
|
1235
|
+
// call from a client opening the conversation mid-stream returns
|
|
1236
|
+
// turn-1's content.
|
|
1237
|
+
const persistedAtEachEmit: number[] = [];
|
|
1238
|
+
const baseline: Message[] = [
|
|
1239
|
+
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
1240
|
+
];
|
|
1241
|
+
const history: Message[] = [...baseline];
|
|
1242
|
+
let processing = false;
|
|
1243
|
+
|
|
1244
|
+
const target: WakeTarget = {
|
|
1245
|
+
conversationId: "conv-stream",
|
|
1246
|
+
agentLoop: {
|
|
1247
|
+
run: async (_input, onEvent, _signal, _requestId, onCheckpoint) => {
|
|
1248
|
+
// Preamble + assistant hint + postamble (mirrors what the
|
|
1249
|
+
// wake injects). The agent-wake helper expects these three
|
|
1250
|
+
// hint messages in the input it hands to run().
|
|
1251
|
+
const runHistory: Message[] = [..._input];
|
|
1252
|
+
|
|
1253
|
+
// Turn 1: stream a text_delta + message_complete, then
|
|
1254
|
+
// fire the checkpoint after the tool_result lands.
|
|
1255
|
+
await onEvent({ type: "text_delta", text: "Working" });
|
|
1256
|
+
runHistory.push(turn1Assistant);
|
|
1257
|
+
await onEvent({
|
|
1258
|
+
type: "message_complete",
|
|
1259
|
+
message: turn1Assistant,
|
|
1260
|
+
});
|
|
1261
|
+
runHistory.push(turn1ToolResult);
|
|
1262
|
+
const dec1 = await onCheckpoint!({
|
|
1263
|
+
turnIndex: 0,
|
|
1264
|
+
toolCount: 1,
|
|
1265
|
+
hasToolUse: true,
|
|
1266
|
+
history: runHistory,
|
|
1267
|
+
});
|
|
1268
|
+
expect(dec1).toBe("continue");
|
|
1269
|
+
|
|
1270
|
+
// Turn 2: another tool turn — must already see the live
|
|
1271
|
+
// streaming because mode flipped after turn 1.
|
|
1272
|
+
await onEvent({ type: "text_delta", text: "Still going" });
|
|
1273
|
+
runHistory.push(turn2Assistant);
|
|
1274
|
+
await onEvent({
|
|
1275
|
+
type: "message_complete",
|
|
1276
|
+
message: turn2Assistant,
|
|
1277
|
+
});
|
|
1278
|
+
runHistory.push(turn2ToolResult);
|
|
1279
|
+
const dec2 = await onCheckpoint!({
|
|
1280
|
+
turnIndex: 1,
|
|
1281
|
+
toolCount: 1,
|
|
1282
|
+
hasToolUse: true,
|
|
1283
|
+
history: runHistory,
|
|
1284
|
+
});
|
|
1285
|
+
expect(dec2).toBe("continue");
|
|
1286
|
+
|
|
1287
|
+
// Final assistant message with no tool calls — loop would
|
|
1288
|
+
// exit. onCheckpoint does NOT fire for the terminal turn,
|
|
1289
|
+
// so the post-run flushPendingTail must catch this one.
|
|
1290
|
+
await onEvent({ type: "text_delta", text: "All done." });
|
|
1291
|
+
runHistory.push(finalAssistant);
|
|
1292
|
+
await onEvent({
|
|
1293
|
+
type: "message_complete",
|
|
1294
|
+
message: finalAssistant,
|
|
1295
|
+
});
|
|
1296
|
+
return runHistory;
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
getMessages: () => history,
|
|
1300
|
+
pushMessage: (msg) => {
|
|
1301
|
+
pushedMessages.push(msg);
|
|
1302
|
+
history.push(msg);
|
|
1303
|
+
},
|
|
1304
|
+
emitAgentEvent: (event) => {
|
|
1305
|
+
emittedEvents.push(event);
|
|
1306
|
+
persistedAtEachEmit.push(persistedTailCalls.length);
|
|
1307
|
+
},
|
|
1308
|
+
isProcessing: () => processing,
|
|
1309
|
+
markProcessing: (on) => {
|
|
1310
|
+
processing = on;
|
|
1311
|
+
},
|
|
1312
|
+
persistTailMessage: async (msg) => {
|
|
1313
|
+
persistedTailCalls.push(msg);
|
|
1314
|
+
},
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
const result = await wakeAgentForOpportunity(
|
|
1318
|
+
{
|
|
1319
|
+
conversationId: "conv-stream",
|
|
1320
|
+
hint: "consolidate",
|
|
1321
|
+
source: "memory_v2_consolidation",
|
|
1322
|
+
},
|
|
1323
|
+
{ resolveTarget: async () => target },
|
|
1324
|
+
);
|
|
1325
|
+
|
|
1326
|
+
expect(result).toEqual({ invoked: true, producedToolCalls: true });
|
|
1327
|
+
|
|
1328
|
+
// All 5 tail messages persisted in order. The first two via
|
|
1329
|
+
// turn-1 checkpoint, the next two via turn-2 checkpoint, and
|
|
1330
|
+
// `finalAssistant` via the post-run flush.
|
|
1331
|
+
expect(persistedTailCalls).toHaveLength(5);
|
|
1332
|
+
expect(persistedTailCalls[0]).toBe(turn1Assistant);
|
|
1333
|
+
expect(persistedTailCalls[1]).toBe(turn1ToolResult);
|
|
1334
|
+
expect(persistedTailCalls[2]).toBe(turn2Assistant);
|
|
1335
|
+
expect(persistedTailCalls[3]).toBe(turn2ToolResult);
|
|
1336
|
+
expect(persistedTailCalls[4]).toBe(finalAssistant);
|
|
1337
|
+
|
|
1338
|
+
// Critical observability invariant: by the time turn-2's
|
|
1339
|
+
// streaming text_delta reached the client, turn-1's messages
|
|
1340
|
+
// were already persisted. A client opening the conversation at
|
|
1341
|
+
// that moment would fetchHistory and see turn-1, plus stream
|
|
1342
|
+
// turn-2 live — instead of seeing an empty welcome view.
|
|
1343
|
+
const turn2DeltaIdx = emittedEvents.findIndex(
|
|
1344
|
+
(e) => e.type === "text_delta" && e.text === "Still going",
|
|
1345
|
+
);
|
|
1346
|
+
expect(turn2DeltaIdx).toBeGreaterThan(-1);
|
|
1347
|
+
expect(persistedAtEachEmit[turn2DeltaIdx]).toBeGreaterThanOrEqual(2);
|
|
1348
|
+
},
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
test(
|
|
1352
|
+
"checkpoint-driven wake injects ui_surface card into the first " +
|
|
1353
|
+
"assistant tail message",
|
|
1354
|
+
async () => {
|
|
1355
|
+
// The wake card ("Conversation Woke") is the visual entry point —
|
|
1356
|
+
// it must land in the first assistant message regardless of
|
|
1357
|
+
// whether the wake produced output via checkpoints or only via
|
|
1358
|
+
// post-run (tool-free) detection. This test covers the
|
|
1359
|
+
// checkpoint path; the existing post-run path is covered by the
|
|
1360
|
+
// tool_use tests above.
|
|
1361
|
+
const firstAssistant: Message = {
|
|
1362
|
+
role: "assistant",
|
|
1363
|
+
content: [
|
|
1364
|
+
{ type: "tool_use", id: "tu-1", name: "some_tool", input: {} },
|
|
1365
|
+
],
|
|
1366
|
+
};
|
|
1367
|
+
const toolResult: Message = {
|
|
1368
|
+
role: "user",
|
|
1369
|
+
content: [{ type: "tool_result", tool_use_id: "tu-1", content: "ok" }],
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
const persistedTailCalls: Message[] = [];
|
|
1373
|
+
const baseline: Message[] = [
|
|
1374
|
+
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
1375
|
+
];
|
|
1376
|
+
const history: Message[] = [...baseline];
|
|
1377
|
+
let processing = false;
|
|
1378
|
+
const wakeProducedOutputCalls: string[] = [];
|
|
1379
|
+
|
|
1380
|
+
const target: WakeTarget = {
|
|
1381
|
+
conversationId: "conv-card",
|
|
1382
|
+
agentLoop: {
|
|
1383
|
+
run: async (_input, _onEvent, _signal, _requestId, onCheckpoint) => {
|
|
1384
|
+
const runHistory: Message[] = [..._input];
|
|
1385
|
+
runHistory.push(firstAssistant);
|
|
1386
|
+
runHistory.push(toolResult);
|
|
1387
|
+
await onCheckpoint!({
|
|
1388
|
+
turnIndex: 0,
|
|
1389
|
+
toolCount: 1,
|
|
1390
|
+
hasToolUse: true,
|
|
1391
|
+
history: runHistory,
|
|
1392
|
+
});
|
|
1393
|
+
return runHistory;
|
|
1394
|
+
},
|
|
1395
|
+
},
|
|
1396
|
+
getMessages: () => history,
|
|
1397
|
+
pushMessage: (msg) => {
|
|
1398
|
+
history.push(msg);
|
|
1399
|
+
},
|
|
1400
|
+
emitAgentEvent: () => {},
|
|
1401
|
+
isProcessing: () => processing,
|
|
1402
|
+
markProcessing: (on) => {
|
|
1403
|
+
processing = on;
|
|
1404
|
+
},
|
|
1405
|
+
persistTailMessage: async (msg) => {
|
|
1406
|
+
persistedTailCalls.push(msg);
|
|
1407
|
+
},
|
|
1408
|
+
onWakeProducedOutput: (_source, _hint, surfaceId) => {
|
|
1409
|
+
wakeProducedOutputCalls.push(surfaceId);
|
|
1410
|
+
},
|
|
1411
|
+
};
|
|
1412
|
+
|
|
1413
|
+
await wakeAgentForOpportunity(
|
|
1414
|
+
{
|
|
1415
|
+
conversationId: "conv-card",
|
|
1416
|
+
hint: "do the thing",
|
|
1417
|
+
source: "memory_v2_consolidation",
|
|
1418
|
+
},
|
|
1419
|
+
{ resolveTarget: async () => target },
|
|
1420
|
+
);
|
|
1421
|
+
|
|
1422
|
+
// ui_surface fired exactly once (idempotent goLive), and the
|
|
1423
|
+
// surfaceId matches the block prepended into the first
|
|
1424
|
+
// assistant message.
|
|
1425
|
+
expect(wakeProducedOutputCalls).toHaveLength(1);
|
|
1426
|
+
const persistedFirst = persistedTailCalls[0];
|
|
1427
|
+
expect(persistedFirst).toBeDefined();
|
|
1428
|
+
const blocks = Array.isArray(persistedFirst!.content)
|
|
1429
|
+
? persistedFirst!.content
|
|
1430
|
+
: [];
|
|
1431
|
+
const uiBlock = blocks.find(
|
|
1432
|
+
(b: { type?: string }) => b.type === "ui_surface",
|
|
1433
|
+
) as { surfaceId?: string } | undefined;
|
|
1434
|
+
expect(uiBlock).toBeDefined();
|
|
1435
|
+
expect(uiBlock!.surfaceId).toBe(wakeProducedOutputCalls[0]);
|
|
1436
|
+
},
|
|
1437
|
+
);
|
|
985
1438
|
});
|