@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
|
@@ -223,9 +223,9 @@ describe("loadExternalPlugin — hooks", () => {
|
|
|
223
223
|
);
|
|
224
224
|
expect(typeof registered?.hooks?.init).toBe("function");
|
|
225
225
|
await registered?.hooks?.init?.({} as never);
|
|
226
|
-
expect(
|
|
227
|
-
|
|
228
|
-
)
|
|
226
|
+
expect((globalThis as Record<string, unknown>).__externalInitCalled).toBe(
|
|
227
|
+
true,
|
|
228
|
+
);
|
|
229
229
|
delete (globalThis as Record<string, unknown>).__externalInitCalled;
|
|
230
230
|
});
|
|
231
231
|
|
|
@@ -290,9 +290,9 @@ describe("loadExternalPlugin — hooks", () => {
|
|
|
290
290
|
expect(Object.keys(registered?.hooks ?? {})).toEqual(["init"]);
|
|
291
291
|
expect(typeof registered?.hooks?.init).toBe("function");
|
|
292
292
|
await registered?.hooks?.init?.({} as never);
|
|
293
|
-
expect(
|
|
294
|
-
|
|
295
|
-
);
|
|
293
|
+
expect(
|
|
294
|
+
(globalThis as Record<string, unknown>).__externalDtsInitCalled,
|
|
295
|
+
).toBe(true);
|
|
296
296
|
delete (globalThis as Record<string, unknown>).__externalDtsInitCalled;
|
|
297
297
|
});
|
|
298
298
|
|
|
@@ -345,7 +345,6 @@ describe("loadExternalPlugin — tools", () => {
|
|
|
345
345
|
dir,
|
|
346
346
|
"tools/alpha.ts",
|
|
347
347
|
`export default {
|
|
348
|
-
name: "two_tools_alpha",
|
|
349
348
|
description: "alpha",
|
|
350
349
|
defaultRiskLevel: "low" as const,
|
|
351
350
|
input_schema: { type: "object", properties: {}, required: [] },
|
|
@@ -357,7 +356,6 @@ describe("loadExternalPlugin — tools", () => {
|
|
|
357
356
|
dir,
|
|
358
357
|
"tools/beta.ts",
|
|
359
358
|
`export default {
|
|
360
|
-
name: "two_tools_beta",
|
|
361
359
|
description: "beta",
|
|
362
360
|
defaultRiskLevel: "low" as const,
|
|
363
361
|
input_schema: { type: "object", properties: {}, required: [] },
|
|
@@ -374,7 +372,7 @@ describe("loadExternalPlugin — tools", () => {
|
|
|
374
372
|
const names = (registered?.tools ?? []).map(
|
|
375
373
|
(t) => (t as { name: string }).name,
|
|
376
374
|
);
|
|
377
|
-
expect(names).toEqual(["
|
|
375
|
+
expect(names).toEqual(["alpha", "beta"]);
|
|
378
376
|
});
|
|
379
377
|
|
|
380
378
|
test("plugin.tools is undefined when tools/ is absent", async () => {
|
|
@@ -403,18 +401,95 @@ describe("loadExternalPlugin — tools", () => {
|
|
|
403
401
|
expect(registeredNames()).toHaveLength(0);
|
|
404
402
|
});
|
|
405
403
|
|
|
406
|
-
test("a tool default export missing
|
|
407
|
-
const dir = freshPluginDir("tool-
|
|
408
|
-
writePackageJson(dir, { name: "tool-
|
|
404
|
+
test("a tool default export with missing fields loads with documented defaults", async () => {
|
|
405
|
+
const dir = freshPluginDir("tool-with-defaults");
|
|
406
|
+
writePackageJson(dir, { name: "tool-with-defaults", version: "0.1.0" });
|
|
407
|
+
// The default export is a bare empty object — no description,
|
|
408
|
+
// defaultRiskLevel, input_schema, or execute. The loader must fill
|
|
409
|
+
// each slot with its documented default and still register the plugin.
|
|
410
|
+
writeSurfaceFile(dir, "tools/empty.ts", `export default {};\n`);
|
|
411
|
+
|
|
412
|
+
await loadExternalPlugin(dir);
|
|
413
|
+
|
|
414
|
+
const registered = getRegisteredPlugins().find(
|
|
415
|
+
(p) => p.manifest.name === "tool-with-defaults",
|
|
416
|
+
);
|
|
417
|
+
expect(registered).toBeDefined();
|
|
418
|
+
const tools = (registered?.tools ?? []) as Array<{
|
|
419
|
+
name: string;
|
|
420
|
+
description: string;
|
|
421
|
+
defaultRiskLevel: string;
|
|
422
|
+
input_schema: Record<string, unknown>;
|
|
423
|
+
execute: (
|
|
424
|
+
input: Record<string, unknown>,
|
|
425
|
+
context: unknown,
|
|
426
|
+
) => Promise<{ content: string; isError: boolean }>;
|
|
427
|
+
}>;
|
|
428
|
+
expect(tools).toHaveLength(1);
|
|
429
|
+
const empty = tools[0]!;
|
|
430
|
+
expect(empty.name).toBe("empty");
|
|
431
|
+
expect(empty.description).toBe("");
|
|
432
|
+
expect(empty.defaultRiskLevel).toBe("medium");
|
|
433
|
+
expect(empty.input_schema).toEqual({
|
|
434
|
+
type: "object",
|
|
435
|
+
properties: {},
|
|
436
|
+
additionalProperties: false,
|
|
437
|
+
});
|
|
438
|
+
expect(typeof empty.execute).toBe("function");
|
|
439
|
+
|
|
440
|
+
const result = await empty.execute({}, {} as unknown);
|
|
441
|
+
expect(result.isError).toBe(true);
|
|
442
|
+
expect(result.content).toContain("empty");
|
|
443
|
+
expect(result.content).toContain("no execute implementation");
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test("a partial tool default export merges author fields with defaults", async () => {
|
|
447
|
+
const dir = freshPluginDir("tool-partial-defaults");
|
|
448
|
+
writePackageJson(dir, {
|
|
449
|
+
name: "tool-partial-defaults",
|
|
450
|
+
version: "0.1.0",
|
|
451
|
+
});
|
|
452
|
+
// Author supplies only description + execute; the loader must default
|
|
453
|
+
// defaultRiskLevel and input_schema while keeping the author's fields.
|
|
409
454
|
writeSurfaceFile(
|
|
410
455
|
dir,
|
|
411
|
-
"tools/
|
|
412
|
-
`export default {
|
|
456
|
+
"tools/partial.ts",
|
|
457
|
+
`export default {
|
|
458
|
+
description: "custom description",
|
|
459
|
+
async execute() {
|
|
460
|
+
return { content: "ran", isError: false };
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
`,
|
|
413
464
|
);
|
|
414
465
|
|
|
415
466
|
await loadExternalPlugin(dir);
|
|
416
467
|
|
|
417
|
-
|
|
468
|
+
const registered = getRegisteredPlugins().find(
|
|
469
|
+
(p) => p.manifest.name === "tool-partial-defaults",
|
|
470
|
+
);
|
|
471
|
+
const tool = (registered?.tools ?? [])[0] as
|
|
472
|
+
| {
|
|
473
|
+
description: string;
|
|
474
|
+
defaultRiskLevel: string;
|
|
475
|
+
input_schema: object;
|
|
476
|
+
execute: (
|
|
477
|
+
input: Record<string, unknown>,
|
|
478
|
+
context: unknown,
|
|
479
|
+
) => Promise<{ content: string; isError: boolean }>;
|
|
480
|
+
}
|
|
481
|
+
| undefined;
|
|
482
|
+
expect(tool).toBeDefined();
|
|
483
|
+
expect(tool?.description).toBe("custom description");
|
|
484
|
+
expect(tool?.defaultRiskLevel).toBe("medium");
|
|
485
|
+
expect(tool?.input_schema).toEqual({
|
|
486
|
+
type: "object",
|
|
487
|
+
properties: {},
|
|
488
|
+
additionalProperties: false,
|
|
489
|
+
});
|
|
490
|
+
const result = await tool!.execute({}, {} as unknown);
|
|
491
|
+
expect(result.isError).toBe(false);
|
|
492
|
+
expect(result.content).toBe("ran");
|
|
418
493
|
});
|
|
419
494
|
});
|
|
420
495
|
|
|
@@ -474,9 +549,6 @@ describe("loadExternalPlugin — end-to-end @vellumai/simple-memory", () => {
|
|
|
474
549
|
const toolNames = (registered?.tools ?? [])
|
|
475
550
|
.map((t) => (t as { name: string }).name)
|
|
476
551
|
.sort();
|
|
477
|
-
expect(toolNames).toEqual([
|
|
478
|
-
"simple_memory_recall",
|
|
479
|
-
"simple_memory_remember",
|
|
480
|
-
]);
|
|
552
|
+
expect(toolNames).toEqual(["recall", "remember"]);
|
|
481
553
|
});
|
|
482
554
|
});
|
|
@@ -4,8 +4,12 @@ import { describe, expect, it } from "bun:test";
|
|
|
4
4
|
|
|
5
5
|
const tempDir = process.env.VELLUM_WORKSPACE_DIR!;
|
|
6
6
|
|
|
7
|
-
const {
|
|
8
|
-
|
|
7
|
+
const {
|
|
8
|
+
isWakeUpGreeting,
|
|
9
|
+
getCannedFirstGreeting,
|
|
10
|
+
buildScanFirstMessage,
|
|
11
|
+
CANNED_FIRST_GREETING,
|
|
12
|
+
} = await import("../daemon/first-greeting.js");
|
|
9
13
|
import type { OnboardingGreetingContext } from "../daemon/first-greeting.js";
|
|
10
14
|
|
|
11
15
|
describe("first-greeting", () => {
|
|
@@ -270,6 +274,23 @@ describe("first-greeting", () => {
|
|
|
270
274
|
});
|
|
271
275
|
});
|
|
272
276
|
|
|
277
|
+
describe("buildScanFirstMessage", () => {
|
|
278
|
+
it("website variant includes 'my website' and the URL", () => {
|
|
279
|
+
const msg = buildScanFirstMessage("https://acme.com", "website");
|
|
280
|
+
expect(msg).toContain("my website");
|
|
281
|
+
expect(msg).toContain("https://acme.com");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("content-source variant includes 'content' and the URL", () => {
|
|
285
|
+
const msg = buildScanFirstMessage(
|
|
286
|
+
"https://blog.acme.com/post",
|
|
287
|
+
"content-source",
|
|
288
|
+
);
|
|
289
|
+
expect(msg).toContain("content");
|
|
290
|
+
expect(msg).toContain("https://blog.acme.com/post");
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
273
294
|
describe("tasks and tools fields are ignored", () => {
|
|
274
295
|
it("tasks do not appear in output", () => {
|
|
275
296
|
const greeting = getCannedFirstGreeting({
|
|
@@ -27,6 +27,7 @@ let cdpSendHandler: (
|
|
|
27
27
|
params?: Record<string, unknown>,
|
|
28
28
|
) => unknown = () => ({});
|
|
29
29
|
let cdpDisposed = false;
|
|
30
|
+
let cdpSetSessionIdCalls: Array<string | undefined> = [];
|
|
30
31
|
|
|
31
32
|
function makeFakeCdp(kind: "local" | "extension", conversationId: string) {
|
|
32
33
|
return {
|
|
@@ -43,6 +44,12 @@ function makeFakeCdp(kind: "local" | "extension", conversationId: string) {
|
|
|
43
44
|
dispose() {
|
|
44
45
|
cdpDisposed = true;
|
|
45
46
|
},
|
|
47
|
+
// Mirrors the optional method on the real CdpClient interface so
|
|
48
|
+
// the --new-tab path can be exercised end-to-end. Recorded for
|
|
49
|
+
// per-test assertions.
|
|
50
|
+
setCdpSessionId(cdpSessionId: string | undefined) {
|
|
51
|
+
cdpSetSessionIdCalls.push(cdpSessionId);
|
|
52
|
+
},
|
|
46
53
|
};
|
|
47
54
|
}
|
|
48
55
|
|
|
@@ -135,6 +142,7 @@ mock.module("../tools/network/url-safety.js", () => ({
|
|
|
135
142
|
}));
|
|
136
143
|
|
|
137
144
|
import { executeBrowserNavigate } from "../tools/browser/browser-execution.js";
|
|
145
|
+
import { __resetPinnedTabsForTests } from "../tools/browser/pinned-tabs.js";
|
|
138
146
|
import type { ToolContext } from "../tools/types.js";
|
|
139
147
|
|
|
140
148
|
const ctx: ToolContext = {
|
|
@@ -212,6 +220,7 @@ function resetCdp() {
|
|
|
212
220
|
cdpSendCalls = [];
|
|
213
221
|
cdpDisposed = false;
|
|
214
222
|
cdpSendHandler = defaultCdpHandler;
|
|
223
|
+
cdpSetSessionIdCalls = [];
|
|
215
224
|
}
|
|
216
225
|
|
|
217
226
|
describe("executeBrowserNavigate", () => {
|
|
@@ -223,6 +232,7 @@ describe("executeBrowserNavigate", () => {
|
|
|
223
232
|
resolveResult = {};
|
|
224
233
|
resetMockPage();
|
|
225
234
|
resetCdp();
|
|
235
|
+
__resetPinnedTabsForTests();
|
|
226
236
|
});
|
|
227
237
|
|
|
228
238
|
// ── Input validation ───────────────────────────────────────────
|
|
@@ -664,6 +674,168 @@ describe("executeBrowserNavigate", () => {
|
|
|
664
674
|
expect(cdpDisposed).toBe(true);
|
|
665
675
|
});
|
|
666
676
|
|
|
677
|
+
// ── --new-tab flag (extension path only) ──────────────────────
|
|
678
|
+
|
|
679
|
+
test("extension path with new_tab: true opens a fresh tab and pins it before Page.navigate", async () => {
|
|
680
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
681
|
+
mockExtensionAvailable = true;
|
|
682
|
+
|
|
683
|
+
cdpSendHandler = (method, params) => {
|
|
684
|
+
if (method === "Vellum.createTab") return { tabId: "999" };
|
|
685
|
+
return defaultCdpHandler(method, params);
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const result = await executeBrowserNavigate(
|
|
689
|
+
{ url: "https://example.com/page", new_tab: true },
|
|
690
|
+
{ ...ctx },
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
expect(result.isError).toBe(false);
|
|
694
|
+
|
|
695
|
+
// Vellum.createTab fired and fired BEFORE Page.navigate.
|
|
696
|
+
const createIdx = cdpSendCalls.findIndex(
|
|
697
|
+
(c) => c.method === "Vellum.createTab",
|
|
698
|
+
);
|
|
699
|
+
const navIdx = cdpSendCalls.findIndex((c) => c.method === "Page.navigate");
|
|
700
|
+
expect(createIdx).toBeGreaterThanOrEqual(0);
|
|
701
|
+
expect(navIdx).toBeGreaterThanOrEqual(0);
|
|
702
|
+
expect(createIdx).toBeLessThan(navIdx);
|
|
703
|
+
|
|
704
|
+
// setCdpSessionId was invoked with the returned tabId so the
|
|
705
|
+
// follow-on commands on this client route to the new tab.
|
|
706
|
+
expect(cdpSetSessionIdCalls).toContain("999");
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
test("new_tab: true on extension path with createTab failure aborts with a clear error", async () => {
|
|
710
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
711
|
+
mockExtensionAvailable = true;
|
|
712
|
+
|
|
713
|
+
cdpSendHandler = (method, params) => {
|
|
714
|
+
if (method === "Vellum.createTab") {
|
|
715
|
+
throw new Error("createTab returned no tabId");
|
|
716
|
+
}
|
|
717
|
+
return defaultCdpHandler(method, params);
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
const result = await executeBrowserNavigate(
|
|
721
|
+
{ url: "https://example.com/page", new_tab: true },
|
|
722
|
+
{ ...ctx },
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
expect(result.isError).toBe(true);
|
|
726
|
+
expect(result.content).toContain("Failed to open a new tab");
|
|
727
|
+
// Page.navigate must NOT fire — that's exactly what --new-tab is
|
|
728
|
+
// supposed to prevent in the failure case (silent fallback to
|
|
729
|
+
// active-tab clobbering would defeat the purpose of the flag).
|
|
730
|
+
expect(cdpSendCalls.some((c) => c.method === "Page.navigate")).toBe(false);
|
|
731
|
+
// The createTab-failure path early-returns BEFORE the main
|
|
732
|
+
// try/finally that wraps the navigate flow, so the executor has to
|
|
733
|
+
// dispose the CDP client manually. Verifying here so future edits
|
|
734
|
+
// to that early-return don't silently regress the cleanup.
|
|
735
|
+
expect(cdpDisposed).toBe(true);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
test("new_tab: true on extension path with no tabId in response clears live session and still continues", async () => {
|
|
739
|
+
// Defensive: dispatcher returns success but no tabId. The
|
|
740
|
+
// executor logs a warn, resets the live cdp session to undefined
|
|
741
|
+
// (so the follow-on Page.navigate routes to the active tab
|
|
742
|
+
// instead of any stale pin the cdp instance was constructed
|
|
743
|
+
// with), and proceeds. The navigate still runs (degraded
|
|
744
|
+
// behaviour but not a hard failure).
|
|
745
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
746
|
+
mockExtensionAvailable = true;
|
|
747
|
+
|
|
748
|
+
cdpSendHandler = (method, params) => {
|
|
749
|
+
if (method === "Vellum.createTab") return {}; // no tabId
|
|
750
|
+
return defaultCdpHandler(method, params);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const result = await executeBrowserNavigate(
|
|
754
|
+
{ url: "https://example.com/page", new_tab: true },
|
|
755
|
+
{ ...ctx },
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
expect(result.isError).toBe(false);
|
|
759
|
+
// No new pin was set, BUT the live session was reset to undefined
|
|
760
|
+
// so the follow-on Page.navigate falls back to active-tab routing
|
|
761
|
+
// instead of any stale pin the cdp instance held at construction.
|
|
762
|
+
expect(cdpSetSessionIdCalls).toEqual([undefined]);
|
|
763
|
+
// Page.navigate still ran.
|
|
764
|
+
expect(cdpSendCalls.some((c) => c.method === "Page.navigate")).toBe(true);
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
test("new_tab: true with no tabId in response clears a pre-existing stale pin AND live session (regression)", async () => {
|
|
768
|
+
// Regression for the Codex round-2 findings (P2 + round-3 P1):
|
|
769
|
+
// when Vellum.createTab returns a malformed response with no
|
|
770
|
+
// tabId, the executor falls back to active-tab routing — but
|
|
771
|
+
// (a) the pin store still held the stale pin (round-2 P2 fix
|
|
772
|
+
// added clearPinnedTab), and (b) the LIVE cdp instance was
|
|
773
|
+
// already constructed with that stale cdpSessionId, so the
|
|
774
|
+
// follow-on Page.navigate would still route to the dead tab
|
|
775
|
+
// unless we reset the session on the cdp instance too (round-3
|
|
776
|
+
// P1 fix added cdp.setCdpSessionId(undefined)).
|
|
777
|
+
const { setPinnedTab, getPinnedTab } = await import(
|
|
778
|
+
"../tools/browser/pinned-tabs.js"
|
|
779
|
+
);
|
|
780
|
+
setPinnedTab(ctx.conversationId, "stale-pinned-tab-id");
|
|
781
|
+
expect(getPinnedTab(ctx.conversationId)).toBe("stale-pinned-tab-id");
|
|
782
|
+
|
|
783
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
784
|
+
mockExtensionAvailable = true;
|
|
785
|
+
|
|
786
|
+
cdpSendHandler = (method, params) => {
|
|
787
|
+
if (method === "Vellum.createTab") return {}; // no tabId
|
|
788
|
+
return defaultCdpHandler(method, params);
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
const result = await executeBrowserNavigate(
|
|
792
|
+
{ url: "https://example.com/page", new_tab: true },
|
|
793
|
+
{ ...ctx },
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
expect(result.isError).toBe(false);
|
|
797
|
+
// (a) Pin store cleared.
|
|
798
|
+
expect(getPinnedTab(ctx.conversationId)).toBeUndefined();
|
|
799
|
+
// (b) Live cdp session reset (the fake records every
|
|
800
|
+
// setCdpSessionId arg; expect exactly one call with undefined).
|
|
801
|
+
expect(cdpSetSessionIdCalls).toEqual([undefined]);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("new_tab: true on LOCAL path is a no-op (Playwright manages its own isolated browser)", async () => {
|
|
805
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
806
|
+
mockExtensionAvailable = false; // local path
|
|
807
|
+
|
|
808
|
+
const result = await executeBrowserNavigate(
|
|
809
|
+
{ url: "https://example.com/page", new_tab: true },
|
|
810
|
+
{ ...ctx },
|
|
811
|
+
);
|
|
812
|
+
|
|
813
|
+
expect(result.isError).toBe(false);
|
|
814
|
+
// No Vellum.createTab was issued on the local path — the flag is
|
|
815
|
+
// silently ignored because Playwright opens its own browser and
|
|
816
|
+
// there's no user-tab to disturb.
|
|
817
|
+
expect(cdpSendCalls.some((c) => c.method === "Vellum.createTab")).toBe(
|
|
818
|
+
false,
|
|
819
|
+
);
|
|
820
|
+
expect(cdpSetSessionIdCalls).toEqual([]);
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
test("absence of new_tab leaves extension path untouched (no Vellum.createTab)", async () => {
|
|
824
|
+
parseUrlResult = new URL("https://example.com/page");
|
|
825
|
+
mockExtensionAvailable = true;
|
|
826
|
+
|
|
827
|
+
const result = await executeBrowserNavigate(
|
|
828
|
+
{ url: "https://example.com/page" }, // no new_tab key at all
|
|
829
|
+
{ ...ctx },
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
expect(result.isError).toBe(false);
|
|
833
|
+
expect(cdpSendCalls.some((c) => c.method === "Vellum.createTab")).toBe(
|
|
834
|
+
false,
|
|
835
|
+
);
|
|
836
|
+
expect(cdpSetSessionIdCalls).toEqual([]);
|
|
837
|
+
});
|
|
838
|
+
|
|
667
839
|
// ── Defense-in-depth: post-navigation final URL check ─────────
|
|
668
840
|
|
|
669
841
|
test("post-nav check blocks when final URL resolves to private target", async () => {
|
|
@@ -43,6 +43,7 @@ let mockConfig = {
|
|
|
43
43
|
timezone: null as string | null,
|
|
44
44
|
activeHoursStart: undefined as number | undefined,
|
|
45
45
|
activeHoursEnd: undefined as number | undefined,
|
|
46
|
+
disposition: "Default disposition text mentioning notifications skill.",
|
|
46
47
|
},
|
|
47
48
|
};
|
|
48
49
|
|
|
@@ -386,6 +387,7 @@ describe("HeartbeatService", () => {
|
|
|
386
387
|
timezone: null,
|
|
387
388
|
activeHoursStart: undefined,
|
|
388
389
|
activeHoursEnd: undefined,
|
|
390
|
+
disposition: "Default disposition text mentioning notifications skill.",
|
|
389
391
|
},
|
|
390
392
|
};
|
|
391
393
|
});
|
|
@@ -418,8 +420,7 @@ describe("HeartbeatService", () => {
|
|
|
418
420
|
expect(processMessageCalls[0].conversationId).toBe("conv-1");
|
|
419
421
|
expect(processMessageCalls[0].content).toContain("<heartbeat-checklist>");
|
|
420
422
|
expect(processMessageCalls[0].content).toContain("<heartbeat-disposition>");
|
|
421
|
-
expect(processMessageCalls[0].content).toContain("
|
|
422
|
-
expect(processMessageCalls[0].content).toContain("HEARTBEAT_ALERT");
|
|
423
|
+
expect(processMessageCalls[0].content).toContain("notifications skill");
|
|
423
424
|
});
|
|
424
425
|
|
|
425
426
|
test("HEARTBEAT.md content is embedded in prompt when file exists", async () => {
|
|
@@ -750,30 +751,13 @@ describe("HeartbeatService", () => {
|
|
|
750
751
|
});
|
|
751
752
|
});
|
|
752
753
|
|
|
753
|
-
test("
|
|
754
|
+
test("conversation surfaces to the sidebar on every successful run", async () => {
|
|
754
755
|
const conversationCreatedCalls: Array<{
|
|
755
756
|
conversationId: string;
|
|
756
757
|
title: string;
|
|
757
758
|
}> = [];
|
|
758
759
|
const service = createService({
|
|
759
760
|
onConversationCreated: (info) => conversationCreatedCalls.push(info),
|
|
760
|
-
processMessage: async (...args: unknown[]) => {
|
|
761
|
-
const conversationId = args[0] as string;
|
|
762
|
-
mockStoredMessages.push({
|
|
763
|
-
id: "assistant-alert-1",
|
|
764
|
-
conversationId,
|
|
765
|
-
role: "assistant",
|
|
766
|
-
content: JSON.stringify([
|
|
767
|
-
{
|
|
768
|
-
type: "text",
|
|
769
|
-
text: "The first heartbeat found a concrete follow-up for the guardian.\nHEARTBEAT_ALERT",
|
|
770
|
-
},
|
|
771
|
-
]),
|
|
772
|
-
createdAt: Date.now(),
|
|
773
|
-
metadata: null,
|
|
774
|
-
});
|
|
775
|
-
return { messageId: "user-heartbeat-1" };
|
|
776
|
-
},
|
|
777
761
|
});
|
|
778
762
|
|
|
779
763
|
await service.runOnce();
|
|
@@ -782,113 +766,6 @@ describe("HeartbeatService", () => {
|
|
|
782
766
|
expect(conversationCreatedCalls).toEqual([
|
|
783
767
|
{ conversationId: "conv-1", title: "Heartbeat" },
|
|
784
768
|
]);
|
|
785
|
-
expect(emittedNotificationSignals).toHaveLength(1);
|
|
786
|
-
expect(emittedNotificationSignals[0]).toMatchObject({
|
|
787
|
-
sourceEventName: "heartbeat.alert",
|
|
788
|
-
sourceChannel: "watcher",
|
|
789
|
-
sourceContextId: "mock-run-id",
|
|
790
|
-
dedupeKey: "heartbeat:alert:mock-run-id",
|
|
791
|
-
attentionHints: {
|
|
792
|
-
requiresAction: true,
|
|
793
|
-
urgency: "medium",
|
|
794
|
-
isAsyncBackground: true,
|
|
795
|
-
visibleInSourceNow: false,
|
|
796
|
-
},
|
|
797
|
-
conversationAffinityHint: { vellum: "conv-1" },
|
|
798
|
-
conversationMetadata: {
|
|
799
|
-
source: "heartbeat",
|
|
800
|
-
groupId: "system:background",
|
|
801
|
-
},
|
|
802
|
-
});
|
|
803
|
-
expect(emittedNotificationSignals[0].contextPayload.summary).toBe(
|
|
804
|
-
"The first heartbeat found a concrete follow-up for the guardian.",
|
|
805
|
-
);
|
|
806
|
-
expect(emittedNotificationSignals[0].contextPayload.messageId).toBe(
|
|
807
|
-
"assistant-alert-1",
|
|
808
|
-
);
|
|
809
|
-
expect(
|
|
810
|
-
emittedNotificationSignals[0].contextPayload.sourceInterface,
|
|
811
|
-
).toBeUndefined();
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
test("HEARTBEAT_OK stays silent", async () => {
|
|
815
|
-
const conversationCreatedCalls: Array<{
|
|
816
|
-
conversationId: string;
|
|
817
|
-
title: string;
|
|
818
|
-
}> = [];
|
|
819
|
-
const service = createService({
|
|
820
|
-
onConversationCreated: (info) => conversationCreatedCalls.push(info),
|
|
821
|
-
processMessage: async (...args: unknown[]) => {
|
|
822
|
-
const conversationId = args[0] as string;
|
|
823
|
-
mockStoredMessages.push({
|
|
824
|
-
id: "assistant-ok-1",
|
|
825
|
-
conversationId,
|
|
826
|
-
role: "assistant",
|
|
827
|
-
content: JSON.stringify([
|
|
828
|
-
{
|
|
829
|
-
type: "text",
|
|
830
|
-
text: "Everything looks good.\nHEARTBEAT_OK",
|
|
831
|
-
},
|
|
832
|
-
]),
|
|
833
|
-
createdAt: Date.now(),
|
|
834
|
-
metadata: null,
|
|
835
|
-
});
|
|
836
|
-
return { messageId: "user-heartbeat-1" };
|
|
837
|
-
},
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
await service.runOnce();
|
|
841
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
842
|
-
|
|
843
|
-
// The conversation surfaces to the sidebar via the runner's bootstrap
|
|
844
|
-
// callback for *every* heartbeat — "silent OK" means no notification
|
|
845
|
-
// signal is emitted, not that the conversation is hidden.
|
|
846
|
-
expect(conversationCreatedCalls).toHaveLength(1);
|
|
847
|
-
expect(emittedNotificationSignals).toHaveLength(0);
|
|
848
|
-
});
|
|
849
|
-
|
|
850
|
-
test("HEARTBEAT_OK stays silent when earlier content mentions HEARTBEAT_ALERT", async () => {
|
|
851
|
-
const conversationCreatedCalls: Array<{
|
|
852
|
-
conversationId: string;
|
|
853
|
-
title: string;
|
|
854
|
-
}> = [];
|
|
855
|
-
const service = createService({
|
|
856
|
-
onConversationCreated: (info) => conversationCreatedCalls.push(info),
|
|
857
|
-
processMessage: async (...args: unknown[]) => {
|
|
858
|
-
const conversationId = args[0] as string;
|
|
859
|
-
mockStoredMessages.push({
|
|
860
|
-
id: "assistant-ok-2",
|
|
861
|
-
conversationId,
|
|
862
|
-
role: "assistant",
|
|
863
|
-
content: JSON.stringify([
|
|
864
|
-
{
|
|
865
|
-
type: "thinking",
|
|
866
|
-
thinking:
|
|
867
|
-
"I should decide between HEARTBEAT_ALERT and HEARTBEAT_OK.",
|
|
868
|
-
},
|
|
869
|
-
{
|
|
870
|
-
type: "tool_result",
|
|
871
|
-
content: "Tool output mentions HEARTBEAT_ALERT.",
|
|
872
|
-
},
|
|
873
|
-
{
|
|
874
|
-
type: "text",
|
|
875
|
-
text: "I considered HEARTBEAT_ALERT, but there is nothing useful to surface.\nHEARTBEAT_OK",
|
|
876
|
-
},
|
|
877
|
-
]),
|
|
878
|
-
createdAt: Date.now(),
|
|
879
|
-
metadata: null,
|
|
880
|
-
});
|
|
881
|
-
return { messageId: "user-heartbeat-1" };
|
|
882
|
-
},
|
|
883
|
-
});
|
|
884
|
-
|
|
885
|
-
await service.runOnce();
|
|
886
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
887
|
-
|
|
888
|
-
// Conversation surfaces via the runner bootstrap, but no notification
|
|
889
|
-
// is emitted since the disposition is OK.
|
|
890
|
-
expect(conversationCreatedCalls).toHaveLength(1);
|
|
891
|
-
expect(emittedNotificationSignals).toHaveLength(0);
|
|
892
769
|
});
|
|
893
770
|
|
|
894
771
|
test("end-to-end: llm.callSites.heartbeatAgent.speed resolves to 'fast'", async () => {
|
|
@@ -1524,43 +1401,6 @@ describe("HeartbeatService", () => {
|
|
|
1524
1401
|
});
|
|
1525
1402
|
});
|
|
1526
1403
|
|
|
1527
|
-
test("CAS false suppresses success surfacing", async () => {
|
|
1528
|
-
mockCompleteHeartbeatRun.mockImplementation(() => false);
|
|
1529
|
-
|
|
1530
|
-
const conversationCreatedCalls: Array<{
|
|
1531
|
-
conversationId: string;
|
|
1532
|
-
title: string;
|
|
1533
|
-
}> = [];
|
|
1534
|
-
const service = createService({
|
|
1535
|
-
onConversationCreated: (info) => conversationCreatedCalls.push(info),
|
|
1536
|
-
processMessage: async (...args: unknown[]) => {
|
|
1537
|
-
const conversationId = args[0] as string;
|
|
1538
|
-
mockStoredMessages.push({
|
|
1539
|
-
id: "assistant-alert-1",
|
|
1540
|
-
conversationId,
|
|
1541
|
-
role: "assistant",
|
|
1542
|
-
content: JSON.stringify([
|
|
1543
|
-
{
|
|
1544
|
-
type: "text",
|
|
1545
|
-
text: "Something worth surfacing.\nHEARTBEAT_ALERT",
|
|
1546
|
-
},
|
|
1547
|
-
]),
|
|
1548
|
-
createdAt: Date.now(),
|
|
1549
|
-
metadata: null,
|
|
1550
|
-
});
|
|
1551
|
-
return { messageId: "msg-1" };
|
|
1552
|
-
},
|
|
1553
|
-
});
|
|
1554
|
-
await service.runOnce();
|
|
1555
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1556
|
-
|
|
1557
|
-
// The bootstrap-time surface fires regardless of CAS (it happens
|
|
1558
|
-
// before completeHeartbeatRun). CAS-false suppresses the alert
|
|
1559
|
-
// notification emit but not the sidebar entry.
|
|
1560
|
-
expect(conversationCreatedCalls).toHaveLength(1);
|
|
1561
|
-
expect(emittedNotificationSignals).toHaveLength(0);
|
|
1562
|
-
});
|
|
1563
|
-
|
|
1564
1404
|
test("CAS false suppresses failure alerter and feed event", async () => {
|
|
1565
1405
|
mockCompleteHeartbeatRun.mockImplementation(() => false);
|
|
1566
1406
|
|
|
@@ -1836,4 +1676,24 @@ describe("HeartbeatService", () => {
|
|
|
1836
1676
|
expect(processMessageCalls[0].content).not.toContain("<early-heartbeat>");
|
|
1837
1677
|
});
|
|
1838
1678
|
});
|
|
1679
|
+
|
|
1680
|
+
describe("configurable disposition", () => {
|
|
1681
|
+
test("injects the configured disposition inside <heartbeat-disposition>", () => {
|
|
1682
|
+
mockConfig.heartbeat.disposition = "Marker text from config.";
|
|
1683
|
+
const service = createService();
|
|
1684
|
+
const { prompt } = service.buildPrompt("- Check things", [], 10);
|
|
1685
|
+
|
|
1686
|
+
expect(prompt).toContain(
|
|
1687
|
+
"<heartbeat-disposition>\nMarker text from config.\n</heartbeat-disposition>",
|
|
1688
|
+
);
|
|
1689
|
+
});
|
|
1690
|
+
|
|
1691
|
+
test("omits the block when disposition is empty", () => {
|
|
1692
|
+
mockConfig.heartbeat.disposition = "";
|
|
1693
|
+
const service = createService();
|
|
1694
|
+
const { prompt } = service.buildPrompt("- Check things", [], 10);
|
|
1695
|
+
|
|
1696
|
+
expect(prompt).not.toContain("<heartbeat-disposition>");
|
|
1697
|
+
});
|
|
1698
|
+
});
|
|
1839
1699
|
});
|
|
@@ -65,7 +65,6 @@ import type {
|
|
|
65
65
|
ApprovalConversationGenerator,
|
|
66
66
|
ApprovalCopyGenerator,
|
|
67
67
|
GuardianActionCopyGenerator,
|
|
68
|
-
GuardianFollowUpConversationGenerator,
|
|
69
68
|
MessageProcessor,
|
|
70
69
|
} from "../../runtime/http-types.js";
|
|
71
70
|
import {
|
|
@@ -113,7 +112,6 @@ export async function handleChannelInbound(
|
|
|
113
112
|
_approvalCopyGenerator?: ApprovalCopyGenerator,
|
|
114
113
|
_approvalConversationGenerator?: ApprovalConversationGenerator,
|
|
115
114
|
_guardianActionCopyGenerator?: GuardianActionCopyGenerator,
|
|
116
|
-
_guardianFollowUpConversationGenerator?: GuardianFollowUpConversationGenerator,
|
|
117
115
|
): Promise<Response> {
|
|
118
116
|
const body = await req.json();
|
|
119
117
|
return wrapHandler(() => _handleChannelInbound({ body }));
|