@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
|
@@ -97,6 +97,16 @@ function recordReengagementTimestamp(): void {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
function refreshBackgroundWakeIntentSoon(reason: string): void {
|
|
101
|
+
void import("../background-wake/publisher.js")
|
|
102
|
+
.then(({ refreshBackgroundWakeIntent }) =>
|
|
103
|
+
refreshBackgroundWakeIntent(reason),
|
|
104
|
+
)
|
|
105
|
+
.catch((err) =>
|
|
106
|
+
log.warn({ err, reason }, "Failed to queue background wake refresh"),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
export interface HeartbeatDeps {
|
|
101
111
|
alerter: (alert: HeartbeatAlert) => void;
|
|
102
112
|
onConversationCreated?: (info: {
|
|
@@ -107,6 +117,19 @@ export interface HeartbeatDeps {
|
|
|
107
117
|
getCurrentHour?: () => number;
|
|
108
118
|
}
|
|
109
119
|
|
|
120
|
+
export interface ManagedWakeHeartbeatRunOptions {
|
|
121
|
+
now?: number;
|
|
122
|
+
toleranceMs?: number;
|
|
123
|
+
assumeDue?: boolean;
|
|
124
|
+
scheduledFor?: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface ManagedWakeHeartbeatRunResult {
|
|
128
|
+
due: boolean;
|
|
129
|
+
completed: number;
|
|
130
|
+
skipped: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
export class HeartbeatService {
|
|
111
134
|
private static instance?: HeartbeatService;
|
|
112
135
|
|
|
@@ -153,12 +176,47 @@ export class HeartbeatService {
|
|
|
153
176
|
return this._nextRunAt;
|
|
154
177
|
}
|
|
155
178
|
|
|
179
|
+
async runManagedWakeIfDue(
|
|
180
|
+
options: ManagedWakeHeartbeatRunOptions = {},
|
|
181
|
+
): Promise<ManagedWakeHeartbeatRunResult> {
|
|
182
|
+
const now = options.now ?? Date.now();
|
|
183
|
+
const toleranceMs = options.toleranceMs ?? 0;
|
|
184
|
+
const dueByLocalTimer =
|
|
185
|
+
this._nextRunAt != null && this._nextRunAt <= now + toleranceMs;
|
|
186
|
+
|
|
187
|
+
if (!dueByLocalTimer && options.assumeDue !== true) {
|
|
188
|
+
return { due: false, completed: 0, skipped: 0 };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!dueByLocalTimer) {
|
|
192
|
+
if (this._pendingRunId) {
|
|
193
|
+
supersedePendingRun(this._pendingRunId);
|
|
194
|
+
this._pendingRunId = null;
|
|
195
|
+
}
|
|
196
|
+
this._nextRunAt = options.scheduledFor ?? now;
|
|
197
|
+
this._pendingRunId = insertPendingHeartbeatRun(this._nextRunAt);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const completed = await this.runOnce({ force: false });
|
|
201
|
+
|
|
202
|
+
if (this.cronMode && !this.stopped) {
|
|
203
|
+
this.scheduleNextCronRun(getConfig().heartbeat);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
due: true,
|
|
208
|
+
completed: completed ? 1 : 0,
|
|
209
|
+
skipped: completed ? 0 : 1,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
156
213
|
start(): void {
|
|
157
214
|
this.stopped = false;
|
|
158
215
|
const config = getConfig().heartbeat;
|
|
159
216
|
if (!config.enabled) {
|
|
160
217
|
log.info("Heartbeat disabled by config");
|
|
161
218
|
this._nextRunAt = null;
|
|
219
|
+
refreshBackgroundWakeIntentSoon("heartbeat-disabled");
|
|
162
220
|
return;
|
|
163
221
|
}
|
|
164
222
|
if (this.timer) return;
|
|
@@ -283,6 +341,7 @@ export class HeartbeatService {
|
|
|
283
341
|
{ nextRunAt: new Date(nextRunAt).toISOString(), delayMs },
|
|
284
342
|
"Heartbeat cron run scheduled",
|
|
285
343
|
);
|
|
344
|
+
refreshBackgroundWakeIntentSoon("heartbeat-cron-scheduled");
|
|
286
345
|
} catch (err) {
|
|
287
346
|
log.warn(
|
|
288
347
|
{ err },
|
|
@@ -309,6 +368,7 @@ export class HeartbeatService {
|
|
|
309
368
|
this._nextRunAt = null;
|
|
310
369
|
this.cronMode = false;
|
|
311
370
|
this.start();
|
|
371
|
+
refreshBackgroundWakeIntentSoon("heartbeat-reconfigured");
|
|
312
372
|
}
|
|
313
373
|
|
|
314
374
|
/**
|
|
@@ -386,6 +446,7 @@ export class HeartbeatService {
|
|
|
386
446
|
|
|
387
447
|
if (!force && !config.enabled) {
|
|
388
448
|
if (runId) skipHeartbeatRun(runId, "disabled");
|
|
449
|
+
refreshBackgroundWakeIntentSoon("heartbeat-disabled");
|
|
389
450
|
return false;
|
|
390
451
|
}
|
|
391
452
|
|
|
@@ -510,6 +571,7 @@ export class HeartbeatService {
|
|
|
510
571
|
if (!this.cronMode) {
|
|
511
572
|
this.scheduleNextRun(getConfig().heartbeat.intervalMs);
|
|
512
573
|
}
|
|
574
|
+
refreshBackgroundWakeIntentSoon("heartbeat-run-complete");
|
|
513
575
|
}
|
|
514
576
|
return true;
|
|
515
577
|
}
|
|
@@ -520,6 +582,7 @@ export class HeartbeatService {
|
|
|
520
582
|
}
|
|
521
583
|
this._nextRunAt = Date.now() + intervalMs;
|
|
522
584
|
this._pendingRunId = insertPendingHeartbeatRun(this._nextRunAt);
|
|
585
|
+
refreshBackgroundWakeIntentSoon("heartbeat-interval-scheduled");
|
|
523
586
|
}
|
|
524
587
|
|
|
525
588
|
/**
|
|
@@ -35,6 +35,7 @@ mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
|
35
35
|
// hub's real shape has more fields. Tests never call them.
|
|
36
36
|
subscribe: () => () => {},
|
|
37
37
|
},
|
|
38
|
+
broadcastMessage: async () => {},
|
|
38
39
|
}));
|
|
39
40
|
|
|
40
41
|
// Dynamic import so the module resolves after the mock above is in
|
|
@@ -44,9 +45,11 @@ const {
|
|
|
44
45
|
HOME_FEED_FILENAME,
|
|
45
46
|
HOME_FEED_VERSION,
|
|
46
47
|
appendFeedItem,
|
|
48
|
+
clearAllConversationIds,
|
|
47
49
|
getHomeFeedPath,
|
|
48
50
|
patchFeedItemStatus,
|
|
49
51
|
readHomeFeed,
|
|
52
|
+
stripConversationIds,
|
|
50
53
|
} = await import("../feed-writer.js");
|
|
51
54
|
|
|
52
55
|
type FeedItemStatus = "new" | "seen" | "acted_on";
|
|
@@ -60,6 +63,7 @@ interface TestFeedItem {
|
|
|
60
63
|
timestamp: string;
|
|
61
64
|
status: FeedItemStatus;
|
|
62
65
|
expiresAt?: string;
|
|
66
|
+
conversationId?: string;
|
|
63
67
|
createdAt: string;
|
|
64
68
|
}
|
|
65
69
|
|
|
@@ -422,6 +426,163 @@ describe("feed-writer", () => {
|
|
|
422
426
|
});
|
|
423
427
|
});
|
|
424
428
|
|
|
429
|
+
describe("stripConversationIds", () => {
|
|
430
|
+
test("removes conversationId from matching items and leaves others untouched", async () => {
|
|
431
|
+
await appendFeedItem(
|
|
432
|
+
makeItem({
|
|
433
|
+
id: "item-a",
|
|
434
|
+
title: "Linked",
|
|
435
|
+
conversationId: "conv-123",
|
|
436
|
+
}),
|
|
437
|
+
);
|
|
438
|
+
await appendFeedItem(
|
|
439
|
+
makeItem({
|
|
440
|
+
id: "item-b",
|
|
441
|
+
title: "Other conv",
|
|
442
|
+
conversationId: "conv-456",
|
|
443
|
+
createdAt: "2026-04-14T12:00:01.000Z",
|
|
444
|
+
}),
|
|
445
|
+
);
|
|
446
|
+
await appendFeedItem(
|
|
447
|
+
makeItem({
|
|
448
|
+
id: "item-c",
|
|
449
|
+
title: "No conv",
|
|
450
|
+
createdAt: "2026-04-14T12:00:02.000Z",
|
|
451
|
+
}),
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
await stripConversationIds("conv-123");
|
|
455
|
+
|
|
456
|
+
const decoded = readFileJson();
|
|
457
|
+
const itemA = decoded.items.find((i) => i.id === "item-a")!;
|
|
458
|
+
const itemB = decoded.items.find((i) => i.id === "item-b")!;
|
|
459
|
+
const itemC = decoded.items.find((i) => i.id === "item-c")!;
|
|
460
|
+
|
|
461
|
+
expect(itemA.conversationId).toBeUndefined();
|
|
462
|
+
expect(itemB.conversationId).toBe("conv-456");
|
|
463
|
+
expect(itemC.conversationId).toBeUndefined();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("returns the count of items modified", async () => {
|
|
467
|
+
await appendFeedItem(
|
|
468
|
+
makeItem({
|
|
469
|
+
id: "m1",
|
|
470
|
+
conversationId: "conv-abc",
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
await appendFeedItem(
|
|
474
|
+
makeItem({
|
|
475
|
+
id: "m2",
|
|
476
|
+
conversationId: "conv-abc",
|
|
477
|
+
createdAt: "2026-04-14T12:00:01.000Z",
|
|
478
|
+
}),
|
|
479
|
+
);
|
|
480
|
+
await appendFeedItem(
|
|
481
|
+
makeItem({
|
|
482
|
+
id: "m3",
|
|
483
|
+
conversationId: "conv-other",
|
|
484
|
+
createdAt: "2026-04-14T12:00:02.000Z",
|
|
485
|
+
}),
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const count = await stripConversationIds("conv-abc");
|
|
489
|
+
expect(count).toBe(2);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("returns 0 when no items match", async () => {
|
|
493
|
+
await appendFeedItem(
|
|
494
|
+
makeItem({
|
|
495
|
+
id: "no-match",
|
|
496
|
+
conversationId: "conv-xyz",
|
|
497
|
+
}),
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
const count = await stripConversationIds("conv-nonexistent");
|
|
501
|
+
expect(count).toBe(0);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("coalesces with pending appends correctly", async () => {
|
|
505
|
+
// Fire an append and a strip concurrently — both should land in
|
|
506
|
+
// the same coalesced write cycle.
|
|
507
|
+
const appendPromise = appendFeedItem(
|
|
508
|
+
makeItem({
|
|
509
|
+
id: "coalesce-item",
|
|
510
|
+
conversationId: "conv-coalesce",
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
513
|
+
const stripPromise = stripConversationIds("conv-coalesce");
|
|
514
|
+
|
|
515
|
+
await Promise.all([appendPromise, stripPromise]);
|
|
516
|
+
|
|
517
|
+
const decoded = readFileJson();
|
|
518
|
+
const item = decoded.items.find((i) => i.id === "coalesce-item")!;
|
|
519
|
+
// The append lands first, then the strip runs — conversationId
|
|
520
|
+
// should be gone.
|
|
521
|
+
expect(item).toBeDefined();
|
|
522
|
+
expect(item.conversationId).toBeUndefined();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
test("items retain all other fields after strip", async () => {
|
|
526
|
+
await appendFeedItem(
|
|
527
|
+
makeItem({
|
|
528
|
+
id: "retain-fields",
|
|
529
|
+
title: "Keep me",
|
|
530
|
+
summary: "Important summary",
|
|
531
|
+
conversationId: "conv-strip",
|
|
532
|
+
priority: 75,
|
|
533
|
+
status: "seen",
|
|
534
|
+
}),
|
|
535
|
+
);
|
|
536
|
+
|
|
537
|
+
await stripConversationIds("conv-strip");
|
|
538
|
+
|
|
539
|
+
const decoded = readFileJson();
|
|
540
|
+
const item = decoded.items.find((i) => i.id === "retain-fields")!;
|
|
541
|
+
expect(item.conversationId).toBeUndefined();
|
|
542
|
+
expect(item.id).toBe("retain-fields");
|
|
543
|
+
expect(item.title).toBe("Keep me");
|
|
544
|
+
expect(item.summary).toBe("Important summary");
|
|
545
|
+
expect(item.priority).toBe(75);
|
|
546
|
+
expect(item.status).toBe("seen");
|
|
547
|
+
expect(item.type).toBe("notification");
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
describe("clearAllConversationIds", () => {
|
|
552
|
+
test("strips conversationId from all items that have one", async () => {
|
|
553
|
+
await appendFeedItem(
|
|
554
|
+
makeItem({
|
|
555
|
+
id: "all-1",
|
|
556
|
+
conversationId: "conv-aaa",
|
|
557
|
+
}),
|
|
558
|
+
);
|
|
559
|
+
await appendFeedItem(
|
|
560
|
+
makeItem({
|
|
561
|
+
id: "all-2",
|
|
562
|
+
conversationId: "conv-bbb",
|
|
563
|
+
createdAt: "2026-04-14T12:00:01.000Z",
|
|
564
|
+
}),
|
|
565
|
+
);
|
|
566
|
+
await appendFeedItem(
|
|
567
|
+
makeItem({
|
|
568
|
+
id: "all-3",
|
|
569
|
+
title: "No conv link",
|
|
570
|
+
createdAt: "2026-04-14T12:00:02.000Z",
|
|
571
|
+
}),
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const count = await clearAllConversationIds();
|
|
575
|
+
expect(count).toBe(2);
|
|
576
|
+
|
|
577
|
+
const decoded = readFileJson();
|
|
578
|
+
for (const item of decoded.items) {
|
|
579
|
+
expect(item.conversationId).toBeUndefined();
|
|
580
|
+
}
|
|
581
|
+
// All three items still exist.
|
|
582
|
+
expect(decoded.items).toHaveLength(3);
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
425
586
|
describe("SSE publish", () => {
|
|
426
587
|
test("publishes home_feed_updated with correct newItemCount", async () => {
|
|
427
588
|
await appendFeedItem(
|
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
import { describe, expect, mock, test } from "bun:test";
|
|
1
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
// ─── Mocks ─────────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
mock.module("../../oauth/oauth-store.js", () => ({
|
|
8
|
-
listProviders: () => [
|
|
9
|
-
{ provider: "google" },
|
|
10
|
-
{ provider: "slack" },
|
|
11
|
-
{ provider: "notion" },
|
|
12
|
-
{ provider: "linear" },
|
|
13
|
-
{ provider: "github" },
|
|
14
|
-
],
|
|
15
|
-
}));
|
|
5
|
+
const mockIntegrationSummary = "Gmail ✓ | Slack ✓ | Twilio ✗ | Telegram ✗";
|
|
6
|
+
let mockSidechainText = "";
|
|
16
7
|
|
|
17
8
|
mock.module("../../schedule/integration-status.js", () => ({
|
|
18
|
-
|
|
19
|
-
mockConnectedProviders.has(provider),
|
|
9
|
+
formatIntegrationSummary: async () => mockIntegrationSummary,
|
|
20
10
|
}));
|
|
21
11
|
|
|
22
12
|
mock.module("../../util/logger.js", () => ({
|
|
@@ -31,7 +21,7 @@ mock.module("../../config/loader.js", () => ({
|
|
|
31
21
|
}));
|
|
32
22
|
|
|
33
23
|
mock.module("../../config/llm-resolver.js", () => ({
|
|
34
|
-
resolveCallSiteConfig: () => ({ provider: "mock" }),
|
|
24
|
+
resolveCallSiteConfig: () => ({ provider: "mock", maxTokens: 256 }),
|
|
35
25
|
}));
|
|
36
26
|
|
|
37
27
|
mock.module("../../providers/provider-send-message.js", () => ({
|
|
@@ -51,70 +41,76 @@ mock.module("../../prompts/system-prompt.js", () => ({
|
|
|
51
41
|
}));
|
|
52
42
|
|
|
53
43
|
mock.module("../../runtime/btw-sidechain.js", () => ({
|
|
54
|
-
runBtwSidechain: async () => ({ text:
|
|
44
|
+
runBtwSidechain: async () => ({ text: mockSidechainText }),
|
|
55
45
|
}));
|
|
56
46
|
|
|
57
|
-
|
|
47
|
+
mock.module("../../runtime/assistant-event.js", () => ({
|
|
48
|
+
buildAssistantEvent: (e: unknown) => e,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
mock.module("../../runtime/assistant-event-hub.js", () => ({
|
|
52
|
+
assistantEventHub: { publish: async () => {} },
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
const {
|
|
56
|
+
getSuggestedPrompts,
|
|
57
|
+
refreshAssistantSuggestedPrompts,
|
|
58
|
+
invalidateAssistantSuggestedPromptsCache,
|
|
59
|
+
} = await import("../suggested-prompts.js");
|
|
58
60
|
|
|
59
61
|
// ─── Tests ─────────────────────────────────────────────────────────────
|
|
60
62
|
|
|
61
63
|
describe("getSuggestedPrompts", () => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
invalidateAssistantSuggestedPromptsCache();
|
|
66
|
+
mockSidechainText = "";
|
|
67
|
+
});
|
|
64
68
|
|
|
69
|
+
test("returns empty array before cache is populated", async () => {
|
|
65
70
|
const prompts = await getSuggestedPrompts();
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(ids).toContain("connect-google");
|
|
69
|
-
expect(ids).toContain("connect-slack");
|
|
70
|
-
expect(prompts.find((p) => p.id === "connect-google")!.label).toBe(
|
|
71
|
-
"Connect Gmail",
|
|
72
|
-
);
|
|
71
|
+
expect(prompts).toEqual([]);
|
|
73
72
|
});
|
|
74
73
|
|
|
75
|
-
test("
|
|
76
|
-
|
|
74
|
+
test("returns LLM-generated prompts after refresh", async () => {
|
|
75
|
+
mockSidechainText = JSON.stringify([
|
|
76
|
+
{ label: "Triage my inbox", prompt: "Help me triage my inbox" },
|
|
77
|
+
{ label: "Check meetings", prompt: "What meetings do I have today?" },
|
|
78
|
+
]);
|
|
77
79
|
|
|
80
|
+
await refreshAssistantSuggestedPrompts();
|
|
78
81
|
const prompts = await getSuggestedPrompts();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
expect(
|
|
83
|
-
|
|
84
|
-
// Should show management prompts
|
|
85
|
-
expect(ids).toContain("manage-google-triage-my-inbox");
|
|
86
|
-
expect(ids).toContain("manage-google-summarize-today's-emails");
|
|
87
|
-
|
|
88
|
-
const triage = prompts.find(
|
|
89
|
-
(p) => p.id === "manage-google-triage-my-inbox",
|
|
90
|
-
);
|
|
91
|
-
expect(triage).toBeDefined();
|
|
92
|
-
expect(triage!.label).toBe("Triage my inbox");
|
|
93
|
-
expect(triage!.icon).toBe("mail");
|
|
94
|
-
expect(triage!.source).toBe("deterministic");
|
|
82
|
+
|
|
83
|
+
expect(prompts).toHaveLength(2);
|
|
84
|
+
expect(prompts[0]!.label).toBe("Triage my inbox");
|
|
85
|
+
expect(prompts[0]!.source).toBe("assistant");
|
|
86
|
+
expect(prompts[1]!.label).toBe("Check meetings");
|
|
95
87
|
});
|
|
96
88
|
|
|
97
|
-
test("
|
|
98
|
-
|
|
89
|
+
test("invalidation clears the cache", async () => {
|
|
90
|
+
mockSidechainText = JSON.stringify([
|
|
91
|
+
{ label: "Do stuff", prompt: "Do stuff for me" },
|
|
92
|
+
]);
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
|
|
94
|
+
await refreshAssistantSuggestedPrompts();
|
|
95
|
+
expect(await getSuggestedPrompts()).toHaveLength(1);
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
expect(
|
|
105
|
-
// Slack still disconnected
|
|
106
|
-
expect(ids).toContain("connect-slack");
|
|
97
|
+
invalidateAssistantSuggestedPromptsCache();
|
|
98
|
+
expect(await getSuggestedPrompts()).toEqual([]);
|
|
107
99
|
});
|
|
108
100
|
|
|
109
|
-
test("
|
|
110
|
-
|
|
101
|
+
test("handles empty LLM response gracefully", async () => {
|
|
102
|
+
mockSidechainText = "";
|
|
111
103
|
|
|
104
|
+
await refreshAssistantSuggestedPrompts();
|
|
112
105
|
const prompts = await getSuggestedPrompts();
|
|
113
|
-
|
|
106
|
+
expect(prompts).toEqual([]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("handles malformed LLM response gracefully", async () => {
|
|
110
|
+
mockSidechainText = "not valid json at all";
|
|
114
111
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
expect(ids.filter((id) => id.startsWith("manage-slack"))).toHaveLength(0);
|
|
112
|
+
await refreshAssistantSuggestedPrompts();
|
|
113
|
+
const prompts = await getSuggestedPrompts();
|
|
114
|
+
expect(prompts).toEqual([]);
|
|
119
115
|
});
|
|
120
116
|
});
|
package/src/home/feed-writer.ts
CHANGED
|
@@ -44,6 +44,7 @@ import { getDataDir } from "../util/platform.js";
|
|
|
44
44
|
import {
|
|
45
45
|
type FeedItem,
|
|
46
46
|
type FeedItemStatus,
|
|
47
|
+
type FeedItemUrgency,
|
|
47
48
|
type HomeFeedFile,
|
|
48
49
|
parseFeedFile,
|
|
49
50
|
} from "./feed-types.js";
|
|
@@ -155,6 +156,72 @@ export async function patchFeedItemStatus(
|
|
|
155
156
|
return resultPromise;
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Patch the user-editable copy fields on a single feed item by id.
|
|
161
|
+
*
|
|
162
|
+
* Returns the updated `FeedItem`, or `null` if no item with the given
|
|
163
|
+
* id exists. Used by the `assistant notifications edit` flow. Patches
|
|
164
|
+
* are applied inside the same coalescing queue as appends and status
|
|
165
|
+
* patches so overlapping writes don't race.
|
|
166
|
+
*
|
|
167
|
+
* Only fields explicitly present on `patch` are touched. Pass an empty
|
|
168
|
+
* object and the call is a no-op that returns the existing item (or
|
|
169
|
+
* `null` if the id isn't on disk).
|
|
170
|
+
*/
|
|
171
|
+
export interface FeedItemContentPatch {
|
|
172
|
+
title?: string;
|
|
173
|
+
summary?: string;
|
|
174
|
+
urgency?: FeedItemUrgency;
|
|
175
|
+
status?: FeedItemStatus;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function patchFeedItemContent(
|
|
179
|
+
id: string,
|
|
180
|
+
patch: FeedItemContentPatch,
|
|
181
|
+
): Promise<FeedItem | null> {
|
|
182
|
+
let resolveResult!: (value: FeedItem | null) => void;
|
|
183
|
+
const resultPromise = new Promise<FeedItem | null>((resolve) => {
|
|
184
|
+
resolveResult = resolve;
|
|
185
|
+
});
|
|
186
|
+
pendingContentPatches.push({ id, patch, resolve: resolveResult });
|
|
187
|
+
void scheduleWrite();
|
|
188
|
+
return resultPromise;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Remove the `conversationId` field from all feed items that reference
|
|
193
|
+
* the given conversation. Returns the number of items modified.
|
|
194
|
+
*
|
|
195
|
+
* This is the daemon-side cleanup path invoked when a conversation is
|
|
196
|
+
* deleted — it prevents "Go to Thread" buttons from linking to a
|
|
197
|
+
* conversation that no longer exists. Goes through the same coalescing
|
|
198
|
+
* queue as appends and patches so there are no file-I/O races.
|
|
199
|
+
*/
|
|
200
|
+
export async function stripConversationIds(
|
|
201
|
+
conversationId: string,
|
|
202
|
+
): Promise<number> {
|
|
203
|
+
let resolveResult!: (count: number) => void;
|
|
204
|
+
const resultPromise = new Promise<number>((resolve) => {
|
|
205
|
+
resolveResult = resolve;
|
|
206
|
+
});
|
|
207
|
+
pendingStrips.push({ conversationId, resolve: resolveResult });
|
|
208
|
+
void scheduleWrite();
|
|
209
|
+
return resultPromise;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Remove the `conversationId` field from ALL feed items that have one.
|
|
214
|
+
* Returns the number of items modified.
|
|
215
|
+
*
|
|
216
|
+
* Used by the clear-all-conversations flow to bulk-invalidate every
|
|
217
|
+
* "Go to Thread" link at once. Uses the same coalescing queue with a
|
|
218
|
+
* `"*"` sentinel that the strip loop in `runWrite` treats as "match
|
|
219
|
+
* all items with a conversationId".
|
|
220
|
+
*/
|
|
221
|
+
export async function clearAllConversationIds(): Promise<number> {
|
|
222
|
+
return stripConversationIds("*");
|
|
223
|
+
}
|
|
224
|
+
|
|
158
225
|
// ─── Internal: coalescing queue ────────────────────────────────────────
|
|
159
226
|
|
|
160
227
|
/**
|
|
@@ -168,6 +235,15 @@ const pendingPatches: Array<{
|
|
|
168
235
|
status: FeedItemStatus;
|
|
169
236
|
resolve: (value: FeedItem | null) => void;
|
|
170
237
|
}> = [];
|
|
238
|
+
const pendingContentPatches: Array<{
|
|
239
|
+
id: string;
|
|
240
|
+
patch: FeedItemContentPatch;
|
|
241
|
+
resolve: (value: FeedItem | null) => void;
|
|
242
|
+
}> = [];
|
|
243
|
+
const pendingStrips: Array<{
|
|
244
|
+
conversationId: string;
|
|
245
|
+
resolve: (count: number) => void;
|
|
246
|
+
}> = [];
|
|
171
247
|
|
|
172
248
|
let writeInFlight: Promise<void> | null = null;
|
|
173
249
|
let writeDirty = false;
|
|
@@ -206,6 +282,11 @@ function scheduleWrite(): Promise<void> {
|
|
|
206
282
|
async function runWrite(): Promise<void> {
|
|
207
283
|
const appendsToApply = pendingAppends.splice(0, pendingAppends.length);
|
|
208
284
|
const patchesToApply = pendingPatches.splice(0, pendingPatches.length);
|
|
285
|
+
const contentPatchesToApply = pendingContentPatches.splice(
|
|
286
|
+
0,
|
|
287
|
+
pendingContentPatches.length,
|
|
288
|
+
);
|
|
289
|
+
const stripsToApply = pendingStrips.splice(0, pendingStrips.length);
|
|
209
290
|
|
|
210
291
|
const current = readHomeFeed();
|
|
211
292
|
let items = current.items.slice();
|
|
@@ -233,6 +314,57 @@ async function runWrite(): Promise<void> {
|
|
|
233
314
|
patchResults.push({ resolve: patch.resolve, value: updated });
|
|
234
315
|
}
|
|
235
316
|
|
|
317
|
+
const contentPatchResults: Array<{
|
|
318
|
+
resolve: (v: FeedItem | null) => void;
|
|
319
|
+
value: FeedItem | null;
|
|
320
|
+
}> = [];
|
|
321
|
+
for (const { id, patch, resolve } of contentPatchesToApply) {
|
|
322
|
+
const idx = items.findIndex((i) => i.id === id);
|
|
323
|
+
if (idx === -1) {
|
|
324
|
+
contentPatchResults.push({ resolve, value: null });
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
const existing = items[idx]!;
|
|
328
|
+
const updated: FeedItem = { ...existing };
|
|
329
|
+
if (patch.title !== undefined) {
|
|
330
|
+
const trimmed = patch.title.trim();
|
|
331
|
+
if (trimmed.length === 0) {
|
|
332
|
+
delete updated.title;
|
|
333
|
+
} else {
|
|
334
|
+
updated.title = trimmed;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (patch.summary !== undefined) {
|
|
338
|
+
updated.summary = patch.summary;
|
|
339
|
+
}
|
|
340
|
+
if (patch.urgency !== undefined) {
|
|
341
|
+
updated.urgency = patch.urgency;
|
|
342
|
+
}
|
|
343
|
+
if (patch.status !== undefined) {
|
|
344
|
+
updated.status = patch.status;
|
|
345
|
+
}
|
|
346
|
+
items[idx] = updated;
|
|
347
|
+
contentPatchResults.push({ resolve, value: updated });
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Strip conversationId from matching items.
|
|
351
|
+
const stripResults: Array<{
|
|
352
|
+
resolve: (count: number) => void;
|
|
353
|
+
count: number;
|
|
354
|
+
}> = [];
|
|
355
|
+
for (const strip of stripsToApply) {
|
|
356
|
+
let count = 0;
|
|
357
|
+
for (let i = 0; i < items.length; i++) {
|
|
358
|
+
const matchAll = strip.conversationId === "*";
|
|
359
|
+
const matchOne = items[i]!.conversationId === strip.conversationId;
|
|
360
|
+
if (matchAll ? items[i]!.conversationId != null : matchOne) {
|
|
361
|
+
items[i] = { ...items[i]!, conversationId: undefined };
|
|
362
|
+
count++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
stripResults.push({ resolve: strip.resolve, count });
|
|
366
|
+
}
|
|
367
|
+
|
|
236
368
|
items.sort(compareFeedItems);
|
|
237
369
|
|
|
238
370
|
const updatedAt = new Date().toISOString();
|
|
@@ -258,17 +390,24 @@ async function runWrite(): Promise<void> {
|
|
|
258
390
|
publishHomeFeedUpdated(updatedAt, newItemCount);
|
|
259
391
|
}
|
|
260
392
|
|
|
261
|
-
// Resolve pending patch promises AFTER we've emitted the
|
|
262
|
-
// so callers awaiting `patchFeedItemStatus`
|
|
263
|
-
//
|
|
264
|
-
//
|
|
393
|
+
// Resolve pending patch and strip promises AFTER we've emitted the
|
|
394
|
+
// SSE event so callers awaiting `patchFeedItemStatus` or
|
|
395
|
+
// `stripConversationIds` observe a fully consistent world: the
|
|
396
|
+
// on-disk file, the SSE event, and the returned value all reflect
|
|
397
|
+
// the same write.
|
|
265
398
|
//
|
|
266
|
-
// If the write failed, resolve
|
|
267
|
-
// state was not persisted, and callers
|
|
268
|
-
//
|
|
399
|
+
// If the write failed, resolve patch promises with `null` and strip
|
|
400
|
+
// promises with `0` — the state was not persisted, and callers must
|
|
401
|
+
// not report success when the underlying write failed.
|
|
269
402
|
for (const { resolve, value } of patchResults) {
|
|
270
403
|
resolve(wrote ? value : null);
|
|
271
404
|
}
|
|
405
|
+
for (const { resolve, value } of contentPatchResults) {
|
|
406
|
+
resolve(wrote ? value : null);
|
|
407
|
+
}
|
|
408
|
+
for (const { resolve, count } of stripResults) {
|
|
409
|
+
resolve(wrote ? count : 0);
|
|
410
|
+
}
|
|
272
411
|
}
|
|
273
412
|
|
|
274
413
|
/**
|