@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,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the `conversation_analyze` job handler.
|
|
3
|
+
*
|
|
4
|
+
* The handler bridges the jobs worker to `analyzeConversation()` via the
|
|
5
|
+
* singleton deps bundle. Tests stub both the singleton and the service so we
|
|
6
|
+
* exercise dispatch logic without pulling in HTTP-layer wiring.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
mock.module("../../util/logger.js", () => ({
|
|
12
|
+
getLogger: () =>
|
|
13
|
+
new Proxy({} as Record<string, unknown>, {
|
|
14
|
+
get: () => () => {},
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
// Mock analyze-deps singleton — each test overrides via mockGetAnalysisDeps.
|
|
19
|
+
type DepsStub = Record<string, unknown>;
|
|
20
|
+
const mockGetAnalysisDeps = mock((): DepsStub | null => null);
|
|
21
|
+
|
|
22
|
+
mock.module("../../runtime/services/analyze-deps-singleton.js", () => ({
|
|
23
|
+
getAnalysisDeps: mockGetAnalysisDeps,
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Mock analyze-conversation service — default resolves with a success result.
|
|
27
|
+
type AnalyzeArgs = {
|
|
28
|
+
conversationId: string;
|
|
29
|
+
deps: DepsStub;
|
|
30
|
+
opts: { trigger: "manual" | "auto" };
|
|
31
|
+
};
|
|
32
|
+
const analyzeCalls: AnalyzeArgs[] = [];
|
|
33
|
+
type AnalyzeResultStub =
|
|
34
|
+
| { analysisConversationId: string; skipped?: true }
|
|
35
|
+
| { error: { kind: string; status: number; message: string } };
|
|
36
|
+
const mockAnalyzeConversation = mock(
|
|
37
|
+
async (
|
|
38
|
+
conversationId: string,
|
|
39
|
+
deps: DepsStub,
|
|
40
|
+
opts: { trigger: "manual" | "auto" },
|
|
41
|
+
): Promise<AnalyzeResultStub> => {
|
|
42
|
+
analyzeCalls.push({ conversationId, deps, opts });
|
|
43
|
+
return { analysisConversationId: "analysis-1" };
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
mock.module("../../runtime/services/analyze-conversation.js", () => ({
|
|
48
|
+
analyzeConversation: mockAnalyzeConversation,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// Mock auto-analysis-enqueue — track calls so we can verify requeue behavior.
|
|
52
|
+
type EnqueueArgs = {
|
|
53
|
+
conversationId: string;
|
|
54
|
+
trigger: string;
|
|
55
|
+
};
|
|
56
|
+
const enqueueCalls: EnqueueArgs[] = [];
|
|
57
|
+
const mockEnqueueAutoAnalysisIfEnabled = mock((args: EnqueueArgs) => {
|
|
58
|
+
enqueueCalls.push(args);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
mock.module("../auto-analysis-enqueue.js", () => ({
|
|
62
|
+
enqueueAutoAnalysisIfEnabled: mockEnqueueAutoAnalysisIfEnabled,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
import { DEFAULT_CONFIG } from "../../config/defaults.js";
|
|
66
|
+
import type { AssistantConfig } from "../../config/types.js";
|
|
67
|
+
import { conversationAnalyzeJob } from "../conversation-analyze-job.js";
|
|
68
|
+
import type { MemoryJob } from "../jobs-store.js";
|
|
69
|
+
|
|
70
|
+
const TEST_CONFIG: AssistantConfig = DEFAULT_CONFIG;
|
|
71
|
+
|
|
72
|
+
function makeJob(payload: Record<string, unknown>): MemoryJob<{
|
|
73
|
+
conversationId?: string;
|
|
74
|
+
}> {
|
|
75
|
+
return {
|
|
76
|
+
id: "job-1",
|
|
77
|
+
type: "conversation_analyze",
|
|
78
|
+
payload: payload as { conversationId?: string },
|
|
79
|
+
status: "running",
|
|
80
|
+
attempts: 0,
|
|
81
|
+
deferrals: 0,
|
|
82
|
+
runAfter: 0,
|
|
83
|
+
lastError: null,
|
|
84
|
+
startedAt: Date.now(),
|
|
85
|
+
createdAt: Date.now(),
|
|
86
|
+
updatedAt: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
describe("conversationAnalyzeJob", () => {
|
|
91
|
+
beforeEach(() => {
|
|
92
|
+
analyzeCalls.length = 0;
|
|
93
|
+
enqueueCalls.length = 0;
|
|
94
|
+
mockGetAnalysisDeps.mockReset();
|
|
95
|
+
mockGetAnalysisDeps.mockImplementation(() => null);
|
|
96
|
+
mockAnalyzeConversation.mockReset();
|
|
97
|
+
mockAnalyzeConversation.mockImplementation(
|
|
98
|
+
async (
|
|
99
|
+
conversationId: string,
|
|
100
|
+
deps: DepsStub,
|
|
101
|
+
opts: { trigger: "manual" | "auto" },
|
|
102
|
+
) => {
|
|
103
|
+
analyzeCalls.push({ conversationId, deps, opts });
|
|
104
|
+
return { analysisConversationId: "analysis-1" };
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("returns without calling the service when conversationId is missing", async () => {
|
|
110
|
+
await conversationAnalyzeJob(makeJob({}), TEST_CONFIG);
|
|
111
|
+
expect(analyzeCalls).toHaveLength(0);
|
|
112
|
+
expect(mockGetAnalysisDeps).not.toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("returns without calling the service when conversationId is empty string", async () => {
|
|
116
|
+
await conversationAnalyzeJob(
|
|
117
|
+
makeJob({ conversationId: "" }),
|
|
118
|
+
TEST_CONFIG,
|
|
119
|
+
);
|
|
120
|
+
expect(analyzeCalls).toHaveLength(0);
|
|
121
|
+
expect(mockGetAnalysisDeps).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("returns without calling the service when deps singleton is not yet initialized", async () => {
|
|
125
|
+
// Deps singleton is unset during slow daemon startup. The handler must
|
|
126
|
+
// NOT throw — a plain Error here is classified as fatal by
|
|
127
|
+
// classifyError() and the worker would mark the job permanently failed
|
|
128
|
+
// (failMemoryJob with maxAttempts: 1). Returning lets the next batch /
|
|
129
|
+
// idle / lifecycle trigger from enqueueAutoAnalysisIfEnabled() produce
|
|
130
|
+
// a fresh job once the daemon has fully started.
|
|
131
|
+
mockGetAnalysisDeps.mockImplementation(() => null);
|
|
132
|
+
await expect(
|
|
133
|
+
conversationAnalyzeJob(
|
|
134
|
+
makeJob({ conversationId: "conv-1" }),
|
|
135
|
+
TEST_CONFIG,
|
|
136
|
+
),
|
|
137
|
+
).resolves.toBeUndefined();
|
|
138
|
+
expect(analyzeCalls).toHaveLength(0);
|
|
139
|
+
expect(mockGetAnalysisDeps).toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("invokes analyzeConversation with trigger=auto and the conversationId", async () => {
|
|
143
|
+
const depsStub: DepsStub = { _tag: "deps-stub" };
|
|
144
|
+
mockGetAnalysisDeps.mockImplementation(() => depsStub);
|
|
145
|
+
|
|
146
|
+
await conversationAnalyzeJob(
|
|
147
|
+
makeJob({ conversationId: "conv-42" }),
|
|
148
|
+
TEST_CONFIG,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(analyzeCalls).toHaveLength(1);
|
|
152
|
+
expect(analyzeCalls[0]!.conversationId).toBe("conv-42");
|
|
153
|
+
expect(analyzeCalls[0]!.opts).toEqual({ trigger: "auto" });
|
|
154
|
+
expect(analyzeCalls[0]!.deps).toBe(depsStub);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("requeues a follow-up idle trigger when the service returns skipped=true", async () => {
|
|
158
|
+
// If the rolling analysis conversation is already processing, the
|
|
159
|
+
// service returns { skipped: true } without running. The job handler
|
|
160
|
+
// completes successfully (no retry), so we must enqueue a follow-up
|
|
161
|
+
// ourselves — otherwise, if no later batch/idle/lifecycle trigger
|
|
162
|
+
// arrives, new source messages would never be analyzed.
|
|
163
|
+
mockGetAnalysisDeps.mockImplementation(() => ({ _tag: "deps" }));
|
|
164
|
+
mockAnalyzeConversation.mockImplementation(async () => ({
|
|
165
|
+
analysisConversationId: "analysis-1",
|
|
166
|
+
skipped: true as const,
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
await conversationAnalyzeJob(
|
|
170
|
+
makeJob({ conversationId: "conv-busy" }),
|
|
171
|
+
TEST_CONFIG,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(enqueueCalls).toHaveLength(1);
|
|
175
|
+
expect(enqueueCalls[0]).toEqual({
|
|
176
|
+
conversationId: "conv-busy",
|
|
177
|
+
trigger: "idle",
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("does not requeue on a normal (non-skipped) successful run", async () => {
|
|
182
|
+
mockGetAnalysisDeps.mockImplementation(() => ({ _tag: "deps" }));
|
|
183
|
+
mockAnalyzeConversation.mockImplementation(async () => ({
|
|
184
|
+
analysisConversationId: "analysis-1",
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
await conversationAnalyzeJob(
|
|
188
|
+
makeJob({ conversationId: "conv-ok" }),
|
|
189
|
+
TEST_CONFIG,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
expect(enqueueCalls).toHaveLength(0);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("does not requeue when the service returns an error result", async () => {
|
|
196
|
+
mockGetAnalysisDeps.mockImplementation(() => ({ _tag: "deps" }));
|
|
197
|
+
mockAnalyzeConversation.mockImplementation(async () => ({
|
|
198
|
+
error: {
|
|
199
|
+
kind: "BAD_REQUEST",
|
|
200
|
+
status: 400,
|
|
201
|
+
message: "Cannot auto-analyze an auto-analysis conversation",
|
|
202
|
+
},
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
await conversationAnalyzeJob(
|
|
206
|
+
makeJob({ conversationId: "conv-err" }),
|
|
207
|
+
TEST_CONFIG,
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(enqueueCalls).toHaveLength(0);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("swallows (does not throw) when the service returns an error result", async () => {
|
|
214
|
+
mockGetAnalysisDeps.mockImplementation(() => ({ _tag: "deps" }));
|
|
215
|
+
mockAnalyzeConversation.mockImplementation(async () => ({
|
|
216
|
+
error: {
|
|
217
|
+
kind: "BAD_REQUEST",
|
|
218
|
+
status: 400,
|
|
219
|
+
message: "Cannot auto-analyze an auto-analysis conversation",
|
|
220
|
+
},
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
// Must not throw — the worker would otherwise retry forever on a
|
|
224
|
+
// deterministic rejection (e.g. the recursion guard).
|
|
225
|
+
await expect(
|
|
226
|
+
conversationAnalyzeJob(
|
|
227
|
+
makeJob({ conversationId: "conv-2" }),
|
|
228
|
+
TEST_CONFIG,
|
|
229
|
+
),
|
|
230
|
+
).resolves.toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
|
|
5
|
+
mock.module("../../util/logger.js", () => ({
|
|
6
|
+
getLogger: () =>
|
|
7
|
+
new Proxy({} as Record<string, unknown>, {
|
|
8
|
+
get: () => () => {},
|
|
9
|
+
}),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
createConversation,
|
|
14
|
+
findAnalysisConversationFor,
|
|
15
|
+
getConversationSource,
|
|
16
|
+
} from "../conversation-crud.js";
|
|
17
|
+
import { getDb, initializeDb } from "../db.js";
|
|
18
|
+
import { conversations } from "../schema.js";
|
|
19
|
+
|
|
20
|
+
initializeDb();
|
|
21
|
+
|
|
22
|
+
function resetTables(): void {
|
|
23
|
+
const db = getDb();
|
|
24
|
+
db.run(`DELETE FROM messages`);
|
|
25
|
+
db.run(`DELETE FROM conversations`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function setForkParent(conversationId: string, parentId: string): void {
|
|
29
|
+
const db = getDb();
|
|
30
|
+
db.update(conversations)
|
|
31
|
+
.set({ forkParentConversationId: parentId })
|
|
32
|
+
.where(eq(conversations.id, conversationId))
|
|
33
|
+
.run();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setUpdatedAt(conversationId: string, updatedAt: number): void {
|
|
37
|
+
const db = getDb();
|
|
38
|
+
db.update(conversations)
|
|
39
|
+
.set({ updatedAt })
|
|
40
|
+
.where(eq(conversations.id, conversationId))
|
|
41
|
+
.run();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("findAnalysisConversationFor", () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
resetTables();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("returns null when no analysis conversation exists for the parent", () => {
|
|
50
|
+
const parent = createConversation("parent");
|
|
51
|
+
expect(findAnalysisConversationFor(parent.id)).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("returns null when an unrelated analysis conversation exists", () => {
|
|
55
|
+
const parent = createConversation("parent");
|
|
56
|
+
const other = createConversation("other");
|
|
57
|
+
const analysisForOther = createConversation({
|
|
58
|
+
title: "analysis of other",
|
|
59
|
+
source: "auto-analysis",
|
|
60
|
+
});
|
|
61
|
+
setForkParent(analysisForOther.id, other.id);
|
|
62
|
+
|
|
63
|
+
expect(findAnalysisConversationFor(parent.id)).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("returns the only matching analysis conversation when one exists", () => {
|
|
67
|
+
const parent = createConversation("parent");
|
|
68
|
+
const analysis = createConversation({
|
|
69
|
+
title: "rolling analysis",
|
|
70
|
+
source: "auto-analysis",
|
|
71
|
+
});
|
|
72
|
+
setForkParent(analysis.id, parent.id);
|
|
73
|
+
|
|
74
|
+
expect(findAnalysisConversationFor(parent.id)).toEqual({ id: analysis.id });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("when multiple match, returns the most recently updated one", () => {
|
|
78
|
+
const parent = createConversation("parent");
|
|
79
|
+
|
|
80
|
+
const older = createConversation({
|
|
81
|
+
title: "older analysis",
|
|
82
|
+
source: "auto-analysis",
|
|
83
|
+
});
|
|
84
|
+
setForkParent(older.id, parent.id);
|
|
85
|
+
setUpdatedAt(older.id, 1_000);
|
|
86
|
+
|
|
87
|
+
const newer = createConversation({
|
|
88
|
+
title: "newer analysis",
|
|
89
|
+
source: "auto-analysis",
|
|
90
|
+
});
|
|
91
|
+
setForkParent(newer.id, parent.id);
|
|
92
|
+
setUpdatedAt(newer.id, 2_000);
|
|
93
|
+
|
|
94
|
+
const middle = createConversation({
|
|
95
|
+
title: "middle analysis",
|
|
96
|
+
source: "auto-analysis",
|
|
97
|
+
});
|
|
98
|
+
setForkParent(middle.id, parent.id);
|
|
99
|
+
setUpdatedAt(middle.id, 1_500);
|
|
100
|
+
|
|
101
|
+
expect(findAnalysisConversationFor(parent.id)).toEqual({ id: newer.id });
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("does not return regular user conversations whose forkParentConversationId matches", () => {
|
|
105
|
+
const parent = createConversation("parent");
|
|
106
|
+
|
|
107
|
+
// A regular user-forked conversation (source defaults to "user").
|
|
108
|
+
const userFork = createConversation("user fork");
|
|
109
|
+
setForkParent(userFork.id, parent.id);
|
|
110
|
+
|
|
111
|
+
expect(findAnalysisConversationFor(parent.id)).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("ignores user forks even when an analysis conversation also exists", () => {
|
|
115
|
+
const parent = createConversation("parent");
|
|
116
|
+
|
|
117
|
+
const userFork = createConversation("user fork");
|
|
118
|
+
setForkParent(userFork.id, parent.id);
|
|
119
|
+
// Force the user fork to have the most recent updatedAt — it should
|
|
120
|
+
// still be ignored because its source is "user".
|
|
121
|
+
setUpdatedAt(userFork.id, 9_999);
|
|
122
|
+
|
|
123
|
+
const analysis = createConversation({
|
|
124
|
+
title: "analysis",
|
|
125
|
+
source: "auto-analysis",
|
|
126
|
+
});
|
|
127
|
+
setForkParent(analysis.id, parent.id);
|
|
128
|
+
setUpdatedAt(analysis.id, 1_000);
|
|
129
|
+
|
|
130
|
+
expect(findAnalysisConversationFor(parent.id)).toEqual({ id: analysis.id });
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("createConversation persists forkParentConversationId when supplied", () => {
|
|
134
|
+
const parent = createConversation("parent");
|
|
135
|
+
|
|
136
|
+
// Auto-analyze path creates the rolling analysis conversation with
|
|
137
|
+
// source + forkParentConversationId in the same call.
|
|
138
|
+
const analysis = createConversation({
|
|
139
|
+
title: "rolling analysis",
|
|
140
|
+
source: "auto-analysis",
|
|
141
|
+
forkParentConversationId: parent.id,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
expect(findAnalysisConversationFor(parent.id)).toEqual({ id: analysis.id });
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("finds rolling analysis conversation regardless of group_id (backward-compat across the dedicated-group migration)", () => {
|
|
148
|
+
const parent = createConversation("parent");
|
|
149
|
+
|
|
150
|
+
const legacyAnalysis = createConversation({
|
|
151
|
+
title: "legacy rolling analysis",
|
|
152
|
+
source: "auto-analysis",
|
|
153
|
+
forkParentConversationId: parent.id,
|
|
154
|
+
});
|
|
155
|
+
setUpdatedAt(legacyAnalysis.id, 1_000);
|
|
156
|
+
|
|
157
|
+
expect(findAnalysisConversationFor(parent.id)).toEqual({
|
|
158
|
+
id: legacyAnalysis.id,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const newAnalysis = createConversation({
|
|
162
|
+
title: "new rolling analysis",
|
|
163
|
+
source: "auto-analysis",
|
|
164
|
+
groupId: "system:background",
|
|
165
|
+
forkParentConversationId: parent.id,
|
|
166
|
+
});
|
|
167
|
+
setUpdatedAt(newAnalysis.id, 2_000);
|
|
168
|
+
|
|
169
|
+
expect(findAnalysisConversationFor(parent.id)).toEqual({
|
|
170
|
+
id: newAnalysis.id,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("getConversationSource", () => {
|
|
176
|
+
beforeEach(() => {
|
|
177
|
+
resetTables();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("returns the source string for an existing conversation", () => {
|
|
181
|
+
const conv = createConversation("user conv");
|
|
182
|
+
expect(getConversationSource(conv.id)).toBe("user");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("returns the custom source for an analysis conversation", () => {
|
|
186
|
+
const conv = createConversation({
|
|
187
|
+
title: "analysis",
|
|
188
|
+
source: "auto-analysis",
|
|
189
|
+
});
|
|
190
|
+
expect(getConversationSource(conv.id)).toBe("auto-analysis");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("returns null for a non-existent conversation ID", () => {
|
|
194
|
+
expect(getConversationSource("does-not-exist")).toBeNull();
|
|
195
|
+
});
|
|
196
|
+
});
|
package/src/memory/app-store.ts
CHANGED
|
@@ -78,7 +78,7 @@ export function resolveEffectiveAppHtml(app: AppDefinition): string {
|
|
|
78
78
|
if (existsSync(distIndex)) {
|
|
79
79
|
return inlineDistAssets(appDir, readFileSync(distIndex, "utf-8"));
|
|
80
80
|
}
|
|
81
|
-
return
|
|
81
|
+
return `<p>App compilation failed. Edit a source file to trigger a rebuild.</p>`;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
existsSync,
|
|
12
12
|
mkdirSync,
|
|
13
13
|
readFileSync,
|
|
14
|
+
realpathSync,
|
|
14
15
|
unlinkSync,
|
|
15
16
|
writeFileSync,
|
|
16
17
|
} from "node:fs";
|
|
@@ -264,7 +265,40 @@ function materializeAttachmentIntoConversation(
|
|
|
264
265
|
copyFileSync(readablePath, targetPath);
|
|
265
266
|
}
|
|
266
267
|
|
|
268
|
+
// Remember the old file path before updating the DB row, so we can
|
|
269
|
+
// clean up the staging copy (e.g. in data/attachments/) after the
|
|
270
|
+
// canonical path moves to the conversation directory.
|
|
271
|
+
const previousFilePath = row.filePath;
|
|
272
|
+
|
|
267
273
|
persistAttachmentFilePath(row.id, targetPath, sourcePath);
|
|
274
|
+
|
|
275
|
+
// Remove the old staging file now that the canonical copy lives in
|
|
276
|
+
// the conversation directory. Only delete files that live in the
|
|
277
|
+
// staging area (workspace/data/attachments/). When an attachment is
|
|
278
|
+
// cloned across conversations (e.g. during a fork), previousFilePath
|
|
279
|
+
// may point to another conversation's directory — deleting that would
|
|
280
|
+
// cause data loss for the source conversation.
|
|
281
|
+
const stagingDirRaw = join(getWorkspaceDir(), "data", "attachments");
|
|
282
|
+
let stagingDir: string;
|
|
283
|
+
try {
|
|
284
|
+
stagingDir = existsSync(stagingDirRaw)
|
|
285
|
+
? realpathSync(stagingDirRaw)
|
|
286
|
+
: stagingDirRaw;
|
|
287
|
+
} catch {
|
|
288
|
+
stagingDir = stagingDirRaw;
|
|
289
|
+
}
|
|
290
|
+
if (
|
|
291
|
+
previousFilePath &&
|
|
292
|
+
previousFilePath !== targetPath &&
|
|
293
|
+
dirname(previousFilePath) === stagingDir &&
|
|
294
|
+
existsSync(previousFilePath)
|
|
295
|
+
) {
|
|
296
|
+
try {
|
|
297
|
+
unlinkSync(previousFilePath);
|
|
298
|
+
} catch {
|
|
299
|
+
/* file may already be gone */
|
|
300
|
+
}
|
|
301
|
+
}
|
|
268
302
|
}
|
|
269
303
|
|
|
270
304
|
function scopeAttachmentToConversation(
|
|
@@ -458,6 +492,42 @@ export function validateAttachmentUpload(
|
|
|
458
492
|
return { ok: true };
|
|
459
493
|
}
|
|
460
494
|
|
|
495
|
+
// ---------------------------------------------------------------------------
|
|
496
|
+
// Binary upload helper (multipart / octet-stream)
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Write raw bytes to the staging directory and register as a file-backed
|
|
501
|
+
* attachment. Used by the multipart/form-data and application/octet-stream
|
|
502
|
+
* upload paths.
|
|
503
|
+
*
|
|
504
|
+
* @param filename Original filename from the client
|
|
505
|
+
* @param mimeType MIME type of the file
|
|
506
|
+
* @param bytes Raw file content
|
|
507
|
+
* @returns The stored attachment record
|
|
508
|
+
*/
|
|
509
|
+
export function uploadAttachmentFromBytes(
|
|
510
|
+
filename: string,
|
|
511
|
+
mimeType: string,
|
|
512
|
+
bytes: Uint8Array,
|
|
513
|
+
): StoredAttachment {
|
|
514
|
+
const dir = join(getWorkspaceDir(), "data", "attachments");
|
|
515
|
+
mkdirSync(dir, { recursive: true });
|
|
516
|
+
|
|
517
|
+
const sanitized = filename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
518
|
+
const stagingFilename = `${Date.now()}-${uuid().slice(0, 8)}-${sanitized}`;
|
|
519
|
+
const stagedPath = join(dir, stagingFilename);
|
|
520
|
+
|
|
521
|
+
writeFileSync(stagedPath, bytes);
|
|
522
|
+
|
|
523
|
+
return uploadFileBackedAttachment(
|
|
524
|
+
filename,
|
|
525
|
+
mimeType,
|
|
526
|
+
stagedPath,
|
|
527
|
+
bytes.length,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
461
531
|
// ---------------------------------------------------------------------------
|
|
462
532
|
// File-backed attachment storage (avoids reading large files into memory)
|
|
463
533
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
2
|
+
import { getConfig } from "../config/loader.js";
|
|
3
|
+
import {
|
|
4
|
+
isUntrustedTrustClass,
|
|
5
|
+
type TrustClass,
|
|
6
|
+
} from "../runtime/actor-trust-resolver.js";
|
|
7
|
+
import { getLogger } from "../util/logger.js";
|
|
8
|
+
import { isAutoAnalysisConversation } from "./auto-analysis-guard.js";
|
|
9
|
+
import { getConversationType } from "./conversation-crud.js";
|
|
10
|
+
import { upsertAutoAnalysisJob } from "./jobs-store.js";
|
|
11
|
+
|
|
12
|
+
const log = getLogger("auto-analysis-enqueue");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Trigger reason for an auto-analysis enqueue.
|
|
16
|
+
* - `"batch"`: source conversation crossed the batch threshold — enqueue
|
|
17
|
+
* immediately (`runAfter = now`) but still upsert so a pending job
|
|
18
|
+
* coalesces rapid threshold crossings into one.
|
|
19
|
+
* - `"idle"`: source conversation has been idle long enough to warrant a
|
|
20
|
+
* debounced analysis pass.
|
|
21
|
+
* - `"lifecycle"`: a conversation lifecycle transition (e.g. resume,
|
|
22
|
+
* close) should trigger a debounced analysis pass.
|
|
23
|
+
* - `"compaction"`: context was just compacted — some recent turns are
|
|
24
|
+
* now hidden behind a summary, so crystallize anything worth
|
|
25
|
+
* remembering before the window narrows further. Fires immediately
|
|
26
|
+
* (`runAfter = now`) like `"batch"`.
|
|
27
|
+
*/
|
|
28
|
+
export type AutoAnalysisTrigger =
|
|
29
|
+
| "batch"
|
|
30
|
+
| "idle"
|
|
31
|
+
| "lifecycle"
|
|
32
|
+
| "compaction";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Conditionally enqueue a `conversation_analyze` job for the given
|
|
36
|
+
* conversation. Skips silently when:
|
|
37
|
+
* - the `auto-analyze` feature flag is disabled, OR
|
|
38
|
+
* - the source conversation is itself an auto-analysis conversation
|
|
39
|
+
* (recursion guard — we never analyze our own analysis output), OR
|
|
40
|
+
* - the source conversation is private (`analyzeConversation` rejects
|
|
41
|
+
* private conversations, so enqueueing would guarantee a failed job).
|
|
42
|
+
*
|
|
43
|
+
* Immediate triggers (`"batch"`, `"compaction"`) and debounced triggers
|
|
44
|
+
* (`"idle"`, `"lifecycle"`) are written to separate rows keyed by a
|
|
45
|
+
* `triggerGroup` discriminator. This prevents an idle enqueue from
|
|
46
|
+
* pushing an already-scheduled batch row's `runAfter` into the future
|
|
47
|
+
* (and vice versa). Within each group, rapid enqueues still coalesce to
|
|
48
|
+
* a single pending row via `upsertAutoAnalysisJob`.
|
|
49
|
+
*/
|
|
50
|
+
export function enqueueAutoAnalysisIfEnabled(args: {
|
|
51
|
+
conversationId: string;
|
|
52
|
+
trigger: AutoAnalysisTrigger;
|
|
53
|
+
}): void {
|
|
54
|
+
const { conversationId, trigger } = args;
|
|
55
|
+
|
|
56
|
+
let config;
|
|
57
|
+
try {
|
|
58
|
+
config = getConfig();
|
|
59
|
+
} catch (err) {
|
|
60
|
+
log.warn(
|
|
61
|
+
{ err, conversationId },
|
|
62
|
+
"Skipping auto-analysis enqueue: failed to load config",
|
|
63
|
+
);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!isAssistantFeatureFlagEnabled("auto-analyze", config)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (isAutoAnalysisConversation(conversationId)) {
|
|
72
|
+
log.debug(
|
|
73
|
+
{ conversationId, trigger },
|
|
74
|
+
"Skipping auto-analysis enqueue: source is an auto-analysis conversation",
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (getConversationType(conversationId) === "private") {
|
|
80
|
+
log.debug(
|
|
81
|
+
{ conversationId, trigger },
|
|
82
|
+
"Skipping auto-analysis enqueue: source is a private conversation",
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const idleTimeoutMs = config.analysis?.idleTimeoutMs ?? 600_000;
|
|
88
|
+
const runImmediately = trigger === "batch" || trigger === "compaction";
|
|
89
|
+
const triggerGroup: "immediate" | "debounced" = runImmediately
|
|
90
|
+
? "immediate"
|
|
91
|
+
: "debounced";
|
|
92
|
+
const runAfter = runImmediately ? Date.now() : Date.now() + idleTimeoutMs;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
upsertAutoAnalysisJob({ conversationId, triggerGroup }, runAfter);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
log.warn(
|
|
98
|
+
{ err, conversationId, trigger },
|
|
99
|
+
"Failed to enqueue auto-analysis job",
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Fire an auto-analysis enqueue from a compaction site. Wraps
|
|
106
|
+
* `enqueueAutoAnalysisIfEnabled` with the trust-class gate and
|
|
107
|
+
* best-effort error handling used at every compaction call site, so
|
|
108
|
+
* the six compaction paths (forceCompact, preflight, overflow reducer,
|
|
109
|
+
* mid-loop, and two emergency paths) stay in sync.
|
|
110
|
+
*
|
|
111
|
+
* Trust gate mirrors the memory-extraction trust boundary applied in
|
|
112
|
+
* `disposeConversation` — we don't trigger analysis (which runs with
|
|
113
|
+
* guardian trust + full tools) for conversations with an untrusted actor.
|
|
114
|
+
*/
|
|
115
|
+
export function enqueueAutoAnalysisOnCompaction(
|
|
116
|
+
conversationId: string,
|
|
117
|
+
trustClass: TrustClass | undefined,
|
|
118
|
+
): void {
|
|
119
|
+
if (isUntrustedTrustClass(trustClass)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
enqueueAutoAnalysisIfEnabled({ conversationId, trigger: "compaction" });
|
|
124
|
+
} catch {
|
|
125
|
+
// Best-effort — never block compaction on enqueue failures.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getConversationSource } from "./conversation-crud.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `source` value used for conversations created by the auto-analysis
|
|
5
|
+
* loop. Single source of truth — downstream code (enqueue helper,
|
|
6
|
+
* service auto-branch) imports this constant rather than hardcoding the
|
|
7
|
+
* string.
|
|
8
|
+
*/
|
|
9
|
+
export const AUTO_ANALYSIS_SOURCE = "auto-analysis";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Dedicated `group_id` value for auto-analysis rolling conversations.
|
|
13
|
+
* Placed in the `system:background` group alongside heartbeat and filing
|
|
14
|
+
* conversations, rendered as a "Reflections" sub-group in the sidebar.
|
|
15
|
+
*/
|
|
16
|
+
export const AUTO_ANALYSIS_GROUP_ID = "system:background";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns true if the conversation's `source` column is `"auto-analysis"`,
|
|
20
|
+
* meaning it was produced by the auto-analysis loop. Callers use this to
|
|
21
|
+
* skip both `graph_extract` and `conversation_analyze` enqueues so we
|
|
22
|
+
* never (a) analyze our own analysis output or (b) extract memory from
|
|
23
|
+
* reflective musings (the analysis agent writes memory directly via tools).
|
|
24
|
+
*/
|
|
25
|
+
export function isAutoAnalysisConversation(conversationId: string): boolean {
|
|
26
|
+
return getConversationSource(conversationId) === AUTO_ANALYSIS_SOURCE;
|
|
27
|
+
}
|