@vellumai/assistant 0.8.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +2 -2
- package/docker-entrypoint.sh +0 -1
- package/docs/browser-use-architecture-phase2.md +1 -1
- package/knip.json +2 -1
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +1492 -100
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +4 -5
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +302 -33
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
- package/src/__tests__/audit-log-rotation.test.ts +70 -16
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -3
- package/src/__tests__/btw-routes.test.ts +2 -3
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-guardian.test.ts +3 -3
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/checker.test.ts +6 -15
- package/src/__tests__/compaction-events.test.ts +2 -1
- package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
- package/src/__tests__/computer-use-tools.test.ts +2 -4
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-token-estimator.test.ts +91 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +55 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +228 -8
- package/src/__tests__/conversation-agent-loop.test.ts +188 -129
- package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
- 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-clear-safety.test.ts +25 -25
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
- package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +31 -0
- package/src/__tests__/conversation-fork-crud.test.ts +324 -0
- package/src/__tests__/conversation-lifecycle.test.ts +53 -12
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +279 -0
- 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 +2 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +264 -81
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-skill-tools.test.ts +2 -5
- 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-store.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-sync-tags.test.ts +99 -32
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -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-execution-feature-gates.test.ts +9 -7
- package/src/__tests__/credential-execution-tools.test.ts +6 -6
- package/src/__tests__/credential-security-invariants.test.ts +7 -0
- package/src/__tests__/credential-vault-unit.test.ts +2 -2
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- 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__/dynamic-page-surface.test.ts +2 -2
- package/src/__tests__/email-html-renderer.test.ts +12 -0
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/gateway-flag-listener.test.ts +237 -0
- package/src/__tests__/gemini-provider.test.ts +78 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/guardian-outbound-http.test.ts +7 -5
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
- package/src/__tests__/heartbeat-service.test.ts +4 -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-shell-tool.test.ts +1 -1
- 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__/init-feature-flag-overrides.test.ts +5 -6
- package/src/__tests__/injector-chain.test.ts +2 -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__/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 +77 -17
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
- package/src/__tests__/llm-resolver.test.ts +161 -9
- package/src/__tests__/llm-usage-store.test.ts +66 -0
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/logger.test.ts +89 -0
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +31 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/model-intents.test.ts +2 -4
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -1
- package/src/__tests__/openai-provider.test.ts +151 -0
- package/src/__tests__/openai-responses-provider.test.ts +118 -16
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +189 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
- package/src/__tests__/platform.test.ts +2 -5
- package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
- package/src/__tests__/plugin-bootstrap.test.ts +2 -2
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
- package/src/__tests__/plugin-types.test.ts +3 -2
- package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
- package/src/__tests__/pricing.test.ts +12 -0
- 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__/prune-jobs-changes-parser.test.ts +61 -0
- package/src/__tests__/registry.test.ts +2 -8
- package/src/__tests__/require-fresh-approval.test.ts +2 -2
- package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
- package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/sync-message-contract.test.ts +59 -0
- package/src/__tests__/system-prompt.test.ts +161 -124
- package/src/__tests__/terminal-tools.test.ts +12 -2
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-approval-handler.test.ts +1 -5
- package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
- package/src/__tests__/tool-executor.test.ts +89 -53
- package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
- package/src/__tests__/usage-routes.test.ts +3 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +94 -10
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
- package/src/acp/prepare-agent-env.ts +78 -0
- package/src/acp/session-manager.ts +1 -1
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +65 -20
- package/src/api/README.md +5 -0
- package/src/api/index.ts +4 -0
- package/src/api/package.json +10 -0
- package/src/background-wake/background-wake-routes.test.ts +233 -0
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/background-wake/runtime-registry.ts +24 -0
- package/src/browser/operations.ts +15 -0
- package/src/cli/commands/__tests__/browser.test.ts +23 -5
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
- package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
- package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +10 -12
- package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
- package/src/cli/commands/browser.ts +247 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/domain.ts +91 -41
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/inference.ts +93 -40
- package/src/cli/commands/memory-v2-compare-render.ts +115 -0
- package/src/cli/commands/memory-v2.ts +483 -0
- package/src/cli/commands/memory-v3-render.ts +344 -0
- package/src/cli/commands/memory-v3.ts +316 -0
- package/src/cli/commands/notifications.ts +24 -2
- package/src/cli/program.ts +2 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +21 -9
- package/src/config/bundled-skills/app-builder/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/SKILL.md +124 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +258 -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-open.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +24 -12
- package/src/config/call-site-defaults.ts +20 -0
- package/src/config/feature-flag-registry.json +115 -3
- package/src/config/llm-resolver.ts +16 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +217 -1
- package/src/config/schemas/call-site-catalog.ts +35 -0
- package/src/config/schemas/llm.ts +14 -0
- package/src/config/schemas/memory-v2.ts +294 -1
- package/src/config/schemas/memory.ts +2 -1
- package/src/context/compactor.ts +60 -1
- package/src/context/token-estimator.ts +47 -4
- package/src/context/window-manager.ts +25 -0
- package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
- package/src/conversations/message-consolidation.ts +404 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
- 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 +155 -36
- package/src/daemon/conversation-agent-loop.ts +307 -88
- package/src/daemon/conversation-error.ts +31 -1
- package/src/daemon/conversation-lifecycle.ts +149 -118
- 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 +145 -84
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +70 -3
- package/src/daemon/conversation-usage.ts +2 -0
- package/src/daemon/conversation.ts +54 -32
- package/src/daemon/disk-pressure-guard.ts +14 -2
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/config-a2a.ts +160 -0
- package/src/daemon/handlers/config-model.test.ts +2 -0
- package/src/daemon/handlers/conversations.ts +90 -3
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-browser-proxy.ts +5 -5
- package/src/daemon/host-cu-proxy.ts +5 -5
- package/src/daemon/host-file-proxy.ts +5 -5
- package/src/daemon/host-proxy-base.ts +4 -4
- package/src/daemon/host-transfer-proxy.ts +11 -11
- package/src/daemon/lifecycle.ts +40 -23
- package/src/daemon/meet-manifest-loader.ts +1 -7
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +14 -9
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/home.ts +1 -13
- package/src/daemon/message-types/messages.ts +66 -7
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/sync.ts +14 -0
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +69 -12
- package/src/daemon/shutdown-handlers.ts +24 -5
- package/src/daemon/switch-inference-profile-tool.ts +52 -0
- package/src/daemon/tool-setup-types.ts +13 -0
- package/src/daemon/trust-context.ts +6 -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/events/relationship-state-updated.ts +25 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -2
- package/src/heartbeat/heartbeat-service.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +6 -1
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +85 -0
- package/src/home/suggested-prompts.ts +168 -9
- package/src/ipc/gateway-flag-listener.ts +123 -0
- package/src/ipc/skill-routes/registries.ts +8 -12
- package/src/memory/__tests__/db-async-query.test.ts +165 -0
- package/src/memory/__tests__/db-maintenance.test.ts +115 -0
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
- package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-job.test.ts +327 -6
- package/src/memory/auto-analysis-enqueue.ts +5 -1
- package/src/memory/conversation-crud.ts +191 -100
- package/src/memory/conversation-starters-cadence.ts +3 -1
- package/src/memory/conversation-title-service.ts +19 -3
- package/src/memory/db-async-query.ts +214 -0
- package/src/memory/db-init.ts +26 -0
- package/src/memory/db-maintenance.ts +30 -21
- 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/bootstrap.ts +8 -1
- package/src/memory/graph/capability-seed.ts +7 -3
- package/src/memory/graph/conversation-graph-memory.ts +100 -17
- package/src/memory/graph/extraction.ts +1 -5
- package/src/memory/graph/graph-search.ts +7 -1
- package/src/memory/indexer.ts +28 -18
- package/src/memory/job-handlers/cleanup.ts +76 -18
- package/src/memory/job-handlers/conversation-starters.ts +1 -4
- package/src/memory/jobs/embed-pkb-file.ts +6 -1
- package/src/memory/jobs-store.ts +14 -0
- package/src/memory/jobs-worker.ts +68 -15
- package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
- package/src/memory/llm-request-log-source-local.ts +7 -0
- package/src/memory/llm-request-log-source.ts +9 -2
- package/src/memory/llm-request-log-store.ts +43 -1
- package/src/memory/llm-usage-store.ts +24 -0
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +11 -3
- package/src/memory/memory-retrospective-job.ts +413 -18
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +41 -14
- 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/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/260-rename-cleaned-at.ts +44 -0
- package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
- package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
- package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
- package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
- package/src/memory/migrations/index.ts +34 -0
- package/src/memory/migrations/registry.ts +58 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/infrastructure.ts +22 -0
- package/src/memory/tool-usage-store.ts +36 -8
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
- package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
- package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
- package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
- package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +158 -112
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
- package/src/memory/v2/__tests__/router.test.ts +660 -4
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/harness/compare.ts +57 -0
- package/src/memory/v2/harness/metrics.ts +124 -0
- package/src/memory/v2/harness/oracle.ts +145 -0
- package/src/memory/v2/harness/replay-input.ts +224 -0
- package/src/memory/v2/harness/retriever.ts +74 -0
- package/src/memory/v2/harness/router-retriever.ts +43 -0
- package/src/memory/v2/harness/runner.ts +106 -0
- package/src/memory/v2/harness/trace.ts +58 -0
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +42 -25
- package/src/memory/v2/page-index.ts +209 -7
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/prompts/router.ts +26 -1
- package/src/memory/v2/qdrant.ts +14 -2
- package/src/memory/v2/router.ts +369 -62
- package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
- package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
- package/src/memory/v3/__tests__/edges.test.ts +563 -0
- package/src/memory/v3/__tests__/filter.test.ts +512 -0
- package/src/memory/v3/__tests__/gate.test.ts +574 -0
- package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
- package/src/memory/v3/__tests__/loop.test.ts +530 -0
- package/src/memory/v3/__tests__/retriever.test.ts +226 -0
- package/src/memory/v3/__tests__/scouts.test.ts +440 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
- package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
- package/src/memory/v3/__tests__/traversal.test.ts +469 -0
- package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
- package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
- package/src/memory/v3/__tests__/validate.test.ts +245 -0
- package/src/memory/v3/auto-edges.ts +223 -0
- package/src/memory/v3/coactivation-store.ts +124 -0
- package/src/memory/v3/consolidation-job.ts +323 -0
- package/src/memory/v3/edge-learning-job.ts +160 -0
- package/src/memory/v3/edges.ts +249 -0
- package/src/memory/v3/filter.ts +281 -0
- package/src/memory/v3/gate.ts +334 -0
- package/src/memory/v3/index-composition.ts +113 -0
- package/src/memory/v3/llm-capture.ts +46 -0
- package/src/memory/v3/loop.ts +382 -0
- package/src/memory/v3/maintenance.ts +144 -0
- package/src/memory/v3/prompt-context.ts +33 -0
- package/src/memory/v3/prompts/consolidation.ts +458 -0
- package/src/memory/v3/prompts/system-prompts.ts +196 -0
- package/src/memory/v3/retriever.ts +33 -0
- package/src/memory/v3/scouts.ts +420 -0
- package/src/memory/v3/shadow-middleware.ts +305 -0
- package/src/memory/v3/traversal.ts +206 -0
- package/src/memory/v3/tree-index.ts +237 -0
- package/src/memory/v3/tree-store.ts +394 -0
- package/src/memory/v3/tree-walk.ts +351 -0
- package/src/memory/v3/types.ts +65 -0
- package/src/memory/v3/validate.ts +300 -0
- package/src/messaging/providers/index.ts +7 -1
- 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__/emit-signal-home-feed.test.ts +4 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +116 -54
- package/src/notifications/adapters/macos.ts +18 -1
- package/src/notifications/adapters/platform.ts +1 -1
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/decision-engine.ts +1 -4
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/emit-signal.ts +38 -50
- package/src/notifications/home-feed-side-effect.ts +60 -30
- 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/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +8 -5
- package/src/permissions/question-prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +6 -3
- package/src/plugin-api/index.ts +4 -0
- package/src/plugin-api/types.ts +7 -33
- package/src/plugins/defaults/index.ts +6 -0
- package/src/plugins/defaults/injectors.ts +100 -20
- package/src/plugins/external-plugin-loader.ts +5 -68
- package/src/plugins/types.ts +11 -16
- package/src/proactive-artifact/aux-message-injector.ts +17 -4
- package/src/prompts/__tests__/system-prompt.test.ts +46 -2
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/persona-resolver.ts +36 -21
- package/src/prompts/sections.ts +69 -19
- package/src/prompts/system-prompt.ts +118 -216
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +10 -2
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +281 -9
- package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
- package/src/providers/__tests__/retry-callsite.test.ts +85 -5
- package/src/providers/anthropic/client.ts +159 -66
- package/src/providers/call-site-routing.ts +14 -2
- package/src/providers/connection-model-compat.ts +38 -0
- package/src/providers/connection-resolution.ts +16 -2
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/gemini/client.ts +49 -6
- 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/adapter-factory.ts +18 -1
- package/src/providers/inference/auth.ts +3 -3
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/resolve-auth.ts +49 -6
- package/src/providers/minimax/client.ts +106 -0
- package/src/providers/model-catalog.ts +91 -1
- package/src/providers/model-intents.ts +1 -1
- package/src/providers/openai/chat-completions-provider.ts +63 -23
- package/src/providers/openai/codex-models.ts +18 -0
- package/src/providers/openai/responses-provider.ts +86 -23
- package/src/providers/openrouter/client.ts +5 -1
- package/src/providers/provider-send-message.ts +7 -1
- package/src/providers/retry.ts +34 -3
- package/src/providers/thinking-config.ts +26 -1
- package/src/providers/types.ts +25 -0
- package/src/providers/usage-tracking.ts +2 -0
- package/src/runtime/AGENTS.md +2 -2
- 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 +152 -56
- package/src/runtime/assistant-event-hub.ts +76 -6
- package/src/runtime/auth/route-policy.ts +43 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/btw-sidechain.ts +0 -6
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-types.ts +7 -6
- package/src/runtime/migrations/vbundle-builder.ts +10 -3
- package/src/runtime/pending-interactions.ts +50 -8
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +161 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +290 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -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/acp-routes.test.ts +255 -6
- package/src/runtime/routes/acp-routes.ts +8 -1
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/avatar-routes.ts +10 -10
- package/src/runtime/routes/background-wake-routes.ts +188 -0
- package/src/runtime/routes/browser-tabs-routes.ts +200 -0
- package/src/runtime/routes/btw-routes.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +147 -2
- package/src/runtime/routes/conversation-list-routes.ts +12 -4
- package/src/runtime/routes/conversation-management-routes.ts +77 -20
- package/src/runtime/routes/conversation-query-routes.ts +196 -31
- package/src/runtime/routes/conversation-routes.ts +472 -425
- package/src/runtime/routes/conversation-starter-routes.ts +6 -3
- package/src/runtime/routes/disk-pressure-routes.ts +1 -1
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/domain-routes.ts +60 -10
- package/src/runtime/routes/email-routes.ts +5 -2
- package/src/runtime/routes/events-routes.ts +54 -10
- package/src/runtime/routes/group-routes.ts +24 -8
- 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 +17 -2
- package/src/runtime/routes/host-cu-routes.ts +2 -2
- 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/acl-enforcement.ts +96 -3
- 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 +20 -4
- package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
- package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
- package/src/runtime/routes/inference-provider-connection-routes.ts +63 -7
- package/src/runtime/routes/integrations/a2a.ts +60 -1
- package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-item-routes.ts +8 -3
- package/src/runtime/routes/memory-v2-routes.ts +427 -0
- package/src/runtime/routes/memory-v3-routes.ts +316 -0
- package/src/runtime/routes/migration-routes.ts +21 -24
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/plugins-routes.ts +337 -0
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/rename-conversation-routes.ts +6 -2
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/secret-routes.ts +25 -5
- package/src/runtime/routes/settings-routes.ts +12 -11
- package/src/runtime/routes/slack-channel-routes.ts +188 -0
- package/src/runtime/routes/workspace-routes.ts +25 -10
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/runtime/sync/resource-sync-events.ts +106 -38
- package/src/runtime/sync/sync-publisher.test.ts +49 -0
- package/src/runtime/sync/sync-publisher.ts +2 -1
- package/src/runtime/verification-outbound-actions.ts +73 -1
- 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/telemetry/types.ts +12 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/acp/spawn.test.ts +119 -0
- package/src/tools/acp/spawn.ts +15 -2
- package/src/tools/apps/definitions.ts +2 -8
- package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
- package/src/tools/ask-question/ask-question-tool.ts +38 -45
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +150 -0
- package/src/tools/browser/browser-execution.ts +106 -0
- package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +4 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +22 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +42 -2
- package/src/tools/browser/cdp-client/factory.ts +171 -4
- package/src/tools/browser/cdp-client/local-cdp-client.ts +21 -0
- package/src/tools/browser/cdp-client/types.ts +101 -0
- package/src/tools/browser/pinned-tabs.ts +146 -0
- package/src/tools/computer-use/definitions.ts +22 -78
- package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
- package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
- package/src/tools/credentials/vault.ts +3 -9
- 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 +187 -2
- package/src/tools/execution-target.ts +21 -23
- package/src/tools/executor.ts +6 -1
- package/src/tools/filesystem/edit.ts +3 -9
- package/src/tools/filesystem/list.ts +3 -9
- package/src/tools/filesystem/read.ts +3 -9
- package/src/tools/filesystem/write.ts +3 -9
- package/src/tools/host-filesystem/edit.ts +3 -9
- package/src/tools/host-filesystem/read.ts +3 -9
- package/src/tools/host-filesystem/transfer.ts +3 -9
- package/src/tools/host-filesystem/write.ts +3 -9
- package/src/tools/host-terminal/host-shell.ts +3 -9
- package/src/tools/mcp/mcp-tool-factory.ts +1 -8
- package/src/tools/memory/register.test.ts +1 -1
- package/src/tools/memory/register.ts +4 -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 +216 -73
- package/src/tools/network/web-search.ts +216 -98
- package/src/tools/registry.ts +7 -23
- package/src/tools/schema-transforms.ts +1 -1
- package/src/tools/skills/execute.ts +3 -9
- package/src/tools/skills/load.ts +3 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -8
- package/src/tools/subagent/notify-parent.ts +3 -9
- package/src/tools/system/request-permission.ts +3 -9
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +3 -9
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/tool-defaults.ts +94 -0
- package/src/tools/types.ts +31 -98
- package/src/tools/ui-surface/definitions.ts +9 -23
- package/src/types/onboarding-context.ts +4 -0
- package/src/usage/pricing.ts +23 -0
- package/src/usage/types.ts +12 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/logger.ts +16 -7
- package/src/util/platform.ts +7 -7
- package/src/util/sqlite3-runtime.ts +65 -0
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
- package/src/__tests__/message-complete-display-id.test.ts +0 -175
- 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/prompts/cache-boundary.ts +0 -8
- 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
|
//
|
|
@@ -97,7 +94,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
97
94
|
},
|
|
98
95
|
});
|
|
99
96
|
|
|
100
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
97
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
101
98
|
|
|
102
99
|
expect(conversationLookups).toEqual(["conv-source-1"]);
|
|
103
100
|
expect(item).not.toBeNull();
|
|
@@ -114,6 +111,10 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
114
111
|
expect(appended.title).toBe("Background job done");
|
|
115
112
|
expect(appended.summary).toBe("Summary of what happened.");
|
|
116
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");
|
|
117
118
|
expect(typeof appended.timestamp).toBe("string");
|
|
118
119
|
expect(appended.createdAt).toBe(appended.timestamp);
|
|
119
120
|
});
|
|
@@ -129,15 +130,16 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
129
130
|
},
|
|
130
131
|
});
|
|
131
132
|
|
|
132
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
133
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
133
134
|
|
|
134
135
|
expect(item).toBeNull();
|
|
135
136
|
expect(appendCalls).toHaveLength(0);
|
|
136
137
|
});
|
|
137
138
|
|
|
138
139
|
test("isAsyncBackground hint writes even when sourceContextId does not resolve", async () => {
|
|
139
|
-
//
|
|
140
|
-
//
|
|
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.
|
|
141
143
|
conversationLookupShouldThrow = true;
|
|
142
144
|
const signal = makeSignal({
|
|
143
145
|
sourceContextId: "not-a-conversation-id",
|
|
@@ -154,21 +156,22 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
154
156
|
},
|
|
155
157
|
});
|
|
156
158
|
|
|
157
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
159
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
158
160
|
|
|
159
161
|
expect(item).not.toBeNull();
|
|
160
162
|
expect(appendCalls).toHaveLength(1);
|
|
161
163
|
expect(appendCalls[0]!.urgency).toBe("high");
|
|
162
|
-
|
|
163
|
-
expect(conversationLookups).
|
|
164
|
+
expect(appendCalls[0]!.conversationId).toBeUndefined();
|
|
165
|
+
expect(conversationLookups).toEqual(["not-a-conversation-id"]);
|
|
164
166
|
});
|
|
165
167
|
|
|
166
168
|
test("assistant_tool source mirrors to the home feed even without a background conversation or async hint", async () => {
|
|
167
169
|
// Regression: the `notifications send` CLI/skill emits with
|
|
168
170
|
// `sourceChannel: "assistant_tool"`, a synthetic `cli-<ts>` source
|
|
169
171
|
// context id that does not resolve to a conversation, and
|
|
170
|
-
// `isAsyncBackground: false`.
|
|
171
|
-
//
|
|
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.
|
|
172
175
|
conversationRow = null;
|
|
173
176
|
const signal = makeSignal({
|
|
174
177
|
sourceChannel: "assistant_tool",
|
|
@@ -188,47 +191,106 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
188
191
|
},
|
|
189
192
|
});
|
|
190
193
|
|
|
191
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
194
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
192
195
|
|
|
193
196
|
expect(item).not.toBeNull();
|
|
194
197
|
expect(appendCalls).toHaveLength(1);
|
|
195
198
|
expect(appendCalls[0]!.title).toBe("Shared from CLI");
|
|
196
199
|
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
197
|
-
|
|
198
|
-
expect(conversationLookups).
|
|
200
|
+
expect(appendCalls[0]!.conversationId).toBeUndefined();
|
|
201
|
+
expect(conversationLookups).toEqual(["cli-12345"]);
|
|
199
202
|
});
|
|
200
203
|
|
|
201
|
-
test("
|
|
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
|
+
});
|
|
204
219
|
const decision = makeDecision({
|
|
205
220
|
renderedCopy: {
|
|
206
221
|
vellum: { title: "Routed title", body: "Routed body" },
|
|
207
222
|
},
|
|
208
223
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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" },
|
|
215
257
|
},
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const item = await writeHomeFeedItemForSignal(
|
|
261
|
+
signal,
|
|
262
|
+
decision,
|
|
263
|
+
"paired-delivery-conv-id",
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(item).not.toBeNull();
|
|
267
|
+
expect(appendCalls).toHaveLength(1);
|
|
268
|
+
expect(appendCalls[0]!.conversationId).toBe("paired-delivery-conv-id");
|
|
269
|
+
});
|
|
270
|
+
|
|
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." },
|
|
221
283
|
},
|
|
222
|
-
|
|
284
|
+
});
|
|
223
285
|
|
|
224
286
|
const item = await writeHomeFeedItemForSignal(
|
|
225
287
|
signal,
|
|
226
288
|
decision,
|
|
227
|
-
|
|
289
|
+
"paired-delivery-conv-id",
|
|
228
290
|
);
|
|
229
291
|
|
|
230
|
-
expect(item
|
|
231
|
-
expect(appendCalls[0]!.conversationId).toBe("conv-
|
|
292
|
+
expect(item).not.toBeNull();
|
|
293
|
+
expect(appendCalls[0]!.conversationId).toBe("conv-source-1");
|
|
232
294
|
});
|
|
233
295
|
|
|
234
296
|
test("returns null and does not write when no rendered copy or payload title/body is present", async () => {
|
|
@@ -238,7 +300,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
238
300
|
contextPayload: {},
|
|
239
301
|
});
|
|
240
302
|
|
|
241
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
303
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
242
304
|
|
|
243
305
|
expect(item).toBeNull();
|
|
244
306
|
expect(appendCalls).toHaveLength(0);
|
|
@@ -251,7 +313,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
251
313
|
contextPayload: { title: "Real title" },
|
|
252
314
|
});
|
|
253
315
|
|
|
254
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
316
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
255
317
|
|
|
256
318
|
expect(item).toBeNull();
|
|
257
319
|
expect(appendCalls).toHaveLength(0);
|
|
@@ -268,7 +330,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
268
330
|
contextPayload: { body: "Real body" },
|
|
269
331
|
});
|
|
270
332
|
|
|
271
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
333
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
272
334
|
|
|
273
335
|
expect(item).not.toBeNull();
|
|
274
336
|
expect(appendCalls).toHaveLength(1);
|
|
@@ -294,7 +356,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
294
356
|
},
|
|
295
357
|
});
|
|
296
358
|
|
|
297
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
359
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
298
360
|
|
|
299
361
|
expect(item).not.toBeNull();
|
|
300
362
|
expect(appendCalls).toHaveLength(1);
|
|
@@ -314,7 +376,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
314
376
|
},
|
|
315
377
|
});
|
|
316
378
|
|
|
317
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
379
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
318
380
|
|
|
319
381
|
expect(item).toBeNull();
|
|
320
382
|
expect(appendCalls).toHaveLength(0);
|
|
@@ -340,7 +402,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
340
402
|
},
|
|
341
403
|
});
|
|
342
404
|
|
|
343
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
405
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
344
406
|
|
|
345
407
|
expect(item).not.toBeNull();
|
|
346
408
|
expect(appendCalls).toHaveLength(1);
|
|
@@ -372,7 +434,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
372
434
|
},
|
|
373
435
|
});
|
|
374
436
|
|
|
375
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
437
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
376
438
|
|
|
377
439
|
expect(item).not.toBeNull();
|
|
378
440
|
expect(appendCalls).toHaveLength(1);
|
|
@@ -400,7 +462,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
400
462
|
},
|
|
401
463
|
});
|
|
402
464
|
|
|
403
|
-
const item = await writeHomeFeedItemForSignal(signal, decision
|
|
465
|
+
const item = await writeHomeFeedItemForSignal(signal, decision);
|
|
404
466
|
|
|
405
467
|
expect(item).toBeNull();
|
|
406
468
|
expect(appendCalls).toHaveLength(0);
|
|
@@ -423,7 +485,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
423
485
|
},
|
|
424
486
|
});
|
|
425
487
|
|
|
426
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
488
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
427
489
|
|
|
428
490
|
expect(item).not.toBeNull();
|
|
429
491
|
expect(appendCalls).toHaveLength(1);
|
|
@@ -438,7 +500,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
438
500
|
contextPayload: { title: "Payload title", body: "Payload body" },
|
|
439
501
|
});
|
|
440
502
|
|
|
441
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
503
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
442
504
|
|
|
443
505
|
expect(item).not.toBeNull();
|
|
444
506
|
expect(item?.title).toBe("Payload title");
|
|
@@ -456,7 +518,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
456
518
|
contextPayload: { title: "Tool share", body: "Body" },
|
|
457
519
|
});
|
|
458
520
|
|
|
459
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
521
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
460
522
|
|
|
461
523
|
expect(item?.noteworthy).toBe(true);
|
|
462
524
|
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
@@ -470,7 +532,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
470
532
|
contextPayload: { title: "Tool share", body: "Body" },
|
|
471
533
|
});
|
|
472
534
|
|
|
473
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
535
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
474
536
|
|
|
475
537
|
expect(item?.fromAssistant).toBe(true);
|
|
476
538
|
expect(appendCalls[0]!.fromAssistant).toBe(true);
|
|
@@ -484,7 +546,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
484
546
|
contextPayload: { title: "Reminder", body: "Time to do thing" },
|
|
485
547
|
});
|
|
486
548
|
|
|
487
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
549
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
488
550
|
|
|
489
551
|
expect(item?.fromAssistant).toBe(false);
|
|
490
552
|
expect(appendCalls[0]!.fromAssistant).toBe(false);
|
|
@@ -498,7 +560,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
498
560
|
contextPayload: { title: "Reminder", body: "Time to do thing" },
|
|
499
561
|
});
|
|
500
562
|
|
|
501
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
563
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
502
564
|
|
|
503
565
|
expect(item?.noteworthy).toBe(false);
|
|
504
566
|
expect(appendCalls[0]!.noteworthy).toBe(false);
|
|
@@ -512,7 +574,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
512
574
|
contextPayload: { title: "Question", body: "Approve?" },
|
|
513
575
|
});
|
|
514
576
|
|
|
515
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
577
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
516
578
|
|
|
517
579
|
expect(item?.noteworthy).toBe(true);
|
|
518
580
|
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
@@ -532,7 +594,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
532
594
|
},
|
|
533
595
|
});
|
|
534
596
|
|
|
535
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
597
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
536
598
|
|
|
537
599
|
expect(item?.noteworthy).toBe(true);
|
|
538
600
|
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
@@ -552,7 +614,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
552
614
|
},
|
|
553
615
|
});
|
|
554
616
|
|
|
555
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
617
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
556
618
|
|
|
557
619
|
expect(item?.noteworthy).toBe(false);
|
|
558
620
|
expect(appendCalls[0]!.noteworthy).toBe(false);
|
|
@@ -578,7 +640,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
578
640
|
},
|
|
579
641
|
});
|
|
580
642
|
|
|
581
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
643
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
582
644
|
|
|
583
645
|
expect(item?.noteworthy).toBe(false);
|
|
584
646
|
expect(appendCalls[0]!.noteworthy).toBe(false);
|
|
@@ -600,7 +662,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
600
662
|
},
|
|
601
663
|
});
|
|
602
664
|
|
|
603
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
665
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
604
666
|
|
|
605
667
|
expect(item?.noteworthy).toBe(true);
|
|
606
668
|
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
@@ -614,7 +676,7 @@ describe("writeHomeFeedItemForSignal", () => {
|
|
|
614
676
|
contextPayload: { title: "Credential expired", body: "Reconnect" },
|
|
615
677
|
});
|
|
616
678
|
|
|
617
|
-
const item = await writeHomeFeedItemForSignal(signal, makeDecision()
|
|
679
|
+
const item = await writeHomeFeedItemForSignal(signal, makeDecision());
|
|
618
680
|
|
|
619
681
|
expect(item?.noteworthy).toBe(true);
|
|
620
682
|
expect(appendCalls[0]!.noteworthy).toBe(true);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* does not match their own identity.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
import type { InterfaceId } from "../../channels/types.js";
|
|
21
22
|
import type { ServerMessage } from "../../daemon/message-protocol.js";
|
|
22
23
|
import { getLogger } from "../../util/logger.js";
|
|
23
24
|
import type {
|
|
@@ -30,7 +31,23 @@ import type {
|
|
|
30
31
|
|
|
31
32
|
const log = getLogger("notif-adapter-vellum");
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Optional targeting/filtering applied at the hub when a broadcast is
|
|
36
|
+
* emitted. Mirrors the third argument of
|
|
37
|
+
* `broadcastMessage()` in `runtime/assistant-event-hub.ts`. Callers can
|
|
38
|
+
* use `targetInterfaceId` to scope a legacy message to a single client
|
|
39
|
+
* surface (e.g. macOS) during a migration window.
|
|
40
|
+
*/
|
|
41
|
+
export interface BroadcastFnOptions {
|
|
42
|
+
targetClientId?: string;
|
|
43
|
+
targetInterfaceId?: InterfaceId;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type BroadcastFn = (
|
|
47
|
+
msg: ServerMessage,
|
|
48
|
+
conversationId?: string,
|
|
49
|
+
options?: BroadcastFnOptions,
|
|
50
|
+
) => void;
|
|
34
51
|
|
|
35
52
|
/**
|
|
36
53
|
* Event name prefixes that carry guardian-sensitive content (approval
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* POSTs a `notification_intent` payload to
|
|
6
6
|
* `/v1/assistants/{id}/push/dispatch/`. The platform endpoint fans the
|
|
7
7
|
* notification out to all registered device tokens for the bound user and
|
|
8
|
-
* gates on the `ios-remote-push-enabled`
|
|
8
|
+
* gates on the `ios-remote-push-enabled` feature flag server-side (returning 202
|
|
9
9
|
* with `{ skipped: "flag_off" }` when the flag is OFF — no action needed
|
|
10
10
|
* from the daemon).
|
|
11
11
|
*
|
|
@@ -156,8 +156,20 @@ export function composeConversationSeed(
|
|
|
156
156
|
|
|
157
157
|
if (verbosity === "rich") {
|
|
158
158
|
const parts: string[] = [];
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
const usableTitle =
|
|
160
|
+
copy.title && copy.title !== "Notification" ? copy.title : "";
|
|
161
|
+
const hasBody = Boolean(copy.body && copy.body.trim());
|
|
162
|
+
// copy.title is used as the conversation header in chat surfaces
|
|
163
|
+
// whenever copy.conversationTitle is absent (see
|
|
164
|
+
// conversation-pairing.ts), so prepending it here would render it
|
|
165
|
+
// twice — once in the header bar and once at the start of the
|
|
166
|
+
// bubble. Skip it in that case, unless there's no body and we'd
|
|
167
|
+
// otherwise produce an empty seed. Treat whitespace-only bodies as
|
|
168
|
+
// empty so the title still populates the bubble.
|
|
169
|
+
if (usableTitle && (copy.conversationTitle || !hasBody)) {
|
|
170
|
+
parts.push(usableTitle);
|
|
171
|
+
}
|
|
172
|
+
if (hasBody) parts.push(copy.body);
|
|
161
173
|
const alreadyMentionsAction = parts.some((part) =>
|
|
162
174
|
/\baction required\b/i.test(part),
|
|
163
175
|
);
|
|
@@ -13,7 +13,6 @@ import { v4 as uuid } from "uuid";
|
|
|
13
13
|
|
|
14
14
|
import { getDeliverableChannels } from "../channels/config.js";
|
|
15
15
|
import { listGuardianChannels } from "../contacts/contact-store.js";
|
|
16
|
-
import { resolveGuardianPersona } from "../prompts/persona-resolver.js";
|
|
17
16
|
import { buildCoreIdentityContext } from "../prompts/system-prompt.js";
|
|
18
17
|
import {
|
|
19
18
|
createTimeout,
|
|
@@ -921,9 +920,7 @@ async function classifyWithLLM(
|
|
|
921
920
|
const candidateContext = candidateSet
|
|
922
921
|
? (serializeCandidatesForPrompt(candidateSet) ?? undefined)
|
|
923
922
|
: undefined;
|
|
924
|
-
const rawIdentityContext = buildCoreIdentityContext(
|
|
925
|
-
userPersona: resolveGuardianPersona(),
|
|
926
|
-
});
|
|
923
|
+
const rawIdentityContext = buildCoreIdentityContext();
|
|
927
924
|
const identityContext = rawIdentityContext
|
|
928
925
|
? truncate(rawIdentityContext, MAX_IDENTITY_CONTEXT_CHARS, "\n…[truncated]")
|
|
929
926
|
: undefined;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-conversation notification buffer used to keep a background job's
|
|
3
|
+
* "success" notification from racing the runner's `activity.failed` when
|
|
4
|
+
* the job times out after the model already invoked `notifications send`.
|
|
5
|
+
*
|
|
6
|
+
* `registerDeferredConversation` arms the buffer before the LLM turn.
|
|
7
|
+
* `bufferIfDeferred` is called from the IPC route handler — it buffers when
|
|
8
|
+
* armed, swallows when tombstoned (post-discard grace window), and returns
|
|
9
|
+
* null otherwise so the route emits normally.
|
|
10
|
+
* `commitDeferredConversation` flushes on success; `discardDeferredConversation`
|
|
11
|
+
* drops on failure and tombstones briefly to catch late tool calls that
|
|
12
|
+
* arrive after `processMessage` keeps running past the runner's timeout.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { v4 as uuid } from "uuid";
|
|
16
|
+
|
|
17
|
+
import { getLogger } from "../util/logger.js";
|
|
18
|
+
import {
|
|
19
|
+
emitNotificationSignal,
|
|
20
|
+
type EmitSignalParams,
|
|
21
|
+
type EmitSignalResult,
|
|
22
|
+
} from "./emit-signal.js";
|
|
23
|
+
|
|
24
|
+
const log = getLogger("notifications-deferred-emit");
|
|
25
|
+
|
|
26
|
+
// How long after the last observed late notification we keep the tombstone
|
|
27
|
+
// alive. `work` in `runBackgroundJob` is not cancelled on timeout — it can
|
|
28
|
+
// continue running and emit skill calls indefinitely. We refresh the timer
|
|
29
|
+
// on every late arrival, so the tombstone persists as long as the turn is
|
|
30
|
+
// still draining and only expires once the conversation has been quiet for
|
|
31
|
+
// this long.
|
|
32
|
+
const TOMBSTONE_TTL_MS = 5 * 60 * 1000;
|
|
33
|
+
|
|
34
|
+
type BufferEntry =
|
|
35
|
+
| { state: "buffered"; items: EmitSignalParams<string>[] }
|
|
36
|
+
| { state: "tombstoned"; timer: ReturnType<typeof setTimeout> };
|
|
37
|
+
|
|
38
|
+
const buffers = new Map<string, BufferEntry>();
|
|
39
|
+
|
|
40
|
+
function scheduleTombstoneEviction(
|
|
41
|
+
conversationId: string,
|
|
42
|
+
): ReturnType<typeof setTimeout> {
|
|
43
|
+
const timer = setTimeout(() => {
|
|
44
|
+
const cur = buffers.get(conversationId);
|
|
45
|
+
if (cur?.state === "tombstoned") buffers.delete(conversationId);
|
|
46
|
+
}, TOMBSTONE_TTL_MS);
|
|
47
|
+
timer.unref?.();
|
|
48
|
+
return timer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function registerDeferredConversation(conversationId: string): void {
|
|
52
|
+
buffers.set(conversationId, { state: "buffered", items: [] });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Buffer the signal when the originating conversation is armed, swallow it
|
|
57
|
+
* when tombstoned, otherwise return null so the caller emits normally.
|
|
58
|
+
*/
|
|
59
|
+
export function bufferIfDeferred(
|
|
60
|
+
originatingConversationId: string | undefined,
|
|
61
|
+
params: EmitSignalParams<string>,
|
|
62
|
+
): EmitSignalResult | null {
|
|
63
|
+
if (!originatingConversationId) return null;
|
|
64
|
+
const entry = buffers.get(originatingConversationId);
|
|
65
|
+
if (!entry) return null;
|
|
66
|
+
if (entry.state === "tombstoned") {
|
|
67
|
+
// Refresh the eviction timer so the tombstone outlives any continuing
|
|
68
|
+
// turn activity. Otherwise a long-running orphan `processMessage` could
|
|
69
|
+
// emit a `notifications send` after the fixed TTL elapsed and bypass
|
|
70
|
+
// buffering, recreating the "success + activity.failed" contradiction.
|
|
71
|
+
clearTimeout(entry.timer);
|
|
72
|
+
entry.timer = scheduleTombstoneEviction(originatingConversationId);
|
|
73
|
+
return {
|
|
74
|
+
signalId: uuid(),
|
|
75
|
+
deduplicated: false,
|
|
76
|
+
dispatched: false,
|
|
77
|
+
reason: "Notification dropped: background job did not complete",
|
|
78
|
+
deliveryResults: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
entry.items.push(params);
|
|
82
|
+
return {
|
|
83
|
+
signalId: uuid(),
|
|
84
|
+
deduplicated: false,
|
|
85
|
+
dispatched: false,
|
|
86
|
+
reason: "Notification deferred until background job completes",
|
|
87
|
+
deliveryResults: [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function commitDeferredConversation(
|
|
92
|
+
conversationId: string,
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
const entry = buffers.get(conversationId);
|
|
95
|
+
if (!entry || entry.state !== "buffered") return;
|
|
96
|
+
buffers.delete(conversationId);
|
|
97
|
+
for (const params of entry.items) {
|
|
98
|
+
try {
|
|
99
|
+
await emitNotificationSignal(params);
|
|
100
|
+
} catch (err) {
|
|
101
|
+
log.warn(
|
|
102
|
+
{ err, conversationId },
|
|
103
|
+
"Buffered notification failed to emit on commit",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Drop any buffered signals and tombstone the conversation. The tombstone
|
|
111
|
+
* persists until the turn has been quiet for `TOMBSTONE_TTL_MS`; each late
|
|
112
|
+
* notification observed via `bufferIfDeferred` refreshes the timer.
|
|
113
|
+
*/
|
|
114
|
+
export function discardDeferredConversation(conversationId: string): number {
|
|
115
|
+
const entry = buffers.get(conversationId);
|
|
116
|
+
if (!entry) return 0;
|
|
117
|
+
const droppedCount = entry.state === "buffered" ? entry.items.length : 0;
|
|
118
|
+
if (entry.state === "tombstoned") clearTimeout(entry.timer);
|
|
119
|
+
buffers.set(conversationId, {
|
|
120
|
+
state: "tombstoned",
|
|
121
|
+
timer: scheduleTombstoneEviction(conversationId),
|
|
122
|
+
});
|
|
123
|
+
if (droppedCount > 0) {
|
|
124
|
+
log.info(
|
|
125
|
+
{ conversationId, droppedCount },
|
|
126
|
+
"Discarded buffered notifications for failed background job",
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return droppedCount;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @internal Test-only reset hook. */
|
|
133
|
+
export function resetDeferredForTest(): void {
|
|
134
|
+
buffers.clear();
|
|
135
|
+
}
|