@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
|
@@ -2,10 +2,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { FeedItem } from "../../home/feed-types.js";
|
|
4
4
|
import type { NotificationSignal } from "../signal.js";
|
|
5
|
-
import type {
|
|
6
|
-
NotificationDecision,
|
|
7
|
-
NotificationDeliveryResult,
|
|
8
|
-
} from "../types.js";
|
|
5
|
+
import type { NotificationDecision } from "../types.js";
|
|
9
6
|
|
|
10
7
|
// ── Module mocks ───────────────────────────────────────────────────────
|
|
11
8
|
//
|
|
@@ -83,9 +80,11 @@ beforeEach(() => {
|
|
|
83
80
|
});
|
|
84
81
|
|
|
85
82
|
describe("writeHomeFeedItemForSignal", () => {
|
|
86
|
-
test("background conversation signal writes a feed item with rendered
|
|
83
|
+
test("background conversation signal writes a feed item with payload title + rendered body", async () => {
|
|
87
84
|
conversationRow = { conversationType: "background" };
|
|
88
|
-
const signal = makeSignal(
|
|
85
|
+
const signal = makeSignal({
|
|
86
|
+
contextPayload: { title: "Background job done" },
|
|
87
|
+
});
|
|
89
88
|
const decision = makeDecision({
|
|
90
89
|
renderedCopy: {
|
|
91
90
|
vellum: {
|
|
@@ -95,7 +94,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
95
94
|
},
|
|
96
95
|
});
|
|
97
96
|
|
|
98
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
97
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
99
98
|
|
|
100
99
|
expect(conversationLookups).toEqual(["conv-source-1"]);
|
|
101
100
|
expect(item).not.toBeNull();
|
|
@@ -112,6 +111,10 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
112
111
|
expect(appended.title).toBe("Background job done");
|
|
113
112
|
expect(appended.summary).toBe("Summary of what happened.");
|
|
114
113
|
expect(appended.urgency).toBe("medium");
|
|
114
|
+
// The button in the home detail panel navigates to the source
|
|
115
|
+
// conversation that emitted the notification, not the conversation the
|
|
116
|
+
// notification pipeline spawned to handle it.
|
|
117
|
+
expect(appended.conversationId).toBe("conv-source-1");
|
|
115
118
|
expect(typeof appended.timestamp).toBe("string");
|
|
116
119
|
expect(appended.createdAt).toBe(appended.timestamp);
|
|
117
120
|
});
|
|
@@ -127,15 +130,16 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
127
130
|
},
|
|
128
131
|
});
|
|
129
132
|
|
|
130
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
133
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
131
134
|
|
|
132
135
|
expect(item).toBeNull();
|
|
133
136
|
expect(appendCalls).toHaveLength(0);
|
|
134
137
|
});
|
|
135
138
|
|
|
136
139
|
test("isAsyncBackground hint writes even when sourceContextId does not resolve", async () => {
|
|
137
|
-
//
|
|
138
|
-
//
|
|
140
|
+
// Source lookup throws — treated as non-navigable, so the item lands
|
|
141
|
+
// without a `conversationId` and the "Go to Thread" button hides on the
|
|
142
|
+
// client. The async-background hint still forces the mirror.
|
|
139
143
|
conversationLookupShouldThrow = true;
|
|
140
144
|
const signal = makeSignal({
|
|
141
145
|
sourceContextId: "not-a-conversation-id",
|
|
@@ -146,54 +150,535 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
146
150
|
visibleInSourceNow: false,
|
|
147
151
|
},
|
|
148
152
|
});
|
|
153
|
+
const decision = makeDecision({
|
|
154
|
+
renderedCopy: {
|
|
155
|
+
vellum: { title: "Async title", body: "Async body" },
|
|
156
|
+
},
|
|
157
|
+
});
|
|
149
158
|
|
|
150
|
-
const item = await writeHomeFeedItemForSignal(signal,
|
|
159
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
151
160
|
|
|
152
161
|
expect(item).not.toBeNull();
|
|
153
162
|
expect(appendCalls).toHaveLength(1);
|
|
154
163
|
expect(appendCalls[0]!.urgency).toBe("high");
|
|
155
|
-
|
|
156
|
-
expect(conversationLookups).
|
|
164
|
+
expect(appendCalls[0]!.conversationId).toBeUndefined();
|
|
165
|
+
expect(conversationLookups).toEqual(["not-a-conversation-id"]);
|
|
157
166
|
});
|
|
158
167
|
|
|
159
|
-
test("
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
test("assistant_tool source mirrors to the home feed even without a background conversation or async hint", async () => {
|
|
169
|
+
// Regression: the `notifications send` CLI/skill emits with
|
|
170
|
+
// `sourceChannel: "assistant_tool"`, a synthetic `cli-<ts>` source
|
|
171
|
+
// context id that does not resolve to a conversation, and
|
|
172
|
+
// `isAsyncBackground: false`. The assistant_tool channel forces the
|
|
173
|
+
// mirror; the source-id lookup misses so the item lands without a
|
|
174
|
+
// `conversationId` and the "Go to Thread" button hides on the client.
|
|
175
|
+
conversationRow = null;
|
|
176
|
+
const signal = makeSignal({
|
|
177
|
+
sourceChannel: "assistant_tool",
|
|
178
|
+
sourceEventName: "assistant.share",
|
|
179
|
+
sourceContextId: "cli-12345",
|
|
180
|
+
contextPayload: { title: "Shared from CLI" },
|
|
181
|
+
attentionHints: {
|
|
182
|
+
requiresAction: false,
|
|
183
|
+
urgency: "low",
|
|
184
|
+
isAsyncBackground: false,
|
|
185
|
+
visibleInSourceNow: false,
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
const decision = makeDecision({
|
|
189
|
+
renderedCopy: {
|
|
190
|
+
vellum: { title: "Shared from CLI", body: "Body from CLI share" },
|
|
168
191
|
},
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
195
|
+
|
|
196
|
+
expect(item).not.toBeNull();
|
|
197
|
+
expect(appendCalls).toHaveLength(1);
|
|
198
|
+
expect(appendCalls[0]!.title).toBe("Shared from CLI");
|
|
199
|
+
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
200
|
+
expect(appendCalls[0]!.conversationId).toBeUndefined();
|
|
201
|
+
expect(conversationLookups).toEqual(["cli-12345"]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("source conversation id does not propagate when the lookup misses", async () => {
|
|
205
|
+
// When `sourceContextId` does not resolve to a real conversation row
|
|
206
|
+
// (e.g. scheduler job id, watcher event id), the item is still mirrored
|
|
207
|
+
// via the `isAsyncBackground` hint but `conversationId` stays undefined
|
|
208
|
+
// so the client hides the "Go to Thread" affordance.
|
|
209
|
+
conversationRow = null;
|
|
210
|
+
const signal = makeSignal({
|
|
211
|
+
sourceContextId: "scheduler-job-42",
|
|
212
|
+
attentionHints: {
|
|
213
|
+
requiresAction: false,
|
|
214
|
+
urgency: "medium",
|
|
215
|
+
isAsyncBackground: true,
|
|
216
|
+
visibleInSourceNow: false,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
const decision = makeDecision({
|
|
220
|
+
renderedCopy: {
|
|
221
|
+
vellum: { title: "Routed title", body: "Routed body" },
|
|
174
222
|
},
|
|
175
|
-
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
226
|
+
|
|
227
|
+
expect(item?.conversationId).toBeUndefined();
|
|
228
|
+
expect(appendCalls[0]!.conversationId).toBeUndefined();
|
|
229
|
+
expect(conversationLookups).toEqual(["scheduler-job-42"]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("falls back to the paired delivery conversation when sourceContextId does not resolve", async () => {
|
|
233
|
+
// Regression: producers whose `sourceContextId` is a sentinel string
|
|
234
|
+
// (heartbeat startup `"heartbeat"`, credential health `connectionId`,
|
|
235
|
+
// watcher `watcher-<ts>`, scheduler retries-exhausted `jobId`, sweep
|
|
236
|
+
// job id) never resolve via `getConversation`. The notification
|
|
237
|
+
// broadcaster pairs each vellum delivery with a real conversation
|
|
238
|
+
// before the home-feed write runs, so the caller threads that paired
|
|
239
|
+
// id through as the fallback — the "Go to Convo" button now points at
|
|
240
|
+
// the conversation the notification was actually delivered into.
|
|
241
|
+
conversationRow = null;
|
|
242
|
+
const signal = makeSignal({
|
|
243
|
+
sourceChannel: "assistant_tool",
|
|
244
|
+
sourceEventName: "assistant.share",
|
|
245
|
+
sourceContextId: "watcher-1700000000",
|
|
246
|
+
contextPayload: { title: "Watcher alert" },
|
|
247
|
+
attentionHints: {
|
|
248
|
+
requiresAction: false,
|
|
249
|
+
urgency: "medium",
|
|
250
|
+
isAsyncBackground: true,
|
|
251
|
+
visibleInSourceNow: false,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
const decision = makeDecision({
|
|
255
|
+
renderedCopy: {
|
|
256
|
+
vellum: { title: "Watcher alert", body: "Watcher body" },
|
|
257
|
+
},
|
|
258
|
+
});
|
|
176
259
|
|
|
177
260
|
const item = await writeHomeFeedItemForSignal(
|
|
178
261
|
signal,
|
|
179
|
-
|
|
180
|
-
|
|
262
|
+
decision,
|
|
263
|
+
"paired-delivery-conv-id",
|
|
181
264
|
);
|
|
182
265
|
|
|
183
|
-
expect(item
|
|
184
|
-
expect(appendCalls
|
|
266
|
+
expect(item).not.toBeNull();
|
|
267
|
+
expect(appendCalls).toHaveLength(1);
|
|
268
|
+
expect(appendCalls[0]!.conversationId).toBe("paired-delivery-conv-id");
|
|
185
269
|
});
|
|
186
270
|
|
|
187
|
-
test("
|
|
271
|
+
test("source conversation id wins over the paired delivery fallback when both are available", async () => {
|
|
272
|
+
// When the producer's `sourceContextId` already points at a real
|
|
273
|
+
// conversation (the canonical "where the work happened"), prefer it
|
|
274
|
+
// over the paired delivery — the fallback is only meant to fill the
|
|
275
|
+
// gap for sentinel-id producers.
|
|
276
|
+
conversationRow = { conversationType: "background" };
|
|
277
|
+
const signal = makeSignal({
|
|
278
|
+
contextPayload: { title: "Background job done" },
|
|
279
|
+
});
|
|
280
|
+
const decision = makeDecision({
|
|
281
|
+
renderedCopy: {
|
|
282
|
+
vellum: { title: "Background job done", body: "Summary." },
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const item = await writeHomeFeedItemForSignal(
|
|
287
|
+
signal,
|
|
288
|
+
decision,
|
|
289
|
+
"paired-delivery-conv-id",
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
expect(item).not.toBeNull();
|
|
293
|
+
expect(appendCalls[0]!.conversationId).toBe("conv-source-1");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("returns null and does not write when no rendered copy or payload title/body is present", async () => {
|
|
188
297
|
conversationRow = { conversationType: "scheduled" };
|
|
189
298
|
const signal = makeSignal({
|
|
190
299
|
sourceEventName: "watcher.notification",
|
|
191
300
|
contextPayload: {},
|
|
192
301
|
});
|
|
193
302
|
|
|
194
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
303
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
304
|
+
|
|
305
|
+
expect(item).toBeNull();
|
|
306
|
+
expect(appendCalls).toHaveLength(0);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("returns null when only the title is available but the summary would fall back to event name", async () => {
|
|
310
|
+
conversationRow = { conversationType: "background" };
|
|
311
|
+
const signal = makeSignal({
|
|
312
|
+
sourceEventName: "example.event",
|
|
313
|
+
contextPayload: { title: "Real title" },
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
317
|
+
|
|
318
|
+
expect(item).toBeNull();
|
|
319
|
+
expect(appendCalls).toHaveLength(0);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("writes a feed item with undefined title when only the body is available", async () => {
|
|
323
|
+
// Regression: when `notifications send` is called without `--title`, the
|
|
324
|
+
// notification pipeline must not manufacture a title (the LLM's rendered
|
|
325
|
+
// copy echoes the body into `renderedCopy.title`). Leave `title`
|
|
326
|
+
// undefined so renderers fall back to `summary` instead of stuttering.
|
|
327
|
+
conversationRow = { conversationType: "background" };
|
|
328
|
+
const signal = makeSignal({
|
|
329
|
+
sourceEventName: "example.event",
|
|
330
|
+
contextPayload: { body: "Real body" },
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
334
|
+
|
|
335
|
+
expect(item).not.toBeNull();
|
|
336
|
+
expect(appendCalls).toHaveLength(1);
|
|
337
|
+
expect(appendCalls[0]!.title).toBeUndefined();
|
|
338
|
+
expect(appendCalls[0]!.summary).toBe("Real body");
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("ignores LLM-rendered title when no payload title was supplied", async () => {
|
|
342
|
+
// The LLM often echoes the body verbatim into `renderedCopy.title` when
|
|
343
|
+
// the source didn't pass one. The home-feed writer must NOT promote that
|
|
344
|
+
// echo into the feed item — only an explicit source title is honored.
|
|
345
|
+
conversationRow = { conversationType: "background" };
|
|
346
|
+
const signal = makeSignal({
|
|
347
|
+
sourceEventName: "example.event",
|
|
348
|
+
contextPayload: { body: "Real body" },
|
|
349
|
+
});
|
|
350
|
+
const decision = makeDecision({
|
|
351
|
+
renderedCopy: {
|
|
352
|
+
vellum: {
|
|
353
|
+
title: "Real body",
|
|
354
|
+
body: "Real body",
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
360
|
+
|
|
361
|
+
expect(item).not.toBeNull();
|
|
362
|
+
expect(appendCalls).toHaveLength(1);
|
|
363
|
+
expect(appendCalls[0]!.title).toBeUndefined();
|
|
364
|
+
expect(appendCalls[0]!.summary).toBe("Real body");
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("treats whitespace-only rendered copy and payload values as missing and returns null", async () => {
|
|
368
|
+
conversationRow = { conversationType: "background" };
|
|
369
|
+
const signal = makeSignal({
|
|
370
|
+
sourceEventName: "example.event",
|
|
371
|
+
contextPayload: { title: " ", body: "\t\n" },
|
|
372
|
+
});
|
|
373
|
+
const decision = makeDecision({
|
|
374
|
+
renderedCopy: {
|
|
375
|
+
vellum: { title: " ", body: " " },
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
380
|
+
|
|
381
|
+
expect(item).toBeNull();
|
|
382
|
+
expect(appendCalls).toHaveLength(0);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("falls back to a non-vellum channel's rendered copy when vellum copy is absent", async () => {
|
|
386
|
+
// Regression: when `preferredChannels` narrows an assistant_tool signal
|
|
387
|
+
// to a non-vellum channel (e.g. telegram), the broadcaster ships real
|
|
388
|
+
// copy on that channel but `renderedCopy.vellum` is undefined. The
|
|
389
|
+
// guard must still write to the home feed using the first available
|
|
390
|
+
// rendered copy entry rather than skipping silently.
|
|
391
|
+
conversationRow = { conversationType: "background" };
|
|
392
|
+
const signal = makeSignal({
|
|
393
|
+
sourceChannel: "assistant_tool",
|
|
394
|
+
sourceEventName: "assistant.share",
|
|
395
|
+
sourceContextId: "cli-12345",
|
|
396
|
+
contextPayload: { title: "Telegram title" },
|
|
397
|
+
});
|
|
398
|
+
const decision = makeDecision({
|
|
399
|
+
selectedChannels: ["telegram"],
|
|
400
|
+
renderedCopy: {
|
|
401
|
+
telegram: { title: "Telegram title", body: "Telegram body" },
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
406
|
+
|
|
407
|
+
expect(item).not.toBeNull();
|
|
408
|
+
expect(appendCalls).toHaveLength(1);
|
|
409
|
+
expect(appendCalls[0]!.title).toBe("Telegram title");
|
|
410
|
+
expect(appendCalls[0]!.summary).toBe("Telegram body");
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
test("ignores rendered copy for channels not in selectedChannels", async () => {
|
|
414
|
+
// Regression: routing-intent enforcement can prune selectedChannels
|
|
415
|
+
// without pruning renderedCopy, leaving copy entries for channels that
|
|
416
|
+
// were never delivered. The fallback must only consider channels that
|
|
417
|
+
// actually shipped — otherwise an unselected channel's copy can land in
|
|
418
|
+
// Home in place of the selected channel's copy.
|
|
419
|
+
conversationRow = { conversationType: "background" };
|
|
420
|
+
const signal = makeSignal({
|
|
421
|
+
sourceChannel: "assistant_tool",
|
|
422
|
+
sourceEventName: "assistant.share",
|
|
423
|
+
sourceContextId: "cli-12345",
|
|
424
|
+
contextPayload: { title: "Telegram title" },
|
|
425
|
+
});
|
|
426
|
+
const decision = makeDecision({
|
|
427
|
+
selectedChannels: ["telegram"],
|
|
428
|
+
renderedCopy: {
|
|
429
|
+
slack: {
|
|
430
|
+
title: "Slack title (unselected)",
|
|
431
|
+
body: "Slack body (unselected)",
|
|
432
|
+
},
|
|
433
|
+
telegram: { title: "Telegram title", body: "Telegram body" },
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
438
|
+
|
|
439
|
+
expect(item).not.toBeNull();
|
|
440
|
+
expect(appendCalls).toHaveLength(1);
|
|
441
|
+
expect(appendCalls[0]!.title).toBe("Telegram title");
|
|
442
|
+
expect(appendCalls[0]!.summary).toBe("Telegram body");
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
test("skips fallback when only unselected channels have rendered copy", async () => {
|
|
446
|
+
// Regression: if every renderedCopy entry is for a channel that was
|
|
447
|
+
// pruned from selectedChannels, treat it as no copy at all rather than
|
|
448
|
+
// surfacing the stale entry.
|
|
449
|
+
conversationRow = { conversationType: "background" };
|
|
450
|
+
const signal = makeSignal({
|
|
451
|
+
sourceChannel: "assistant_tool",
|
|
452
|
+
sourceEventName: "assistant.share",
|
|
453
|
+
sourceContextId: "cli-12345",
|
|
454
|
+
});
|
|
455
|
+
const decision = makeDecision({
|
|
456
|
+
selectedChannels: ["telegram"],
|
|
457
|
+
renderedCopy: {
|
|
458
|
+
slack: {
|
|
459
|
+
title: "Slack title (unselected)",
|
|
460
|
+
body: "Slack body (unselected)",
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
466
|
+
|
|
467
|
+
expect(item).toBeNull();
|
|
468
|
+
expect(appendCalls).toHaveLength(0);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("falls back to requestedTitle/requestedMessage payload keys", async () => {
|
|
472
|
+
// Regression: the `notifications send` CLI surface stores the
|
|
473
|
+
// user-supplied copy on the signal payload under `requestedTitle` and
|
|
474
|
+
// `requestedMessage`. If the decision strips renderedCopy.vellum (e.g.
|
|
475
|
+
// routed only to a non-vellum channel that also lacks renderedCopy),
|
|
476
|
+
// the home-feed guard must still recover the copy from the payload.
|
|
477
|
+
conversationRow = { conversationType: "background" };
|
|
478
|
+
const signal = makeSignal({
|
|
479
|
+
sourceChannel: "assistant_tool",
|
|
480
|
+
sourceEventName: "assistant.share",
|
|
481
|
+
sourceContextId: "cli-12345",
|
|
482
|
+
contextPayload: {
|
|
483
|
+
requestedTitle: "Requested title",
|
|
484
|
+
requestedMessage: "Requested message body",
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
489
|
+
|
|
490
|
+
expect(item).not.toBeNull();
|
|
491
|
+
expect(appendCalls).toHaveLength(1);
|
|
492
|
+
expect(appendCalls[0]!.title).toBe("Requested title");
|
|
493
|
+
expect(appendCalls[0]!.summary).toBe("Requested message body");
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
test("uses payload title/body when rendered copy is absent", async () => {
|
|
497
|
+
conversationRow = { conversationType: "background" };
|
|
498
|
+
const signal = makeSignal({
|
|
499
|
+
sourceEventName: "watcher.notification",
|
|
500
|
+
contextPayload: { title: "Payload title", body: "Payload body" },
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
504
|
+
|
|
505
|
+
expect(item).not.toBeNull();
|
|
506
|
+
expect(item?.title).toBe("Payload title");
|
|
507
|
+
expect(item?.summary).toBe("Payload body");
|
|
508
|
+
expect(appendCalls).toHaveLength(1);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
// ── noteworthy derivation ────────────────────────────────────────────
|
|
512
|
+
|
|
513
|
+
test("assistant_tool source marks the feed item noteworthy", async () => {
|
|
514
|
+
conversationRow = { conversationType: "background" };
|
|
515
|
+
const signal = makeSignal({
|
|
516
|
+
sourceChannel: "assistant_tool",
|
|
517
|
+
sourceEventName: "user.send_notification",
|
|
518
|
+
contextPayload: { title: "Tool share", body: "Body" },
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
522
|
+
|
|
523
|
+
expect(item?.noteworthy).toBe(true);
|
|
524
|
+
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test("assistant_tool source sets fromAssistant=true", async () => {
|
|
528
|
+
conversationRow = { conversationType: "background" };
|
|
529
|
+
const signal = makeSignal({
|
|
530
|
+
sourceChannel: "assistant_tool",
|
|
531
|
+
sourceEventName: "user.send_notification",
|
|
532
|
+
contextPayload: { title: "Tool share", body: "Body" },
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
536
|
+
|
|
537
|
+
expect(item?.fromAssistant).toBe(true);
|
|
538
|
+
expect(appendCalls[0]!.fromAssistant).toBe(true);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
test("non-assistant_tool source sets fromAssistant=false", async () => {
|
|
542
|
+
conversationRow = { conversationType: "background" };
|
|
543
|
+
const signal = makeSignal({
|
|
544
|
+
sourceChannel: "scheduler",
|
|
545
|
+
sourceEventName: "schedule.notify",
|
|
546
|
+
contextPayload: { title: "Reminder", body: "Time to do thing" },
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
550
|
+
|
|
551
|
+
expect(item?.fromAssistant).toBe(false);
|
|
552
|
+
expect(appendCalls[0]!.fromAssistant).toBe(false);
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
test("scheduler source with schedule.notify is not noteworthy", async () => {
|
|
556
|
+
conversationRow = { conversationType: "background" };
|
|
557
|
+
const signal = makeSignal({
|
|
558
|
+
sourceChannel: "scheduler",
|
|
559
|
+
sourceEventName: "schedule.notify",
|
|
560
|
+
contextPayload: { title: "Reminder", body: "Time to do thing" },
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
564
|
+
|
|
565
|
+
expect(item?.noteworthy).toBe(false);
|
|
566
|
+
expect(appendCalls[0]!.noteworthy).toBe(false);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
test("assistant_tool source with guardian.question event still wins (noteworthy true)", async () => {
|
|
570
|
+
conversationRow = { conversationType: "background" };
|
|
571
|
+
const signal = makeSignal({
|
|
572
|
+
sourceChannel: "assistant_tool",
|
|
573
|
+
sourceEventName: "guardian.question",
|
|
574
|
+
contextPayload: { title: "Question", body: "Approve?" },
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
578
|
+
|
|
579
|
+
expect(item?.noteworthy).toBe(true);
|
|
580
|
+
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
test("activity.failed with critical urgency is noteworthy (scheduler source)", async () => {
|
|
584
|
+
conversationRow = { conversationType: "background" };
|
|
585
|
+
const signal = makeSignal({
|
|
586
|
+
sourceChannel: "scheduler",
|
|
587
|
+
sourceEventName: "activity.failed",
|
|
588
|
+
contextPayload: { title: "Job failed", body: "Critical failure" },
|
|
589
|
+
attentionHints: {
|
|
590
|
+
requiresAction: false,
|
|
591
|
+
urgency: "critical",
|
|
592
|
+
isAsyncBackground: false,
|
|
593
|
+
visibleInSourceNow: false,
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
598
|
+
|
|
599
|
+
expect(item?.noteworthy).toBe(true);
|
|
600
|
+
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
test("activity.failed with low urgency is not noteworthy (scheduler source)", async () => {
|
|
604
|
+
conversationRow = { conversationType: "background" };
|
|
605
|
+
const signal = makeSignal({
|
|
606
|
+
sourceChannel: "scheduler",
|
|
607
|
+
sourceEventName: "activity.failed",
|
|
608
|
+
contextPayload: { title: "Job failed", body: "Routine failure" },
|
|
609
|
+
attentionHints: {
|
|
610
|
+
requiresAction: false,
|
|
611
|
+
urgency: "low",
|
|
612
|
+
isAsyncBackground: false,
|
|
613
|
+
visibleInSourceNow: false,
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
618
|
+
|
|
619
|
+
expect(item?.noteworthy).toBe(false);
|
|
620
|
+
expect(appendCalls[0]!.noteworthy).toBe(false);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test("activity.failed from background-job-runner shape (assistant_tool + medium) is NOT noteworthy", async () => {
|
|
624
|
+
// Regression: `runtime/background-job-runner.ts` emits activity.failed
|
|
625
|
+
// with `sourceChannel: "assistant_tool"` and `urgency: "medium"`. Before
|
|
626
|
+
// the fix, the assistant_tool short-circuit short-circuited noteworthy
|
|
627
|
+
// to true, so every routine watcher/heartbeat failure landed in the
|
|
628
|
+
// Inbox. The activity.failed rule must run first and require critical
|
|
629
|
+
// urgency.
|
|
630
|
+
conversationRow = { conversationType: "background" };
|
|
631
|
+
const signal = makeSignal({
|
|
632
|
+
sourceChannel: "assistant_tool",
|
|
633
|
+
sourceEventName: "activity.failed",
|
|
634
|
+
contextPayload: { title: "Job failed", body: "Routine failure" },
|
|
635
|
+
attentionHints: {
|
|
636
|
+
requiresAction: false,
|
|
637
|
+
urgency: "medium",
|
|
638
|
+
isAsyncBackground: true,
|
|
639
|
+
visibleInSourceNow: false,
|
|
640
|
+
},
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
644
|
+
|
|
645
|
+
expect(item?.noteworthy).toBe(false);
|
|
646
|
+
expect(appendCalls[0]!.noteworthy).toBe(false);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test("activity.failed from assistant_tool with critical urgency IS noteworthy", async () => {
|
|
650
|
+
// Companion to the regression test above: a background-job-runner
|
|
651
|
+
// shape that opts up to critical urgency should still reach the Inbox.
|
|
652
|
+
conversationRow = { conversationType: "background" };
|
|
653
|
+
const signal = makeSignal({
|
|
654
|
+
sourceChannel: "assistant_tool",
|
|
655
|
+
sourceEventName: "activity.failed",
|
|
656
|
+
contextPayload: { title: "Job failed", body: "Critical failure" },
|
|
657
|
+
attentionHints: {
|
|
658
|
+
requiresAction: false,
|
|
659
|
+
urgency: "critical",
|
|
660
|
+
isAsyncBackground: true,
|
|
661
|
+
visibleInSourceNow: false,
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
666
|
+
|
|
667
|
+
expect(item?.noteworthy).toBe(true);
|
|
668
|
+
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("credential.health_alert is noteworthy regardless of source channel", async () => {
|
|
672
|
+
conversationRow = { conversationType: "background" };
|
|
673
|
+
const signal = makeSignal({
|
|
674
|
+
sourceChannel: "watcher",
|
|
675
|
+
sourceEventName: "credential.health_alert",
|
|
676
|
+
contextPayload: { title: "Credential expired", body: "Reconnect" },
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
195
680
|
|
|
196
|
-
expect(item?.
|
|
197
|
-
expect(
|
|
681
|
+
expect(item?.noteworthy).toBe(true);
|
|
682
|
+
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
198
683
|
});
|
|
199
684
|
});
|
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
* and mobile clients via the daemon's event broadcast mechanism.
|
|
4
4
|
*
|
|
5
5
|
* The adapter broadcasts a `notification_intent` message that the Vellum
|
|
6
|
-
* client
|
|
7
|
-
*
|
|
6
|
+
* client uses for two distinct purposes: paired-conversation bookkeeping
|
|
7
|
+
* (mark-unseen + history catch-up, fallback dedup) and posting an OS
|
|
8
|
+
* banner via `UNUserNotificationCenter`. The banner posting is gated by
|
|
9
|
+
* the `silent` flag — set to true for non-urgent (`low`/`medium`) signals
|
|
10
|
+
* so the notification center inbox still receives the entry but the OS
|
|
11
|
+
* does not surface a push banner. Urgent signals (`high`/`critical`)
|
|
12
|
+
* broadcast with `silent: false` and fire the banner.
|
|
8
13
|
*
|
|
9
14
|
* Guardian-sensitive notifications (approval requests, escalation alerts)
|
|
10
15
|
* are annotated with `targetGuardianPrincipalId` so that only clients
|
|
@@ -75,6 +80,9 @@ export class VellumAdapter implements ChannelAdapter {
|
|
|
75
80
|
? guardianPrincipalId
|
|
76
81
|
: undefined;
|
|
77
82
|
|
|
83
|
+
const silent =
|
|
84
|
+
payload.urgency !== "high" && payload.urgency !== "critical";
|
|
85
|
+
|
|
78
86
|
this.broadcast({
|
|
79
87
|
type: "notification_intent",
|
|
80
88
|
deliveryId: payload.deliveryId,
|
|
@@ -83,6 +91,7 @@ export class VellumAdapter implements ChannelAdapter {
|
|
|
83
91
|
body: payload.copy.body,
|
|
84
92
|
deepLinkMetadata: payload.deepLinkTarget,
|
|
85
93
|
targetGuardianPrincipalId,
|
|
94
|
+
silent,
|
|
86
95
|
} as ServerMessage);
|
|
87
96
|
|
|
88
97
|
log.info(
|
|
@@ -90,6 +99,7 @@ export class VellumAdapter implements ChannelAdapter {
|
|
|
90
99
|
sourceEventName: payload.sourceEventName,
|
|
91
100
|
title: payload.copy.title,
|
|
92
101
|
guardianScoped: targetGuardianPrincipalId != null,
|
|
102
|
+
silent,
|
|
93
103
|
},
|
|
94
104
|
"Vellum notification intent broadcast",
|
|
95
105
|
);
|