@vellumai/assistant 0.8.2 → 0.8.4
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 +11 -12
- package/docker-entrypoint.sh +13 -2
- package/docker-init-apt-root.sh +79 -6
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +945 -36
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +112 -57
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +77 -3
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +7 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +340 -3
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +323 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +57 -101
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +208 -22
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +89 -37
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +124 -0
- package/src/config/feature-flag-registry.json +111 -23
- package/src/config/llm-resolver.ts +66 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +4 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +51 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +80 -13
- package/src/context/token-estimator.ts +72 -31
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
- package/src/daemon/conversation-agent-loop.ts +252 -56
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +144 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -12
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +449 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +80 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +67 -65
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +72 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
- package/src/heartbeat/heartbeat-service.ts +35 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +20 -3
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +22 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +4 -22
- package/src/memory/memory-retrospective-job.ts +438 -21
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +20 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +2 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +221 -17
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +69 -29
- package/src/memory/v2/page-index.ts +246 -19
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +18 -3
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +136 -27
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +118 -26
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +44 -45
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -76
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +51 -10
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +24 -21
- package/src/providers/inference/auth.ts +15 -3
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +50 -5
- package/src/providers/model-catalog.ts +244 -242
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +215 -25
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +46 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +212 -57
- package/src/runtime/auth/route-policy.ts +20 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +7 -51
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +130 -12
- package/src/runtime/routes/conversation-routes.ts +288 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +14 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
- package/src/runtime/routes/integrations/a2a.ts +294 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/subagent/manager.ts +2 -0
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/registry.ts +2 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +41 -2
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the `agent_loop_exit` instrumentation added in this PR.
|
|
3
|
+
*
|
|
4
|
+
* Coverage targets:
|
|
5
|
+
* 1. **One emit per run** — the idempotency guard fires once, even if the
|
|
6
|
+
* code path would otherwise reach two emit sites (the empty-response
|
|
7
|
+
* throw → catch-block fallback case).
|
|
8
|
+
* 2. **Reason matches break site** — for each reachable break site, the
|
|
9
|
+
* emitted reason is the one documented in `AgentLoopExitReason`.
|
|
10
|
+
* 3. **Always the last AgentEvent of terminal runs** — consumers can rely on
|
|
11
|
+
* positional ordering to find it when a run reaches a terminal state.
|
|
12
|
+
*
|
|
13
|
+
* Sites not exercised here (`empty_response_exhausted`, `aborted_via_error`)
|
|
14
|
+
* require deeper provider fakery and are best covered by integration tests
|
|
15
|
+
* once we wire up the empty-response pipeline mock.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, expect, test } from "bun:test";
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
AgentEvent,
|
|
21
|
+
CheckpointDecision,
|
|
22
|
+
CheckpointInfo,
|
|
23
|
+
} from "../agent/loop.js";
|
|
24
|
+
import { AgentLoop } from "../agent/loop.js";
|
|
25
|
+
import type {
|
|
26
|
+
Message,
|
|
27
|
+
Provider,
|
|
28
|
+
ProviderResponse,
|
|
29
|
+
SendMessageOptions,
|
|
30
|
+
ToolDefinition,
|
|
31
|
+
} from "../providers/types.js";
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Helpers (mirrored from agent-loop.test.ts so this file is self-contained)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
function createMockProvider(responses: ProviderResponse[]): {
|
|
38
|
+
provider: Provider;
|
|
39
|
+
} {
|
|
40
|
+
let callIndex = 0;
|
|
41
|
+
const provider: Provider = {
|
|
42
|
+
name: "mock",
|
|
43
|
+
async sendMessage(
|
|
44
|
+
_messages: Message[],
|
|
45
|
+
_tools?: ToolDefinition[],
|
|
46
|
+
_systemPrompt?: string,
|
|
47
|
+
_options?: SendMessageOptions,
|
|
48
|
+
): Promise<ProviderResponse> {
|
|
49
|
+
const response = responses[callIndex] ?? responses[responses.length - 1];
|
|
50
|
+
callIndex++;
|
|
51
|
+
return response;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return { provider };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function textResponse(text: string): ProviderResponse {
|
|
58
|
+
return {
|
|
59
|
+
content: [{ type: "text", text }],
|
|
60
|
+
model: "mock-model",
|
|
61
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
62
|
+
stopReason: "end_turn",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function toolUseResponse(
|
|
67
|
+
id: string,
|
|
68
|
+
name: string,
|
|
69
|
+
input: Record<string, unknown>,
|
|
70
|
+
): ProviderResponse {
|
|
71
|
+
return {
|
|
72
|
+
content: [{ type: "tool_use", id, name, input }],
|
|
73
|
+
model: "mock-model",
|
|
74
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
75
|
+
stopReason: "tool_use",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const dummyTools: ToolDefinition[] = [
|
|
80
|
+
{
|
|
81
|
+
name: "read_file",
|
|
82
|
+
description: "Read a file",
|
|
83
|
+
input_schema: { type: "object", properties: { path: { type: "string" } } },
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const userMessage: Message = {
|
|
88
|
+
role: "user",
|
|
89
|
+
content: [{ type: "text", text: "Hello" }],
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
function lastExitEvent(
|
|
93
|
+
events: AgentEvent[],
|
|
94
|
+
): Extract<AgentEvent, { type: "agent_loop_exit" }> | undefined {
|
|
95
|
+
return events.find(
|
|
96
|
+
(e): e is Extract<AgentEvent, { type: "agent_loop_exit" }> =>
|
|
97
|
+
e.type === "agent_loop_exit",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function countExitEvents(events: AgentEvent[]): number {
|
|
102
|
+
return events.filter((e) => e.type === "agent_loop_exit").length;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Tests
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
describe("AgentLoop exit-reason instrumentation", () => {
|
|
110
|
+
test("emits exit event exactly once with 'no_tool_calls' on plain text response", async () => {
|
|
111
|
+
const { provider } = createMockProvider([textResponse("Hi there!")]);
|
|
112
|
+
const loop = new AgentLoop(provider, "system prompt");
|
|
113
|
+
|
|
114
|
+
const events: AgentEvent[] = [];
|
|
115
|
+
await loop.run([userMessage], (e) => {
|
|
116
|
+
events.push(e);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(countExitEvents(events)).toBe(1);
|
|
120
|
+
const exit = lastExitEvent(events);
|
|
121
|
+
expect(exit?.reason).toBe("no_tool_calls");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("agent_loop_exit is the last event emitted", async () => {
|
|
125
|
+
const { provider } = createMockProvider([textResponse("Hi there!")]);
|
|
126
|
+
const loop = new AgentLoop(provider, "system prompt");
|
|
127
|
+
|
|
128
|
+
const events: AgentEvent[] = [];
|
|
129
|
+
await loop.run([userMessage], (e) => {
|
|
130
|
+
events.push(e);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(events.length).toBeGreaterThan(0);
|
|
134
|
+
expect(events[events.length - 1].type).toBe("agent_loop_exit");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("emits 'aborted_pre_call' when signal is already aborted at run start", async () => {
|
|
138
|
+
const { provider } = createMockProvider([textResponse("never sent")]);
|
|
139
|
+
const loop = new AgentLoop(provider, "system prompt");
|
|
140
|
+
|
|
141
|
+
const controller = new AbortController();
|
|
142
|
+
controller.abort();
|
|
143
|
+
|
|
144
|
+
const events: AgentEvent[] = [];
|
|
145
|
+
await loop.run([userMessage], (e) => { events.push(e); }, controller.signal);
|
|
146
|
+
|
|
147
|
+
expect(countExitEvents(events)).toBe(1);
|
|
148
|
+
expect(lastExitEvent(events)?.reason).toBe("aborted_pre_call");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("emits 'yield_to_user' when tool result requests yieldToUser", async () => {
|
|
152
|
+
const { provider } = createMockProvider([
|
|
153
|
+
toolUseResponse("t1", "read_file", { path: "/a.txt" }),
|
|
154
|
+
]);
|
|
155
|
+
const toolExecutor = async () => ({
|
|
156
|
+
content: "ok",
|
|
157
|
+
isError: false,
|
|
158
|
+
yieldToUser: true,
|
|
159
|
+
});
|
|
160
|
+
const loop = new AgentLoop(
|
|
161
|
+
provider,
|
|
162
|
+
"system",
|
|
163
|
+
{},
|
|
164
|
+
dummyTools,
|
|
165
|
+
toolExecutor,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const events: AgentEvent[] = [];
|
|
169
|
+
await loop.run([userMessage], (e) => { events.push(e); });
|
|
170
|
+
|
|
171
|
+
expect(countExitEvents(events)).toBe(1);
|
|
172
|
+
expect(lastExitEvent(events)?.reason).toBe("yield_to_user");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("does not emit agent_loop_exit when onCheckpoint yields control", async () => {
|
|
176
|
+
const { provider } = createMockProvider([
|
|
177
|
+
toolUseResponse("t1", "read_file", { path: "/a.txt" }),
|
|
178
|
+
textResponse("never reached"),
|
|
179
|
+
]);
|
|
180
|
+
const toolExecutor = async () => ({ content: "ok", isError: false });
|
|
181
|
+
const loop = new AgentLoop(
|
|
182
|
+
provider,
|
|
183
|
+
"system",
|
|
184
|
+
{},
|
|
185
|
+
dummyTools,
|
|
186
|
+
toolExecutor,
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const onCheckpoint = (_info: CheckpointInfo): CheckpointDecision =>
|
|
190
|
+
"yield";
|
|
191
|
+
|
|
192
|
+
const events: AgentEvent[] = [];
|
|
193
|
+
await loop.run(
|
|
194
|
+
[userMessage],
|
|
195
|
+
(e) => { events.push(e); },
|
|
196
|
+
undefined,
|
|
197
|
+
undefined,
|
|
198
|
+
onCheckpoint,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(countExitEvents(events)).toBe(0);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("emits 'error' when provider throws an unhandled error", async () => {
|
|
205
|
+
const provider: Provider = {
|
|
206
|
+
name: "broken",
|
|
207
|
+
async sendMessage(): Promise<ProviderResponse> {
|
|
208
|
+
throw new Error("provider exploded");
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
const loop = new AgentLoop(provider, "system prompt");
|
|
212
|
+
|
|
213
|
+
const events: AgentEvent[] = [];
|
|
214
|
+
await loop.run([userMessage], (e) => { events.push(e); });
|
|
215
|
+
|
|
216
|
+
expect(countExitEvents(events)).toBe(1);
|
|
217
|
+
expect(lastExitEvent(events)?.reason).toBe("error");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test("does not double-emit when multiple exit conditions stack", async () => {
|
|
221
|
+
// Tool returns yieldToUser AND the controller is aborted post-response —
|
|
222
|
+
// the first reached condition wins, but the guard prevents a second
|
|
223
|
+
// emit even if subsequent code paths attempt one.
|
|
224
|
+
const { provider } = createMockProvider([
|
|
225
|
+
toolUseResponse("t1", "read_file", { path: "/a.txt" }),
|
|
226
|
+
]);
|
|
227
|
+
const toolExecutor = async () => ({
|
|
228
|
+
content: "ok",
|
|
229
|
+
isError: false,
|
|
230
|
+
yieldToUser: true,
|
|
231
|
+
});
|
|
232
|
+
const loop = new AgentLoop(
|
|
233
|
+
provider,
|
|
234
|
+
"system",
|
|
235
|
+
{},
|
|
236
|
+
dummyTools,
|
|
237
|
+
toolExecutor,
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const events: AgentEvent[] = [];
|
|
241
|
+
await loop.run([userMessage], (e) => { events.push(e); });
|
|
242
|
+
|
|
243
|
+
expect(countExitEvents(events)).toBe(1);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("emits 'aborted_during_tools' when signal aborts after tool execution", async () => {
|
|
247
|
+
const controller = new AbortController();
|
|
248
|
+
const { provider } = createMockProvider([
|
|
249
|
+
toolUseResponse("t1", "read_file", { path: "/a.txt" }),
|
|
250
|
+
]);
|
|
251
|
+
// Abort the signal inside the tool executor so by the time the loop
|
|
252
|
+
// re-checks signal.aborted post-tools the abort has landed.
|
|
253
|
+
const toolExecutor = async () => {
|
|
254
|
+
controller.abort();
|
|
255
|
+
return { content: "ok", isError: false };
|
|
256
|
+
};
|
|
257
|
+
const loop = new AgentLoop(
|
|
258
|
+
provider,
|
|
259
|
+
"system",
|
|
260
|
+
{},
|
|
261
|
+
dummyTools,
|
|
262
|
+
toolExecutor,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const events: AgentEvent[] = [];
|
|
266
|
+
await loop.run([userMessage], (e) => { events.push(e); }, controller.signal);
|
|
267
|
+
|
|
268
|
+
expect(countExitEvents(events)).toBe(1);
|
|
269
|
+
expect(lastExitEvent(events)?.reason).toBe("aborted_during_tools");
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -257,7 +257,7 @@ const anthropicStub = { name: "anthropic" };
|
|
|
257
257
|
mock.module("../providers/registry.js", () => ({
|
|
258
258
|
getProvider: () => anthropicStub,
|
|
259
259
|
listProviders: () => ["anthropic"],
|
|
260
|
-
initializeProviders: () => {},
|
|
260
|
+
initializeProviders: async () => {},
|
|
261
261
|
resolveProviderFromConnection: async () => anthropicStub,
|
|
262
262
|
}));
|
|
263
263
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for the agent loop's `provider_error` recording path.
|
|
3
|
+
*
|
|
4
|
+
* When the `llmCall` pipeline throws (provider rejected the request before
|
|
5
|
+
* returning a usable response), the loop must emit a `provider_error` event
|
|
6
|
+
* carrying the loop-level raw request and the thrown error so downstream
|
|
7
|
+
* consumers can persist an `llm_request_logs` row. Without this, rejected
|
|
8
|
+
* calls leave nothing in the LLM inspector — only a pino log line.
|
|
9
|
+
*
|
|
10
|
+
* Coverage:
|
|
11
|
+
* - Emits `provider_error` with `rawRequest`, `error`, and `actualProvider`
|
|
12
|
+
* when the provider throws a `ProviderError`.
|
|
13
|
+
* - `rawRequest` carries the message history, tools, and system prompt the
|
|
14
|
+
* loop attempted to send — so the row replays/debugs cleanly.
|
|
15
|
+
* - `actualProvider` echoes `ProviderError.provider` when available, falling
|
|
16
|
+
* back to `provider.name` for non-ProviderError throws.
|
|
17
|
+
* - The error is still re-thrown internally (the existing `error` event
|
|
18
|
+
* still fires after the new `provider_error` event), preserving the
|
|
19
|
+
* outer-catch behavior (abort/Sentry/break).
|
|
20
|
+
* - Skips emission on user-aborted runs — there is no provider rejection
|
|
21
|
+
* worth recording when the user cancelled.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { describe, expect, test } from "bun:test";
|
|
25
|
+
|
|
26
|
+
import type { AgentEvent } from "../agent/loop.js";
|
|
27
|
+
import { AgentLoop } from "../agent/loop.js";
|
|
28
|
+
import type {
|
|
29
|
+
Message,
|
|
30
|
+
Provider,
|
|
31
|
+
ProviderResponse,
|
|
32
|
+
SendMessageOptions,
|
|
33
|
+
ToolDefinition,
|
|
34
|
+
} from "../providers/types.js";
|
|
35
|
+
import { ProviderError } from "../util/errors.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build a provider that throws on every `sendMessage` call. Records what
|
|
39
|
+
* the loop attempted to send so the test can assert `rawRequest` carries
|
|
40
|
+
* the right payload.
|
|
41
|
+
*/
|
|
42
|
+
function makeThrowingProvider(
|
|
43
|
+
name: string,
|
|
44
|
+
throwFn: () => Error,
|
|
45
|
+
): {
|
|
46
|
+
provider: Provider;
|
|
47
|
+
calls: Array<{
|
|
48
|
+
messages: Message[];
|
|
49
|
+
tools?: ToolDefinition[];
|
|
50
|
+
systemPrompt?: string;
|
|
51
|
+
}>;
|
|
52
|
+
} {
|
|
53
|
+
const calls: Array<{
|
|
54
|
+
messages: Message[];
|
|
55
|
+
tools?: ToolDefinition[];
|
|
56
|
+
systemPrompt?: string;
|
|
57
|
+
}> = [];
|
|
58
|
+
const provider: Provider = {
|
|
59
|
+
name,
|
|
60
|
+
async sendMessage(
|
|
61
|
+
messages: Message[],
|
|
62
|
+
tools?: ToolDefinition[],
|
|
63
|
+
systemPrompt?: string,
|
|
64
|
+
_options?: SendMessageOptions,
|
|
65
|
+
): Promise<ProviderResponse> {
|
|
66
|
+
calls.push({ messages: [...messages], tools, systemPrompt });
|
|
67
|
+
throw throwFn();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return { provider, calls };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
describe("AgentLoop provider_error event emission", () => {
|
|
74
|
+
test("emits provider_error with loop-level rawRequest when provider throws ProviderError", async () => {
|
|
75
|
+
const thrown = new ProviderError(
|
|
76
|
+
"Anthropic API error (429): rate limited",
|
|
77
|
+
"anthropic",
|
|
78
|
+
429,
|
|
79
|
+
{ retryAfterMs: 1500 },
|
|
80
|
+
);
|
|
81
|
+
const { provider, calls } = makeThrowingProvider("anthropic", () => thrown);
|
|
82
|
+
|
|
83
|
+
const events: AgentEvent[] = [];
|
|
84
|
+
const loop = new AgentLoop(provider, "you are a helpful assistant");
|
|
85
|
+
|
|
86
|
+
await loop.run(
|
|
87
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
88
|
+
(e) => {
|
|
89
|
+
events.push(e);
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(calls).toHaveLength(1);
|
|
94
|
+
|
|
95
|
+
const providerErrorEvent = events.find((e) => e.type === "provider_error");
|
|
96
|
+
expect(providerErrorEvent).toBeDefined();
|
|
97
|
+
if (providerErrorEvent?.type !== "provider_error") {
|
|
98
|
+
throw new Error("type narrowing");
|
|
99
|
+
}
|
|
100
|
+
expect(providerErrorEvent.error).toBe(thrown);
|
|
101
|
+
expect(providerErrorEvent.actualProvider).toBe("anthropic");
|
|
102
|
+
|
|
103
|
+
// rawRequest should carry the loop-level abstract shape: messages,
|
|
104
|
+
// tools, systemPrompt, and the provider name we tried to dispatch
|
|
105
|
+
// through. The provider-specific shape (e.g. Gemini's `contents`) is
|
|
106
|
+
// never built because the provider threw before returning it.
|
|
107
|
+
const raw = providerErrorEvent.rawRequest as Record<string, unknown>;
|
|
108
|
+
expect(raw.provider).toBe("anthropic");
|
|
109
|
+
expect(raw.systemPrompt).toBe("you are a helpful assistant");
|
|
110
|
+
expect(Array.isArray(raw.messages)).toBe(true);
|
|
111
|
+
expect((raw.messages as Message[])[0].role).toBe("user");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("error event still fires after provider_error (outer catch behavior unchanged)", async () => {
|
|
115
|
+
const thrown = new ProviderError(
|
|
116
|
+
"Gemini API error (500): internal",
|
|
117
|
+
"gemini",
|
|
118
|
+
500,
|
|
119
|
+
);
|
|
120
|
+
const { provider } = makeThrowingProvider("gemini", () => thrown);
|
|
121
|
+
|
|
122
|
+
const events: AgentEvent[] = [];
|
|
123
|
+
const loop = new AgentLoop(provider, "system");
|
|
124
|
+
|
|
125
|
+
await loop.run(
|
|
126
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
127
|
+
(e) => {
|
|
128
|
+
events.push(e);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const providerErrorIdx = events.findIndex(
|
|
133
|
+
(e) => e.type === "provider_error",
|
|
134
|
+
);
|
|
135
|
+
const errorIdx = events.findIndex((e) => e.type === "error");
|
|
136
|
+
expect(providerErrorIdx).toBeGreaterThanOrEqual(0);
|
|
137
|
+
expect(errorIdx).toBeGreaterThanOrEqual(0);
|
|
138
|
+
// Recording-first ordering is load-bearing: a consumer that sees the
|
|
139
|
+
// generic `error` event and shuts the stream down must have already
|
|
140
|
+
// received the `provider_error` row for the rejected call.
|
|
141
|
+
expect(providerErrorIdx).toBeLessThan(errorIdx);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("falls back to provider.name when a non-ProviderError is thrown", async () => {
|
|
145
|
+
const thrown = new Error("unexpected SDK boom");
|
|
146
|
+
const { provider } = makeThrowingProvider("openai", () => thrown);
|
|
147
|
+
|
|
148
|
+
const events: AgentEvent[] = [];
|
|
149
|
+
const loop = new AgentLoop(provider, "system");
|
|
150
|
+
|
|
151
|
+
await loop.run(
|
|
152
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
153
|
+
(e) => {
|
|
154
|
+
events.push(e);
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const providerErrorEvent = events.find((e) => e.type === "provider_error");
|
|
159
|
+
expect(providerErrorEvent).toBeDefined();
|
|
160
|
+
if (providerErrorEvent?.type !== "provider_error") {
|
|
161
|
+
throw new Error("type narrowing");
|
|
162
|
+
}
|
|
163
|
+
// The thrown Error has no `.provider` field, so the event falls back to
|
|
164
|
+
// the dispatching provider's `name` — keeps the persisted log row's
|
|
165
|
+
// `provider` column populated even for surprise errors.
|
|
166
|
+
expect(providerErrorEvent.actualProvider).toBe("openai");
|
|
167
|
+
expect(providerErrorEvent.error).toBe(thrown);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("does NOT emit provider_error on user-aborted runs", async () => {
|
|
171
|
+
const controller = new AbortController();
|
|
172
|
+
const thrown = new Error("aborted");
|
|
173
|
+
const { provider } = makeThrowingProvider("anthropic", () => {
|
|
174
|
+
// Pre-abort then throw so the loop's catch sees `signal.aborted === true`.
|
|
175
|
+
controller.abort();
|
|
176
|
+
return thrown;
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const events: AgentEvent[] = [];
|
|
180
|
+
const loop = new AgentLoop(provider, "system");
|
|
181
|
+
|
|
182
|
+
await loop.run(
|
|
183
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
184
|
+
(e) => {
|
|
185
|
+
events.push(e);
|
|
186
|
+
},
|
|
187
|
+
controller.signal,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const providerErrorEvent = events.find((e) => e.type === "provider_error");
|
|
191
|
+
// Cancellation should never produce a recording row — there's no
|
|
192
|
+
// provider rejection worth logging when the user pulled the plug.
|
|
193
|
+
expect(providerErrorEvent).toBeUndefined();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
@@ -181,6 +181,88 @@ describe("AgentLoop", () => {
|
|
|
181
181
|
]);
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
+
test("re-resolves override profile before each provider call", async () => {
|
|
185
|
+
const toolCallId = "tool-1";
|
|
186
|
+
const { provider, calls } = createMockProvider([
|
|
187
|
+
toolUseResponse(toolCallId, "read_file", { path: "/tmp/test.txt" }),
|
|
188
|
+
textResponse("File contents received."),
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
let overrideProfile: string | undefined;
|
|
192
|
+
const toolExecutor = async () => {
|
|
193
|
+
overrideProfile = "quality-optimized";
|
|
194
|
+
return { content: "ok", isError: false };
|
|
195
|
+
};
|
|
196
|
+
const loop = new AgentLoop(
|
|
197
|
+
provider,
|
|
198
|
+
"system",
|
|
199
|
+
{},
|
|
200
|
+
dummyTools,
|
|
201
|
+
toolExecutor,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
await loop.run(
|
|
205
|
+
[userMessage],
|
|
206
|
+
collectEvents([]),
|
|
207
|
+
undefined,
|
|
208
|
+
"req-1",
|
|
209
|
+
undefined,
|
|
210
|
+
"mainAgent",
|
|
211
|
+
undefined,
|
|
212
|
+
undefined,
|
|
213
|
+
undefined,
|
|
214
|
+
() => overrideProfile,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(calls).toHaveLength(2);
|
|
218
|
+
expect(calls[0].options?.config?.overrideProfile).toBeUndefined();
|
|
219
|
+
expect(calls[1].options?.config?.overrideProfile).toBe("quality-optimized");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test("re-resolves max input tokens before truncating tool results", async () => {
|
|
223
|
+
const toolCallId = "tool-1";
|
|
224
|
+
const toolOutput = "x".repeat(2_500);
|
|
225
|
+
const { provider, calls } = createMockProvider([
|
|
226
|
+
toolUseResponse(toolCallId, "read_file", { path: "/tmp/test.txt" }),
|
|
227
|
+
textResponse("File contents received."),
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
let maxInputTokens = 1_000;
|
|
231
|
+
const toolExecutor = async () => {
|
|
232
|
+
maxInputTokens = 10_000;
|
|
233
|
+
return { content: toolOutput, isError: false };
|
|
234
|
+
};
|
|
235
|
+
const loop = new AgentLoop(
|
|
236
|
+
provider,
|
|
237
|
+
"system",
|
|
238
|
+
{},
|
|
239
|
+
dummyTools,
|
|
240
|
+
toolExecutor,
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
await loop.run(
|
|
244
|
+
[userMessage],
|
|
245
|
+
collectEvents([]),
|
|
246
|
+
undefined,
|
|
247
|
+
"req-1",
|
|
248
|
+
undefined,
|
|
249
|
+
"mainAgent",
|
|
250
|
+
undefined,
|
|
251
|
+
undefined,
|
|
252
|
+
1_000,
|
|
253
|
+
undefined,
|
|
254
|
+
() => maxInputTokens,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const secondCallMessages = calls[1].messages;
|
|
258
|
+
const lastMsg = secondCallMessages[secondCallMessages.length - 1];
|
|
259
|
+
const toolResultBlock = lastMsg.content.find(
|
|
260
|
+
(b): b is Extract<ContentBlock, { type: "tool_result" }> =>
|
|
261
|
+
b.type === "tool_result",
|
|
262
|
+
);
|
|
263
|
+
expect(toolResultBlock?.content).toBe(toolOutput);
|
|
264
|
+
});
|
|
265
|
+
|
|
184
266
|
// 3. Multi-turn tool loop
|
|
185
267
|
test("supports multi-turn tool execution", async () => {
|
|
186
268
|
const { provider, calls } = createMockProvider([
|
|
@@ -470,7 +552,6 @@ describe("AgentLoop", () => {
|
|
|
470
552
|
).toBe(false);
|
|
471
553
|
});
|
|
472
554
|
|
|
473
|
-
|
|
474
555
|
// 9. Tool executor error results are forwarded correctly
|
|
475
556
|
test("forwards tool error results to provider", async () => {
|
|
476
557
|
const { provider, calls } = createMockProvider([
|
|
@@ -1767,7 +1848,9 @@ describe("AgentLoop", () => {
|
|
|
1767
1848
|
]);
|
|
1768
1849
|
|
|
1769
1850
|
// message_complete emitted for tool_use response + retry text response (not the empty one)
|
|
1770
|
-
const messageCompletes = events.filter(
|
|
1851
|
+
const messageCompletes = events.filter(
|
|
1852
|
+
(e) => e.type === "message_complete",
|
|
1853
|
+
);
|
|
1771
1854
|
expect(messageCompletes).toHaveLength(2);
|
|
1772
1855
|
});
|
|
1773
1856
|
|
|
@@ -1883,7 +1966,9 @@ describe("AgentLoop", () => {
|
|
|
1883
1966
|
expect(calls).toHaveLength(3);
|
|
1884
1967
|
|
|
1885
1968
|
// message_complete: tool_use response + final empty response (retry exhausted)
|
|
1886
|
-
const messageCompletes = events.filter(
|
|
1969
|
+
const messageCompletes = events.filter(
|
|
1970
|
+
(e) => e.type === "message_complete",
|
|
1971
|
+
);
|
|
1887
1972
|
expect(messageCompletes).toHaveLength(2);
|
|
1888
1973
|
|
|
1889
1974
|
// The last assistant message in history is the empty one
|