@vellumai/assistant 0.8.5 → 0.8.6
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/AGENTS.md +33 -1
- package/ARCHITECTURE.md +1 -1
- package/bunfig.toml +6 -1
- package/docs/credential-execution-service.md +6 -6
- package/docs/plugins.md +4 -3
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
- package/openapi.yaml +1900 -166
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
- package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/annotate-risk-options.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +9 -13
- package/src/__tests__/assert-not-live-db.ts +79 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
- package/src/__tests__/audit-log-rotation.test.ts +2 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
- package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +3 -2
- package/src/__tests__/call-controller.test.ts +3 -2
- package/src/__tests__/channel-approval-routes.test.ts +3 -2
- package/src/__tests__/channel-guardian.test.ts +3 -2
- package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
- package/src/__tests__/channel-reply-delivery.test.ts +35 -0
- package/src/__tests__/channel-retry-sweep.test.ts +320 -3
- package/src/__tests__/checker.test.ts +12 -12
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compaction-trail-store.test.ts +264 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
- package/src/__tests__/computer-use-tools.test.ts +12 -14
- package/src/__tests__/config-loader-backfill.test.ts +13 -28
- package/src/__tests__/config-loader-corrupt.test.ts +5 -5
- package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
- package/src/__tests__/config-schema.test.ts +10 -10
- package/src/__tests__/connection-model-compat.test.ts +83 -0
- package/src/__tests__/contacts-tools.test.ts +3 -2
- package/src/__tests__/context-token-estimator.test.ts +22 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
- package/src/__tests__/conversation-agent-loop.test.ts +488 -2
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-attention-store.test.ts +101 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-error.test.ts +30 -0
- package/src/__tests__/conversation-fork-crud.test.ts +69 -8
- package/src/__tests__/conversation-fork-route.test.ts +3 -2
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
- package/src/__tests__/conversation-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +53 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
- package/src/__tests__/conversation-queue.test.ts +333 -291
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
- package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
- package/src/__tests__/conversation-skill-tools.test.ts +38 -142
- package/src/__tests__/conversation-slash-queue.test.ts +84 -32
- package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
- package/src/__tests__/conversation-sync-tags.test.ts +128 -12
- package/src/__tests__/conversation-title-service.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
- package/src/__tests__/credential-broker-server-use.test.ts +5 -5
- package/src/__tests__/credential-execution-client.test.ts +72 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
- package/src/__tests__/credential-health-service.test.ts +252 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -5
- package/src/__tests__/credential-vault-unit.test.ts +19 -19
- package/src/__tests__/credential-vault.test.ts +5 -5
- package/src/__tests__/cross-provider-web-search.test.ts +56 -2
- package/src/__tests__/db-connection-isolation.test.ts +7 -6
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
- package/src/__tests__/db-test-helpers.ts +58 -0
- package/src/__tests__/disk-pressure-guard.test.ts +58 -41
- package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
- package/src/__tests__/disk-pressure-routes.test.ts +0 -33
- package/src/__tests__/disk-pressure-tools.test.ts +0 -4
- package/src/__tests__/dm-persistence.test.ts +26 -40
- package/src/__tests__/document-create-dedupe.test.ts +189 -0
- package/src/__tests__/document-find-replace.test.ts +3 -2
- package/src/__tests__/document-tool-security.test.ts +81 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
- package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
- package/src/__tests__/encrypted-store.test.ts +11 -9
- package/src/__tests__/feature-flag-test-helpers.ts +53 -0
- package/src/__tests__/filing-service.test.ts +1 -0
- package/src/__tests__/first-greeting.test.ts +62 -12
- package/src/__tests__/gateway-flag-listener.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +26 -0
- package/src/__tests__/guardian-action-sweep.test.ts +3 -2
- package/src/__tests__/guardian-outbound-http.test.ts +3 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-logger.ts +26 -0
- package/src/__tests__/host-bash-routes.test.ts +1 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +5 -4
- package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +29 -7
- package/src/__tests__/identity-intro-cache.test.ts +133 -22
- package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
- package/src/__tests__/inference-profile-reaper.test.ts +3 -2
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
- package/src/__tests__/injector-disk-pressure.test.ts +3 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
- package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
- package/src/__tests__/llm-context-normalization.test.ts +42 -0
- package/src/__tests__/llm-resolver.test.ts +331 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
- package/src/__tests__/mcp-abort-signal.test.ts +14 -0
- package/src/__tests__/mcp-client-auth.test.ts +14 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +18 -2
- package/src/__tests__/model-intents.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +30 -2
- package/src/__tests__/notification-deep-link.test.ts +62 -0
- package/src/__tests__/oauth-commands-routes.test.ts +37 -0
- package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
- package/src/__tests__/oauth-store.test.ts +3 -2
- package/src/__tests__/onboarding-template-contract.test.ts +3 -2
- package/src/__tests__/openai-provider.test.ts +8 -9
- package/src/__tests__/openai-responses-provider.test.ts +70 -10
- package/src/__tests__/openrouter-provider-only.test.ts +27 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
- package/src/__tests__/persistence-pipeline.test.ts +139 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
- package/src/__tests__/plugin-bootstrap.test.ts +9 -11
- package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
- package/src/__tests__/process-message-background-slack.test.ts +21 -16
- package/src/__tests__/process-message-display-content.test.ts +19 -22
- package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
- package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
- package/src/__tests__/provider-registry-ollama.test.ts +45 -22
- package/src/__tests__/recording-handler.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +82 -76
- package/src/__tests__/relay-server.test.ts +10 -10
- package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
- package/src/__tests__/schedule-store.test.ts +16 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
- package/src/__tests__/secret-ingress-http.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +3 -3
- package/src/__tests__/send-endpoint-busy.test.ts +81 -42
- package/src/__tests__/server-history-render.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
- package/src/__tests__/skill-feature-flags.test.ts +14 -16
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
- package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
- package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
- package/src/__tests__/skill-tool-factory.test.ts +96 -95
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
- package/src/__tests__/subagent-disposal.test.ts +27 -8
- package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
- package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
- package/src/__tests__/subagent-manager-notify.test.ts +20 -8
- package/src/__tests__/subagent-notify-parent.test.ts +5 -4
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
- package/src/__tests__/subagent-tools.test.ts +2 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +38 -0
- package/src/__tests__/test-preload-verifier.ts +68 -0
- package/src/__tests__/test-preload.ts +32 -39
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
- package/src/__tests__/tool-executor.test.ts +55 -10
- package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/twilio-routes.test.ts +3 -2
- package/src/__tests__/validate-input.test.ts +381 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
- package/src/__tests__/voice-session-bridge.test.ts +37 -28
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
- package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
- package/src/acp/session-manager.ts +5 -6
- package/src/agent/loop.ts +80 -0
- package/src/api/README.md +124 -2
- package/src/api/constants/call-sites.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +51 -0
- package/src/api/events/assistant-text-delta.ts +32 -0
- package/src/api/events/assistant-turn-start.ts +33 -0
- package/src/api/events/document-comment-created.ts +48 -0
- package/src/api/events/document-comment-deleted.ts +24 -0
- package/src/api/events/document-comment-reopened.ts +25 -0
- package/src/api/events/document-comment-resolved.ts +27 -0
- package/src/api/events/generation-cancelled.ts +24 -0
- package/src/api/events/generation-handoff.ts +41 -0
- package/src/api/events/message-complete.ts +42 -0
- package/src/api/events/open-url.ts +30 -0
- package/src/{events → api/events}/relationship-state-updated.ts +3 -3
- package/src/api/events/tool-use-start.ts +32 -0
- package/src/api/index.ts +128 -3
- package/src/api/responses/llm-context-response.ts +39 -0
- package/src/api/responses/llm-request-log-entry.ts +93 -0
- package/src/api/responses/memory-recall-log.ts +65 -0
- package/src/api/responses/memory-v2-activation-log.ts +78 -0
- package/src/background-wake/background-wake-routes.test.ts +687 -52
- package/src/background-wake/platform-client.test.ts +308 -0
- package/src/background-wake/platform-client.ts +167 -0
- package/src/background-wake/publisher.ts +91 -0
- package/src/background-wake/runtime-registry.ts +2 -2
- package/src/background-wake/wake-intent-hooks.test.ts +282 -0
- package/src/calls/guardian-dispatch.ts +1 -0
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
- package/src/cli/commands/__tests__/notifications.test.ts +184 -40
- package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
- package/src/cli/commands/channels/index.ts +229 -0
- package/src/cli/commands/memory-v3-render.ts +147 -0
- package/src/cli/commands/memory-v3.ts +255 -4
- package/src/cli/commands/notifications.ts +365 -55
- package/src/cli/lib/open-browser.ts +7 -2
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +23 -42
- package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
- package/src/config/call-site-defaults.ts +1 -1
- package/src/config/feature-flag-cache.ts +86 -0
- package/src/config/feature-flag-registry.json +17 -17
- package/src/config/llm-context-resolution.ts +10 -1
- package/src/config/llm-resolver.ts +121 -15
- package/src/config/loader.ts +4 -5
- package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +90 -1
- package/src/config/schemas/memory-v2.ts +26 -0
- package/src/config/schemas/services.ts +6 -2
- package/src/config/seed-inference-profiles.ts +36 -16
- package/src/context/token-estimator.ts +10 -5
- package/src/credential-execution/executable-discovery.ts +40 -0
- package/src/credential-execution/process-manager.ts +6 -2
- package/src/credential-health/credential-health-service.ts +125 -40
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
- package/src/daemon/conversation-agent-loop.ts +132 -28
- package/src/daemon/conversation-error.ts +33 -5
- package/src/daemon/conversation-messaging.ts +84 -43
- package/src/daemon/conversation-process.ts +74 -37
- package/src/daemon/conversation-runtime-assembly.ts +29 -9
- package/src/daemon/conversation-skill-tools.ts +14 -30
- package/src/daemon/conversation-surfaces.ts +69 -34
- package/src/daemon/conversation-tool-setup.ts +33 -48
- package/src/daemon/conversation.ts +26 -46
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/daemon-skill-host.ts +9 -2
- package/src/daemon/disk-pressure-guard.ts +27 -29
- package/src/daemon/first-greeting.ts +31 -13
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/lifecycle.ts +12 -12
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/meet-manifest-loader.ts +10 -17
- package/src/daemon/message-types/conversations.ts +20 -22
- package/src/daemon/message-types/document-comments.ts +8 -44
- package/src/daemon/message-types/home.ts +2 -2
- package/src/daemon/message-types/integrations.ts +2 -7
- package/src/daemon/message-types/messages.ts +23 -38
- package/src/daemon/message-types/subagents.ts +6 -0
- package/src/daemon/process-message.ts +9 -9
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +16 -0
- package/src/daemon/switch-inference-profile-tool.ts +13 -3
- package/src/daemon/tool-setup-types.ts +0 -6
- package/src/daemon/wake-target-adapter.ts +10 -0
- package/src/documents/document-store.ts +38 -0
- package/src/export/__tests__/transcript-formatter.test.ts +1 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
- package/src/heartbeat/heartbeat-service.ts +63 -0
- package/src/home/__tests__/feed-writer.test.ts +161 -0
- package/src/home/__tests__/post-connect-feed.test.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +55 -59
- package/src/home/feed-writer.ts +146 -7
- package/src/home/suggested-prompts.ts +27 -145
- package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
- package/src/ipc/gateway-client.test.ts +4 -1
- package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
- package/src/ipc/skill-routes/memory.ts +4 -3
- package/src/ipc/skill-routes/registries.ts +28 -29
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
- package/src/memory/conversation-attention-store.ts +17 -3
- package/src/memory/conversation-crud.ts +352 -112
- package/src/memory/db-connection.ts +29 -19
- package/src/memory/db-init.ts +4 -0
- package/src/memory/db-singleton.ts +77 -0
- package/src/memory/delivery-channels.ts +82 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
- package/src/memory/graph/retriever.test.ts +3 -3
- package/src/memory/job-handlers/embedding.test.ts +3 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
- package/src/memory/jobs-worker.ts +12 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
- package/src/memory/llm-request-log-source-local.ts +24 -0
- package/src/memory/llm-request-log-source.ts +31 -0
- package/src/memory/llm-request-log-store.ts +188 -3
- package/src/memory/memory-v2-activation-log-store.ts +95 -1
- package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
- package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema/conversations.ts +9 -1
- package/src/memory/schema/inference.ts +0 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
- package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
- package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
- package/src/memory/v2/harness/metrics.ts +5 -1
- package/src/memory/v2/harness/replay-input.ts +19 -3
- package/src/memory/v2/harness/runner.ts +6 -0
- package/src/memory/v2/harness/trace.ts +6 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
- package/src/memory/v3/__tests__/edges.test.ts +144 -1
- package/src/memory/v3/__tests__/filter.test.ts +48 -0
- package/src/memory/v3/__tests__/gate.test.ts +96 -33
- package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
- package/src/memory/v3/__tests__/loop.test.ts +250 -5
- package/src/memory/v3/__tests__/scouts.test.ts +49 -0
- package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
- package/src/memory/v3/__tests__/traversal.test.ts +39 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
- package/src/memory/v3/__tests__/validate.test.ts +32 -0
- package/src/memory/v3/coretrieval-seed.ts +240 -0
- package/src/memory/v3/edges.ts +58 -21
- package/src/memory/v3/filter.ts +27 -22
- package/src/memory/v3/gate.ts +51 -36
- package/src/memory/v3/index-composition.ts +18 -5
- package/src/memory/v3/loop.ts +65 -17
- package/src/memory/v3/scouts.ts +15 -4
- package/src/memory/v3/shadow-diff.ts +287 -0
- package/src/memory/v3/shadow-middleware.ts +44 -2
- package/src/memory/v3/traversal.ts +6 -1
- package/src/memory/v3/tree-walk.ts +6 -1
- package/src/memory/v3/validate.ts +56 -33
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
- package/src/notifications/adapters/slack.ts +45 -11
- package/src/notifications/broadcaster.ts +114 -63
- package/src/notifications/conversation-pairing.ts +23 -3
- package/src/notifications/decisions-store.ts +32 -1
- package/src/notifications/deliveries-store.ts +45 -0
- package/src/notifications/edit-notification.ts +201 -0
- package/src/notifications/emit-signal.ts +11 -1
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +37 -0
- package/src/oauth/byo-connection.test.ts +67 -3
- package/src/oauth/byo-connection.ts +32 -5
- package/src/oauth/connect-orchestrator.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +76 -0
- package/src/oauth/connection-resolver.ts +49 -10
- package/src/oauth/manual-token-connection.ts +51 -3
- package/src/oauth/seed-providers.ts +3 -0
- package/src/permissions/approval-policy.test.ts +19 -5
- package/src/permissions/approval-policy.ts +14 -3
- package/src/permissions/checker.ts +21 -8
- package/src/platform/client.test.ts +24 -1
- package/src/platform/client.ts +8 -0
- package/src/platform/feature-gate.ts +15 -0
- package/src/plugins/defaults/injectors.ts +2 -8
- package/src/plugins/defaults/persistence.ts +25 -6
- package/src/plugins/types.ts +57 -13
- package/src/proactive-artifact/job.test.ts +1 -0
- package/src/prompts/__tests__/system-prompt.test.ts +4 -4
- package/src/prompts/system-prompt.ts +38 -40
- package/src/prompts/template-detection.ts +10 -4
- package/src/prompts/templates/BOOTSTRAP.md +7 -11
- package/src/prompts/templates/IDENTITY.md +0 -2
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
- package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
- package/src/providers/call-site-routing.ts +33 -9
- package/src/providers/connection-model-compat.ts +23 -0
- package/src/providers/connection-resolution.ts +39 -20
- package/src/providers/fireworks/client.ts +1 -0
- package/src/providers/gemini/client.ts +24 -3
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
- package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
- package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
- package/src/providers/inference/auth.ts +0 -8
- package/src/providers/inference/connections.ts +3 -66
- package/src/providers/inference/resolve-auth.ts +2 -3
- package/src/providers/model-catalog.ts +35 -1
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
- package/src/providers/openai/chat-completions-provider.ts +110 -12
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/openai/responses-provider.ts +53 -53
- package/src/providers/openrouter/client.ts +13 -8
- package/src/providers/provider-send-message.ts +18 -9
- package/src/providers/registry.ts +48 -8
- package/src/providers/retry.ts +16 -4
- package/src/providers/search-provider-catalog.ts +17 -9
- package/src/providers/types.ts +9 -0
- package/src/runtime/__tests__/agent-wake.test.ts +1 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
- package/src/runtime/access-request-helper.ts +1 -0
- package/src/runtime/auth/route-policy.ts +10 -0
- package/src/runtime/channel-readiness-service.ts +68 -0
- package/src/runtime/channel-reply-delivery.ts +23 -0
- package/src/runtime/channel-retry-sweep.ts +47 -14
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/migrations/vbundle-builder.ts +3 -2
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
- package/src/runtime/routes/acp-routes-list.test.ts +3 -0
- package/src/runtime/routes/app-management-routes.ts +111 -4
- package/src/runtime/routes/background-wake-routes.ts +188 -20
- package/src/runtime/routes/btw-routes.ts +4 -4
- package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
- package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
- package/src/runtime/routes/conversation-list-routes.ts +147 -0
- package/src/runtime/routes/conversation-management-routes.ts +39 -14
- package/src/runtime/routes/conversation-query-routes.ts +60 -10
- package/src/runtime/routes/conversation-routes.ts +186 -140
- package/src/runtime/routes/conversations-import-routes.ts +19 -6
- package/src/runtime/routes/documents-routes.ts +10 -1
- package/src/runtime/routes/group-routes.ts +11 -0
- package/src/runtime/routes/home-feed-routes.ts +129 -0
- package/src/runtime/routes/identity-intro-cache.ts +61 -16
- package/src/runtime/routes/identity-routes.ts +30 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
- package/src/runtime/routes/integrations/vercel.ts +15 -0
- package/src/runtime/routes/llm-context-normalization.ts +7 -2
- package/src/runtime/routes/memory-v3-routes.ts +160 -2
- package/src/runtime/routes/migration-routes.ts +20 -13
- package/src/runtime/routes/notification-routes.ts +63 -1
- package/src/runtime/routes/oauth-commands-routes.ts +6 -1
- package/src/runtime/routes/surface-action-routes.ts +1 -38
- package/src/runtime/routes/surface-content-routes.ts +12 -5
- package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
- package/src/runtime/slack-dm-text-delivery.ts +177 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/schedule/schedule-store.ts +8 -1
- package/src/schedule/scheduler.ts +111 -15
- package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
- package/src/security/encrypted-store.ts +7 -16
- package/src/security/store-path-override.ts +61 -0
- package/src/signals/user-message.ts +5 -8
- package/src/skills/validate-input.ts +177 -0
- package/src/subagent/manager.ts +13 -13
- package/src/subagent/types.ts +6 -0
- package/src/tasks/tool-sanitizer.ts +2 -2
- package/src/tools/apps/definitions.ts +35 -21
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
- package/src/tools/computer-use/definitions.ts +268 -266
- package/src/tools/document/document-tool.ts +131 -8
- package/src/tools/execution-target.ts +2 -5
- package/src/tools/executor.ts +18 -55
- package/src/tools/host-filesystem/edit.test.ts +1 -0
- package/src/tools/host-filesystem/read.test.ts +1 -0
- package/src/tools/host-filesystem/transfer.test.ts +31 -6
- package/src/tools/host-filesystem/write.test.ts +1 -0
- package/src/tools/mcp/mcp-tool-factory.ts +0 -2
- package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
- package/src/tools/network/__tests__/web-search.test.ts +211 -3
- package/src/tools/network/managed-search-proxy.ts +183 -0
- package/src/tools/network/web-search.ts +199 -44
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/registry.ts +146 -103
- package/src/tools/schedule/create.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +17 -36
- package/src/tools/subagent/spawn.ts +3 -0
- package/src/tools/tool-approval-handler.ts +10 -4
- package/src/tools/tool-name-aliases.ts +72 -14
- package/src/tools/types.ts +17 -15
- package/src/tools/ui-surface/definitions.ts +98 -86
- package/src/types/onboarding-context.ts +6 -0
- package/src/usage/attribution.ts +32 -1
- package/src/util/browser.ts +7 -2
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
- package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -134,58 +134,81 @@ function nodeChildrenOf(tree: TreeIndex, nodeId: string): string[] {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/**
|
|
137
|
-
* Full DFS over `node:` adjacency
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
137
|
+
* Full DFS over `node:` adjacency. Returns the set of nodes reachable from
|
|
138
|
+
* `tree.root` (for orphan-page reachability) and the back-edges that close a
|
|
139
|
+
* cycle. A back-edge is an edge into a node still on the active recursion stack
|
|
140
|
+
* (classic gray-node cycle detection); `visited` (black) prevents re-walking
|
|
141
|
+
* shared DAG sub-nodes.
|
|
142
|
+
*
|
|
143
|
+
* The walk runs in two phases. The first seeds from the root and records every
|
|
144
|
+
* node it reaches in `reachableNodes`. The second sweeps any node still
|
|
145
|
+
* unvisited — a disconnected component that the root cannot reach — so cycles
|
|
146
|
+
* living entirely outside the root's reach are still reported. Sweep-only nodes
|
|
147
|
+
* are deliberately kept out of `reachableNodes`: they are *not* reachable from
|
|
148
|
+
* the root, and pages hanging off them must still surface as orphans.
|
|
142
149
|
*/
|
|
143
150
|
function descend(tree: TreeIndex): {
|
|
144
151
|
reachableNodes: Set<string>;
|
|
145
152
|
cycles: Array<{ from: string; to: string }>;
|
|
146
153
|
} {
|
|
147
154
|
const reachableNodes = new Set<string>();
|
|
148
|
-
const
|
|
155
|
+
const visited = new Set<string>();
|
|
149
156
|
const cycles: Array<{ from: string; to: string }> = [];
|
|
150
157
|
|
|
151
158
|
// Iterative DFS with an explicit stack so deep trees don't blow the call
|
|
152
159
|
// stack. Each frame tracks its child cursor; we push a child frame, and on
|
|
153
160
|
// exhaustion pop the parent off the recursion stack (`onStack`).
|
|
154
161
|
type Frame = { node: string; children: string[]; cursor: number };
|
|
155
|
-
const stack: Frame[] = [];
|
|
156
162
|
|
|
157
|
-
function
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
function walkFrom(start: string, trackReachable: boolean): void {
|
|
164
|
+
const onStack = new Set<string>();
|
|
165
|
+
const stack: Frame[] = [];
|
|
166
|
+
|
|
167
|
+
function enter(nodeId: string): void {
|
|
168
|
+
visited.add(nodeId);
|
|
169
|
+
if (trackReachable) reachableNodes.add(nodeId);
|
|
170
|
+
onStack.add(nodeId);
|
|
171
|
+
stack.push({
|
|
172
|
+
node: nodeId,
|
|
173
|
+
children: nodeChildrenOf(tree, nodeId),
|
|
174
|
+
cursor: 0,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
enter(start);
|
|
179
|
+
|
|
180
|
+
while (stack.length > 0) {
|
|
181
|
+
const frame = stack[stack.length - 1];
|
|
182
|
+
if (frame.cursor >= frame.children.length) {
|
|
183
|
+
onStack.delete(frame.node);
|
|
184
|
+
stack.pop();
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const child = frame.children[frame.cursor++];
|
|
188
|
+
if (onStack.has(child)) {
|
|
189
|
+
// Edge into an ancestor still on the stack → cycle-closing back-edge.
|
|
190
|
+
cycles.push({ from: frame.node, to: child });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (visited.has(child)) {
|
|
194
|
+
// Already fully explored (shared DAG sub-node or an earlier sweep).
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
enter(child);
|
|
198
|
+
}
|
|
165
199
|
}
|
|
166
200
|
|
|
167
201
|
if (tree.nodes.has(tree.root)) {
|
|
168
|
-
|
|
202
|
+
walkFrom(tree.root, true);
|
|
169
203
|
}
|
|
170
204
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
const child = frame.children[frame.cursor++];
|
|
179
|
-
if (onStack.has(child)) {
|
|
180
|
-
// Edge into an ancestor still on the stack → cycle-closing back-edge.
|
|
181
|
-
cycles.push({ from: frame.node, to: child });
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (reachableNodes.has(child)) {
|
|
185
|
-
// Already fully explored via another parent (shared DAG sub-node).
|
|
186
|
-
continue;
|
|
205
|
+
// Cover nodes the root never reached (disconnected components) so a cycle
|
|
206
|
+
// among them is not silently missed. These are not root-reachable, so their
|
|
207
|
+
// pages stay eligible for the orphan-page report.
|
|
208
|
+
for (const nodeId of tree.nodes.keys()) {
|
|
209
|
+
if (!visited.has(nodeId)) {
|
|
210
|
+
walkFrom(nodeId, false);
|
|
187
211
|
}
|
|
188
|
-
enter(child);
|
|
189
212
|
}
|
|
190
213
|
|
|
191
214
|
cycles.sort(
|
|
@@ -34,6 +34,7 @@ mock.module("../../home/feed-writer.js", () => ({
|
|
|
34
34
|
// home-feed-side-effect.ts only consumes `getConversation`.
|
|
35
35
|
mock.module("../../memory/conversation-crud.js", () => ({
|
|
36
36
|
getConversation: () => conversationRow,
|
|
37
|
+
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
37
38
|
}));
|
|
38
39
|
|
|
39
40
|
// Stub the broadcaster so emit-signal's `getBroadcaster()` does not need
|
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
ChannelAdapter,
|
|
17
17
|
ChannelDeliveryPayload,
|
|
18
18
|
ChannelDestination,
|
|
19
|
+
ChannelUpdateContext,
|
|
20
|
+
ChannelUpdatePayload,
|
|
19
21
|
DeliveryResult,
|
|
20
22
|
NotificationChannel,
|
|
21
23
|
} from "../types.js";
|
|
@@ -52,9 +54,7 @@ function resolveSlackMessageText(payload: ChannelDeliveryPayload): string {
|
|
|
52
54
|
* - Optional context: message preview
|
|
53
55
|
* - Context: approval code instructions + invite directive
|
|
54
56
|
*/
|
|
55
|
-
function buildAccessRequestBlocks(
|
|
56
|
-
payload: Record<string, unknown>,
|
|
57
|
-
): unknown[] {
|
|
57
|
+
function buildAccessRequestBlocks(payload: Record<string, unknown>): unknown[] {
|
|
58
58
|
const blocks: unknown[] = [];
|
|
59
59
|
|
|
60
60
|
// Header
|
|
@@ -209,19 +209,18 @@ export class SlackAdapter implements ChannelAdapter {
|
|
|
209
209
|
payload.contextPayload != null;
|
|
210
210
|
|
|
211
211
|
try {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
await sendSlackReply(chatId, messageText, { useBlocks: true });
|
|
217
|
-
}
|
|
212
|
+
const result = isAccessRequest
|
|
213
|
+
? await sendSlackReply(chatId, messageText, {
|
|
214
|
+
blocks: buildAccessRequestBlocks(payload.contextPayload!),
|
|
215
|
+
})
|
|
216
|
+
: await sendSlackReply(chatId, messageText, { useBlocks: true });
|
|
218
217
|
|
|
219
218
|
log.info(
|
|
220
|
-
{ sourceEventName: payload.sourceEventName, chatId },
|
|
219
|
+
{ sourceEventName: payload.sourceEventName, chatId, ts: result.ts },
|
|
221
220
|
"Slack notification delivered",
|
|
222
221
|
);
|
|
223
222
|
|
|
224
|
-
return { success: true };
|
|
223
|
+
return { success: true, messageId: result.ts };
|
|
225
224
|
} catch (err) {
|
|
226
225
|
const message = err instanceof Error ? err.message : String(err);
|
|
227
226
|
log.error(
|
|
@@ -231,4 +230,39 @@ export class SlackAdapter implements ChannelAdapter {
|
|
|
231
230
|
return { success: false, error: message };
|
|
232
231
|
}
|
|
233
232
|
}
|
|
233
|
+
|
|
234
|
+
async update(
|
|
235
|
+
delivery: ChannelUpdateContext,
|
|
236
|
+
patch: ChannelUpdatePayload,
|
|
237
|
+
): Promise<DeliveryResult> {
|
|
238
|
+
if (!delivery.messageId) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error:
|
|
242
|
+
"missing_message_id: this delivery has no captured Slack ts (sent before edit support landed)",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const text = patch.body?.trim() || patch.title?.trim();
|
|
246
|
+
if (!text) {
|
|
247
|
+
return { success: false, error: "no body or title supplied for update" };
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const result = await sendSlackReply(delivery.destination, text, {
|
|
251
|
+
messageTs: delivery.messageId,
|
|
252
|
+
useBlocks: true,
|
|
253
|
+
});
|
|
254
|
+
log.info(
|
|
255
|
+
{ chatId: delivery.destination, messageTs: delivery.messageId },
|
|
256
|
+
"Slack notification updated",
|
|
257
|
+
);
|
|
258
|
+
return { success: true, messageId: result.ts ?? delivery.messageId };
|
|
259
|
+
} catch (err) {
|
|
260
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
261
|
+
log.error(
|
|
262
|
+
{ err, chatId: delivery.destination, messageTs: delivery.messageId },
|
|
263
|
+
"Failed to update Slack notification",
|
|
264
|
+
);
|
|
265
|
+
return { success: false, error: message };
|
|
266
|
+
}
|
|
267
|
+
}
|
|
234
268
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { v4 as uuid } from "uuid";
|
|
13
13
|
|
|
14
|
+
import { getConversation } from "../memory/conversation-crud.js";
|
|
14
15
|
import { getLogger } from "../util/logger.js";
|
|
15
16
|
import { isGuardianSensitiveEvent } from "./adapters/macos.js";
|
|
16
17
|
import { pairDeliveryWithConversation } from "./conversation-pairing.js";
|
|
@@ -72,6 +73,11 @@ export class NotificationBroadcaster {
|
|
|
72
73
|
this.onConversationCreated = fn;
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/** Return the registered adapter for a channel, if any. */
|
|
77
|
+
getAdapter(channel: NotificationChannel): ChannelAdapter | undefined {
|
|
78
|
+
return this.adapters.get(channel);
|
|
79
|
+
}
|
|
80
|
+
|
|
75
81
|
/**
|
|
76
82
|
* Broadcast a notification decision to all selected channels.
|
|
77
83
|
*
|
|
@@ -231,78 +237,93 @@ export class NotificationBroadcaster {
|
|
|
231
237
|
);
|
|
232
238
|
|
|
233
239
|
// For the vellum channel, merge the conversationId into deep-link metadata
|
|
234
|
-
// so the macOS client can navigate directly to the
|
|
240
|
+
// so the macOS client can navigate directly to the conversation. Prefer
|
|
241
|
+
// the paired conversation (interactive opt-in flows); otherwise fall back
|
|
242
|
+
// to the originating conversation referenced by `sourceContextId` when it
|
|
243
|
+
// resolves to a real row. Sentinel context ids (job IDs, call session IDs,
|
|
244
|
+
// access-req-* strings) leave the deep link without a conversation, and
|
|
245
|
+
// the macOS handler opens the app to its default landing.
|
|
235
246
|
let deepLinkTarget = decision.deepLinkTarget;
|
|
236
|
-
if (channel === "vellum"
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
? destination.metadata.guardianPrincipalId
|
|
251
|
-
: undefined;
|
|
252
|
-
const targetGuardianPrincipalId =
|
|
253
|
-
guardianPrincipalId &&
|
|
254
|
-
isGuardianSensitiveEvent(signal.sourceEventName)
|
|
255
|
-
? guardianPrincipalId
|
|
256
|
-
: undefined;
|
|
257
|
-
|
|
258
|
-
const conversationTitle =
|
|
259
|
-
copy.conversationTitle ?? copy.title ?? signal.sourceEventName;
|
|
260
|
-
const conversationSilent =
|
|
261
|
-
signal.attentionHints.urgency !== "high" &&
|
|
262
|
-
signal.attentionHints.urgency !== "critical";
|
|
263
|
-
const info: ConversationCreatedInfo = {
|
|
264
|
-
conversationId: pairing.conversationId,
|
|
265
|
-
title: conversationTitle,
|
|
266
|
-
sourceEventName: signal.sourceEventName,
|
|
267
|
-
targetGuardianPrincipalId,
|
|
268
|
-
groupId: signal.conversationMetadata?.groupId,
|
|
269
|
-
source: signal.conversationMetadata?.source,
|
|
270
|
-
silent: conversationSilent,
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
// The per-dispatch onConversationCreated callback fires whenever a vellum
|
|
274
|
-
// conversation is paired (new or reused) because callers like
|
|
275
|
-
// dispatchGuardianQuestion rely on it to create delivery bookkeeping
|
|
276
|
-
// rows before emitNotificationSignal() returns.
|
|
277
|
-
if (options?.onConversationCreated) {
|
|
278
|
-
try {
|
|
279
|
-
options.onConversationCreated(info);
|
|
280
|
-
} catch (err) {
|
|
281
|
-
log.error(
|
|
282
|
-
{ err, signalId: signal.signalId },
|
|
283
|
-
"per-dispatch onConversationCreated callback failed — continuing broadcast",
|
|
284
|
-
);
|
|
247
|
+
if (channel === "vellum") {
|
|
248
|
+
const deepLinkConversationId =
|
|
249
|
+
pairing.conversationId ??
|
|
250
|
+
resolveSourceConversationId(signal.sourceContextId);
|
|
251
|
+
if (deepLinkConversationId) {
|
|
252
|
+
deepLinkTarget = {
|
|
253
|
+
...deepLinkTarget,
|
|
254
|
+
conversationId: deepLinkConversationId,
|
|
255
|
+
};
|
|
256
|
+
if (pairing.messageId) {
|
|
257
|
+
deepLinkTarget = {
|
|
258
|
+
...deepLinkTarget,
|
|
259
|
+
messageId: pairing.messageId,
|
|
260
|
+
};
|
|
285
261
|
}
|
|
286
262
|
}
|
|
287
263
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
264
|
+
if (pairing.conversationId) {
|
|
265
|
+
// Resolve guardian scoping for conversation-created events so clients
|
|
266
|
+
// can filter guardian-sensitive conversations the same way they filter
|
|
267
|
+
// guardian-sensitive notification intents.
|
|
268
|
+
const guardianPrincipalId =
|
|
269
|
+
typeof destination.metadata?.guardianPrincipalId === "string"
|
|
270
|
+
? destination.metadata.guardianPrincipalId
|
|
271
|
+
: undefined;
|
|
272
|
+
const targetGuardianPrincipalId =
|
|
273
|
+
guardianPrincipalId &&
|
|
274
|
+
isGuardianSensitiveEvent(signal.sourceEventName)
|
|
275
|
+
? guardianPrincipalId
|
|
276
|
+
: undefined;
|
|
277
|
+
|
|
278
|
+
const conversationTitle =
|
|
279
|
+
copy.conversationTitle ?? copy.title ?? signal.sourceEventName;
|
|
280
|
+
const conversationSilent =
|
|
281
|
+
signal.attentionHints.urgency !== "high" &&
|
|
282
|
+
signal.attentionHints.urgency !== "critical";
|
|
283
|
+
const info: ConversationCreatedInfo = {
|
|
284
|
+
conversationId: pairing.conversationId,
|
|
285
|
+
title: conversationTitle,
|
|
286
|
+
sourceEventName: signal.sourceEventName,
|
|
287
|
+
targetGuardianPrincipalId,
|
|
288
|
+
groupId: signal.conversationMetadata?.groupId,
|
|
289
|
+
source: signal.conversationMetadata?.source,
|
|
290
|
+
silent: conversationSilent,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// The per-dispatch onConversationCreated callback fires whenever a vellum
|
|
294
|
+
// conversation is paired (new or reused) because callers like
|
|
295
|
+
// dispatchGuardianQuestion rely on it to create delivery bookkeeping
|
|
296
|
+
// rows before emitNotificationSignal() returns.
|
|
297
|
+
if (options?.onConversationCreated) {
|
|
297
298
|
try {
|
|
298
|
-
|
|
299
|
+
options.onConversationCreated(info);
|
|
299
300
|
} catch (err) {
|
|
300
301
|
log.error(
|
|
301
302
|
{ err, signalId: signal.signalId },
|
|
302
|
-
"onConversationCreated callback failed — continuing broadcast",
|
|
303
|
+
"per-dispatch onConversationCreated callback failed — continuing broadcast",
|
|
303
304
|
);
|
|
304
305
|
}
|
|
305
306
|
}
|
|
307
|
+
|
|
308
|
+
// Emit notification_conversation_created event only when a NEW
|
|
309
|
+
// conversation was actually created. Reusing an existing conversation
|
|
310
|
+
// should not fire the event — the client already knows about the
|
|
311
|
+
// conversation.
|
|
312
|
+
if (
|
|
313
|
+
pairing.createdNewConversation &&
|
|
314
|
+
pairing.strategy === "start_new_conversation"
|
|
315
|
+
) {
|
|
316
|
+
if (this.onConversationCreated) {
|
|
317
|
+
try {
|
|
318
|
+
this.onConversationCreated(info);
|
|
319
|
+
} catch (err) {
|
|
320
|
+
log.error(
|
|
321
|
+
{ err, signalId: signal.signalId },
|
|
322
|
+
"onConversationCreated callback failed — continuing broadcast",
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
306
327
|
}
|
|
307
328
|
}
|
|
308
329
|
|
|
@@ -354,8 +375,21 @@ export class NotificationBroadcaster {
|
|
|
354
375
|
const adapterResult = await adapter.send(payload, destination);
|
|
355
376
|
|
|
356
377
|
if (adapterResult.success) {
|
|
378
|
+
// Prefer the channel-native id the adapter just captured (e.g.
|
|
379
|
+
// Slack `ts`) so later edits can target the same message; fall
|
|
380
|
+
// back to the pairing-supplied id for channels that surface it
|
|
381
|
+
// through conversation pairing instead.
|
|
382
|
+
const resolvedMessageId =
|
|
383
|
+
adapterResult.messageId ?? pairing.messageId ?? undefined;
|
|
357
384
|
if (hasPersistedDecision) {
|
|
358
|
-
updateDeliveryStatus(
|
|
385
|
+
updateDeliveryStatus(
|
|
386
|
+
deliveryId,
|
|
387
|
+
"sent",
|
|
388
|
+
undefined,
|
|
389
|
+
adapterResult.messageId
|
|
390
|
+
? { messageId: adapterResult.messageId }
|
|
391
|
+
: undefined,
|
|
392
|
+
);
|
|
359
393
|
}
|
|
360
394
|
results.push({
|
|
361
395
|
channel,
|
|
@@ -363,7 +397,7 @@ export class NotificationBroadcaster {
|
|
|
363
397
|
status: "sent",
|
|
364
398
|
sentAt: Date.now(),
|
|
365
399
|
conversationId: pairing.conversationId ?? undefined,
|
|
366
|
-
messageId:
|
|
400
|
+
messageId: resolvedMessageId,
|
|
367
401
|
conversationStrategy: pairing.strategy,
|
|
368
402
|
});
|
|
369
403
|
} else {
|
|
@@ -414,3 +448,20 @@ export class NotificationBroadcaster {
|
|
|
414
448
|
return results;
|
|
415
449
|
}
|
|
416
450
|
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Resolve a signal's `sourceContextId` to a conversation id if it points at a
|
|
454
|
+
* real row. Producers may pass sentinels (job IDs, call session IDs,
|
|
455
|
+
* `access-req-*` strings) here; those simply return undefined so the deep
|
|
456
|
+
* link omits the conversation target.
|
|
457
|
+
*/
|
|
458
|
+
function resolveSourceConversationId(
|
|
459
|
+
sourceContextId: string | undefined,
|
|
460
|
+
): string | undefined {
|
|
461
|
+
if (!sourceContextId) return undefined;
|
|
462
|
+
try {
|
|
463
|
+
return getConversation(sourceContextId) ? sourceContextId : undefined;
|
|
464
|
+
} catch {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
@@ -113,6 +113,29 @@ export async function pairDeliveryWithConversation(
|
|
|
113
113
|
};
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
const conversationAction = options?.conversationAction;
|
|
117
|
+
const bindingContext = options?.bindingContext;
|
|
118
|
+
|
|
119
|
+
// Passive vellum notifications surface via the home feed alone and link
|
|
120
|
+
// back to the originating conversation via `signal.sourceContextId`.
|
|
121
|
+
// Materializing a fresh per-notification conversation just to host the
|
|
122
|
+
// seed message leaves a graveyard entry in the sidebar; skip it unless
|
|
123
|
+
// the producer opted in via `requiresConversation` or the decision engine
|
|
124
|
+
// requested explicit reuse of a target conversation.
|
|
125
|
+
if (
|
|
126
|
+
strategy === "start_new_conversation" &&
|
|
127
|
+
!signal.requiresConversation &&
|
|
128
|
+
conversationAction?.action !== "reuse_existing"
|
|
129
|
+
) {
|
|
130
|
+
return {
|
|
131
|
+
conversationId: null,
|
|
132
|
+
messageId: null,
|
|
133
|
+
strategy,
|
|
134
|
+
createdNewConversation: false,
|
|
135
|
+
conversationFallbackUsed: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
116
139
|
const title =
|
|
117
140
|
copy.conversationTitle ?? copy.title ?? signal.sourceEventName;
|
|
118
141
|
|
|
@@ -130,9 +153,6 @@ export async function pairDeliveryWithConversation(
|
|
|
130
153
|
? copy.conversationSeedMessage
|
|
131
154
|
: composeConversationSeed(signal, channel, copy);
|
|
132
155
|
|
|
133
|
-
const conversationAction = options?.conversationAction;
|
|
134
|
-
const bindingContext = options?.bindingContext;
|
|
135
|
-
|
|
136
156
|
// Attempt to reuse an existing conversation when the model requests it
|
|
137
157
|
if (conversationAction?.action === "reuse_existing") {
|
|
138
158
|
const targetId = conversationAction.conversationId;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* were routed.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { eq } from "drizzle-orm";
|
|
10
|
+
import { desc, eq } from "drizzle-orm";
|
|
11
11
|
|
|
12
12
|
import { getDb } from "../memory/db-connection.js";
|
|
13
13
|
import { notificationDecisions } from "../memory/schema.js";
|
|
@@ -94,3 +94,34 @@ export function updateDecision(id: string, params: UpdateDecisionParams): void {
|
|
|
94
94
|
.where(eq(notificationDecisions.id, id))
|
|
95
95
|
.run();
|
|
96
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Return the most recent decision for a given notification event, or
|
|
100
|
+
* `undefined` if none exists. The decision engine writes exactly one
|
|
101
|
+
* decision per event today, but ordering by createdAt DESC keeps this
|
|
102
|
+
* stable if that ever changes (e.g. re-decisions on retry).
|
|
103
|
+
*/
|
|
104
|
+
export function findLatestDecisionByEventId(
|
|
105
|
+
eventId: string,
|
|
106
|
+
): NotificationDecisionRow | undefined {
|
|
107
|
+
const db = getDb();
|
|
108
|
+
const row = db
|
|
109
|
+
.select()
|
|
110
|
+
.from(notificationDecisions)
|
|
111
|
+
.where(eq(notificationDecisions.notificationEventId, eventId))
|
|
112
|
+
.orderBy(desc(notificationDecisions.createdAt))
|
|
113
|
+
.get();
|
|
114
|
+
if (!row) return undefined;
|
|
115
|
+
return {
|
|
116
|
+
id: row.id,
|
|
117
|
+
notificationEventId: row.notificationEventId,
|
|
118
|
+
shouldNotify: row.shouldNotify === 1,
|
|
119
|
+
selectedChannels: row.selectedChannels,
|
|
120
|
+
reasoningSummary: row.reasoningSummary,
|
|
121
|
+
confidence: row.confidence,
|
|
122
|
+
fallbackUsed: row.fallbackUsed === 1,
|
|
123
|
+
promptVersion: row.promptVersion,
|
|
124
|
+
validationResults: row.validationResults,
|
|
125
|
+
createdAt: row.createdAt,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -137,6 +137,7 @@ export function updateDeliveryStatus(
|
|
|
137
137
|
id: string,
|
|
138
138
|
status: NotificationDeliveryStatus,
|
|
139
139
|
error?: { code?: string; message?: string },
|
|
140
|
+
patch?: { messageId?: string },
|
|
140
141
|
): boolean {
|
|
141
142
|
const db = getDb();
|
|
142
143
|
const now = Date.now();
|
|
@@ -151,6 +152,9 @@ export function updateDeliveryStatus(
|
|
|
151
152
|
if (error?.message) {
|
|
152
153
|
updates.errorMessage = error.message;
|
|
153
154
|
}
|
|
155
|
+
if (patch?.messageId !== undefined) {
|
|
156
|
+
updates.messageId = patch.messageId;
|
|
157
|
+
}
|
|
154
158
|
|
|
155
159
|
db.update(notificationDeliveries)
|
|
156
160
|
.set(updates)
|
|
@@ -160,6 +164,34 @@ export function updateDeliveryStatus(
|
|
|
160
164
|
return rawChanges() > 0;
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Update the rendered copy on an existing delivery row.
|
|
169
|
+
*
|
|
170
|
+
* Used by the edit pipeline so the delivery audit reflects the latest
|
|
171
|
+
* title/body shown to the user, not just the original send.
|
|
172
|
+
*/
|
|
173
|
+
export function updateDeliveryRenderedCopy(
|
|
174
|
+
id: string,
|
|
175
|
+
patch: { renderedTitle?: string; renderedBody?: string },
|
|
176
|
+
): boolean {
|
|
177
|
+
const updates: Record<string, unknown> = { updatedAt: Date.now() };
|
|
178
|
+
if (patch.renderedTitle !== undefined) {
|
|
179
|
+
updates.renderedTitle = patch.renderedTitle;
|
|
180
|
+
}
|
|
181
|
+
if (patch.renderedBody !== undefined) {
|
|
182
|
+
updates.renderedBody = patch.renderedBody;
|
|
183
|
+
}
|
|
184
|
+
if (Object.keys(updates).length === 1) return false;
|
|
185
|
+
|
|
186
|
+
getDb()
|
|
187
|
+
.update(notificationDeliveries)
|
|
188
|
+
.set(updates)
|
|
189
|
+
.where(eq(notificationDeliveries.id, id))
|
|
190
|
+
.run();
|
|
191
|
+
|
|
192
|
+
return rawChanges() > 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
163
195
|
/** Check whether a delivery already exists for a given decision+channel pair. */
|
|
164
196
|
export function findDeliveryByDecisionAndChannel(
|
|
165
197
|
decisionId: string,
|
|
@@ -178,3 +210,16 @@ export function findDeliveryByDecisionAndChannel(
|
|
|
178
210
|
.get();
|
|
179
211
|
return row ? rowToDelivery(row) : undefined;
|
|
180
212
|
}
|
|
213
|
+
|
|
214
|
+
/** Return every delivery row for a given decision. */
|
|
215
|
+
export function findDeliveriesByDecisionId(
|
|
216
|
+
decisionId: string,
|
|
217
|
+
): NotificationDeliveryRow[] {
|
|
218
|
+
const db = getDb();
|
|
219
|
+
const rows = db
|
|
220
|
+
.select()
|
|
221
|
+
.from(notificationDeliveries)
|
|
222
|
+
.where(eq(notificationDeliveries.notificationDecisionId, decisionId))
|
|
223
|
+
.all();
|
|
224
|
+
return rows.map(rowToDelivery);
|
|
225
|
+
}
|