@vellumai/assistant 0.6.4 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { shouldCaptureAgentLoopError } from "../agent/loop.js";
|
|
4
|
+
import { ProviderError } from "../util/errors.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Regression coverage for JARVIS-446 and JARVIS-513.
|
|
8
|
+
*
|
|
9
|
+
* The agent loop reports uncaught turn-processing errors to Sentry, but two
|
|
10
|
+
* categories of errors are user-environment noise and should not page:
|
|
11
|
+
*
|
|
12
|
+
* - JARVIS-446: billing/auth/forbidden from the provider (402/401/403). The
|
|
13
|
+
* user-facing error path already surfaces a credits-exhausted message; a
|
|
14
|
+
* Sentry issue adds no engineering signal.
|
|
15
|
+
* - JARVIS-513: retry-exhausted transient network errors (ECONNRESET, Bun's
|
|
16
|
+
* "socket closed unexpectedly", etc.). The retry loop already did its job.
|
|
17
|
+
*
|
|
18
|
+
* `shouldCaptureAgentLoopError` gates the `Sentry.captureException` call.
|
|
19
|
+
*/
|
|
20
|
+
describe("shouldCaptureAgentLoopError", () => {
|
|
21
|
+
describe("JARVIS-446 — billing/auth/forbidden ProviderError", () => {
|
|
22
|
+
test("skips capture for 402 (billing exhausted)", () => {
|
|
23
|
+
const err = new ProviderError(
|
|
24
|
+
"Anthropic API error (402): credit balance too low",
|
|
25
|
+
"anthropic",
|
|
26
|
+
402,
|
|
27
|
+
);
|
|
28
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("skips capture for 401 (bad API key)", () => {
|
|
32
|
+
const err = new ProviderError(
|
|
33
|
+
"Anthropic API error (401): invalid x-api-key",
|
|
34
|
+
"anthropic",
|
|
35
|
+
401,
|
|
36
|
+
);
|
|
37
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("skips capture for 403 (forbidden / plan-gated)", () => {
|
|
41
|
+
const err = new ProviderError(
|
|
42
|
+
"Anthropic API error (403): permission denied",
|
|
43
|
+
"anthropic",
|
|
44
|
+
403,
|
|
45
|
+
);
|
|
46
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("still captures 500 (real server error)", () => {
|
|
50
|
+
const err = new ProviderError(
|
|
51
|
+
"Anthropic API error (500): internal server error",
|
|
52
|
+
"anthropic",
|
|
53
|
+
500,
|
|
54
|
+
);
|
|
55
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("still captures 400 (bad request — engineering bug)", () => {
|
|
59
|
+
const err = new ProviderError(
|
|
60
|
+
"Anthropic API error (400): invalid tool definition",
|
|
61
|
+
"anthropic",
|
|
62
|
+
400,
|
|
63
|
+
);
|
|
64
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("still captures ProviderError with no status (surprise error)", () => {
|
|
68
|
+
const err = new ProviderError(
|
|
69
|
+
"Anthropic API error: unexpected internal state",
|
|
70
|
+
"anthropic",
|
|
71
|
+
);
|
|
72
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("JARVIS-513 — retry-exhausted transient network errors", () => {
|
|
77
|
+
test("skips capture when retriesExhausted is set on an ECONNRESET", () => {
|
|
78
|
+
const err = Object.assign(new Error("connection reset"), {
|
|
79
|
+
code: "ECONNRESET",
|
|
80
|
+
retriesExhausted: true,
|
|
81
|
+
});
|
|
82
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("skips capture for Bun 'socket closed unexpectedly' with retriesExhausted", () => {
|
|
86
|
+
const err = new Error("The socket connection was closed unexpectedly");
|
|
87
|
+
(err as Error & { retriesExhausted?: boolean }).retriesExhausted = true;
|
|
88
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("skips capture for wrapped ProviderError whose cause is a transient socket error", () => {
|
|
92
|
+
const cause = new Error("The socket connection was closed unexpectedly");
|
|
93
|
+
const err = new ProviderError(
|
|
94
|
+
"Anthropic request failed: The socket connection was closed unexpectedly",
|
|
95
|
+
"anthropic",
|
|
96
|
+
undefined,
|
|
97
|
+
{ cause },
|
|
98
|
+
);
|
|
99
|
+
(err as Error & { retriesExhausted?: boolean }).retriesExhausted = true;
|
|
100
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("still captures ECONNRESET when retries were NOT exhausted", () => {
|
|
104
|
+
// If the first attempt threw with ECONNRESET and the retry loop somehow
|
|
105
|
+
// didn't run (e.g. a caller bypassed RetryProvider), we still want
|
|
106
|
+
// visibility — `retriesExhausted` wasn't set.
|
|
107
|
+
const err = Object.assign(new Error("connection reset"), {
|
|
108
|
+
code: "ECONNRESET",
|
|
109
|
+
});
|
|
110
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("still captures retriesExhausted marker on a non-network error", () => {
|
|
114
|
+
// The suppression is narrow: only retryable-network errors with the
|
|
115
|
+
// marker. A 500 with retriesExhausted still merits Sentry attention.
|
|
116
|
+
const err = new ProviderError(
|
|
117
|
+
"Anthropic API error (500): internal server error",
|
|
118
|
+
"anthropic",
|
|
119
|
+
500,
|
|
120
|
+
);
|
|
121
|
+
(err as Error & { retriesExhausted?: boolean }).retriesExhausted = true;
|
|
122
|
+
expect(shouldCaptureAgentLoopError(err)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("default behavior — everything else still pages", () => {
|
|
127
|
+
test("captures a plain surprise Error", () => {
|
|
128
|
+
expect(shouldCaptureAgentLoopError(new Error("boom"))).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("captures a TypeError", () => {
|
|
132
|
+
expect(shouldCaptureAgentLoopError(new TypeError("x is not a fn"))).toBe(
|
|
133
|
+
true,
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -26,12 +26,14 @@ function createMockProvider(responses: ProviderResponse[]): {
|
|
|
26
26
|
messages: Message[];
|
|
27
27
|
tools?: ToolDefinition[];
|
|
28
28
|
systemPrompt?: string;
|
|
29
|
+
options?: SendMessageOptions;
|
|
29
30
|
}[];
|
|
30
31
|
} {
|
|
31
32
|
const calls: {
|
|
32
33
|
messages: Message[];
|
|
33
34
|
tools?: ToolDefinition[];
|
|
34
35
|
systemPrompt?: string;
|
|
36
|
+
options?: SendMessageOptions;
|
|
35
37
|
}[] = [];
|
|
36
38
|
let callIndex = 0;
|
|
37
39
|
|
|
@@ -43,7 +45,7 @@ function createMockProvider(responses: ProviderResponse[]): {
|
|
|
43
45
|
systemPrompt?: string,
|
|
44
46
|
options?: SendMessageOptions,
|
|
45
47
|
): Promise<ProviderResponse> {
|
|
46
|
-
calls.push({ messages: [...messages], tools, systemPrompt });
|
|
48
|
+
calls.push({ messages: [...messages], tools, systemPrompt, options });
|
|
47
49
|
const response = responses[callIndex] ?? responses[responses.length - 1];
|
|
48
50
|
callIndex++;
|
|
49
51
|
|
|
@@ -1769,6 +1771,85 @@ describe("AgentLoop", () => {
|
|
|
1769
1771
|
expect(messageCompletes).toHaveLength(2);
|
|
1770
1772
|
});
|
|
1771
1773
|
|
|
1774
|
+
// Regression: when the model emits [text, tool_use] in a single turn and then
|
|
1775
|
+
// returns an empty response after the tool result, the loop must NOT nudge —
|
|
1776
|
+
// the model already delivered its reply before the tool call, and nudging
|
|
1777
|
+
// would trick it into re-sending the same text verbatim.
|
|
1778
|
+
test("does not nudge empty response when prior turn had visible text", async () => {
|
|
1779
|
+
const textPlusToolUseResponse: ProviderResponse = {
|
|
1780
|
+
content: [
|
|
1781
|
+
{ type: "text", text: "your move, husband." },
|
|
1782
|
+
{
|
|
1783
|
+
type: "tool_use",
|
|
1784
|
+
id: "t1",
|
|
1785
|
+
name: "read_file",
|
|
1786
|
+
input: { path: "/note.txt" },
|
|
1787
|
+
},
|
|
1788
|
+
],
|
|
1789
|
+
model: "mock-model",
|
|
1790
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
1791
|
+
stopReason: "tool_use",
|
|
1792
|
+
};
|
|
1793
|
+
const emptyResponse: ProviderResponse = {
|
|
1794
|
+
content: [],
|
|
1795
|
+
model: "mock-model",
|
|
1796
|
+
usage: { inputTokens: 10, outputTokens: 0 },
|
|
1797
|
+
stopReason: "end_turn",
|
|
1798
|
+
};
|
|
1799
|
+
|
|
1800
|
+
const { provider, calls } = createMockProvider([
|
|
1801
|
+
textPlusToolUseResponse,
|
|
1802
|
+
emptyResponse,
|
|
1803
|
+
]);
|
|
1804
|
+
|
|
1805
|
+
const toolExecutor = async () => ({
|
|
1806
|
+
content: "noted",
|
|
1807
|
+
isError: false,
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
const loop = new AgentLoop(
|
|
1811
|
+
provider,
|
|
1812
|
+
"system",
|
|
1813
|
+
{},
|
|
1814
|
+
dummyTools,
|
|
1815
|
+
toolExecutor,
|
|
1816
|
+
);
|
|
1817
|
+
const events: AgentEvent[] = [];
|
|
1818
|
+
const history = await loop.run([userMessage], collectEvents(events));
|
|
1819
|
+
|
|
1820
|
+
// Provider called exactly 2 times: initial [text+tool_use], then empty.
|
|
1821
|
+
// No third (retry) call because the prior turn had visible text.
|
|
1822
|
+
expect(calls).toHaveLength(2);
|
|
1823
|
+
|
|
1824
|
+
// No nudge message should appear anywhere in history.
|
|
1825
|
+
const nudgeInHistory = history.some(
|
|
1826
|
+
(m) =>
|
|
1827
|
+
m.role === "user" &&
|
|
1828
|
+
m.content.some(
|
|
1829
|
+
(b) =>
|
|
1830
|
+
b.type === "text" &&
|
|
1831
|
+
"text" in b &&
|
|
1832
|
+
(b as { text: string }).text.includes(
|
|
1833
|
+
"previous response was empty",
|
|
1834
|
+
),
|
|
1835
|
+
),
|
|
1836
|
+
);
|
|
1837
|
+
expect(nudgeInHistory).toBe(false);
|
|
1838
|
+
|
|
1839
|
+
// The [text, tool_use] assistant message is preserved in history.
|
|
1840
|
+
const firstAssistant = history.find((m) => m.role === "assistant");
|
|
1841
|
+
expect(firstAssistant).toBeDefined();
|
|
1842
|
+
expect(firstAssistant!.content).toEqual([
|
|
1843
|
+
{ type: "text", text: "your move, husband." },
|
|
1844
|
+
{
|
|
1845
|
+
type: "tool_use",
|
|
1846
|
+
id: "t1",
|
|
1847
|
+
name: "read_file",
|
|
1848
|
+
input: { path: "/note.txt" },
|
|
1849
|
+
},
|
|
1850
|
+
]);
|
|
1851
|
+
});
|
|
1852
|
+
|
|
1772
1853
|
test("gives up after max empty response retries", async () => {
|
|
1773
1854
|
const emptyResponse: ProviderResponse = {
|
|
1774
1855
|
content: [],
|
|
@@ -1831,4 +1912,34 @@ describe("AgentLoop", () => {
|
|
|
1831
1912
|
expect(calls).toHaveLength(1);
|
|
1832
1913
|
expect(history).toHaveLength(2); // user + empty assistant
|
|
1833
1914
|
});
|
|
1915
|
+
|
|
1916
|
+
// PR 6: callSite threading from AgentLoop.run() into provider config.
|
|
1917
|
+
// Verifies the per-call config exposes `callSite` so RetryProvider can route
|
|
1918
|
+
// through `resolveCallSiteConfig` instead of the legacy `modelIntent` path.
|
|
1919
|
+
test("threads callSite from AgentLoop.run() into per-call provider config", async () => {
|
|
1920
|
+
const { provider, calls } = createMockProvider([textResponse("ok")]);
|
|
1921
|
+
|
|
1922
|
+
const loop = new AgentLoop(provider, "system");
|
|
1923
|
+
await loop.run(
|
|
1924
|
+
[userMessage],
|
|
1925
|
+
() => {},
|
|
1926
|
+
undefined, // signal
|
|
1927
|
+
undefined, // requestId
|
|
1928
|
+
undefined, // onCheckpoint
|
|
1929
|
+
"heartbeatAgent",
|
|
1930
|
+
);
|
|
1931
|
+
|
|
1932
|
+
expect(calls).toHaveLength(1);
|
|
1933
|
+
expect(calls[0].options?.config?.callSite).toBe("heartbeatAgent");
|
|
1934
|
+
});
|
|
1935
|
+
|
|
1936
|
+
test("omits callSite from provider config when not supplied", async () => {
|
|
1937
|
+
const { provider, calls } = createMockProvider([textResponse("ok")]);
|
|
1938
|
+
|
|
1939
|
+
const loop = new AgentLoop(provider, "system");
|
|
1940
|
+
await loop.run([userMessage], () => {});
|
|
1941
|
+
|
|
1942
|
+
expect(calls).toHaveLength(1);
|
|
1943
|
+
expect(calls[0].options?.config?.callSite).toBeUndefined();
|
|
1944
|
+
});
|
|
1834
1945
|
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { Message } from "../providers/types.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mock Anthropic SDK — inject a throwing stream so we can assert on the
|
|
7
|
+
// message format produced by the client's error-mapping path (JARVIS-390).
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
class FakeAPIError extends Error {
|
|
11
|
+
status: number | undefined;
|
|
12
|
+
headers: Map<string, string> = new Map();
|
|
13
|
+
constructor(status: number | undefined, message: string) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.status = status;
|
|
16
|
+
this.name = "APIError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let nextThrown: FakeAPIError | null = null;
|
|
21
|
+
|
|
22
|
+
mock.module("@anthropic-ai/sdk", () => ({
|
|
23
|
+
default: class MockAnthropic {
|
|
24
|
+
static APIError = FakeAPIError;
|
|
25
|
+
constructor(_args: Record<string, unknown>) {}
|
|
26
|
+
#streamImpl = () => ({
|
|
27
|
+
on() {
|
|
28
|
+
return this;
|
|
29
|
+
},
|
|
30
|
+
async finalMessage() {
|
|
31
|
+
if (nextThrown) throw nextThrown;
|
|
32
|
+
return {
|
|
33
|
+
content: [],
|
|
34
|
+
model: "claude-sonnet-4-6",
|
|
35
|
+
usage: {
|
|
36
|
+
input_tokens: 0,
|
|
37
|
+
output_tokens: 0,
|
|
38
|
+
cache_creation_input_tokens: 0,
|
|
39
|
+
cache_read_input_tokens: 0,
|
|
40
|
+
},
|
|
41
|
+
stop_reason: "end_turn",
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
messages = { stream: () => this.#streamImpl() };
|
|
46
|
+
beta = { messages: { stream: () => this.#streamImpl() } };
|
|
47
|
+
},
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
import { AnthropicProvider } from "../providers/anthropic/client.js";
|
|
51
|
+
import { ProviderError } from "../util/errors.js";
|
|
52
|
+
|
|
53
|
+
function userMsg(text: string): Message {
|
|
54
|
+
return { role: "user", content: [{ type: "text", text }] };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe("AnthropicProvider — error message formatting (JARVIS-390)", () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
nextThrown = null;
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("omits the `(status)` parenthetical when the SDK reports no HTTP status", async () => {
|
|
63
|
+
// Reproduces the abort/mid-stream path where `error.status` is undefined.
|
|
64
|
+
nextThrown = new FakeAPIError(undefined, "Request was aborted.");
|
|
65
|
+
|
|
66
|
+
const provider = new AnthropicProvider("sk-ant-test", "claude-sonnet-4-6");
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await provider.sendMessage([userMsg("hi")]);
|
|
70
|
+
throw new Error("expected sendMessage to throw");
|
|
71
|
+
} catch (err) {
|
|
72
|
+
expect(err).toBeInstanceOf(ProviderError);
|
|
73
|
+
const message = (err as Error).message;
|
|
74
|
+
expect(message).toBe("Anthropic API error: Request was aborted.");
|
|
75
|
+
// Belt-and-suspenders: the literal "(undefined)" must never appear.
|
|
76
|
+
expect(message).not.toContain("(undefined)");
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("includes the `(status)` parenthetical when the SDK reports an HTTP status", async () => {
|
|
81
|
+
nextThrown = new FakeAPIError(
|
|
82
|
+
402,
|
|
83
|
+
"Billing issue: your credit balance is too low.",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const provider = new AnthropicProvider("sk-ant-test", "claude-sonnet-4-6");
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await provider.sendMessage([userMsg("hi")]);
|
|
90
|
+
throw new Error("expected sendMessage to throw");
|
|
91
|
+
} catch (err) {
|
|
92
|
+
expect(err).toBeInstanceOf(ProviderError);
|
|
93
|
+
const message = (err as Error).message;
|
|
94
|
+
expect(message).toContain("Anthropic API error (402):");
|
|
95
|
+
expect((err as ProviderError).statusCode).toBe(402);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -10,6 +10,15 @@ let lastStreamParams: Record<string, unknown> | null = null;
|
|
|
10
10
|
let _lastStreamOptions: Record<string, unknown> | null = null;
|
|
11
11
|
let lastConstructorArgs: Record<string, unknown> | null = null;
|
|
12
12
|
|
|
13
|
+
type ScriptedStreamEvent =
|
|
14
|
+
| { kind: "text"; text: string }
|
|
15
|
+
| { kind: "blockStart"; blockType?: "text" }
|
|
16
|
+
| { kind: "blockStop" };
|
|
17
|
+
|
|
18
|
+
// When set, the mock fires these scripted stream events in order instead of
|
|
19
|
+
// the default single "Hello" text event. Tests reset this in beforeEach.
|
|
20
|
+
let scriptedStream: ScriptedStreamEvent[] | null = null;
|
|
21
|
+
|
|
13
22
|
const fakeResponse = {
|
|
14
23
|
content: [{ type: "text", text: "Hello" }],
|
|
15
24
|
model: "claude-sonnet-4-6",
|
|
@@ -50,8 +59,25 @@ mock.module("@anthropic-ai/sdk", () => ({
|
|
|
50
59
|
return this;
|
|
51
60
|
},
|
|
52
61
|
async finalMessage() {
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
if (scriptedStream) {
|
|
63
|
+
for (const event of scriptedStream) {
|
|
64
|
+
if (event.kind === "text") {
|
|
65
|
+
for (const cb of handlers["text"] ?? []) cb(event.text);
|
|
66
|
+
} else if (event.kind === "blockStart") {
|
|
67
|
+
for (const cb of handlers["streamEvent"] ?? [])
|
|
68
|
+
cb({
|
|
69
|
+
type: "content_block_start",
|
|
70
|
+
content_block: { type: event.blockType ?? "text" },
|
|
71
|
+
});
|
|
72
|
+
} else if (event.kind === "blockStop") {
|
|
73
|
+
for (const cb of handlers["streamEvent"] ?? [])
|
|
74
|
+
cb({ type: "content_block_stop" });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
// Default: a single "Hello" text event (preserves existing tests).
|
|
79
|
+
for (const cb of handlers["text"] ?? []) cb("Hello");
|
|
80
|
+
}
|
|
55
81
|
return fakeResponse;
|
|
56
82
|
},
|
|
57
83
|
};
|
|
@@ -77,6 +103,7 @@ mock.module("@anthropic-ai/sdk", () => ({
|
|
|
77
103
|
import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../prompts/system-prompt.js";
|
|
78
104
|
import {
|
|
79
105
|
AnthropicProvider,
|
|
106
|
+
isPlaceholderSentinelText,
|
|
80
107
|
PLACEHOLDER_BLOCKS_OMITTED,
|
|
81
108
|
PLACEHOLDER_EMPTY_TURN,
|
|
82
109
|
} from "../providers/anthropic/client.js";
|
|
@@ -144,6 +171,7 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
144
171
|
lastStreamParams = null;
|
|
145
172
|
_lastStreamOptions = null;
|
|
146
173
|
lastConstructorArgs = null;
|
|
174
|
+
scriptedStream = null;
|
|
147
175
|
provider = new AnthropicProvider("sk-ant-test", "claude-sonnet-4-6");
|
|
148
176
|
});
|
|
149
177
|
|
|
@@ -1254,6 +1282,147 @@ describe("AnthropicProvider — Cache-Control Characterization", () => {
|
|
|
1254
1282
|
}
|
|
1255
1283
|
});
|
|
1256
1284
|
|
|
1285
|
+
test("streams normal text through without delay when text is not a sentinel prefix", async () => {
|
|
1286
|
+
scriptedStream = [
|
|
1287
|
+
{ kind: "blockStart" },
|
|
1288
|
+
{ kind: "text", text: "Hello world" },
|
|
1289
|
+
{ kind: "blockStop" },
|
|
1290
|
+
];
|
|
1291
|
+
const emitted: string[] = [];
|
|
1292
|
+
await provider.sendMessage([userMsg("Hi")], undefined, undefined, {
|
|
1293
|
+
onEvent: (event) => {
|
|
1294
|
+
if (event.type === "text_delta") emitted.push(event.text);
|
|
1295
|
+
},
|
|
1296
|
+
});
|
|
1297
|
+
expect(emitted).toEqual(["Hello world"]);
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
test("suppresses placeholder sentinel streamed as a single chunk", async () => {
|
|
1301
|
+
// Model echoes a sentinel in one chunk; buffer should hold it and
|
|
1302
|
+
// drop it on content_block_stop since it matches exactly.
|
|
1303
|
+
scriptedStream = [
|
|
1304
|
+
{ kind: "blockStart" },
|
|
1305
|
+
{ kind: "text", text: PLACEHOLDER_EMPTY_TURN },
|
|
1306
|
+
{ kind: "blockStop" },
|
|
1307
|
+
];
|
|
1308
|
+
const emitted: string[] = [];
|
|
1309
|
+
await provider.sendMessage([userMsg("Hi")], undefined, undefined, {
|
|
1310
|
+
onEvent: (event) => {
|
|
1311
|
+
if (event.type === "text_delta") emitted.push(event.text);
|
|
1312
|
+
},
|
|
1313
|
+
});
|
|
1314
|
+
expect(emitted).toEqual([]);
|
|
1315
|
+
});
|
|
1316
|
+
|
|
1317
|
+
test("suppresses bare-variant sentinel streamed as a single chunk", async () => {
|
|
1318
|
+
scriptedStream = [
|
|
1319
|
+
{ kind: "blockStart" },
|
|
1320
|
+
{ kind: "text", text: "__PLACEHOLDER__[empty assistant turn]" },
|
|
1321
|
+
{ kind: "blockStop" },
|
|
1322
|
+
];
|
|
1323
|
+
const emitted: string[] = [];
|
|
1324
|
+
await provider.sendMessage([userMsg("Hi")], undefined, undefined, {
|
|
1325
|
+
onEvent: (event) => {
|
|
1326
|
+
if (event.type === "text_delta") emitted.push(event.text);
|
|
1327
|
+
},
|
|
1328
|
+
});
|
|
1329
|
+
expect(emitted).toEqual([]);
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
test("suppresses placeholder sentinel streamed across multiple chunks", async () => {
|
|
1333
|
+
scriptedStream = [
|
|
1334
|
+
{ kind: "blockStart" },
|
|
1335
|
+
{ kind: "text", text: "\x00__PLACE" },
|
|
1336
|
+
{ kind: "text", text: "HOLDER__[empty" },
|
|
1337
|
+
{ kind: "text", text: " assistant turn]" },
|
|
1338
|
+
{ kind: "blockStop" },
|
|
1339
|
+
];
|
|
1340
|
+
const emitted: string[] = [];
|
|
1341
|
+
await provider.sendMessage([userMsg("Hi")], undefined, undefined, {
|
|
1342
|
+
onEvent: (event) => {
|
|
1343
|
+
if (event.type === "text_delta") emitted.push(event.text);
|
|
1344
|
+
},
|
|
1345
|
+
});
|
|
1346
|
+
expect(emitted).toEqual([]);
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
test("flushes buffered prefix when the continuation diverges from all sentinels", async () => {
|
|
1350
|
+
// "__PLACEHOLDER__" is a prefix of the sentinels, so it stays buffered.
|
|
1351
|
+
// Once the next chunk diverges (bracket instead of the expected opening),
|
|
1352
|
+
// the buffer must flush.
|
|
1353
|
+
scriptedStream = [
|
|
1354
|
+
{ kind: "blockStart" },
|
|
1355
|
+
{ kind: "text", text: "__PLACEHOLDER__" },
|
|
1356
|
+
{ kind: "text", text: " is bold in markdown" },
|
|
1357
|
+
{ kind: "blockStop" },
|
|
1358
|
+
];
|
|
1359
|
+
const emitted: string[] = [];
|
|
1360
|
+
await provider.sendMessage([userMsg("Hi")], undefined, undefined, {
|
|
1361
|
+
onEvent: (event) => {
|
|
1362
|
+
if (event.type === "text_delta") emitted.push(event.text);
|
|
1363
|
+
},
|
|
1364
|
+
});
|
|
1365
|
+
expect(emitted.join("")).toBe("__PLACEHOLDER__ is bold in markdown");
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
test("flushes non-sentinel residual buffer at content_block_stop", async () => {
|
|
1369
|
+
// If the stream ends mid-prefix, the residual must be flushed (the
|
|
1370
|
+
// accumulated text isn't a complete sentinel, so it's real content).
|
|
1371
|
+
scriptedStream = [
|
|
1372
|
+
{ kind: "blockStart" },
|
|
1373
|
+
{ kind: "text", text: "__PLACEHOLDER__" },
|
|
1374
|
+
{ kind: "blockStop" },
|
|
1375
|
+
];
|
|
1376
|
+
const emitted: string[] = [];
|
|
1377
|
+
await provider.sendMessage([userMsg("Hi")], undefined, undefined, {
|
|
1378
|
+
onEvent: (event) => {
|
|
1379
|
+
if (event.type === "text_delta") emitted.push(event.text);
|
|
1380
|
+
},
|
|
1381
|
+
});
|
|
1382
|
+
expect(emitted).toEqual(["__PLACEHOLDER__"]);
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
test("resets buffer across content blocks so a sentinel in one block doesn't poison the next", async () => {
|
|
1386
|
+
scriptedStream = [
|
|
1387
|
+
{ kind: "blockStart" },
|
|
1388
|
+
{ kind: "text", text: PLACEHOLDER_EMPTY_TURN },
|
|
1389
|
+
{ kind: "blockStop" },
|
|
1390
|
+
{ kind: "blockStart" },
|
|
1391
|
+
{ kind: "text", text: "Fresh block content" },
|
|
1392
|
+
{ kind: "blockStop" },
|
|
1393
|
+
];
|
|
1394
|
+
const emitted: string[] = [];
|
|
1395
|
+
await provider.sendMessage([userMsg("Hi")], undefined, undefined, {
|
|
1396
|
+
onEvent: (event) => {
|
|
1397
|
+
if (event.type === "text_delta") emitted.push(event.text);
|
|
1398
|
+
},
|
|
1399
|
+
});
|
|
1400
|
+
expect(emitted).toEqual(["Fresh block content"]);
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
test("isPlaceholderSentinelText matches sentinel with and without the null-byte prefix", () => {
|
|
1404
|
+
// The runtime filter must be lenient enough to catch sentinel text that
|
|
1405
|
+
// lost its `\x00` prefix in transit (e.g. a model echoing it back from
|
|
1406
|
+
// input history without reproducing the control character). Migration 222
|
|
1407
|
+
// handles the same two variants.
|
|
1408
|
+
expect(isPlaceholderSentinelText(PLACEHOLDER_EMPTY_TURN)).toBe(true);
|
|
1409
|
+
expect(isPlaceholderSentinelText(PLACEHOLDER_BLOCKS_OMITTED)).toBe(true);
|
|
1410
|
+
expect(
|
|
1411
|
+
isPlaceholderSentinelText("__PLACEHOLDER__[empty assistant turn]"),
|
|
1412
|
+
).toBe(true);
|
|
1413
|
+
expect(
|
|
1414
|
+
isPlaceholderSentinelText("__PLACEHOLDER__[internal blocks omitted]"),
|
|
1415
|
+
).toBe(true);
|
|
1416
|
+
// Nearby strings must NOT match — guard against over-broad matching.
|
|
1417
|
+
expect(isPlaceholderSentinelText("")).toBe(false);
|
|
1418
|
+
expect(isPlaceholderSentinelText("__PLACEHOLDER__")).toBe(false);
|
|
1419
|
+
expect(
|
|
1420
|
+
isPlaceholderSentinelText(
|
|
1421
|
+
"prefix __PLACEHOLDER__[empty assistant turn]",
|
|
1422
|
+
),
|
|
1423
|
+
).toBe(false);
|
|
1424
|
+
});
|
|
1425
|
+
|
|
1257
1426
|
// -----------------------------------------------------------------------
|
|
1258
1427
|
// Workspace context injection + cache control
|
|
1259
1428
|
// -----------------------------------------------------------------------
|
|
@@ -56,16 +56,34 @@ mock.module("../providers/registry.js", () => ({
|
|
|
56
56
|
|
|
57
57
|
mock.module("../config/loader.js", () => ({
|
|
58
58
|
getConfig: () => ({
|
|
59
|
-
ui: {},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
ui: {},
|
|
60
|
+
llm: {
|
|
61
|
+
default: {
|
|
62
|
+
provider: "mock-provider",
|
|
63
|
+
model: "mock-model",
|
|
64
|
+
maxTokens: 4096,
|
|
65
|
+
effort: "max" as const,
|
|
66
|
+
speed: "standard" as const,
|
|
67
|
+
temperature: null,
|
|
68
|
+
thinking: { enabled: false, streamThinking: true },
|
|
69
|
+
contextWindow: {
|
|
70
|
+
enabled: true,
|
|
71
|
+
maxInputTokens: 100000,
|
|
72
|
+
targetBudgetRatio: 0.3,
|
|
73
|
+
compactThreshold: 0.8,
|
|
74
|
+
summaryBudgetRatio: 0.05,
|
|
75
|
+
overflowRecovery: {
|
|
76
|
+
enabled: true,
|
|
77
|
+
safetyMarginRatio: 0.05,
|
|
78
|
+
maxAttempts: 3,
|
|
79
|
+
interactiveLatestTurnCompression: "summarize",
|
|
80
|
+
nonInteractiveLatestTurnCompression: "truncate",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
profiles: {},
|
|
85
|
+
callSites: {},
|
|
86
|
+
pricingOverrides: [],
|
|
69
87
|
},
|
|
70
88
|
rateLimit: { maxRequestsPerMinute: 0 },
|
|
71
89
|
timeouts: { permissionTimeoutSec: 300 },
|
|
@@ -187,6 +205,9 @@ mock.module("../agent/loop.js", () => ({
|
|
|
187
205
|
getToolTokenBudget() {
|
|
188
206
|
return 0;
|
|
189
207
|
}
|
|
208
|
+
getActiveModel() {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
190
211
|
async run(
|
|
191
212
|
_messages: Message[],
|
|
192
213
|
_onEvent: (event: AgentEvent) => void,
|