@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,82 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getWorkspaceDir } from "../../../../util/platform.js";
|
|
5
|
+
|
|
6
|
+
interface GmailPreferences {
|
|
7
|
+
/** Sender emails archived in previous sessions — auto-archive candidates. */
|
|
8
|
+
blocklist: string[];
|
|
9
|
+
/** Sender emails the user explicitly kept (deselected) — exclude from future tables. */
|
|
10
|
+
safelist: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PREFS_FILENAME = "gmail-preferences.json";
|
|
14
|
+
|
|
15
|
+
function getPrefsPath(): string {
|
|
16
|
+
return join(getWorkspaceDir(), "data", PREFS_FILENAME);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function loadPreferences(): GmailPreferences {
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(getPrefsPath(), "utf-8");
|
|
22
|
+
const parsed = JSON.parse(raw) as Partial<GmailPreferences>;
|
|
23
|
+
return {
|
|
24
|
+
blocklist: Array.isArray(parsed.blocklist) ? parsed.blocklist : [],
|
|
25
|
+
safelist: Array.isArray(parsed.safelist) ? parsed.safelist : [],
|
|
26
|
+
};
|
|
27
|
+
} catch {
|
|
28
|
+
return { blocklist: [], safelist: [] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function savePreferences(prefs: GmailPreferences): void {
|
|
33
|
+
const path = getPrefsPath();
|
|
34
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
35
|
+
writeFileSync(path, JSON.stringify(prefs, null, 2));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Add sender emails to the blocklist (deduplicated). */
|
|
39
|
+
export function addToBlocklist(emails: string[]): void {
|
|
40
|
+
const prefs = loadPreferences();
|
|
41
|
+
const existing = new Set(prefs.blocklist);
|
|
42
|
+
for (const email of emails) {
|
|
43
|
+
const normalized = email.toLowerCase();
|
|
44
|
+
existing.add(normalized);
|
|
45
|
+
// If a sender is blocklisted, remove them from safelist
|
|
46
|
+
const safeIdx = prefs.safelist.indexOf(normalized);
|
|
47
|
+
if (safeIdx !== -1) prefs.safelist.splice(safeIdx, 1);
|
|
48
|
+
}
|
|
49
|
+
prefs.blocklist = [...existing];
|
|
50
|
+
savePreferences(prefs);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Add sender emails to the safelist (deduplicated). */
|
|
54
|
+
export function addToSafelist(emails: string[]): void {
|
|
55
|
+
const prefs = loadPreferences();
|
|
56
|
+
const existing = new Set(prefs.safelist);
|
|
57
|
+
for (const email of emails) {
|
|
58
|
+
const normalized = email.toLowerCase();
|
|
59
|
+
existing.add(normalized);
|
|
60
|
+
// If a sender is safelisted, remove them from blocklist
|
|
61
|
+
const blockIdx = prefs.blocklist.indexOf(normalized);
|
|
62
|
+
if (blockIdx !== -1) prefs.blocklist.splice(blockIdx, 1);
|
|
63
|
+
}
|
|
64
|
+
prefs.safelist = [...existing];
|
|
65
|
+
savePreferences(prefs);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Remove sender emails from the blocklist. */
|
|
69
|
+
export function removeFromBlocklist(emails: string[]): void {
|
|
70
|
+
const prefs = loadPreferences();
|
|
71
|
+
const toRemove = new Set(emails.map((e) => e.toLowerCase()));
|
|
72
|
+
prefs.blocklist = prefs.blocklist.filter((e) => !toRemove.has(e));
|
|
73
|
+
savePreferences(prefs);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Remove sender emails from the safelist. */
|
|
77
|
+
export function removeFromSafelist(emails: string[]): void {
|
|
78
|
+
const prefs = loadPreferences();
|
|
79
|
+
const toRemove = new Set(emails.map((e) => e.toLowerCase()));
|
|
80
|
+
prefs.safelist = prefs.safelist.filter((e) => !toRemove.has(e));
|
|
81
|
+
savePreferences(prefs);
|
|
82
|
+
}
|
|
@@ -11,9 +11,31 @@ import type {
|
|
|
11
11
|
import { storeScanResult } from "./scan-result-store.js";
|
|
12
12
|
import { err, ok } from "./shared.js";
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
function isRateLimitError(e: unknown): boolean {
|
|
15
|
+
if (!(e instanceof Error)) return false;
|
|
16
|
+
return /\b429\b/.test(e.message);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isAbortError(e: unknown): boolean {
|
|
20
|
+
if (e instanceof DOMException && e.name === "AbortError") return true;
|
|
21
|
+
if (e instanceof Error && e.message === "fetch deadline exceeded") return true;
|
|
22
|
+
// AbortSignal.reason propagated through promise chains
|
|
23
|
+
if (
|
|
24
|
+
e instanceof Error &&
|
|
25
|
+
"cause" in e &&
|
|
26
|
+
e.cause instanceof Error &&
|
|
27
|
+
e.cause.message === "fetch deadline exceeded"
|
|
28
|
+
)
|
|
29
|
+
return true;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const MAX_MESSAGES_CAP = 5000;
|
|
15
34
|
const MAX_IDS_PER_SENDER = 5000;
|
|
16
35
|
const MAX_SAMPLE_SUBJECTS = 3;
|
|
36
|
+
const MAX_SENDERS_CAP = 75;
|
|
37
|
+
const MAX_SUBJECT_LENGTH = 80;
|
|
38
|
+
const MAX_RESULT_BYTES = 24_000;
|
|
17
39
|
|
|
18
40
|
interface SenderAggregation {
|
|
19
41
|
displayName: string;
|
|
@@ -52,10 +74,13 @@ export async function run(
|
|
|
52
74
|
const query =
|
|
53
75
|
(input.query as string) ?? "in:inbox category:promotions newer_than:90d";
|
|
54
76
|
const maxMessages = Math.min(
|
|
55
|
-
(input.max_messages as number) ??
|
|
77
|
+
(input.max_messages as number) ?? 2000,
|
|
56
78
|
MAX_MESSAGES_CAP,
|
|
57
79
|
);
|
|
58
|
-
const maxSenders = (
|
|
80
|
+
const maxSenders = Math.min(
|
|
81
|
+
(input.max_senders as number) ?? 50,
|
|
82
|
+
MAX_SENDERS_CAP,
|
|
83
|
+
);
|
|
59
84
|
const inputPageToken = input.page_token as string | undefined;
|
|
60
85
|
|
|
61
86
|
try {
|
|
@@ -66,6 +91,7 @@ export async function run(
|
|
|
66
91
|
// overlapping fetch latency with pagination latency
|
|
67
92
|
const allMessageIds: string[] = [];
|
|
68
93
|
const fetchPromises: Promise<GmailMessage[]>[] = [];
|
|
94
|
+
const fetchAbort = new AbortController();
|
|
69
95
|
let pageToken: string | undefined = inputPageToken;
|
|
70
96
|
let truncated = false;
|
|
71
97
|
let timeBudgetExceeded = false;
|
|
@@ -73,6 +99,8 @@ export async function run(
|
|
|
73
99
|
const startTime = Date.now();
|
|
74
100
|
const TIME_BUDGET_MS = 90_000;
|
|
75
101
|
|
|
102
|
+
let rateLimited = false;
|
|
103
|
+
|
|
76
104
|
while (allMessageIds.length < maxMessages) {
|
|
77
105
|
if (Date.now() - startTime > TIME_BUDGET_MS) {
|
|
78
106
|
timeBudgetExceeded = true;
|
|
@@ -80,12 +108,22 @@ export async function run(
|
|
|
80
108
|
break;
|
|
81
109
|
}
|
|
82
110
|
const pageSize = Math.min(100, maxMessages - allMessageIds.length);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
let listResp;
|
|
112
|
+
try {
|
|
113
|
+
listResp = await listMessages(
|
|
114
|
+
connection,
|
|
115
|
+
query,
|
|
116
|
+
pageSize,
|
|
117
|
+
pageToken,
|
|
118
|
+
);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (isRateLimitError(e)) {
|
|
121
|
+
rateLimited = true;
|
|
122
|
+
truncated = true;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
throw e;
|
|
126
|
+
}
|
|
89
127
|
const ids = (listResp.messages ?? []).map((m) => m.id);
|
|
90
128
|
if (ids.length === 0) break;
|
|
91
129
|
allMessageIds.push(...ids);
|
|
@@ -96,6 +134,7 @@ export async function run(
|
|
|
96
134
|
"metadata",
|
|
97
135
|
metadataHeaders,
|
|
98
136
|
"id,internalDate,payload/headers",
|
|
137
|
+
fetchAbort.signal,
|
|
99
138
|
),
|
|
100
139
|
);
|
|
101
140
|
pageToken = listResp.nextPageToken ?? undefined;
|
|
@@ -108,6 +147,18 @@ export async function run(
|
|
|
108
147
|
}
|
|
109
148
|
|
|
110
149
|
if (allMessageIds.length === 0) {
|
|
150
|
+
if (rateLimited) {
|
|
151
|
+
return ok(
|
|
152
|
+
JSON.stringify({
|
|
153
|
+
senders: [],
|
|
154
|
+
total_scanned: 0,
|
|
155
|
+
rate_limited: true,
|
|
156
|
+
truncated: true,
|
|
157
|
+
message:
|
|
158
|
+
"Rate limited before any messages could be fetched. Try again later or reduce max_messages.",
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
111
162
|
return ok(
|
|
112
163
|
JSON.stringify({
|
|
113
164
|
senders: [],
|
|
@@ -118,7 +169,30 @@ export async function run(
|
|
|
118
169
|
);
|
|
119
170
|
}
|
|
120
171
|
|
|
121
|
-
|
|
172
|
+
// Settle all fetch promises — collect successes and tolerate 429 failures.
|
|
173
|
+
// Abort in-flight requests when the deadline fires so they don't continue
|
|
174
|
+
// consuming API quota in the background.
|
|
175
|
+
const elapsedMs = Date.now() - startTime;
|
|
176
|
+
const settleDeadlineMs = Math.max(TIME_BUDGET_MS - elapsedMs, 5_000);
|
|
177
|
+
const deadlineTimer = setTimeout(() => {
|
|
178
|
+
fetchAbort.abort(new Error("fetch deadline exceeded"));
|
|
179
|
+
}, settleDeadlineMs);
|
|
180
|
+
const settled = await Promise.allSettled(fetchPromises);
|
|
181
|
+
clearTimeout(deadlineTimer);
|
|
182
|
+
const messages: GmailMessage[] = [];
|
|
183
|
+
for (const result of settled) {
|
|
184
|
+
if (result.status === "fulfilled") {
|
|
185
|
+
messages.push(...result.value);
|
|
186
|
+
} else if (isRateLimitError(result.reason)) {
|
|
187
|
+
rateLimited = true;
|
|
188
|
+
truncated = true;
|
|
189
|
+
} else if (isAbortError(result.reason)) {
|
|
190
|
+
timeBudgetExceeded = true;
|
|
191
|
+
truncated = true;
|
|
192
|
+
} else {
|
|
193
|
+
throw result.reason;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
122
196
|
|
|
123
197
|
// Group by sender email
|
|
124
198
|
const senderMap = new Map<string, SenderAggregation>();
|
|
@@ -221,17 +295,38 @@ export async function run(
|
|
|
221
295
|
newest_date: s.newestDate,
|
|
222
296
|
// Preserve original query filters so follow-up searches stay scoped
|
|
223
297
|
search_query: `from:${s.email} ${query}`,
|
|
224
|
-
sample_subjects: s.sampleSubjects
|
|
298
|
+
sample_subjects: s.sampleSubjects.map((subj) =>
|
|
299
|
+
subj.length > MAX_SUBJECT_LENGTH
|
|
300
|
+
? subj.slice(0, MAX_SUBJECT_LENGTH) + "…"
|
|
301
|
+
: subj,
|
|
302
|
+
),
|
|
225
303
|
}));
|
|
226
304
|
|
|
305
|
+
// Trim senders if the serialized result would exceed the byte budget.
|
|
306
|
+
// Senders are already sorted by message_count desc, so we drop the
|
|
307
|
+
// least-active ones first.
|
|
308
|
+
while (resultSenders.length > 1) {
|
|
309
|
+
const probe = JSON.stringify({ senders: resultSenders });
|
|
310
|
+
if (probe.length <= MAX_RESULT_BYTES) break;
|
|
311
|
+
resultSenders.pop();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Build a set of sender IDs that survived the trim so the scan store
|
|
315
|
+
// only holds entries the LLM can reference.
|
|
316
|
+
const keptSenderIds = new Set(resultSenders.map((s) => s.id));
|
|
317
|
+
|
|
227
318
|
// Store message IDs server-side to keep them out of LLM context
|
|
228
319
|
const scanId = storeScanResult(
|
|
229
|
-
sorted
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
320
|
+
sorted
|
|
321
|
+
.filter((s) =>
|
|
322
|
+
keptSenderIds.has(Buffer.from(s.email).toString("base64url")),
|
|
323
|
+
)
|
|
324
|
+
.map((s) => ({
|
|
325
|
+
id: Buffer.from(s.email).toString("base64url"),
|
|
326
|
+
messageIds: s.messageIds,
|
|
327
|
+
newestMessageId: s.newestMessageId,
|
|
328
|
+
newestUnsubscribableMessageId: s.newestUnsubscribableMessageId,
|
|
329
|
+
})),
|
|
235
330
|
);
|
|
236
331
|
|
|
237
332
|
return ok(
|
|
@@ -242,6 +337,7 @@ export async function run(
|
|
|
242
337
|
query_used: query,
|
|
243
338
|
...(truncated ? { truncated: true } : {}),
|
|
244
339
|
...(timeBudgetExceeded ? { time_budget_exceeded: true } : {}),
|
|
340
|
+
...(rateLimited ? { rate_limited: true } : {}),
|
|
245
341
|
note: `message_count reflects emails found per sender within the ${allMessageIds.length} messages scanned. Use scan_id with gmail_archive to archive messages (pass scan_id + sender_ids instead of message_ids).`,
|
|
246
342
|
}),
|
|
247
343
|
);
|
|
@@ -19,9 +19,9 @@ export async function run(
|
|
|
19
19
|
context: ToolContext,
|
|
20
20
|
): Promise<ToolExecutionResult> {
|
|
21
21
|
const account = input.account as string | undefined;
|
|
22
|
-
if (!context.triggeredBySurfaceAction) {
|
|
22
|
+
if (!context.triggeredBySurfaceAction && !context.batchAuthorizedByTask) {
|
|
23
23
|
return err(
|
|
24
|
-
"This tool requires
|
|
24
|
+
"This tool requires either a surface action or a scheduled task run with this tool in required_tools. Present results in a selection table with action buttons and wait for the user to click before proceeding.",
|
|
25
25
|
);
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -45,8 +45,7 @@ Parameters:
|
|
|
45
45
|
- `section_config` - Path to a JSON file with manual section boundaries.
|
|
46
46
|
- `detect_dead_time` - Whether to detect and skip dead time (default: false). Dead-time detection can be too aggressive for continuous action video like sports - it may incorrectly skip live play. Enable only for content with clear idle periods (e.g., lectures, surveillance footage).
|
|
47
47
|
- `short_edge` - Short edge resolution for downscaled frames in pixels (default: 480).
|
|
48
|
-
- `include_audio` - Whether to extract and transcribe audio for each segment (default: false). When enabled, each segment's audio is transcribed and stored alongside visual frames.
|
|
49
|
-
- `transcription_mode` - Transcription backend: `'api'` (OpenAI Whisper cloud) or `'local'` (whisper.cpp on-device). Default: `'local'`. The `'api'` mode requires an OpenAI API key configured in settings.
|
|
48
|
+
- `include_audio` - Whether to extract and transcribe audio for each segment (default: false). When enabled, each segment's audio is transcribed using the configured STT service and stored alongside visual frames.
|
|
50
49
|
|
|
51
50
|
### analyze_keyframes
|
|
52
51
|
|
|
@@ -121,7 +120,7 @@ Tracks estimated API costs during pipeline execution.
|
|
|
121
120
|
|
|
122
121
|
## Audio + Vision Multimodal Analysis
|
|
123
122
|
|
|
124
|
-
When `include_audio` is enabled on `extract_keyframes`, the pipeline transcribes each segment's audio track and attaches the transcript to the segment data. During the Map phase (`analyze_keyframes`), Gemini receives both the visual frames and the audio transcript for each segment, enabling multimodal analysis that combines what is seen with what is said.
|
|
123
|
+
When `include_audio` is enabled on `extract_keyframes`, the pipeline transcribes each segment's audio track using the configured STT service and attaches the transcript to the segment data. During the Map phase (`analyze_keyframes`), Gemini receives both the visual frames and the audio transcript for each segment, enabling multimodal analysis that combines what is seen with what is said.
|
|
125
124
|
|
|
126
125
|
This is useful for:
|
|
127
126
|
|
|
@@ -130,12 +129,7 @@ This is useful for:
|
|
|
130
129
|
- **Meetings and interviews**: Pair facial expressions and gestures with spoken dialogue.
|
|
131
130
|
- **Tutorials and demos**: Link on-screen actions with verbal instructions.
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
- **`local`** (default): Uses whisper.cpp for on-device transcription. Requires `whisper-cpp` to be installed (`brew install whisper-cpp`). No API costs, but slower.
|
|
136
|
-
- **`api`**: Uses the OpenAI Whisper API for cloud-based transcription. Faster and more accurate, but requires an OpenAI API key and incurs per-minute costs.
|
|
137
|
-
|
|
138
|
-
The audio transcription degrades gracefully - if transcription fails for a segment (missing tools, no audio track, API errors), the segment proceeds with visual-only analysis.
|
|
132
|
+
Audio transcription uses the STT service configured in assistant settings. If no STT service is configured or transcription fails for a segment (no audio track, service errors), the segment gracefully degrades to visual-only analysis.
|
|
139
133
|
|
|
140
134
|
## Best Practices
|
|
141
135
|
|
|
@@ -99,12 +99,7 @@
|
|
|
99
99
|
},
|
|
100
100
|
"include_audio": {
|
|
101
101
|
"type": "boolean",
|
|
102
|
-
"description": "Whether to extract and transcribe audio for each segment. Default: false."
|
|
103
|
-
},
|
|
104
|
-
"transcription_mode": {
|
|
105
|
-
"type": "string",
|
|
106
|
-
"enum": ["api", "local"],
|
|
107
|
-
"description": "Transcription backend: 'api' (OpenAI Whisper cloud) or 'local' (whisper.cpp). Default: 'local'."
|
|
102
|
+
"description": "Whether to extract and transcribe audio for each segment using the configured STT service. Default: false."
|
|
108
103
|
},
|
|
109
104
|
"activity": {
|
|
110
105
|
"type": "string",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { BatchTranscriber } from "../../../../stt/types.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mocks — must be declared before the subject import
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
let mockTranscriber: BatchTranscriber | null = null;
|
|
10
|
+
|
|
11
|
+
mock.module("../../../../providers/speech-to-text/resolve.js", () => ({
|
|
12
|
+
resolveBatchTranscriber: async () => mockTranscriber,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
let spawnResult: { exitCode: number; stdout: string; stderr: string } = {
|
|
16
|
+
exitCode: 0,
|
|
17
|
+
stdout: "",
|
|
18
|
+
stderr: "",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
mock.module("../../../../util/spawn.js", () => ({
|
|
22
|
+
spawnWithTimeout: async () => spawnResult,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
let mockFileContents: Buffer = Buffer.alloc(0);
|
|
26
|
+
|
|
27
|
+
mock.module("node:fs/promises", () => ({
|
|
28
|
+
readFile: async () => mockFileContents,
|
|
29
|
+
unlink: async () => {},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Subject import (after mocks)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
import { transcribeSegmentAudio } from "../services/audio-transcribe.js";
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Helpers
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
function makeMockTranscriber(text: string): BatchTranscriber {
|
|
43
|
+
return {
|
|
44
|
+
providerId: "openai-whisper",
|
|
45
|
+
boundaryId: "daemon-batch",
|
|
46
|
+
transcribe: async () => ({ text }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function makeFailingTranscriber(error: Error): BatchTranscriber {
|
|
51
|
+
return {
|
|
52
|
+
providerId: "openai-whisper",
|
|
53
|
+
boundaryId: "daemon-batch",
|
|
54
|
+
transcribe: async () => {
|
|
55
|
+
throw error;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Tests
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
describe("transcribeSegmentAudio", () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
mockTranscriber = null;
|
|
67
|
+
spawnResult = { exitCode: 0, stdout: "", stderr: "" };
|
|
68
|
+
mockFileContents = Buffer.from("fake-wav-data");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("returns transcript text on successful transcription", async () => {
|
|
72
|
+
mockTranscriber = makeMockTranscriber("Hello, this is a test transcript.");
|
|
73
|
+
|
|
74
|
+
const result = await transcribeSegmentAudio("/tmp/video.mp4", 10, 15);
|
|
75
|
+
|
|
76
|
+
expect(result).toBe("Hello, this is a test transcript.");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("returns empty string when no STT provider is configured", async () => {
|
|
80
|
+
mockTranscriber = null;
|
|
81
|
+
|
|
82
|
+
const result = await transcribeSegmentAudio("/tmp/video.mp4", 0, 30);
|
|
83
|
+
|
|
84
|
+
expect(result).toBe("");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("returns empty string when ffmpeg extraction fails", async () => {
|
|
88
|
+
mockTranscriber = makeMockTranscriber("should not reach here");
|
|
89
|
+
spawnResult = {
|
|
90
|
+
exitCode: 1,
|
|
91
|
+
stdout: "",
|
|
92
|
+
stderr: "ffmpeg: error extracting audio",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const result = await transcribeSegmentAudio("/tmp/video.mp4", 5, 10);
|
|
96
|
+
|
|
97
|
+
expect(result).toBe("");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("returns empty string when provider throws an error", async () => {
|
|
101
|
+
mockTranscriber = makeFailingTranscriber(
|
|
102
|
+
new Error("Provider API rate limited"),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const result = await transcribeSegmentAudio("/tmp/video.mp4", 0, 20);
|
|
106
|
+
|
|
107
|
+
expect(result).toBe("");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("trims whitespace from transcript result", async () => {
|
|
111
|
+
mockTranscriber = makeMockTranscriber(" trimmed text ");
|
|
112
|
+
|
|
113
|
+
const result = await transcribeSegmentAudio("/tmp/video.mp4", 0, 10);
|
|
114
|
+
|
|
115
|
+
expect(result).toBe("trimmed text");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("returns empty string when provider returns empty text", async () => {
|
|
119
|
+
mockTranscriber = makeMockTranscriber("");
|
|
120
|
+
|
|
121
|
+
const result = await transcribeSegmentAudio("/tmp/video.mp4", 0, 10);
|
|
122
|
+
|
|
123
|
+
expect(result).toBe("");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { PreprocessManifest } from "../services/preprocess.js";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mocks — must be declared before the subject import
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
let lastPreprocessOptions: Record<string, unknown> | undefined;
|
|
10
|
+
let mockManifest: PreprocessManifest;
|
|
11
|
+
let mockPreprocessError: Error | null = null;
|
|
12
|
+
|
|
13
|
+
mock.module("../services/preprocess.js", () => ({
|
|
14
|
+
preprocessForAsset: async (
|
|
15
|
+
_assetId: string,
|
|
16
|
+
options: Record<string, unknown>,
|
|
17
|
+
) => {
|
|
18
|
+
lastPreprocessOptions = options;
|
|
19
|
+
if (mockPreprocessError) throw mockPreprocessError;
|
|
20
|
+
return mockManifest;
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
mock.module("../../../../memory/media-store.js", () => ({
|
|
25
|
+
getMediaAssetById: () => ({ filePath: "/tmp/videos/test.mp4" }),
|
|
26
|
+
getKeyframesForAsset: () => [{ id: "kf-1" }, { id: "kf-2" }, { id: "kf-3" }],
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Subject import (after mocks)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
import { run } from "../tools/extract-keyframes.js";
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Helpers
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
function makeManifest(
|
|
40
|
+
overrides: Partial<PreprocessManifest> = {},
|
|
41
|
+
): PreprocessManifest {
|
|
42
|
+
return {
|
|
43
|
+
assetId: "asset-1",
|
|
44
|
+
videoPath: "/tmp/videos/test.mp4",
|
|
45
|
+
durationSeconds: 60,
|
|
46
|
+
segments: [
|
|
47
|
+
{
|
|
48
|
+
id: "seg-001",
|
|
49
|
+
startSeconds: 0,
|
|
50
|
+
endSeconds: 15,
|
|
51
|
+
framePaths: ["/tmp/frame-1.jpg"],
|
|
52
|
+
frameTimestamps: [0],
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
deadTimeRanges: [],
|
|
56
|
+
subjectRegistry: { groups: [] },
|
|
57
|
+
sectionBoundaries: [],
|
|
58
|
+
config: {
|
|
59
|
+
intervalSeconds: 1,
|
|
60
|
+
segmentDuration: 15,
|
|
61
|
+
deadTimeThreshold: 0.02,
|
|
62
|
+
shortEdge: 480,
|
|
63
|
+
},
|
|
64
|
+
...overrides,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function makeContext() {
|
|
69
|
+
return {
|
|
70
|
+
workingDir: "/tmp",
|
|
71
|
+
conversationId: "conv-1",
|
|
72
|
+
trustClass: "guardian" as const,
|
|
73
|
+
onOutput: () => {},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Tests
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
|
|
81
|
+
describe("extract_keyframes tool", () => {
|
|
82
|
+
beforeEach(() => {
|
|
83
|
+
lastPreprocessOptions = undefined;
|
|
84
|
+
mockPreprocessError = null;
|
|
85
|
+
mockManifest = makeManifest();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("returns error when asset_id is missing", async () => {
|
|
89
|
+
const result = await run({}, makeContext());
|
|
90
|
+
|
|
91
|
+
expect(result.isError).toBe(true);
|
|
92
|
+
expect(result.content).toBe("asset_id is required.");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("passes include_audio through to preprocessForAsset", async () => {
|
|
96
|
+
await run({ asset_id: "asset-1", include_audio: true }, makeContext());
|
|
97
|
+
|
|
98
|
+
expect(lastPreprocessOptions?.includeAudio).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("defaults include_audio to false when not provided", async () => {
|
|
102
|
+
await run({ asset_id: "asset-1" }, makeContext());
|
|
103
|
+
|
|
104
|
+
expect(lastPreprocessOptions?.includeAudio).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("maps all numeric option fields from tool input", async () => {
|
|
108
|
+
await run(
|
|
109
|
+
{
|
|
110
|
+
asset_id: "asset-1",
|
|
111
|
+
interval_seconds: 2,
|
|
112
|
+
segment_duration: 30,
|
|
113
|
+
dead_time_threshold: 0.05,
|
|
114
|
+
short_edge: 720,
|
|
115
|
+
},
|
|
116
|
+
makeContext(),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
expect(lastPreprocessOptions?.intervalSeconds).toBe(2);
|
|
120
|
+
expect(lastPreprocessOptions?.segmentDuration).toBe(30);
|
|
121
|
+
expect(lastPreprocessOptions?.deadTimeThreshold).toBe(0.05);
|
|
122
|
+
expect(lastPreprocessOptions?.shortEdge).toBe(720);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("passes detect_dead_time and section_config through", async () => {
|
|
126
|
+
await run(
|
|
127
|
+
{
|
|
128
|
+
asset_id: "asset-1",
|
|
129
|
+
detect_dead_time: true,
|
|
130
|
+
section_config: "/tmp/sections.json",
|
|
131
|
+
},
|
|
132
|
+
makeContext(),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
expect(lastPreprocessOptions?.detectDeadTime).toBe(true);
|
|
136
|
+
expect(lastPreprocessOptions?.sectionConfigPath).toBe("/tmp/sections.json");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("does not pass transcriptionMode or openaiApiKey", async () => {
|
|
140
|
+
await run(
|
|
141
|
+
{
|
|
142
|
+
asset_id: "asset-1",
|
|
143
|
+
include_audio: true,
|
|
144
|
+
transcription_mode: "api",
|
|
145
|
+
},
|
|
146
|
+
makeContext(),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(lastPreprocessOptions).toBeDefined();
|
|
150
|
+
expect("transcriptionMode" in lastPreprocessOptions!).toBe(false);
|
|
151
|
+
expect("openaiApiKey" in lastPreprocessOptions!).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("returns successful result with manifest summary", async () => {
|
|
155
|
+
const result = await run({ asset_id: "asset-1" }, makeContext());
|
|
156
|
+
|
|
157
|
+
expect(result.isError).toBe(false);
|
|
158
|
+
const parsed = JSON.parse(result.content);
|
|
159
|
+
expect(parsed.assetId).toBe("asset-1");
|
|
160
|
+
expect(parsed.segmentCount).toBe(1);
|
|
161
|
+
expect(parsed.keyframeCount).toBe(3);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("returns error content for known preprocess failures", async () => {
|
|
165
|
+
mockPreprocessError = new Error("Media asset not found: asset-bad");
|
|
166
|
+
|
|
167
|
+
const result = await run({ asset_id: "asset-bad" }, makeContext());
|
|
168
|
+
|
|
169
|
+
expect(result.isError).toBe(true);
|
|
170
|
+
expect(result.content).toBe("Media asset not found: asset-bad");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("wraps unknown errors in generic message", async () => {
|
|
174
|
+
mockPreprocessError = new Error("Something unexpected");
|
|
175
|
+
|
|
176
|
+
const result = await run({ asset_id: "asset-1" }, makeContext());
|
|
177
|
+
|
|
178
|
+
expect(result.isError).toBe(true);
|
|
179
|
+
expect(result.content).toBe("Preprocess failed: Something unexpected");
|
|
180
|
+
});
|
|
181
|
+
});
|