@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
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests that the in-flight activity status text emitted for `web_search`
|
|
3
|
+
* and `web_fetch` tool starts surfaces per-call detail (the query / domain)
|
|
4
|
+
* instead of a generic placeholder. Covers both the Anthropic native
|
|
5
|
+
* `server_tool_start` path and the non-native `tool_use` path.
|
|
6
|
+
*/
|
|
7
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
8
|
+
|
|
9
|
+
// ── Mock platform (must precede imports that read it) ─────────────────────────
|
|
10
|
+
mock.module("../../util/logger.js", () => ({
|
|
11
|
+
getLogger: () =>
|
|
12
|
+
new Proxy({} as Record<string, unknown>, {
|
|
13
|
+
get: () => () => {},
|
|
14
|
+
}),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
mock.module("../../config/loader.js", () => ({
|
|
18
|
+
getConfig: () => ({
|
|
19
|
+
skills: {
|
|
20
|
+
entries: {},
|
|
21
|
+
load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
|
|
22
|
+
install: { nodeManager: "npm" },
|
|
23
|
+
allowBundled: null,
|
|
24
|
+
remoteProviders: {
|
|
25
|
+
skillssh: { enabled: true },
|
|
26
|
+
clawhub: { enabled: true },
|
|
27
|
+
},
|
|
28
|
+
remotePolicy: {
|
|
29
|
+
blockSuspicious: true,
|
|
30
|
+
blockMalware: true,
|
|
31
|
+
maxSkillsShRisk: "medium",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
loadConfig: () => ({}),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
mock.module("../../memory/conversation-crud.js", () => ({
|
|
39
|
+
addMessage: () => ({ id: "mock-msg-id" }),
|
|
40
|
+
getMessageById: () => null,
|
|
41
|
+
updateMessageContent: () => {},
|
|
42
|
+
provenanceFromTrustContext: () => ({}),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
mock.module("../../memory/llm-request-log-store.js", () => ({
|
|
46
|
+
recordRequestLog: () => {},
|
|
47
|
+
backfillMessageIdOnLogs: () => {},
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
// ── Imports (after mocks) ─────────────────────────────────────────────────────
|
|
51
|
+
import type {
|
|
52
|
+
EventHandlerDeps,
|
|
53
|
+
EventHandlerState,
|
|
54
|
+
} from "../conversation-agent-loop-handlers.js";
|
|
55
|
+
import {
|
|
56
|
+
createEventHandlerState,
|
|
57
|
+
dispatchAgentEvent,
|
|
58
|
+
formatFetchStatusText,
|
|
59
|
+
formatSearchStatusText,
|
|
60
|
+
} from "../conversation-agent-loop-handlers.js";
|
|
61
|
+
import type { ServerMessage } from "../message-protocol.js";
|
|
62
|
+
|
|
63
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
interface ActivityStateCapture {
|
|
66
|
+
phase: string;
|
|
67
|
+
reason: string;
|
|
68
|
+
scope: string;
|
|
69
|
+
reqId: string;
|
|
70
|
+
statusText?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function createCollectorDeps(): {
|
|
74
|
+
deps: EventHandlerDeps;
|
|
75
|
+
activityStates: ActivityStateCapture[];
|
|
76
|
+
} {
|
|
77
|
+
const activityStates: ActivityStateCapture[] = [];
|
|
78
|
+
const deps = {
|
|
79
|
+
ctx: {
|
|
80
|
+
conversationId: "conv-status-text",
|
|
81
|
+
provider: { name: "anthropic" },
|
|
82
|
+
traceEmitter: { emit: () => {} },
|
|
83
|
+
streamThinking: false,
|
|
84
|
+
emitActivityState: (
|
|
85
|
+
phase: string,
|
|
86
|
+
reason: string,
|
|
87
|
+
scope: string,
|
|
88
|
+
reqId: string,
|
|
89
|
+
statusText?: string,
|
|
90
|
+
) => {
|
|
91
|
+
activityStates.push({ phase, reason, scope, reqId, statusText });
|
|
92
|
+
},
|
|
93
|
+
markWorkspaceTopLevelDirty: () => {},
|
|
94
|
+
currentTurnSurfaces: [],
|
|
95
|
+
} as unknown as EventHandlerDeps["ctx"],
|
|
96
|
+
onEvent: (_msg: ServerMessage) => {},
|
|
97
|
+
reqId: "req-status-text",
|
|
98
|
+
isFirstMessage: false,
|
|
99
|
+
shouldGenerateTitle: false,
|
|
100
|
+
rlog: new Proxy({} as Record<string, unknown>, {
|
|
101
|
+
get: () => () => {},
|
|
102
|
+
}) as unknown as EventHandlerDeps["rlog"],
|
|
103
|
+
turnChannelContext: {
|
|
104
|
+
userMessageChannel: "vellum",
|
|
105
|
+
assistantMessageChannel: "vellum",
|
|
106
|
+
} as EventHandlerDeps["turnChannelContext"],
|
|
107
|
+
turnInterfaceContext: {
|
|
108
|
+
userMessageInterface: "macos",
|
|
109
|
+
assistantMessageInterface: "macos",
|
|
110
|
+
} as EventHandlerDeps["turnInterfaceContext"],
|
|
111
|
+
} as EventHandlerDeps;
|
|
112
|
+
return { deps, activityStates };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Pure helper tests ─────────────────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
describe("formatSearchStatusText", () => {
|
|
118
|
+
test("surfaces the query in quotes", () => {
|
|
119
|
+
expect(formatSearchStatusText("web_search", "foo")).toBe(
|
|
120
|
+
'Searching "foo"',
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("truncates queries longer than 60 chars with an ellipsis", () => {
|
|
125
|
+
const longQuery = "a".repeat(120);
|
|
126
|
+
const result = formatSearchStatusText("web_search", longQuery);
|
|
127
|
+
// 57 chars + "..." = 60 char body, wrapped in 'Searching "..."'
|
|
128
|
+
expect(result).toBe(`Searching "${"a".repeat(57)}..."`);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("preserves 60-char queries as-is (no truncation at the boundary)", () => {
|
|
132
|
+
const sixty = "a".repeat(60);
|
|
133
|
+
expect(formatSearchStatusText("web_search", sixty)).toBe(
|
|
134
|
+
`Searching "${sixty}"`,
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("falls back to the generic phrasing when the query is empty", () => {
|
|
139
|
+
expect(formatSearchStatusText("web_search", "")).toBe("Searching the web");
|
|
140
|
+
expect(formatSearchStatusText("web_search", " ")).toBe(
|
|
141
|
+
"Searching the web",
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("emits 'Running <tool>' for non-web_search tools", () => {
|
|
146
|
+
expect(formatSearchStatusText("other_tool", "x")).toBe(
|
|
147
|
+
"Running other_tool",
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("formatFetchStatusText", () => {
|
|
153
|
+
test("returns the domain when given a parseable URL", () => {
|
|
154
|
+
expect(formatFetchStatusText("https://example.com/path")).toBe(
|
|
155
|
+
"Reading example.com",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("lowercases the host", () => {
|
|
160
|
+
expect(formatFetchStatusText("https://EXAMPLE.COM/x")).toBe(
|
|
161
|
+
"Reading example.com",
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("falls back when the URL is unparseable or missing", () => {
|
|
166
|
+
expect(formatFetchStatusText("not a url")).toBe("Reading a page");
|
|
167
|
+
expect(formatFetchStatusText(undefined)).toBe("Reading a page");
|
|
168
|
+
expect(formatFetchStatusText(42)).toBe("Reading a page");
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ── Dispatch-path tests ───────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
describe("server_tool_start status text", () => {
|
|
175
|
+
let state: EventHandlerState;
|
|
176
|
+
|
|
177
|
+
beforeEach(() => {
|
|
178
|
+
state = createEventHandlerState();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("native web_search emits 'Searching \"<query>\"'", async () => {
|
|
182
|
+
const { deps, activityStates } = createCollectorDeps();
|
|
183
|
+
|
|
184
|
+
await dispatchAgentEvent(state, deps, {
|
|
185
|
+
type: "server_tool_start",
|
|
186
|
+
name: "web_search",
|
|
187
|
+
toolUseId: "tu_native_q",
|
|
188
|
+
input: { query: "foo" },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const toolStart = activityStates.find((s) => s.reason === "tool_use_start");
|
|
192
|
+
expect(toolStart?.statusText).toBe('Searching "foo"');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("native web_search with long query truncates", async () => {
|
|
196
|
+
const { deps, activityStates } = createCollectorDeps();
|
|
197
|
+
const longQuery = "a".repeat(120);
|
|
198
|
+
|
|
199
|
+
await dispatchAgentEvent(state, deps, {
|
|
200
|
+
type: "server_tool_start",
|
|
201
|
+
name: "web_search",
|
|
202
|
+
toolUseId: "tu_native_long",
|
|
203
|
+
input: { query: longQuery },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const toolStart = activityStates.find((s) => s.reason === "tool_use_start");
|
|
207
|
+
expect(toolStart?.statusText).toBe(`Searching "${"a".repeat(57)}..."`);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("native web_search with missing query falls back to the generic phrasing", async () => {
|
|
211
|
+
const { deps, activityStates } = createCollectorDeps();
|
|
212
|
+
|
|
213
|
+
await dispatchAgentEvent(state, deps, {
|
|
214
|
+
type: "server_tool_start",
|
|
215
|
+
name: "web_search",
|
|
216
|
+
toolUseId: "tu_native_empty",
|
|
217
|
+
input: {},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const toolStart = activityStates.find((s) => s.reason === "tool_use_start");
|
|
221
|
+
expect(toolStart?.statusText).toBe("Searching the web");
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("tool_use status text (non-native)", () => {
|
|
226
|
+
let state: EventHandlerState;
|
|
227
|
+
|
|
228
|
+
beforeEach(() => {
|
|
229
|
+
state = createEventHandlerState();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("web_search emits 'Searching \"<query>\"'", async () => {
|
|
233
|
+
const { deps, activityStates } = createCollectorDeps();
|
|
234
|
+
|
|
235
|
+
await dispatchAgentEvent(state, deps, {
|
|
236
|
+
type: "tool_use",
|
|
237
|
+
id: "tu_ws",
|
|
238
|
+
name: "web_search",
|
|
239
|
+
input: { query: "bar" },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const toolStart = activityStates.find((s) => s.reason === "tool_use_start");
|
|
243
|
+
expect(toolStart?.statusText).toBe('Searching "bar"');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test("web_fetch emits 'Reading <domain>'", async () => {
|
|
247
|
+
const { deps, activityStates } = createCollectorDeps();
|
|
248
|
+
|
|
249
|
+
await dispatchAgentEvent(state, deps, {
|
|
250
|
+
type: "tool_use",
|
|
251
|
+
id: "tu_wf",
|
|
252
|
+
name: "web_fetch",
|
|
253
|
+
input: { url: "https://www.nytimes.com/article" },
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const toolStart = activityStates.find((s) => s.reason === "tool_use_start");
|
|
257
|
+
expect(toolStart?.statusText).toBe("Reading www.nytimes.com");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
test("web_fetch with malformed url falls back to a generic phrase", async () => {
|
|
261
|
+
const { deps, activityStates } = createCollectorDeps();
|
|
262
|
+
|
|
263
|
+
await dispatchAgentEvent(state, deps, {
|
|
264
|
+
type: "tool_use",
|
|
265
|
+
id: "tu_wf_bad",
|
|
266
|
+
name: "web_fetch",
|
|
267
|
+
input: { url: "not-a-url" },
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const toolStart = activityStates.find((s) => s.reason === "tool_use_start");
|
|
271
|
+
expect(toolStart?.statusText).toBe("Reading a page");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test("unrelated tools keep the existing 'Running <friendly>' fallback", async () => {
|
|
275
|
+
const { deps, activityStates } = createCollectorDeps();
|
|
276
|
+
|
|
277
|
+
await dispatchAgentEvent(state, deps, {
|
|
278
|
+
type: "tool_use",
|
|
279
|
+
id: "tu_bash",
|
|
280
|
+
name: "bash",
|
|
281
|
+
input: { command: "ls" },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const toolStart = activityStates.find((s) => s.reason === "tool_use_start");
|
|
285
|
+
expect(toolStart?.statusText).toBe("Running command");
|
|
286
|
+
});
|
|
287
|
+
});
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
TurnChannelContext,
|
|
14
14
|
TurnInterfaceContext,
|
|
15
15
|
} from "../channels/types.js";
|
|
16
|
+
import { getConfig } from "../config/loader.js";
|
|
16
17
|
import { recordEstimate } from "../context/estimator-calibration.js";
|
|
17
18
|
import { getCalibrationProviderKey } from "../context/token-estimator.js";
|
|
18
19
|
import {
|
|
@@ -23,12 +24,15 @@ import {
|
|
|
23
24
|
} from "../memory/conversation-crud.js";
|
|
24
25
|
import {
|
|
25
26
|
backfillMessageIdOnLogs,
|
|
27
|
+
buildProviderErrorResponsePayload,
|
|
26
28
|
recordRequestLog,
|
|
29
|
+
setAgentLoopExitReasonOnLatestLog,
|
|
27
30
|
} from "../memory/llm-request-log-store.js";
|
|
28
31
|
import { backfillMemoryRecallLogMessageId } from "../memory/memory-recall-log-store.js";
|
|
29
32
|
import { backfillMemoryV2ActivationMessageId } from "../memory/memory-v2-activation-log-store.js";
|
|
30
33
|
import { getThreadTs } from "../memory/slack-thread-store.js";
|
|
31
34
|
import {
|
|
35
|
+
formatSlackTimezoneLabel,
|
|
32
36
|
type SlackMessageMetadata,
|
|
33
37
|
writeSlackMetadata,
|
|
34
38
|
} from "../messaging/providers/slack/message-metadata.js";
|
|
@@ -44,7 +48,9 @@ import type {
|
|
|
44
48
|
import type { ContentBlock, ImageContent } from "../providers/types.js";
|
|
45
49
|
import { isContextOverflowError } from "../providers/types.js";
|
|
46
50
|
import { redactSecrets } from "../security/secret-scanner.js";
|
|
51
|
+
import { extractDomain } from "../tools/network/domain-normalize.js";
|
|
47
52
|
import { ProviderError } from "../util/errors.js";
|
|
53
|
+
import { faviconUrlForDomain } from "../util/favicon.js";
|
|
48
54
|
import { getLogger } from "../util/logger.js";
|
|
49
55
|
import type { DirectiveRequest } from "./assistant-attachments.js";
|
|
50
56
|
import {
|
|
@@ -58,7 +64,12 @@ import {
|
|
|
58
64
|
isContextTooLarge,
|
|
59
65
|
} from "./conversation-error.js";
|
|
60
66
|
import { isProviderOrderingError } from "./conversation-slash.js";
|
|
67
|
+
import { resolveTurnTimezoneContext } from "./date-context.js";
|
|
61
68
|
import type { ServerMessage } from "./message-protocol.js";
|
|
69
|
+
import type {
|
|
70
|
+
WebSearchMetadata,
|
|
71
|
+
WebSearchResultItem,
|
|
72
|
+
} from "./message-types/web-activity.js";
|
|
62
73
|
|
|
63
74
|
const log = getLogger("agent-loop-handlers");
|
|
64
75
|
|
|
@@ -197,6 +208,10 @@ export interface EventHandlerState {
|
|
|
197
208
|
currentTurnToolUseIds: string[];
|
|
198
209
|
/** Wall-clock time (ms since epoch) when the agent loop turn started, used as the display timestamp for assistant messages. */
|
|
199
210
|
turnStartedAt: number;
|
|
211
|
+
/** Wall-clock start time of native server tool calls, keyed by tool_use_id. */
|
|
212
|
+
readonly serverToolStartedAt: Map<string, number>;
|
|
213
|
+
/** Original input from server_tool_start, keyed by tool_use_id, so the complete handler can read the query. */
|
|
214
|
+
readonly serverToolInputs: Map<string, Record<string, unknown>>;
|
|
200
215
|
}
|
|
201
216
|
|
|
202
217
|
/** Immutable context shared across event handlers within a single agent loop run. */
|
|
@@ -255,6 +270,8 @@ export function createEventHandlerState(): EventHandlerState {
|
|
|
255
270
|
toolRiskOutcomes: new Map(),
|
|
256
271
|
currentTurnToolUseIds: [],
|
|
257
272
|
turnStartedAt: Date.now(),
|
|
273
|
+
serverToolStartedAt: new Map(),
|
|
274
|
+
serverToolInputs: new Map(),
|
|
258
275
|
};
|
|
259
276
|
}
|
|
260
277
|
|
|
@@ -322,6 +339,68 @@ function friendlyToolName(name: string): string {
|
|
|
322
339
|
return TOOL_FRIENDLY_NAMES[name] ?? name.replace(/_/g, " ");
|
|
323
340
|
}
|
|
324
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Status text shown to the client while a web search is in flight.
|
|
344
|
+
* Surfaces the actual query so the loading state is meaningful instead of
|
|
345
|
+
* a generic "Searching the web". Used for both Anthropic native server-tool
|
|
346
|
+
* starts and non-native `web_search` tool starts.
|
|
347
|
+
*/
|
|
348
|
+
export function formatSearchStatusText(
|
|
349
|
+
toolName: string,
|
|
350
|
+
query: string,
|
|
351
|
+
): string {
|
|
352
|
+
if (toolName !== "web_search") return `Running ${toolName}`;
|
|
353
|
+
const trimmed = query.trim();
|
|
354
|
+
if (!trimmed) return "Searching the web";
|
|
355
|
+
const truncated =
|
|
356
|
+
trimmed.length > 60 ? trimmed.slice(0, 57) + "..." : trimmed;
|
|
357
|
+
return `Searching "${truncated}"`;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Status text shown to the client while a web fetch is in flight.
|
|
362
|
+
* Surfaces the domain so users can tell what page is being read.
|
|
363
|
+
*/
|
|
364
|
+
export function formatFetchStatusText(url: unknown): string {
|
|
365
|
+
if (typeof url !== "string") return "Reading a page";
|
|
366
|
+
const domain = extractDomain(url);
|
|
367
|
+
if (!domain) return "Reading a page";
|
|
368
|
+
return `Reading ${domain}`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function computeToolUseStatusText(
|
|
372
|
+
name: string,
|
|
373
|
+
input: Record<string, unknown>,
|
|
374
|
+
): string {
|
|
375
|
+
if (name === "web_search") {
|
|
376
|
+
const query = typeof input.query === "string" ? input.query : "";
|
|
377
|
+
return formatSearchStatusText("web_search", query);
|
|
378
|
+
}
|
|
379
|
+
if (name === "web_fetch") {
|
|
380
|
+
return formatFetchStatusText(input.url);
|
|
381
|
+
}
|
|
382
|
+
if (
|
|
383
|
+
name === "skill_execute" &&
|
|
384
|
+
typeof input.activity === "string" &&
|
|
385
|
+
input.activity.length > 0
|
|
386
|
+
) {
|
|
387
|
+
return input.activity;
|
|
388
|
+
}
|
|
389
|
+
return `Running ${friendlyToolName(name)}`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function resolveAssistantReplyTimestampTimezone(
|
|
393
|
+
ctx: AgentLoopConversationContext,
|
|
394
|
+
): string {
|
|
395
|
+
const config = getConfig();
|
|
396
|
+
return resolveTurnTimezoneContext({
|
|
397
|
+
configuredUserTimeZone: config.ui?.userTimezone ?? null,
|
|
398
|
+
clientTimezone: ctx.clientTimezone ?? null,
|
|
399
|
+
detectedTimezone: config.ui?.detectedTimezone ?? null,
|
|
400
|
+
hostTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
401
|
+
}).effectiveTimezone;
|
|
402
|
+
}
|
|
403
|
+
|
|
325
404
|
// ── Individual Handlers ──────────────────────────────────────────────
|
|
326
405
|
|
|
327
406
|
function handleTextDelta(
|
|
@@ -403,12 +482,7 @@ export function handleToolUse(
|
|
|
403
482
|
state.toolCallTimestamps.set(event.id, { startedAt: Date.now() });
|
|
404
483
|
state.currentToolUseId = event.id;
|
|
405
484
|
state.currentTurnToolUseIds.push(event.id);
|
|
406
|
-
const statusText =
|
|
407
|
-
event.name === "skill_execute" &&
|
|
408
|
-
typeof event.input.activity === "string" &&
|
|
409
|
-
event.input.activity.length > 0
|
|
410
|
-
? event.input.activity
|
|
411
|
-
: `Running ${friendlyToolName(event.name)}`;
|
|
485
|
+
const statusText = computeToolUseStatusText(event.name, event.input);
|
|
412
486
|
deps.ctx.emitActivityState(
|
|
413
487
|
"tool_running",
|
|
414
488
|
"tool_use_start",
|
|
@@ -675,6 +749,7 @@ export function handleToolResult(
|
|
|
675
749
|
approvalMode: event.approvalMode,
|
|
676
750
|
approvalReason: event.approvalReason,
|
|
677
751
|
riskThreshold: event.riskThreshold,
|
|
752
|
+
activityMetadata: event.activityMetadata,
|
|
678
753
|
});
|
|
679
754
|
}
|
|
680
755
|
|
|
@@ -983,11 +1058,20 @@ export async function handleMessageComplete(
|
|
|
983
1058
|
const channelId = deps.ctx.trustContext?.requesterChatId;
|
|
984
1059
|
if (channelId) {
|
|
985
1060
|
const threadTs = getThreadTs(deps.ctx.conversationId);
|
|
1061
|
+
const timestampTimezone = resolveAssistantReplyTimestampTimezone(
|
|
1062
|
+
deps.ctx,
|
|
1063
|
+
);
|
|
1064
|
+
const timestampTimezoneLabel = formatSlackTimezoneLabel(
|
|
1065
|
+
timestampTimezone,
|
|
1066
|
+
{ nowMs: state.turnStartedAt },
|
|
1067
|
+
);
|
|
986
1068
|
const partialSlackMeta: Partial<SlackMessageMetadata> = {
|
|
987
1069
|
source: "slack",
|
|
988
1070
|
eventKind: "message",
|
|
989
1071
|
channelId,
|
|
990
1072
|
...(threadTs ? { threadTs } : {}),
|
|
1073
|
+
timestampTimezone,
|
|
1074
|
+
...(timestampTimezoneLabel ? { timestampTimezoneLabel } : {}),
|
|
991
1075
|
};
|
|
992
1076
|
assistantChannelMetadata.slackMeta = writeSlackMetadata(
|
|
993
1077
|
// `channelTs` is filled in by the post-send reconciliation step in
|
|
@@ -1166,6 +1250,52 @@ function handleUsage(
|
|
|
1166
1250
|
state.llmCallStartedEmitted = false;
|
|
1167
1251
|
}
|
|
1168
1252
|
|
|
1253
|
+
/**
|
|
1254
|
+
* Persist a provider-rejected LLM call as an `llm_request_logs` row.
|
|
1255
|
+
*
|
|
1256
|
+
* Mirrors `handleUsage`'s recording side-effect for the failure path: the
|
|
1257
|
+
* loop only reaches the success branch (and emits `usage`) when the
|
|
1258
|
+
* provider returns a response, so without this handler a rejected call
|
|
1259
|
+
* leaves nothing in the inspector — only a pino line saying "The AI
|
|
1260
|
+
* provider rejected the request." The row's `messageId` is left null
|
|
1261
|
+
* here and linked via one of two backfill paths, depending on how the
|
|
1262
|
+
* turn unwinds:
|
|
1263
|
+
*
|
|
1264
|
+
* - Multi-call turn where a later call also produces a real assistant
|
|
1265
|
+
* response: `handleMessageComplete` -> `backfillMessageIdOnLogs`
|
|
1266
|
+
* sweeps this row with the rest, same as a successful-call row.
|
|
1267
|
+
* - Pure provider-failure turn (no real assistant response): the
|
|
1268
|
+
* synthetic error-message branch in `conversation-agent-loop.ts`
|
|
1269
|
+
* persists a stand-in assistant message and calls
|
|
1270
|
+
* `backfillMessageIdOnLogs` itself, since `message_complete` is
|
|
1271
|
+
* never emitted on that path. Closing the orphan window inside the
|
|
1272
|
+
* same synchronous turn prevents a later turn's sweep from wrong-
|
|
1273
|
+
* attaching this row to an unrelated assistant message.
|
|
1274
|
+
*
|
|
1275
|
+
* Failures inside the recording itself are logged and swallowed — this
|
|
1276
|
+
* mirrors `handleUsage`'s non-fatal stance so a DB hiccup never escalates
|
|
1277
|
+
* a provider rejection into a dispatcher-level throw.
|
|
1278
|
+
*/
|
|
1279
|
+
function handleProviderError(
|
|
1280
|
+
deps: EventHandlerDeps,
|
|
1281
|
+
event: Extract<AgentEvent, { type: "provider_error" }>,
|
|
1282
|
+
): void {
|
|
1283
|
+
try {
|
|
1284
|
+
recordRequestLog(
|
|
1285
|
+
deps.ctx.conversationId,
|
|
1286
|
+
JSON.stringify(event.rawRequest),
|
|
1287
|
+
JSON.stringify(buildProviderErrorResponsePayload(event.error)),
|
|
1288
|
+
undefined,
|
|
1289
|
+
event.actualProvider,
|
|
1290
|
+
);
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
deps.rlog.warn(
|
|
1293
|
+
{ err },
|
|
1294
|
+
"Failed to persist provider-error LLM request log (non-fatal)",
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1169
1299
|
// ── Dispatcher ───────────────────────────────────────────────────────
|
|
1170
1300
|
|
|
1171
1301
|
/** Routes an AgentEvent to the appropriate handler. */
|
|
@@ -1198,10 +1328,9 @@ export async function dispatchAgentEvent(
|
|
|
1198
1328
|
handleToolResult(state, deps, event);
|
|
1199
1329
|
break;
|
|
1200
1330
|
case "server_tool_start": {
|
|
1201
|
-
const
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
const statusText = friendlyNames[event.name] ?? `Running ${event.name}`;
|
|
1331
|
+
const query =
|
|
1332
|
+
typeof event.input.query === "string" ? event.input.query : "";
|
|
1333
|
+
const statusText = formatSearchStatusText(event.name, query);
|
|
1205
1334
|
deps.ctx.emitActivityState(
|
|
1206
1335
|
"tool_running",
|
|
1207
1336
|
"tool_use_start",
|
|
@@ -1209,6 +1338,8 @@ export async function dispatchAgentEvent(
|
|
|
1209
1338
|
deps.reqId,
|
|
1210
1339
|
statusText,
|
|
1211
1340
|
);
|
|
1341
|
+
state.serverToolStartedAt.set(event.toolUseId, Date.now());
|
|
1342
|
+
state.serverToolInputs.set(event.toolUseId, event.input);
|
|
1212
1343
|
deps.onEvent({
|
|
1213
1344
|
type: "tool_use_start",
|
|
1214
1345
|
toolName: event.name,
|
|
@@ -1227,19 +1358,65 @@ export async function dispatchAgentEvent(
|
|
|
1227
1358
|
"Thinking",
|
|
1228
1359
|
);
|
|
1229
1360
|
|
|
1230
|
-
//
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1361
|
+
// Prefer `resolvedInput` (Anthropic's accumulated server-tool input,
|
|
1362
|
+
// populated on content_block_stop) over the input captured at
|
|
1363
|
+
// server_tool_start, which is `{}` on Anthropic until the deltas land.
|
|
1364
|
+
const inputForCall =
|
|
1365
|
+
event.resolvedInput ?? state.serverToolInputs.get(event.toolUseId);
|
|
1366
|
+
const query =
|
|
1367
|
+
typeof inputForCall?.query === "string" ? inputForCall.query : "";
|
|
1368
|
+
const startedAt =
|
|
1369
|
+
state.serverToolStartedAt.get(event.toolUseId) ?? Date.now();
|
|
1370
|
+
const durationMs = Date.now() - startedAt;
|
|
1371
|
+
state.serverToolStartedAt.delete(event.toolUseId);
|
|
1372
|
+
state.serverToolInputs.delete(event.toolUseId);
|
|
1373
|
+
|
|
1374
|
+
const rawBlocks = Array.isArray(event.content) ? event.content : [];
|
|
1375
|
+
const results: WebSearchResultItem[] = rawBlocks
|
|
1376
|
+
.filter(
|
|
1377
|
+
(r): r is { type: string; title: string; url: string } =>
|
|
1378
|
+
typeof r === "object" &&
|
|
1379
|
+
r != null &&
|
|
1380
|
+
(r as { type?: string }).type === "web_search_result",
|
|
1381
|
+
)
|
|
1382
|
+
.map((r, i) => {
|
|
1383
|
+
const domain = extractDomain(r.url);
|
|
1384
|
+
return {
|
|
1385
|
+
rank: i + 1,
|
|
1386
|
+
title: r.title,
|
|
1387
|
+
url: r.url,
|
|
1388
|
+
domain,
|
|
1389
|
+
faviconUrl: faviconUrlForDomain(domain),
|
|
1390
|
+
// snippet intentionally absent — Anthropic native content is encrypted/opaque
|
|
1391
|
+
};
|
|
1392
|
+
});
|
|
1393
|
+
|
|
1394
|
+
// Only Anthropic produces structured `web_search_tool_result` blocks
|
|
1395
|
+
// that map cleanly onto `WebSearchMetadata` (provider-tagged
|
|
1396
|
+
// "anthropic-native"). Other providers (e.g. OpenAI's responses
|
|
1397
|
+
// `web_search_call`) share this event channel but their results are
|
|
1398
|
+
// woven into the text stream — emitting "anthropic-native" metadata
|
|
1399
|
+
// for them would mis-label the provider and ship empty results.
|
|
1400
|
+
const isAnthropicNative = deps.ctx.provider.name === "anthropic";
|
|
1401
|
+
|
|
1402
|
+
const errorMessage = event.isError
|
|
1403
|
+
? (event.errorMessage ?? event.errorCode ?? "Search failed")
|
|
1404
|
+
: undefined;
|
|
1405
|
+
|
|
1406
|
+
const metadata: WebSearchMetadata | undefined = isAnthropicNative
|
|
1407
|
+
? {
|
|
1408
|
+
query,
|
|
1409
|
+
provider: "anthropic-native",
|
|
1410
|
+
resultCount: results.length,
|
|
1411
|
+
durationMs,
|
|
1412
|
+
results,
|
|
1413
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
1414
|
+
}
|
|
1415
|
+
: undefined;
|
|
1416
|
+
|
|
1417
|
+
const resultText = results
|
|
1418
|
+
.map((r) => `${r.title}\n${r.url}`)
|
|
1419
|
+
.join("\n\n");
|
|
1243
1420
|
|
|
1244
1421
|
deps.onEvent({
|
|
1245
1422
|
type: "tool_result",
|
|
@@ -1248,18 +1425,49 @@ export async function dispatchAgentEvent(
|
|
|
1248
1425
|
isError: event.isError,
|
|
1249
1426
|
conversationId: deps.ctx.conversationId,
|
|
1250
1427
|
toolUseId: event.toolUseId,
|
|
1428
|
+
...(metadata ? { activityMetadata: { webSearch: metadata } } : {}),
|
|
1251
1429
|
});
|
|
1252
1430
|
break;
|
|
1253
1431
|
}
|
|
1254
1432
|
case "error":
|
|
1255
1433
|
handleError(state, deps, event);
|
|
1256
1434
|
break;
|
|
1435
|
+
case "provider_error":
|
|
1436
|
+
handleProviderError(deps, event);
|
|
1437
|
+
break;
|
|
1257
1438
|
case "message_complete":
|
|
1258
1439
|
await handleMessageComplete(state, deps, event);
|
|
1259
1440
|
break;
|
|
1260
1441
|
case "usage":
|
|
1261
1442
|
handleUsage(state, deps, event);
|
|
1262
1443
|
break;
|
|
1444
|
+
case "agent_loop_exit":
|
|
1445
|
+
// Stamp the exit reason onto the most-recent llm_request_logs
|
|
1446
|
+
// row for this conversation. The final `usage` event of the run
|
|
1447
|
+
// lands its row immediately before this event arrives (in the
|
|
1448
|
+
// normal-dispatch path; the wake path handles ordering
|
|
1449
|
+
// explicitly via `pendingExitReason`).
|
|
1450
|
+
//
|
|
1451
|
+
// Wrapped in try/catch so a DB hiccup here can't tear down the
|
|
1452
|
+
// surrounding dispatch — the outer try/catch already swallows
|
|
1453
|
+
// errors, but logging here gives the diagnosis hook a clear
|
|
1454
|
+
// attribution to the exit handler specifically.
|
|
1455
|
+
try {
|
|
1456
|
+
setAgentLoopExitReasonOnLatestLog(
|
|
1457
|
+
deps.ctx.conversationId,
|
|
1458
|
+
event.reason,
|
|
1459
|
+
);
|
|
1460
|
+
} catch (err) {
|
|
1461
|
+
log.warn(
|
|
1462
|
+
{
|
|
1463
|
+
err,
|
|
1464
|
+
conversationId: deps.ctx.conversationId,
|
|
1465
|
+
reason: event.reason,
|
|
1466
|
+
},
|
|
1467
|
+
"Failed to persist agent_loop_exit_reason (non-fatal)",
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
break;
|
|
1263
1471
|
}
|
|
1264
1472
|
} catch (err) {
|
|
1265
1473
|
log.error(
|