@vellumai/assistant 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +5 -13
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +11 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +2 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +1 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +99 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -61
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +38 -14
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +54 -10
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the home-feed rollup producer.
|
|
3
|
+
*
|
|
4
|
+
* All dependencies are injected via `RollupProducerDeps` spies so the
|
|
5
|
+
* tests never touch `mock.module`, which leaks across files in Bun's
|
|
6
|
+
* test runner. The production caller passes `undefined` and the
|
|
7
|
+
* producer falls through to the real config loader, feed reader,
|
|
8
|
+
* relationship-state reader, and provider registry.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ContentBlock,
|
|
15
|
+
Provider,
|
|
16
|
+
ProviderResponse,
|
|
17
|
+
} from "../../providers/types.js";
|
|
18
|
+
import type { WriteAssistantFeedItemParams } from "../assistant-feed-authoring.js";
|
|
19
|
+
import type { FeedItem } from "../feed-types.js";
|
|
20
|
+
import { runRollupProducer } from "../rollup-producer.js";
|
|
21
|
+
|
|
22
|
+
const writeItem = mock<
|
|
23
|
+
(params: WriteAssistantFeedItemParams) => Promise<unknown>
|
|
24
|
+
>(async () => ({}));
|
|
25
|
+
|
|
26
|
+
const stubRelationshipState = async () =>
|
|
27
|
+
({
|
|
28
|
+
version: 1,
|
|
29
|
+
assistantId: "self",
|
|
30
|
+
tier: 2,
|
|
31
|
+
progressPercent: 42,
|
|
32
|
+
facts: [
|
|
33
|
+
{ category: "voice", text: "Ships fast, explains the why." },
|
|
34
|
+
{ category: "priorities", text: "JARVIS is the current focus." },
|
|
35
|
+
],
|
|
36
|
+
capabilities: [],
|
|
37
|
+
conversationCount: 17,
|
|
38
|
+
hatchedDate: "2026-03-01T00:00:00.000Z",
|
|
39
|
+
assistantName: "Vellum",
|
|
40
|
+
userName: "Alex",
|
|
41
|
+
updatedAt: "2026-04-14T12:00:00.000Z",
|
|
42
|
+
// biome-ignore lint/suspicious/noExplicitAny: the real shape is
|
|
43
|
+
// internal-only and we only need the subset the producer reads.
|
|
44
|
+
}) as any;
|
|
45
|
+
|
|
46
|
+
function makeAction(overrides: Partial<FeedItem> & { id: string }): FeedItem {
|
|
47
|
+
return {
|
|
48
|
+
id: overrides.id,
|
|
49
|
+
type: "action",
|
|
50
|
+
priority: 50,
|
|
51
|
+
title: overrides.title ?? "Action title",
|
|
52
|
+
summary: overrides.summary ?? "Action summary.",
|
|
53
|
+
timestamp: overrides.timestamp ?? "2026-04-14T12:00:00.000Z",
|
|
54
|
+
status: overrides.status ?? "new",
|
|
55
|
+
author: overrides.author ?? "assistant",
|
|
56
|
+
createdAt: overrides.createdAt ?? "2026-04-14T12:00:00.000Z",
|
|
57
|
+
source: overrides.source,
|
|
58
|
+
expiresAt: overrides.expiresAt,
|
|
59
|
+
minTimeAway: overrides.minTimeAway,
|
|
60
|
+
actions: overrides.actions,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const stubLoadRecentActions = (items: FeedItem[]) => () => items;
|
|
65
|
+
|
|
66
|
+
function makeProvider(
|
|
67
|
+
handler: (
|
|
68
|
+
messages: Parameters<Provider["sendMessage"]>[0],
|
|
69
|
+
tools: Parameters<Provider["sendMessage"]>[1],
|
|
70
|
+
systemPrompt: Parameters<Provider["sendMessage"]>[2],
|
|
71
|
+
options: Parameters<Provider["sendMessage"]>[3],
|
|
72
|
+
) => Promise<ProviderResponse>,
|
|
73
|
+
): Provider {
|
|
74
|
+
return {
|
|
75
|
+
name: "mock",
|
|
76
|
+
sendMessage: handler,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function scriptedProvider(content: ContentBlock[]): Provider {
|
|
81
|
+
return makeProvider(async () => ({
|
|
82
|
+
content,
|
|
83
|
+
model: "mock-model",
|
|
84
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
85
|
+
stopReason: "tool_use",
|
|
86
|
+
}));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function throwingProvider(error: Error): Provider {
|
|
90
|
+
return makeProvider(async () => {
|
|
91
|
+
throw error;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function toolUseContent(input: unknown): ContentBlock {
|
|
96
|
+
return {
|
|
97
|
+
type: "tool_use",
|
|
98
|
+
id: "tu_1",
|
|
99
|
+
name: "write_feed_items",
|
|
100
|
+
input: input as Record<string, unknown>,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const oneAction: FeedItem[] = [
|
|
105
|
+
makeAction({
|
|
106
|
+
id: "a1",
|
|
107
|
+
source: "gmail",
|
|
108
|
+
title: "Replied to Alice",
|
|
109
|
+
summary: "Sent a reply to alice@example.com.",
|
|
110
|
+
createdAt: "2026-04-14T11:30:00.000Z",
|
|
111
|
+
}),
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
beforeEach(() => {
|
|
115
|
+
writeItem.mockClear();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("runRollupProducer", () => {
|
|
119
|
+
test("writes each digest/thread returned in the tool call", async () => {
|
|
120
|
+
const provider = scriptedProvider([
|
|
121
|
+
toolUseContent({
|
|
122
|
+
items: [
|
|
123
|
+
{
|
|
124
|
+
type: "digest",
|
|
125
|
+
source: "gmail",
|
|
126
|
+
title: "3 replies sent this morning",
|
|
127
|
+
summary: "Replied to Alice, Bob, and Carol over the past hour.",
|
|
128
|
+
priority: 70,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: "thread",
|
|
132
|
+
source: "assistant",
|
|
133
|
+
title: "Outreach sequence 'Q2 renewals'",
|
|
134
|
+
summary: "Step 1 sent to 2 of 5 contacts; awaiting replies.",
|
|
135
|
+
priority: 55,
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
}),
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const result = await runRollupProducer(new Date(), {
|
|
142
|
+
writeItem,
|
|
143
|
+
loadRelationshipState: stubRelationshipState,
|
|
144
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
145
|
+
resolveProvider: () => provider,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
expect(result.skippedReason).toBeNull();
|
|
149
|
+
expect(result.wroteCount).toBe(2);
|
|
150
|
+
expect(writeItem).toHaveBeenCalledTimes(2);
|
|
151
|
+
const firstCall = writeItem.mock.calls[0]![0];
|
|
152
|
+
expect(firstCall.type).toBe("digest");
|
|
153
|
+
expect(firstCall.title).toBe("3 replies sent this morning");
|
|
154
|
+
expect(firstCall.priority).toBe(70);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("returns no_actions when the activity log is empty", async () => {
|
|
158
|
+
// When there's nothing to roll up, we don't even hit the provider.
|
|
159
|
+
// The scheduler uses this to avoid advancing the cooldown gate.
|
|
160
|
+
const provider = scriptedProvider([toolUseContent({ items: [] })]);
|
|
161
|
+
const providerSpy = mock(provider.sendMessage);
|
|
162
|
+
provider.sendMessage = providerSpy;
|
|
163
|
+
|
|
164
|
+
const result = await runRollupProducer(new Date(), {
|
|
165
|
+
writeItem,
|
|
166
|
+
loadRelationshipState: stubRelationshipState,
|
|
167
|
+
loadRecentActions: stubLoadRecentActions([]),
|
|
168
|
+
resolveProvider: () => provider,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
expect(result.skippedReason).toBe("no_actions");
|
|
172
|
+
expect(result.wroteCount).toBe(0);
|
|
173
|
+
expect(providerSpy).not.toHaveBeenCalled();
|
|
174
|
+
expect(writeItem).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("serializes recent actions into the user prompt", async () => {
|
|
178
|
+
let capturedPrompt = "";
|
|
179
|
+
const provider = makeProvider(async (messages) => {
|
|
180
|
+
capturedPrompt = messages
|
|
181
|
+
.flatMap((m) => m.content)
|
|
182
|
+
.filter((b): b is { type: "text"; text: string } => b.type === "text")
|
|
183
|
+
.map((b) => b.text)
|
|
184
|
+
.join("\n");
|
|
185
|
+
return {
|
|
186
|
+
content: [toolUseContent({ items: [] })],
|
|
187
|
+
model: "mock-model",
|
|
188
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
189
|
+
stopReason: "tool_use",
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const actions: FeedItem[] = [
|
|
194
|
+
makeAction({
|
|
195
|
+
id: "a1",
|
|
196
|
+
source: "gmail",
|
|
197
|
+
title: "Replied to Alice",
|
|
198
|
+
summary: "Sent a reply to alice@example.com.",
|
|
199
|
+
createdAt: "2026-04-14T11:30:00.000Z",
|
|
200
|
+
}),
|
|
201
|
+
makeAction({
|
|
202
|
+
id: "a2",
|
|
203
|
+
source: "slack",
|
|
204
|
+
title: "Posted in #general",
|
|
205
|
+
summary: "Answered a question about the deploy.",
|
|
206
|
+
createdAt: "2026-04-14T11:45:00.000Z",
|
|
207
|
+
}),
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
await runRollupProducer(new Date(), {
|
|
211
|
+
writeItem,
|
|
212
|
+
loadRelationshipState: stubRelationshipState,
|
|
213
|
+
loadRecentActions: stubLoadRecentActions(actions),
|
|
214
|
+
resolveProvider: () => provider,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(capturedPrompt).toContain("Replied to Alice");
|
|
218
|
+
expect(capturedPrompt).toContain("alice@example.com");
|
|
219
|
+
expect(capturedPrompt).toContain("Posted in #general");
|
|
220
|
+
expect(capturedPrompt).toContain("[gmail]");
|
|
221
|
+
expect(capturedPrompt).toContain("[slack]");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("returns empty_items when the model emits an empty items array", async () => {
|
|
225
|
+
const provider = scriptedProvider([toolUseContent({ items: [] })]);
|
|
226
|
+
|
|
227
|
+
const result = await runRollupProducer(new Date(), {
|
|
228
|
+
writeItem,
|
|
229
|
+
loadRelationshipState: stubRelationshipState,
|
|
230
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
231
|
+
resolveProvider: () => provider,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(result.skippedReason).toBe("empty_items");
|
|
235
|
+
expect(result.wroteCount).toBe(0);
|
|
236
|
+
expect(writeItem).not.toHaveBeenCalled();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("caps the batch at MAX_ITEMS_PER_ROLLUP (3)", async () => {
|
|
240
|
+
const provider = scriptedProvider([
|
|
241
|
+
toolUseContent({
|
|
242
|
+
items: [
|
|
243
|
+
{ type: "digest", title: "One", summary: "One summary" },
|
|
244
|
+
{ type: "digest", title: "Two", summary: "Two summary" },
|
|
245
|
+
{ type: "thread", title: "Three", summary: "Three summary" },
|
|
246
|
+
{
|
|
247
|
+
type: "digest",
|
|
248
|
+
title: "Four",
|
|
249
|
+
summary: "Four summary — should be dropped.",
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
}),
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
const result = await runRollupProducer(new Date(), {
|
|
256
|
+
writeItem,
|
|
257
|
+
loadRelationshipState: stubRelationshipState,
|
|
258
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
259
|
+
resolveProvider: () => provider,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(result.wroteCount).toBe(3);
|
|
263
|
+
expect(writeItem).toHaveBeenCalledTimes(3);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("rejects nudge and action types at coercion time", async () => {
|
|
267
|
+
// The tool schema narrows `type` to digest/thread, but the runtime
|
|
268
|
+
// coercion enforces it too so a drifted model can't sneak through.
|
|
269
|
+
const provider = scriptedProvider([
|
|
270
|
+
toolUseContent({
|
|
271
|
+
items: [
|
|
272
|
+
{
|
|
273
|
+
type: "nudge",
|
|
274
|
+
title: "Should be rejected",
|
|
275
|
+
summary: "Rollup must never emit nudges.",
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
type: "action",
|
|
279
|
+
title: "Also rejected",
|
|
280
|
+
summary: "Rollup must never emit actions.",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
type: "digest",
|
|
284
|
+
title: "Valid digest",
|
|
285
|
+
summary: "This one should land.",
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
}),
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
const result = await runRollupProducer(new Date(), {
|
|
292
|
+
writeItem,
|
|
293
|
+
loadRelationshipState: stubRelationshipState,
|
|
294
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
295
|
+
resolveProvider: () => provider,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
expect(result.wroteCount).toBe(1);
|
|
299
|
+
expect(writeItem).toHaveBeenCalledTimes(1);
|
|
300
|
+
expect(writeItem.mock.calls[0]![0].title).toBe("Valid digest");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("reports malformed_output when every item in a non-empty batch fails coercion", async () => {
|
|
304
|
+
const provider = scriptedProvider([
|
|
305
|
+
toolUseContent({
|
|
306
|
+
items: [
|
|
307
|
+
{ type: "digest", title: "", summary: "empty title, rejected" },
|
|
308
|
+
{ type: "bogus", title: "bad type", summary: "also rejected" },
|
|
309
|
+
{ type: "digest", title: "valid title" }, // missing summary
|
|
310
|
+
],
|
|
311
|
+
}),
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
const result = await runRollupProducer(new Date(), {
|
|
315
|
+
writeItem,
|
|
316
|
+
loadRelationshipState: stubRelationshipState,
|
|
317
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
318
|
+
resolveProvider: () => provider,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(result.skippedReason).toBe("malformed_output");
|
|
322
|
+
expect(result.wroteCount).toBe(0);
|
|
323
|
+
expect(writeItem).not.toHaveBeenCalled();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("returns no_provider when the resolver returns null", async () => {
|
|
327
|
+
const result = await runRollupProducer(new Date(), {
|
|
328
|
+
writeItem,
|
|
329
|
+
loadRelationshipState: stubRelationshipState,
|
|
330
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
331
|
+
resolveProvider: () => null,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
expect(result.skippedReason).toBe("no_provider");
|
|
335
|
+
expect(result.wroteCount).toBe(0);
|
|
336
|
+
expect(writeItem).not.toHaveBeenCalled();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("returns provider_error when sendMessage throws", async () => {
|
|
340
|
+
const provider = throwingProvider(new Error("network down"));
|
|
341
|
+
|
|
342
|
+
const result = await runRollupProducer(new Date(), {
|
|
343
|
+
writeItem,
|
|
344
|
+
loadRelationshipState: stubRelationshipState,
|
|
345
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
346
|
+
resolveProvider: () => provider,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
expect(result.skippedReason).toBe("provider_error");
|
|
350
|
+
expect(result.wroteCount).toBe(0);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("returns malformed_output when the response has no matching tool_use block", async () => {
|
|
354
|
+
const provider = scriptedProvider([{ type: "text", text: "just prose" }]);
|
|
355
|
+
|
|
356
|
+
const result = await runRollupProducer(new Date(), {
|
|
357
|
+
writeItem,
|
|
358
|
+
loadRelationshipState: stubRelationshipState,
|
|
359
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
360
|
+
resolveProvider: () => provider,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
expect(result.skippedReason).toBe("malformed_output");
|
|
364
|
+
expect(result.wroteCount).toBe(0);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("clamps priority to the valid [0, 100] window", async () => {
|
|
368
|
+
const provider = scriptedProvider([
|
|
369
|
+
toolUseContent({
|
|
370
|
+
items: [
|
|
371
|
+
{
|
|
372
|
+
type: "digest",
|
|
373
|
+
title: "High priority",
|
|
374
|
+
summary: "Model returned 150",
|
|
375
|
+
priority: 150,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
type: "thread",
|
|
379
|
+
title: "Low priority",
|
|
380
|
+
summary: "Model returned -5",
|
|
381
|
+
priority: -5,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
}),
|
|
385
|
+
]);
|
|
386
|
+
|
|
387
|
+
await runRollupProducer(new Date(), {
|
|
388
|
+
writeItem,
|
|
389
|
+
loadRelationshipState: stubRelationshipState,
|
|
390
|
+
loadRecentActions: stubLoadRecentActions(oneAction),
|
|
391
|
+
resolveProvider: () => provider,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(writeItem).toHaveBeenCalledTimes(2);
|
|
395
|
+
expect(writeItem.mock.calls[0]![0].priority).toBe(100);
|
|
396
|
+
expect(writeItem.mock.calls[1]![0].priority).toBe(0);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assistant-authoring helper for the home activity feed.
|
|
3
|
+
*
|
|
4
|
+
* This is the in-process API the assistant daemon (or a skill / tool
|
|
5
|
+
* running inside it) calls to append a nudge, digest, action, or
|
|
6
|
+
* thread to the macOS Home page feed under `author: "assistant"`.
|
|
7
|
+
*
|
|
8
|
+
* The helper exists so callers don't have to hand-roll the ceremony
|
|
9
|
+
* around a `FeedItem`:
|
|
10
|
+
*
|
|
11
|
+
* - generate a stable `id`
|
|
12
|
+
* - set the `author` discriminator to `"assistant"` (so the hybrid
|
|
13
|
+
* authoring resolver in `feed-writer.ts` lets it override
|
|
14
|
+
* platform baseline items for the same `(type, source)` pair)
|
|
15
|
+
* - seed `status` / `timestamp` / `createdAt`
|
|
16
|
+
* - pick a sensible default `priority` that outranks the platform
|
|
17
|
+
* baseline (40) so assistant-authored items surface above
|
|
18
|
+
* background generators unless an explicit priority is passed
|
|
19
|
+
* - validate the constructed item against `feedItemSchema` so a
|
|
20
|
+
* malformed call throws loudly at the source instead of corrupting
|
|
21
|
+
* the on-disk snapshot via `appendFeedItem`
|
|
22
|
+
*
|
|
23
|
+
* Persistence is delegated to `appendFeedItem` — all of the merge
|
|
24
|
+
* semantics (digest replacement, thread in-place update, nudge author
|
|
25
|
+
* precedence, action append-without-replace, per-source action cap)
|
|
26
|
+
* continue to live in the writer and are not re-implemented here.
|
|
27
|
+
*
|
|
28
|
+
* NOTE: This helper is intentionally in-process only. There is no
|
|
29
|
+
* HTTP route wrapping it. Callers (skills, tools, daemon code) import
|
|
30
|
+
* and call `writeAssistantFeedItem` directly. Wiring the trigger side
|
|
31
|
+
* — prompt-driven calls, skill entry points, etc. — is deliberately
|
|
32
|
+
* out of scope for Phase 5 of the home-activity-feed plan and lives
|
|
33
|
+
* in follow-up work.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { randomUUID } from "node:crypto";
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
type FeedAction,
|
|
40
|
+
type FeedItem,
|
|
41
|
+
feedItemSchema,
|
|
42
|
+
type FeedItemSource,
|
|
43
|
+
type FeedItemType,
|
|
44
|
+
} from "./feed-types.js";
|
|
45
|
+
import { appendFeedItem } from "./feed-writer.js";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Default priority for assistant-authored feed items. Sits above the
|
|
49
|
+
* platform baseline (40) so an assistant-authored digest / nudge
|
|
50
|
+
* surfaces above a same-source platform default unless the caller
|
|
51
|
+
* passes an explicit value.
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_ASSISTANT_PRIORITY = 60;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parameters accepted by {@link writeAssistantFeedItem}.
|
|
57
|
+
*
|
|
58
|
+
* The helper hard-codes `author: "assistant"`, `status: "new"`, and
|
|
59
|
+
* the id / timestamps, so callers only have to supply the semantic
|
|
60
|
+
* fields that describe *what* the item is about.
|
|
61
|
+
*/
|
|
62
|
+
export interface WriteAssistantFeedItemParams {
|
|
63
|
+
/** Kind of feed item — drives the Swift view used to render it. */
|
|
64
|
+
type: FeedItemType;
|
|
65
|
+
/** Origin of the underlying event (gmail, slack, calendar, assistant). */
|
|
66
|
+
source?: FeedItemSource;
|
|
67
|
+
/** Short headline rendered in the feed row. */
|
|
68
|
+
title: string;
|
|
69
|
+
/** Body copy rendered below the title. */
|
|
70
|
+
summary: string;
|
|
71
|
+
/**
|
|
72
|
+
* Priority in [0, 100]. Defaults to {@link DEFAULT_ASSISTANT_PRIORITY}
|
|
73
|
+
* (60) — higher than the platform baseline of 40 so assistant items
|
|
74
|
+
* beat same-source platform defaults by default.
|
|
75
|
+
*/
|
|
76
|
+
priority?: number;
|
|
77
|
+
/** Action buttons surfaced on the feed row. */
|
|
78
|
+
actions?: FeedAction[];
|
|
79
|
+
/** Minimum seconds the user must be away before the item is shown. */
|
|
80
|
+
minTimeAway?: number;
|
|
81
|
+
/** Absolute ISO-8601 expiry timestamp. */
|
|
82
|
+
expiresAt?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build a fully-formed assistant-authored {@link FeedItem}, validate
|
|
87
|
+
* it against the canonical schema, persist it through
|
|
88
|
+
* {@link appendFeedItem}, and return the constructed item so the
|
|
89
|
+
* caller can log / reference it downstream.
|
|
90
|
+
*
|
|
91
|
+
* Throws a `ZodError` if the constructed item fails validation —
|
|
92
|
+
* that is a programming error in the caller (e.g. empty `title`) and
|
|
93
|
+
* must not be silently swallowed. Persistence-layer failures are
|
|
94
|
+
* absorbed by `appendFeedItem` itself per its warn-log contract.
|
|
95
|
+
*/
|
|
96
|
+
export async function writeAssistantFeedItem(
|
|
97
|
+
params: WriteAssistantFeedItemParams,
|
|
98
|
+
): Promise<FeedItem> {
|
|
99
|
+
const now = new Date().toISOString();
|
|
100
|
+
|
|
101
|
+
const item: FeedItem = {
|
|
102
|
+
id: randomUUID(),
|
|
103
|
+
type: params.type,
|
|
104
|
+
source: params.source,
|
|
105
|
+
title: params.title,
|
|
106
|
+
summary: params.summary,
|
|
107
|
+
priority: params.priority ?? DEFAULT_ASSISTANT_PRIORITY,
|
|
108
|
+
status: "new",
|
|
109
|
+
author: "assistant",
|
|
110
|
+
timestamp: now,
|
|
111
|
+
createdAt: now,
|
|
112
|
+
actions: params.actions,
|
|
113
|
+
minTimeAway: params.minTimeAway,
|
|
114
|
+
expiresAt: params.expiresAt,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Programming-error guardrail: invalid input throws at the source
|
|
118
|
+
// instead of corrupting the on-disk snapshot via the writer.
|
|
119
|
+
feedItemSchema.parse(item);
|
|
120
|
+
|
|
121
|
+
await appendFeedItem(item);
|
|
122
|
+
|
|
123
|
+
return item;
|
|
124
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background-job → home feed event helper.
|
|
3
|
+
*
|
|
4
|
+
* The "force-write, not taught-write" entry point for the activity
|
|
5
|
+
* log. Every background job that wants to surface something on the
|
|
6
|
+
* Home page calls `emitFeedEvent({ source, title, summary, ... })`
|
|
7
|
+
* at the end of its completion path — no LLM involved, no prompt
|
|
8
|
+
* instruction, just a deterministic side effect. This keeps the
|
|
9
|
+
* "what got surfaced" question grep-able to a single symbol.
|
|
10
|
+
*
|
|
11
|
+
* Opinionated defaults for action items:
|
|
12
|
+
*
|
|
13
|
+
* - `type` is hard-coded to `"action"` — this helper is specifically
|
|
14
|
+
* for the activity log. Nudges / digests / threads continue to
|
|
15
|
+
* go through `writeAssistantFeedItem` or platform baseline
|
|
16
|
+
* generators.
|
|
17
|
+
* - `author` is hard-coded to `"assistant"` so hybrid authoring
|
|
18
|
+
* resolution treats these as assistant-produced (platform
|
|
19
|
+
* defaults can never overwrite them).
|
|
20
|
+
* - `source` is REQUIRED — actions always have an origin (gmail,
|
|
21
|
+
* slack, calendar, assistant). The per-source volume cap in
|
|
22
|
+
* `feed-writer.ts` depends on this.
|
|
23
|
+
* - No default `expiresAt`. Action items persist until the user
|
|
24
|
+
* dismisses them. Callers that want auto-expiry pass `expiresAt`
|
|
25
|
+
* explicitly.
|
|
26
|
+
* - Optional `dedupKey` — when set, the helper derives a
|
|
27
|
+
* deterministic id so a second emit for the same logical event
|
|
28
|
+
* (e.g. the same background job running twice on the same
|
|
29
|
+
* signal) updates the existing entry in place instead of
|
|
30
|
+
* appending a duplicate. When absent, a fresh `randomUUID` is
|
|
31
|
+
* used and every call produces a new entry.
|
|
32
|
+
*
|
|
33
|
+
* Persistence goes through `appendFeedItem`, inheriting its
|
|
34
|
+
* warn-log-on-failure contract — callers never need a try/catch.
|
|
35
|
+
* Schema validation runs at build time so a malformed call throws
|
|
36
|
+
* loudly at the source rather than silently corrupting the file.
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { randomUUID } from "node:crypto";
|
|
40
|
+
|
|
41
|
+
import {
|
|
42
|
+
type FeedAction,
|
|
43
|
+
type FeedItem,
|
|
44
|
+
feedItemSchema,
|
|
45
|
+
type FeedItemSource,
|
|
46
|
+
} from "./feed-types.js";
|
|
47
|
+
import { appendFeedItem } from "./feed-writer.js";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Default priority for background-job action items. Sits below the
|
|
51
|
+
* assistant nudge default (60) so an explicit nudge from
|
|
52
|
+
* `writeAssistantFeedItem` surfaces above routine activity log
|
|
53
|
+
* entries, but above the platform baseline (40) so background job
|
|
54
|
+
* traces outrank same-source platform defaults.
|
|
55
|
+
*/
|
|
56
|
+
const DEFAULT_EMIT_PRIORITY = 50;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parameters accepted by {@link emitFeedEvent}.
|
|
60
|
+
*
|
|
61
|
+
* All action items emitted by background jobs have an origin, so
|
|
62
|
+
* `source` is required. Everything else is optional — callers supply
|
|
63
|
+
* only the fields that describe the specific event.
|
|
64
|
+
*/
|
|
65
|
+
export interface EmitFeedEventParams {
|
|
66
|
+
/** Origin of the underlying event (gmail, slack, calendar, assistant). */
|
|
67
|
+
source: FeedItemSource;
|
|
68
|
+
/** Short headline rendered in the feed row. */
|
|
69
|
+
title: string;
|
|
70
|
+
/** Body copy rendered below the title. */
|
|
71
|
+
summary: string;
|
|
72
|
+
/**
|
|
73
|
+
* Stable key used to derive a deterministic id so a second emit
|
|
74
|
+
* for the same logical event updates the existing feed entry in
|
|
75
|
+
* place. Should include enough structure to identify the event
|
|
76
|
+
* uniquely (e.g. `"gmail-unread:msg-<messageId>"`,
|
|
77
|
+
* `"task-runner:<taskId>"`). When omitted, every call produces a
|
|
78
|
+
* fresh id and appends a new entry.
|
|
79
|
+
*/
|
|
80
|
+
dedupKey?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Priority in [0, 100]. Defaults to {@link DEFAULT_EMIT_PRIORITY}
|
|
83
|
+
* (50) — above the platform baseline of 40, below the assistant
|
|
84
|
+
* nudge default of 60.
|
|
85
|
+
*/
|
|
86
|
+
priority?: number;
|
|
87
|
+
/** Action buttons surfaced on the feed row. */
|
|
88
|
+
actions?: FeedAction[];
|
|
89
|
+
/** Minimum seconds the user must be away before the item is shown. */
|
|
90
|
+
minTimeAway?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Absolute ISO-8601 expiry timestamp. Omit to let the item persist
|
|
93
|
+
* until the user dismisses it (default for activity-log actions).
|
|
94
|
+
*/
|
|
95
|
+
expiresAt?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a deterministic feed item id from a source + dedup key.
|
|
100
|
+
*
|
|
101
|
+
* The id is intentionally human-readable: `emit:<source>:<dedupKey>`.
|
|
102
|
+
* This makes debugging easier than a hash (you can eyeball the file
|
|
103
|
+
* and immediately see which background job produced which entry)
|
|
104
|
+
* and `FeedItem.id` is a free-form string so there is no length or
|
|
105
|
+
* charset constraint to worry about.
|
|
106
|
+
*/
|
|
107
|
+
function deterministicId(source: FeedItemSource, dedupKey: string): string {
|
|
108
|
+
return `emit:${source}:${dedupKey}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Emit a background-job activity-log entry onto the home feed.
|
|
113
|
+
*
|
|
114
|
+
* Builds a fully-formed assistant-authored `action` {@link FeedItem},
|
|
115
|
+
* validates it against the canonical schema, and persists it via
|
|
116
|
+
* {@link appendFeedItem}. Returns the constructed item so the caller
|
|
117
|
+
* can log / reference it downstream.
|
|
118
|
+
*
|
|
119
|
+
* Throws a `ZodError` if the constructed item fails validation
|
|
120
|
+
* (e.g. a `priority` outside `[0, 100]`) — a programming error in
|
|
121
|
+
* the caller that must not be silently swallowed. Persistence-layer
|
|
122
|
+
* failures are absorbed by `appendFeedItem` per its warn-log
|
|
123
|
+
* contract.
|
|
124
|
+
*/
|
|
125
|
+
export async function emitFeedEvent(
|
|
126
|
+
params: EmitFeedEventParams,
|
|
127
|
+
): Promise<FeedItem> {
|
|
128
|
+
const now = new Date().toISOString();
|
|
129
|
+
|
|
130
|
+
const id =
|
|
131
|
+
params.dedupKey !== undefined
|
|
132
|
+
? deterministicId(params.source, params.dedupKey)
|
|
133
|
+
: randomUUID();
|
|
134
|
+
|
|
135
|
+
const item: FeedItem = {
|
|
136
|
+
id,
|
|
137
|
+
type: "action",
|
|
138
|
+
source: params.source,
|
|
139
|
+
title: params.title,
|
|
140
|
+
summary: params.summary,
|
|
141
|
+
priority: params.priority ?? DEFAULT_EMIT_PRIORITY,
|
|
142
|
+
status: "new",
|
|
143
|
+
author: "assistant",
|
|
144
|
+
timestamp: now,
|
|
145
|
+
createdAt: now,
|
|
146
|
+
actions: params.actions,
|
|
147
|
+
minTimeAway: params.minTimeAway,
|
|
148
|
+
expiresAt: params.expiresAt,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Programming-error guardrail: invalid input throws at the source
|
|
152
|
+
// instead of corrupting the on-disk snapshot via the writer.
|
|
153
|
+
feedItemSchema.parse(item);
|
|
154
|
+
|
|
155
|
+
await appendFeedItem(item);
|
|
156
|
+
|
|
157
|
+
return item;
|
|
158
|
+
}
|