@vellumai/assistant 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +2 -0
- package/README.md +88 -2
- package/eslint.config.mjs +31 -0
- package/package.json +1 -1
- package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
- package/scripts/ipc/generate-swift.ts +31 -2
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
- package/src/__tests__/approval-conversation-turn.test.ts +214 -0
- package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
- package/src/__tests__/approval-message-composer.test.ts +253 -0
- package/src/__tests__/browser-manager.test.ts +1 -0
- package/src/__tests__/call-conversation-messages.test.ts +130 -0
- package/src/__tests__/call-domain.test.ts +12 -2
- package/src/__tests__/call-orchestrator.test.ts +799 -249
- package/src/__tests__/call-pointer-messages.test.ts +148 -0
- package/src/__tests__/call-recovery.test.ts +3 -0
- package/src/__tests__/call-routes-http.test.ts +32 -2
- package/src/__tests__/call-store.test.ts +3 -0
- package/src/__tests__/channel-approval-routes.test.ts +1277 -98
- package/src/__tests__/channel-approval.test.ts +37 -0
- package/src/__tests__/channel-approvals.test.ts +36 -50
- package/src/__tests__/channel-guardian.test.ts +630 -22
- package/src/__tests__/channel-readiness-service.test.ts +324 -0
- package/src/__tests__/checker.test.ts +14 -7
- package/src/__tests__/clarification-resolver.test.ts +44 -24
- package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
- package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
- package/src/__tests__/config-schema.test.ts +14 -8
- package/src/__tests__/context-window-manager.test.ts +30 -2
- package/src/__tests__/contradiction-checker.test.ts +20 -5
- package/src/__tests__/credential-security-invariants.test.ts +7 -2
- package/src/__tests__/daemon-lifecycle.test.ts +13 -12
- package/src/__tests__/db-migration-rollback.test.ts +752 -0
- package/src/__tests__/dictation-mode-detection.test.ts +63 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
- package/src/__tests__/entity-search.test.ts +615 -0
- package/src/__tests__/fuzzy-match-property.test.ts +5 -5
- package/src/__tests__/guardian-action-store.test.ts +123 -0
- package/src/__tests__/guardian-action-sweep.test.ts +277 -0
- package/src/__tests__/guardian-dispatch.test.ts +389 -0
- package/src/__tests__/guardian-question-copy.test.ts +47 -0
- package/src/__tests__/handlers-telegram-config.test.ts +4 -2
- package/src/__tests__/handlers-twilio-config.test.ts +533 -0
- package/src/__tests__/intent-routing.test.ts +2 -0
- package/src/__tests__/ipc-snapshot.test.ts +291 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
- package/src/__tests__/messaging-send-tool.test.ts +65 -0
- package/src/__tests__/model-intents.test.ts +96 -0
- package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
- package/src/__tests__/provider-error-scenarios.test.ts +621 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
- package/src/__tests__/qdrant-manager.test.ts +27 -20
- package/src/__tests__/relay-server.test.ts +779 -40
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
- package/src/__tests__/run-orchestrator.test.ts +42 -4
- package/src/__tests__/runtime-runs-http.test.ts +17 -1
- package/src/__tests__/runtime-runs.test.ts +16 -0
- package/src/__tests__/schedule-store.test.ts +18 -4
- package/src/__tests__/scheduler-recurrence.test.ts +13 -4
- package/src/__tests__/session-abort-tool-results.test.ts +6 -0
- package/src/__tests__/session-agent-loop.test.ts +857 -0
- package/src/__tests__/session-conflict-gate.test.ts +6 -0
- package/src/__tests__/session-pre-run-repair.test.ts +6 -0
- package/src/__tests__/session-profile-injection.test.ts +6 -0
- package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
- package/src/__tests__/session-queue.test.ts +6 -0
- package/src/__tests__/session-runtime-assembly.test.ts +321 -13
- package/src/__tests__/session-slash-known.test.ts +6 -0
- package/src/__tests__/session-slash-queue.test.ts +6 -0
- package/src/__tests__/session-slash-unknown.test.ts +6 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/session-workspace-injection.test.ts +6 -0
- package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
- package/src/__tests__/skills.test.ts +2 -0
- package/src/__tests__/sms-messaging-provider.test.ts +126 -0
- package/src/__tests__/starter-task-flow.test.ts +2 -0
- package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +2 -0
- package/src/__tests__/task-management-tools.test.ts +2 -2
- package/src/__tests__/task-runner.test.ts +14 -4
- package/src/__tests__/terminal-tools.test.ts +25 -19
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
- package/src/__tests__/tool-executor.test.ts +23 -24
- package/src/__tests__/trust-store.test.ts +3 -3
- package/src/__tests__/twilio-rest.test.ts +29 -0
- package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
- package/src/__tests__/twilio-routes.test.ts +167 -11
- package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
- package/src/__tests__/user-reference.test.ts +2 -0
- package/src/__tests__/voice-quality.test.ts +222 -0
- package/src/__tests__/web-search.test.ts +46 -30
- package/src/__tests__/work-item-output.test.ts +110 -0
- package/src/agent/loop.ts +1 -1
- package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
- package/src/amazon/client.ts +1418 -0
- package/src/amazon/request-extractor.ts +135 -0
- package/src/amazon/session.ts +109 -0
- package/src/autonomy/autonomy-store.ts +5 -5
- package/src/browser-extension-relay/client.ts +124 -0
- package/src/browser-extension-relay/protocol.ts +63 -0
- package/src/browser-extension-relay/server.ts +177 -0
- package/src/bundler/app-bundler.ts +3 -3
- package/src/bundler/bundle-signer.ts +1 -1
- package/src/bundler/signature-verifier.ts +1 -1
- package/src/calls/call-conversation-messages.ts +33 -0
- package/src/calls/call-domain.ts +114 -10
- package/src/calls/call-orchestrator.ts +268 -59
- package/src/calls/call-pointer-messages.ts +53 -0
- package/src/calls/call-recovery.ts +3 -8
- package/src/calls/call-store.ts +69 -87
- package/src/calls/elevenlabs-config.ts +3 -2
- package/src/calls/guardian-action-sweep.ts +105 -0
- package/src/calls/guardian-dispatch.ts +203 -0
- package/src/calls/guardian-question-copy.ts +133 -0
- package/src/calls/relay-server.ts +466 -8
- package/src/calls/speaker-identification.ts +1 -1
- package/src/calls/twilio-config.ts +22 -14
- package/src/calls/twilio-provider.ts +6 -4
- package/src/calls/twilio-rest.ts +308 -7
- package/src/calls/twilio-routes.ts +65 -12
- package/src/calls/types.ts +3 -1
- package/src/channels/types.ts +25 -0
- package/src/cli/amazon.ts +815 -0
- package/src/cli/config-commands.ts +2 -2
- package/src/cli/core-commands.ts +4 -3
- package/src/cli/influencer.ts +244 -0
- package/src/cli/map.ts +89 -6
- package/src/cli.ts +1 -1
- package/src/config/agent-schema.ts +171 -0
- package/src/config/bundled-skills/amazon/SKILL.md +127 -0
- package/src/config/bundled-skills/amazon/icon.svg +13 -0
- package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
- package/src/config/bundled-skills/browser/SKILL.md +1 -0
- package/src/config/bundled-skills/browser/TOOLS.json +17 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
- package/src/config/bundled-skills/doordash/SKILL.md +51 -51
- package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
- package/src/config/bundled-skills/influencer/SKILL.md +144 -0
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
- package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
- package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
- package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
- package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
- package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
- package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
- package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
- package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
- package/src/config/bundled-skills/messaging/SKILL.md +33 -8
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
- package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
- package/src/config/bundled-skills/twitter/SKILL.md +19 -3
- package/src/config/bundled-skills/twitter/icon.svg +14 -0
- package/src/config/bundled-tool-registry.ts +310 -0
- package/src/config/calls-schema.ts +181 -0
- package/src/config/core-schema.ts +309 -0
- package/src/config/defaults.ts +28 -3
- package/src/config/env-registry.ts +162 -0
- package/src/config/env.ts +175 -0
- package/src/config/loader.ts +6 -6
- package/src/config/memory-schema.ts +528 -0
- package/src/config/sandbox-schema.ts +55 -0
- package/src/config/schema.ts +158 -1133
- package/src/config/skill-state.ts +1 -1
- package/src/config/skills-schema.ts +32 -0
- package/src/config/skills.ts +35 -24
- package/src/config/system-prompt.ts +131 -56
- package/src/config/templates/IDENTITY.md +2 -2
- package/src/config/templates/SOUL.md +1 -1
- package/src/config/types.ts +1 -0
- package/src/config/user-reference.ts +4 -9
- package/src/config/vellum-skills/catalog.json +6 -7
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
- package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
- package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
- package/src/context/window-manager.ts +27 -7
- package/src/daemon/approval-generators.ts +186 -0
- package/src/daemon/approved-devices-store.ts +140 -0
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/classifier.ts +35 -32
- package/src/daemon/config-watcher.ts +1 -1
- package/src/daemon/daemon-control.ts +217 -0
- package/src/daemon/handlers/apps.ts +2 -3
- package/src/daemon/handlers/config-channels.ts +158 -0
- package/src/daemon/handlers/config-inbox.ts +540 -0
- package/src/daemon/handlers/config-ingress.ts +231 -0
- package/src/daemon/handlers/config-integrations.ts +258 -0
- package/src/daemon/handlers/config-model.ts +143 -0
- package/src/daemon/handlers/config-parental.ts +163 -0
- package/src/daemon/handlers/config-scheduling.ts +172 -0
- package/src/daemon/handlers/config-slack.ts +92 -0
- package/src/daemon/handlers/config-telegram.ts +301 -0
- package/src/daemon/handlers/config-tools.ts +177 -0
- package/src/daemon/handlers/config-trust.ts +104 -0
- package/src/daemon/handlers/config-twilio.ts +1080 -0
- package/src/daemon/handlers/config.ts +53 -1689
- package/src/daemon/handlers/diagnostics.ts +1 -1
- package/src/daemon/handlers/dictation.ts +180 -0
- package/src/daemon/handlers/documents.ts +18 -32
- package/src/daemon/handlers/identity.ts +14 -23
- package/src/daemon/handlers/index.ts +11 -0
- package/src/daemon/handlers/misc.ts +3 -5
- package/src/daemon/handlers/pairing.ts +98 -0
- package/src/daemon/handlers/sessions.ts +56 -5
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/handlers/skills.ts +1 -1
- package/src/daemon/handlers/twitter-auth.ts +2 -0
- package/src/daemon/handlers/work-items.ts +17 -9
- package/src/daemon/handlers/workspace-files.ts +4 -3
- package/src/daemon/install-cli-launchers.ts +113 -0
- package/src/daemon/ipc-contract/apps.ts +356 -0
- package/src/daemon/ipc-contract/browser.ts +74 -0
- package/src/daemon/ipc-contract/computer-use.ts +151 -0
- package/src/daemon/ipc-contract/diagnostics.ts +56 -0
- package/src/daemon/ipc-contract/documents.ts +74 -0
- package/src/daemon/ipc-contract/inbox.ts +209 -0
- package/src/daemon/ipc-contract/integrations.ts +284 -0
- package/src/daemon/ipc-contract/memory.ts +48 -0
- package/src/daemon/ipc-contract/messages.ts +211 -0
- package/src/daemon/ipc-contract/pairing.ts +45 -0
- package/src/daemon/ipc-contract/parental-control.ts +95 -0
- package/src/daemon/ipc-contract/schedules.ts +97 -0
- package/src/daemon/ipc-contract/sessions.ts +315 -0
- package/src/daemon/ipc-contract/shared.ts +42 -0
- package/src/daemon/ipc-contract/skills.ts +120 -0
- package/src/daemon/ipc-contract/subagents.ts +58 -0
- package/src/daemon/ipc-contract/surfaces.ts +250 -0
- package/src/daemon/ipc-contract/trust.ts +60 -0
- package/src/daemon/ipc-contract/work-items.ts +225 -0
- package/src/daemon/ipc-contract/workspace.ts +113 -0
- package/src/daemon/ipc-contract-inventory.json +70 -0
- package/src/daemon/ipc-contract-inventory.ts +55 -29
- package/src/daemon/ipc-contract.ts +229 -2426
- package/src/daemon/ipc-protocol.ts +1 -1
- package/src/daemon/ipc-validate.ts +7 -0
- package/src/daemon/lifecycle.ts +97 -377
- package/src/daemon/pairing-store.ts +177 -0
- package/src/daemon/providers-setup.ts +43 -0
- package/src/daemon/ride-shotgun-handler.ts +68 -3
- package/src/daemon/server.ts +66 -46
- package/src/daemon/session-agent-loop-handlers.ts +421 -0
- package/src/daemon/session-agent-loop.ts +117 -275
- package/src/daemon/session-dynamic-profile.ts +1 -1
- package/src/daemon/session-history.ts +1 -1
- package/src/daemon/session-media-retry.ts +1 -1
- package/src/daemon/session-messaging.ts +37 -2
- package/src/daemon/session-notifiers.ts +5 -25
- package/src/daemon/session-process.ts +99 -59
- package/src/daemon/session-queue-manager.ts +96 -4
- package/src/daemon/session-runtime-assembly.ts +199 -10
- package/src/daemon/session-surfaces.ts +19 -4
- package/src/daemon/session-tool-setup.ts +30 -30
- package/src/daemon/session-workspace.ts +1 -1
- package/src/daemon/session.ts +35 -2
- package/src/daemon/shutdown-handlers.ts +122 -0
- package/src/daemon/trace-emitter.ts +1 -1
- package/src/daemon/watch-handler.ts +36 -33
- package/src/doordash/cart-queries.ts +787 -0
- package/src/doordash/client.ts +144 -127
- package/src/doordash/order-queries.ts +85 -0
- package/src/doordash/queries.ts +10 -1308
- package/src/doordash/search-queries.ts +203 -0
- package/src/doordash/session.ts +3 -2
- package/src/doordash/store-queries.ts +246 -0
- package/src/doordash/types.ts +367 -0
- package/src/email/providers/agentmail.ts +2 -1
- package/src/email/providers/index.ts +3 -2
- package/src/email/service.ts +3 -2
- package/src/errors.ts +43 -0
- package/src/home-base/prebuilt/seed.ts +1 -1
- package/src/hooks/cli.ts +6 -5
- package/src/hooks/config.ts +6 -8
- package/src/hooks/discovery.ts +6 -5
- package/src/hooks/manager.ts +4 -3
- package/src/hooks/runner.ts +2 -2
- package/src/hooks/templates.ts +5 -5
- package/src/inbound/public-ingress-urls.ts +6 -4
- package/src/index.ts +4 -2
- package/src/influencer/client.ts +1104 -0
- package/src/instrument.ts +4 -3
- package/src/logfire.ts +4 -3
- package/src/memory/admin.ts +25 -35
- package/src/memory/attachments-store.ts +4 -7
- package/src/memory/channel-delivery-store.ts +30 -1
- package/src/memory/channel-guardian-store.ts +202 -2
- package/src/memory/clarification-resolver.ts +37 -33
- package/src/memory/conflict-store.ts +67 -61
- package/src/memory/contradiction-checker.ts +141 -117
- package/src/memory/conversation-store.ts +335 -51
- package/src/memory/db-connection.ts +27 -4
- package/src/memory/db-init.ts +265 -4
- package/src/memory/db.ts +14 -1
- package/src/memory/embedding-backend.ts +27 -5
- package/src/memory/embedding-ollama.ts +2 -1
- package/src/memory/entity-extractor.ts +38 -35
- package/src/memory/guardian-action-store.ts +430 -0
- package/src/memory/inbox-escalation-projection.ts +59 -0
- package/src/memory/inbox-thread-store.ts +218 -0
- package/src/memory/ingress-invite-store.ts +338 -0
- package/src/memory/ingress-member-store.ts +350 -0
- package/src/memory/items-extractor.ts +91 -97
- package/src/memory/job-handlers/index-maintenance.ts +3 -3
- package/src/memory/job-handlers/media-processing.ts +69 -0
- package/src/memory/job-handlers/summarization.ts +32 -26
- package/src/memory/job-utils.ts +3 -10
- package/src/memory/jobs-store.ts +8 -10
- package/src/memory/jobs-worker.ts +55 -36
- package/src/memory/media-store.ts +759 -0
- package/src/memory/migrations/001-job-deferrals.ts +45 -0
- package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
- package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
- package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
- package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
- package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
- package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
- package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
- package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
- package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
- package/src/memory/migrations/index.ts +24 -0
- package/src/memory/migrations/registry.ts +79 -0
- package/src/memory/migrations/validate-migration-state.ts +69 -0
- package/src/memory/qdrant-manager.ts +49 -8
- package/src/memory/query-builder.ts +1 -1
- package/src/memory/raw-query.ts +119 -0
- package/src/memory/recall-cache.ts +4 -1
- package/src/memory/retriever.ts +165 -47
- package/src/memory/schema-migration.ts +25 -984
- package/src/memory/schema.ts +228 -7
- package/src/memory/search/entity.ts +205 -31
- package/src/memory/search/lexical.ts +81 -52
- package/src/memory/search/ranking.ts +27 -23
- package/src/memory/search/semantic.ts +157 -19
- package/src/memory/search/types.ts +24 -0
- package/src/memory/shared-app-links-store.ts +4 -5
- package/src/memory/validation.ts +19 -0
- package/src/messaging/draft-store.ts +5 -6
- package/src/messaging/provider-types.ts +2 -0
- package/src/messaging/providers/sms/adapter.ts +201 -0
- package/src/messaging/providers/sms/client.ts +93 -0
- package/src/messaging/providers/sms/types.ts +7 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
- package/src/messaging/providers/whatsapp/adapter.ts +136 -0
- package/src/messaging/providers/whatsapp/client.ts +67 -0
- package/src/messaging/style-analyzer.ts +5 -4
- package/src/messaging/thread-summarizer.ts +61 -69
- package/src/messaging/triage-engine.ts +62 -71
- package/src/migrations/config-merge.ts +53 -0
- package/src/migrations/data-layout.ts +68 -0
- package/src/migrations/data-merge.ts +33 -0
- package/src/migrations/hooks-merge.ts +90 -0
- package/src/migrations/index.ts +6 -0
- package/src/migrations/log.ts +23 -0
- package/src/migrations/skills-merge.ts +33 -0
- package/src/migrations/workspace-layout.ts +79 -0
- package/src/permissions/checker.ts +133 -11
- package/src/permissions/prompter.ts +14 -0
- package/src/permissions/shell-identity.ts +31 -1
- package/src/permissions/trust-store.ts +21 -1
- package/src/providers/anthropic/client.ts +4 -4
- package/src/providers/failover.ts +2 -2
- package/src/providers/model-intents.ts +70 -0
- package/src/providers/ollama/client.ts +2 -1
- package/src/providers/provider-send-message.ts +176 -0
- package/src/providers/registry.ts +71 -30
- package/src/providers/retry.ts +35 -1
- package/src/providers/types.ts +12 -1
- package/src/runtime/approval-conversation-turn.ts +97 -0
- package/src/runtime/approval-message-composer.ts +253 -0
- package/src/runtime/channel-approval-parser.ts +36 -2
- package/src/runtime/channel-approvals.ts +11 -24
- package/src/runtime/channel-guardian-service.ts +88 -21
- package/src/runtime/channel-readiness-service.ts +418 -0
- package/src/runtime/channel-readiness-types.ts +35 -0
- package/src/runtime/channel-retry-sweep.ts +184 -0
- package/src/runtime/guardian-context-resolver.ts +108 -0
- package/src/runtime/http-server.ts +275 -717
- package/src/runtime/http-types.ts +59 -3
- package/src/runtime/middleware/auth.ts +116 -0
- package/src/runtime/middleware/error-handler.ts +33 -0
- package/src/runtime/middleware/twilio-validation.ts +127 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/call-routes.ts +51 -7
- package/src/runtime/routes/channel-delivery-routes.ts +170 -0
- package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
- package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
- package/src/runtime/routes/channel-route-shared.ts +144 -0
- package/src/runtime/routes/channel-routes.ts +32 -1588
- package/src/runtime/routes/conversation-routes.ts +50 -7
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/identity-routes.ts +126 -0
- package/src/runtime/routes/pairing-routes.ts +143 -0
- package/src/runtime/routes/run-routes.ts +15 -1
- package/src/runtime/run-orchestrator.ts +86 -35
- package/src/schedule/schedule-store.ts +36 -32
- package/src/schedule/scheduler.ts +3 -3
- package/src/security/encrypted-store.ts +5 -7
- package/src/security/oauth2.ts +45 -15
- package/src/security/parental-control-store.ts +183 -0
- package/src/security/secret-allowlist.ts +4 -3
- package/src/security/secret-scanner.ts +5 -5
- package/src/security/secure-keys.ts +1 -1
- package/src/security/token-manager.ts +3 -2
- package/src/services/vercel-deploy.ts +6 -2
- package/src/skills/tool-manifest.ts +3 -3
- package/src/skills/vellum-catalog-remote.ts +75 -16
- package/src/slack/slack-webhook.ts +2 -1
- package/src/swarm/orchestrator.ts +92 -1
- package/src/swarm/router-planner.ts +6 -9
- package/src/swarm/worker-prompts.ts +9 -12
- package/src/tasks/task-compiler.ts +19 -28
- package/src/tasks/task-runner.ts +1 -1
- package/src/tools/assets/materialize.ts +2 -2
- package/src/tools/assets/search.ts +15 -14
- package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
- package/src/tools/browser/auto-navigate.ts +1 -0
- package/src/tools/browser/browser-execution.ts +10 -1
- package/src/tools/browser/browser-manager.ts +119 -4
- package/src/tools/browser/network-recorder.ts +5 -0
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/broker.ts +11 -2
- package/src/tools/credentials/metadata-store.ts +18 -14
- package/src/tools/credentials/post-connect-hooks.ts +61 -0
- package/src/tools/credentials/vault.ts +49 -23
- package/src/tools/execution-target.ts +11 -1
- package/src/tools/executor.ts +68 -9
- package/src/tools/host-terminal/cli-discover.ts +1 -1
- package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
- package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
- package/src/tools/network/script-proxy/server.ts +1 -1
- package/src/tools/network/script-proxy/session-manager.ts +6 -5
- package/src/tools/network/web-fetch.ts +18 -2
- package/src/tools/network/web-search.ts +8 -4
- package/src/tools/reminder/reminder-store.ts +14 -15
- package/src/tools/schedule/create.ts +1 -0
- package/src/tools/schedule/list.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
- package/src/tools/skills/skill-script-runner.ts +24 -9
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/tasks/work-item-enqueue.ts +2 -2
- package/src/tools/terminal/evaluate-typescript.ts +21 -12
- package/src/tools/terminal/parser.ts +50 -0
- package/src/tools/types.ts +2 -0
- package/src/tools/watcher/delete.ts +6 -0
- package/src/tools/weather/service.ts +1 -1
- package/src/twitter/client.ts +190 -24
- package/src/twitter/router.ts +1 -1
- package/src/twitter/session.ts +4 -3
- package/src/util/clipboard.ts +1 -1
- package/src/util/errors.ts +65 -8
- package/src/util/fs.ts +40 -0
- package/src/util/json.ts +10 -0
- package/src/util/log-redact.ts +189 -0
- package/src/util/logger.ts +19 -17
- package/src/util/object.ts +3 -0
- package/src/util/platform.ts +105 -363
- package/src/util/pricing.ts +1 -1
- package/src/util/promise-guard.ts +1 -1
- package/src/util/retry.ts +19 -0
- package/src/util/row-mapper.ts +79 -0
- package/src/util/silently.ts +21 -0
- package/src/watcher/engine.ts +5 -1
- package/src/watcher/provider-types.ts +20 -0
- package/src/watcher/providers/github.ts +156 -0
- package/src/watcher/providers/gmail.ts +1 -0
- package/src/watcher/providers/google-calendar.ts +1 -0
- package/src/watcher/providers/linear.ts +460 -0
- package/src/watcher/providers/slack.ts +1 -0
- package/src/work-items/work-item-runner.ts +1 -1
- package/src/workspace/git-service.ts +1 -1
- package/src/workspace/provider-commit-message-generator.ts +51 -22
- package/src/__tests__/call-bridge.test.ts +0 -517
- package/src/__tests__/session-process-bridge.test.ts +0 -244
- package/src/calls/call-bridge.ts +0 -168
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Processing pipeline service.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the full media processing pipeline with reliability features:
|
|
5
|
+
* - Sequential stage execution: preprocess -> map -> reduce
|
|
6
|
+
* - Stage-level retries with exponential backoff
|
|
7
|
+
* - Resumability: checks processing_stages to find last completed stage
|
|
8
|
+
* - Cancellation support: cooperative cancellation via asset status = 'cancelled'
|
|
9
|
+
* - Idempotency: respects content-hash dedup from media-store
|
|
10
|
+
* - Graceful degradation: saves partial results on failure
|
|
11
|
+
*
|
|
12
|
+
* All reliability infrastructure is generic media-processing, not domain-specific.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
getMediaAssetById,
|
|
17
|
+
getProcessingStagesForAsset,
|
|
18
|
+
createProcessingStage,
|
|
19
|
+
updateProcessingStage,
|
|
20
|
+
updateMediaAssetStatus,
|
|
21
|
+
type ProcessingStage,
|
|
22
|
+
} from '../../../../memory/media-store.js';
|
|
23
|
+
import { computeRetryDelay, sleep } from '../../../../util/retry.js';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Types
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export type PipelineStageName =
|
|
30
|
+
| 'preprocess'
|
|
31
|
+
| 'map'
|
|
32
|
+
| 'reduce';
|
|
33
|
+
|
|
34
|
+
export interface StageHandler {
|
|
35
|
+
/** Execute the stage. Throw on failure. */
|
|
36
|
+
execute: (assetId: string, onProgress?: (msg: string) => void) => Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PipelineOptions {
|
|
40
|
+
/** Maximum retry attempts per stage (default: 3). */
|
|
41
|
+
maxRetries?: number;
|
|
42
|
+
/** Base delay in ms for exponential backoff between retries (default: 1000). */
|
|
43
|
+
baseDelayMs?: number;
|
|
44
|
+
/** Progress callback for streaming status updates. */
|
|
45
|
+
onProgress?: (message: string) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PipelineResult {
|
|
49
|
+
assetId: string;
|
|
50
|
+
completedStages: PipelineStageName[];
|
|
51
|
+
failedStage: PipelineStageName | null;
|
|
52
|
+
failureReason: string | null;
|
|
53
|
+
cancelled: boolean;
|
|
54
|
+
resumedFrom: PipelineStageName | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Pipeline stage ordering
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
const STAGE_ORDER: PipelineStageName[] = [
|
|
62
|
+
'preprocess',
|
|
63
|
+
'map',
|
|
64
|
+
'reduce',
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Helpers
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
function findOrCreateStage(assetId: string, stageName: string): ProcessingStage {
|
|
72
|
+
const stages = getProcessingStagesForAsset(assetId);
|
|
73
|
+
const existing = stages.find((s) => s.stage === stageName);
|
|
74
|
+
if (existing) return existing;
|
|
75
|
+
return createProcessingStage({ assetId, stage: stageName });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isStageCompleted(stage: ProcessingStage): boolean {
|
|
79
|
+
return stage.status === 'completed';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if the asset has been cancelled. Cooperative cancellation:
|
|
84
|
+
* the pipeline checks this between stages to allow graceful stopping.
|
|
85
|
+
*/
|
|
86
|
+
function isAssetCancelled(assetId: string): boolean {
|
|
87
|
+
const asset = getMediaAssetById(assetId);
|
|
88
|
+
if (!asset) return true;
|
|
89
|
+
return (asset.status as string) === 'cancelled';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Main pipeline
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run the full processing pipeline for a media asset.
|
|
98
|
+
*
|
|
99
|
+
* The pipeline is resumable: if previous stages are already completed,
|
|
100
|
+
* execution resumes from the first incomplete stage. Each stage is
|
|
101
|
+
* retried with exponential backoff on failure. If a stage exhausts
|
|
102
|
+
* its retries, partial results are preserved and the pipeline stops.
|
|
103
|
+
*/
|
|
104
|
+
export async function runPipeline(
|
|
105
|
+
assetId: string,
|
|
106
|
+
handlers: Record<PipelineStageName, StageHandler>,
|
|
107
|
+
options?: PipelineOptions,
|
|
108
|
+
): Promise<PipelineResult> {
|
|
109
|
+
const maxRetries = options?.maxRetries ?? 3;
|
|
110
|
+
const baseDelayMs = options?.baseDelayMs ?? 1000;
|
|
111
|
+
const onProgress = options?.onProgress;
|
|
112
|
+
|
|
113
|
+
const asset = getMediaAssetById(assetId);
|
|
114
|
+
if (!asset) {
|
|
115
|
+
throw new Error(`Media asset not found: ${assetId}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check if asset is already cancelled before forcing processing
|
|
119
|
+
if ((asset.status as string) === 'cancelled') {
|
|
120
|
+
return {
|
|
121
|
+
assetId,
|
|
122
|
+
completedStages: [],
|
|
123
|
+
failedStage: null,
|
|
124
|
+
failureReason: null,
|
|
125
|
+
cancelled: true,
|
|
126
|
+
resumedFrom: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Mark asset as processing
|
|
131
|
+
updateMediaAssetStatus(assetId, 'processing');
|
|
132
|
+
|
|
133
|
+
const completedStages: PipelineStageName[] = [];
|
|
134
|
+
let failedStage: PipelineStageName | null = null;
|
|
135
|
+
let failureReason: string | null = null;
|
|
136
|
+
let cancelled = false;
|
|
137
|
+
let resumedFrom: PipelineStageName | null = null;
|
|
138
|
+
|
|
139
|
+
// Find where to resume from by checking existing stage records
|
|
140
|
+
let startIndex = 0;
|
|
141
|
+
for (let i = 0; i < STAGE_ORDER.length; i++) {
|
|
142
|
+
const stage = findOrCreateStage(assetId, STAGE_ORDER[i]);
|
|
143
|
+
if (isStageCompleted(stage)) {
|
|
144
|
+
completedStages.push(STAGE_ORDER[i]);
|
|
145
|
+
startIndex = i + 1;
|
|
146
|
+
} else {
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (startIndex > 0 && startIndex < STAGE_ORDER.length) {
|
|
152
|
+
resumedFrom = STAGE_ORDER[startIndex];
|
|
153
|
+
onProgress?.(`Resuming pipeline from stage: ${resumedFrom}`);
|
|
154
|
+
} else if (startIndex >= STAGE_ORDER.length) {
|
|
155
|
+
// All stages already completed — idempotent no-op
|
|
156
|
+
onProgress?.('All pipeline stages already completed.');
|
|
157
|
+
updateMediaAssetStatus(assetId, 'indexed');
|
|
158
|
+
return {
|
|
159
|
+
assetId,
|
|
160
|
+
completedStages,
|
|
161
|
+
failedStage: null,
|
|
162
|
+
failureReason: null,
|
|
163
|
+
cancelled: false,
|
|
164
|
+
resumedFrom: null,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Execute stages sequentially from the resume point
|
|
169
|
+
for (let i = startIndex; i < STAGE_ORDER.length; i++) {
|
|
170
|
+
const stageName = STAGE_ORDER[i];
|
|
171
|
+
|
|
172
|
+
// Cooperative cancellation check between stages
|
|
173
|
+
if (isAssetCancelled(assetId)) {
|
|
174
|
+
onProgress?.(`Pipeline cancelled before stage: ${stageName}`);
|
|
175
|
+
cancelled = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const stageRecord = findOrCreateStage(assetId, stageName);
|
|
180
|
+
const handler = handlers[stageName];
|
|
181
|
+
|
|
182
|
+
onProgress?.(`Starting stage: ${stageName}`);
|
|
183
|
+
updateProcessingStage(stageRecord.id, {
|
|
184
|
+
status: 'running',
|
|
185
|
+
startedAt: Date.now(),
|
|
186
|
+
lastError: null,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
let succeeded = false;
|
|
190
|
+
|
|
191
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
192
|
+
try {
|
|
193
|
+
if (attempt > 0) {
|
|
194
|
+
const delay = computeRetryDelay(attempt - 1, baseDelayMs);
|
|
195
|
+
onProgress?.(`Retrying stage ${stageName} (attempt ${attempt + 1}/${maxRetries + 1}) after ${Math.round(delay)}ms...`);
|
|
196
|
+
await sleep(delay);
|
|
197
|
+
|
|
198
|
+
// Re-check cancellation before retry
|
|
199
|
+
if (isAssetCancelled(assetId)) {
|
|
200
|
+
onProgress?.(`Pipeline cancelled during retry of stage: ${stageName}`);
|
|
201
|
+
cancelled = true;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
await handler.execute(assetId, onProgress);
|
|
207
|
+
|
|
208
|
+
// Mark stage as completed
|
|
209
|
+
updateProcessingStage(stageRecord.id, {
|
|
210
|
+
status: 'completed',
|
|
211
|
+
progress: 100,
|
|
212
|
+
completedAt: Date.now(),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
completedStages.push(stageName);
|
|
216
|
+
succeeded = true;
|
|
217
|
+
onProgress?.(`Completed stage: ${stageName}`);
|
|
218
|
+
break;
|
|
219
|
+
} catch (err) {
|
|
220
|
+
const errorMsg = (err as Error).message.slice(0, 500);
|
|
221
|
+
onProgress?.(`Stage ${stageName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${errorMsg}`);
|
|
222
|
+
|
|
223
|
+
// Save partial progress — the stage handler should have already
|
|
224
|
+
// persisted any partial results before throwing
|
|
225
|
+
updateProcessingStage(stageRecord.id, {
|
|
226
|
+
status: attempt >= maxRetries ? 'failed' : 'running',
|
|
227
|
+
lastError: errorMsg,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (cancelled) break;
|
|
233
|
+
|
|
234
|
+
if (!succeeded) {
|
|
235
|
+
failedStage = stageName;
|
|
236
|
+
failureReason = `Stage ${stageName} failed after ${maxRetries + 1} attempts`;
|
|
237
|
+
onProgress?.(`Pipeline stopped: ${failureReason}`);
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Update final asset status
|
|
243
|
+
if (cancelled) {
|
|
244
|
+
// Leave status as-is (already 'cancelled')
|
|
245
|
+
} else if (failedStage) {
|
|
246
|
+
updateMediaAssetStatus(assetId, 'failed');
|
|
247
|
+
} else {
|
|
248
|
+
updateMediaAssetStatus(assetId, 'indexed');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
assetId,
|
|
253
|
+
completedStages,
|
|
254
|
+
failedStage,
|
|
255
|
+
failureReason,
|
|
256
|
+
cancelled,
|
|
257
|
+
resumedFrom,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reduce service — sends Map output to Claude as text-only for analysis.
|
|
3
|
+
*
|
|
4
|
+
* Two modes:
|
|
5
|
+
* - One-shot merge: assembles all Map results into a single document,
|
|
6
|
+
* sends to Claude with the provided system prompt for analysis.
|
|
7
|
+
* - Interactive Q&A: loads existing map output + user query, sends to
|
|
8
|
+
* Claude, returns the answer.
|
|
9
|
+
*
|
|
10
|
+
* Uses the existing provider infrastructure (getConfiguredProvider) so
|
|
11
|
+
* it works with whatever LLM provider is configured.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import { join, dirname } from 'node:path';
|
|
16
|
+
import { getMediaAssetById } from '../../../../memory/media-store.js';
|
|
17
|
+
import {
|
|
18
|
+
getConfiguredProvider,
|
|
19
|
+
createTimeout,
|
|
20
|
+
extractAllText,
|
|
21
|
+
userMessage,
|
|
22
|
+
} from '../../../../providers/provider-send-message.js';
|
|
23
|
+
import type { MapOutput } from './gemini-map.js';
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Types
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
export interface ReduceOptions {
|
|
30
|
+
/** Natural language query about the video data. Optional for one-shot merge mode. */
|
|
31
|
+
query?: string;
|
|
32
|
+
/** Optional system prompt for Claude. */
|
|
33
|
+
systemPrompt?: string;
|
|
34
|
+
/** Model override. When omitted, the configured provider's default is used. */
|
|
35
|
+
model?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ReduceResult {
|
|
39
|
+
answer: string;
|
|
40
|
+
model: string;
|
|
41
|
+
inputTokens: number;
|
|
42
|
+
outputTokens: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
const REDUCE_TIMEOUT_MS = 120_000;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Load map-output.json for an asset from its pipeline directory.
|
|
53
|
+
*/
|
|
54
|
+
async function loadMapOutput(assetId: string): Promise<MapOutput> {
|
|
55
|
+
const asset = getMediaAssetById(assetId);
|
|
56
|
+
if (!asset) {
|
|
57
|
+
throw new Error(`Media asset not found: ${assetId}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const pipelineDir = join(dirname(asset.filePath), 'pipeline', assetId);
|
|
61
|
+
const mapOutputPath = join(pipelineDir, 'map-output.json');
|
|
62
|
+
|
|
63
|
+
let raw: string;
|
|
64
|
+
try {
|
|
65
|
+
raw = await readFile(mapOutputPath, 'utf-8');
|
|
66
|
+
} catch {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'No map output found. Run analyze_keyframes first to generate map-output.json.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return JSON.parse(raw) as MapOutput;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Format map output segments into a text document for Claude.
|
|
77
|
+
* Strips image data — text only.
|
|
78
|
+
*/
|
|
79
|
+
function formatMapOutputAsText(mapOutput: MapOutput): string {
|
|
80
|
+
const lines: string[] = [];
|
|
81
|
+
|
|
82
|
+
lines.push(`Video Analysis Data (asset: ${mapOutput.assetId})`);
|
|
83
|
+
lines.push(`Model: ${mapOutput.model}`);
|
|
84
|
+
lines.push(`Segments analyzed: ${mapOutput.successCount}/${mapOutput.segmentCount}`);
|
|
85
|
+
if (mapOutput.failedCount > 0) {
|
|
86
|
+
lines.push(`Failed segments: ${mapOutput.failedCount}`);
|
|
87
|
+
}
|
|
88
|
+
if (mapOutput.skippedCount > 0) {
|
|
89
|
+
lines.push(`Skipped segments: ${mapOutput.skippedCount}`);
|
|
90
|
+
}
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push('--- Segment Results ---');
|
|
93
|
+
lines.push('');
|
|
94
|
+
|
|
95
|
+
for (const segment of mapOutput.segments) {
|
|
96
|
+
const startMin = Math.floor(segment.startSeconds / 60);
|
|
97
|
+
const startSec = Math.floor(segment.startSeconds % 60);
|
|
98
|
+
const endMin = Math.floor(segment.endSeconds / 60);
|
|
99
|
+
const endSec = Math.floor(segment.endSeconds % 60);
|
|
100
|
+
const timeRange = `${startMin}:${String(startSec).padStart(2, '0')} - ${endMin}:${String(endSec).padStart(2, '0')}`;
|
|
101
|
+
|
|
102
|
+
lines.push(`[Segment ${segment.segmentId}] ${timeRange}`);
|
|
103
|
+
lines.push(JSON.stringify(segment.result, null, 2));
|
|
104
|
+
lines.push('');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return lines.join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
// Core: send to Claude via provider infrastructure
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
async function sendToClaude(
|
|
115
|
+
mapText: string,
|
|
116
|
+
query: string,
|
|
117
|
+
systemPrompt?: string,
|
|
118
|
+
model?: string,
|
|
119
|
+
onProgress?: (msg: string) => void,
|
|
120
|
+
): Promise<ReduceResult> {
|
|
121
|
+
const provider = getConfiguredProvider();
|
|
122
|
+
if (!provider) {
|
|
123
|
+
throw new Error('No LLM provider available. Please configure an API key.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const effectiveSystemPrompt = systemPrompt
|
|
127
|
+
?? 'You are an expert video analyst. You have been given structured analysis data extracted from a video. Answer the user\'s question based on this data. Be specific, reference timestamps when relevant, and provide clear, actionable insights.';
|
|
128
|
+
|
|
129
|
+
const userContent = `Here is the video analysis data:\n\n${mapText}\n\n---\n\nUser query: ${query}`;
|
|
130
|
+
|
|
131
|
+
onProgress?.('Sending map output to Claude for analysis...\n');
|
|
132
|
+
|
|
133
|
+
const { signal, cleanup } = createTimeout(REDUCE_TIMEOUT_MS);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const response = await provider.sendMessage(
|
|
137
|
+
[userMessage(userContent)],
|
|
138
|
+
[],
|
|
139
|
+
effectiveSystemPrompt,
|
|
140
|
+
{
|
|
141
|
+
config: model ? { model } : {},
|
|
142
|
+
signal,
|
|
143
|
+
},
|
|
144
|
+
);
|
|
145
|
+
cleanup();
|
|
146
|
+
|
|
147
|
+
const answer = extractAllText(response);
|
|
148
|
+
|
|
149
|
+
onProgress?.(`Reduce complete (${response.usage.inputTokens} input + ${response.usage.outputTokens} output tokens).\n`);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
answer,
|
|
153
|
+
model: response.model,
|
|
154
|
+
inputTokens: response.usage.inputTokens,
|
|
155
|
+
outputTokens: response.usage.outputTokens,
|
|
156
|
+
};
|
|
157
|
+
} finally {
|
|
158
|
+
cleanup();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// Public API
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* One-shot merge mode: load map output for an asset, send all data to
|
|
168
|
+
* Claude with a system prompt, and return the analysis.
|
|
169
|
+
*/
|
|
170
|
+
export async function reduceForAsset(
|
|
171
|
+
assetId: string,
|
|
172
|
+
options: ReduceOptions,
|
|
173
|
+
onProgress?: (msg: string) => void,
|
|
174
|
+
): Promise<ReduceResult> {
|
|
175
|
+
const mapOutput = await loadMapOutput(assetId);
|
|
176
|
+
|
|
177
|
+
if (mapOutput.segments.length === 0) {
|
|
178
|
+
throw new Error('Map output contains no segments. Run analyze_keyframes first.');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const mapText = formatMapOutputAsText(mapOutput);
|
|
182
|
+
const effectiveQuery = options.query ?? 'Summarize the video content.';
|
|
183
|
+
return sendToClaude(mapText, effectiveQuery, options.systemPrompt, options.model, onProgress);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Interactive Q&A mode: load existing map output + user query, send to
|
|
188
|
+
* Claude, return the answer. Functionally identical to reduceForAsset
|
|
189
|
+
* but named distinctly for clarity in call sites.
|
|
190
|
+
*/
|
|
191
|
+
export async function queryMapOutput(
|
|
192
|
+
assetId: string,
|
|
193
|
+
options: ReduceOptions,
|
|
194
|
+
onProgress?: (msg: string) => void,
|
|
195
|
+
): Promise<ReduceResult> {
|
|
196
|
+
return reduceForAsset(assetId, options, onProgress);
|
|
197
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
4
|
+
import { getConfig } from '../../../../config/loader.js';
|
|
5
|
+
import {
|
|
6
|
+
getMediaAssetById,
|
|
7
|
+
} from '../../../../memory/media-store.js';
|
|
8
|
+
import { mapSegments, type MapOutput } from '../services/gemini-map.js';
|
|
9
|
+
import type { PreprocessManifest } from '../services/preprocess.js';
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Exported function for job handler use
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface MapSegmentsOptions {
|
|
16
|
+
systemPrompt: string;
|
|
17
|
+
outputSchema: Record<string, unknown>;
|
|
18
|
+
context?: Record<string, unknown>;
|
|
19
|
+
model?: string;
|
|
20
|
+
concurrency?: number;
|
|
21
|
+
maxRetries?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function mapSegmentsForAsset(
|
|
25
|
+
assetId: string,
|
|
26
|
+
options: MapSegmentsOptions,
|
|
27
|
+
onProgress?: (msg: string) => void,
|
|
28
|
+
): Promise<MapOutput> {
|
|
29
|
+
const config = getConfig();
|
|
30
|
+
const apiKey = config.apiKeys.gemini;
|
|
31
|
+
|
|
32
|
+
if (!apiKey) {
|
|
33
|
+
throw new Error('No Gemini API key configured. Please set your Gemini API key to use keyframe analysis.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const asset = getMediaAssetById(assetId);
|
|
37
|
+
if (!asset) {
|
|
38
|
+
throw new Error(`Media asset not found: ${assetId}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Load preprocess manifest
|
|
42
|
+
const pipelineDir = join(dirname(asset.filePath), 'pipeline', assetId);
|
|
43
|
+
const manifestPath = join(pipelineDir, 'manifest.json');
|
|
44
|
+
|
|
45
|
+
let manifest: PreprocessManifest;
|
|
46
|
+
try {
|
|
47
|
+
const raw = await readFile(manifestPath, 'utf-8');
|
|
48
|
+
manifest = JSON.parse(raw) as PreprocessManifest;
|
|
49
|
+
} catch {
|
|
50
|
+
throw new Error('No preprocess manifest found. Run extract_keyframes first.');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (manifest.segments.length === 0) {
|
|
54
|
+
throw new Error('No segments found in preprocess manifest. Run extract_keyframes first.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return mapSegments(assetId, pipelineDir, manifest.segments, {
|
|
58
|
+
apiKey,
|
|
59
|
+
systemPrompt: options.systemPrompt,
|
|
60
|
+
outputSchema: options.outputSchema,
|
|
61
|
+
context: options.context,
|
|
62
|
+
model: options.model,
|
|
63
|
+
concurrency: options.concurrency,
|
|
64
|
+
maxRetries: options.maxRetries,
|
|
65
|
+
}, onProgress);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Tool entry point
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export async function run(
|
|
73
|
+
input: Record<string, unknown>,
|
|
74
|
+
context: ToolContext,
|
|
75
|
+
): Promise<ToolExecutionResult> {
|
|
76
|
+
const assetId = input.asset_id as string | undefined;
|
|
77
|
+
if (!assetId) {
|
|
78
|
+
return { content: 'asset_id is required.', isError: true };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const systemPrompt = input.system_prompt as string | undefined;
|
|
82
|
+
if (!systemPrompt) {
|
|
83
|
+
return { content: 'system_prompt is required.', isError: true };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const outputSchema = input.output_schema as Record<string, unknown> | undefined;
|
|
87
|
+
if (!outputSchema) {
|
|
88
|
+
return { content: 'output_schema is required.', isError: true };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const contextObj = input.context as Record<string, unknown> | undefined;
|
|
92
|
+
const model = input.model as string | undefined;
|
|
93
|
+
const concurrency = input.concurrency as number | undefined;
|
|
94
|
+
const maxRetries = input.max_retries as number | undefined;
|
|
95
|
+
|
|
96
|
+
if (concurrency !== undefined && concurrency < 1) {
|
|
97
|
+
return { content: 'concurrency must be at least 1.', isError: true };
|
|
98
|
+
}
|
|
99
|
+
if (maxRetries !== undefined && maxRetries < 0) {
|
|
100
|
+
return { content: 'max_retries must be non-negative.', isError: true };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const output = await mapSegmentsForAsset(
|
|
105
|
+
assetId,
|
|
106
|
+
{
|
|
107
|
+
systemPrompt,
|
|
108
|
+
outputSchema,
|
|
109
|
+
context: contextObj,
|
|
110
|
+
model,
|
|
111
|
+
concurrency,
|
|
112
|
+
maxRetries,
|
|
113
|
+
},
|
|
114
|
+
context.onOutput,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
content: JSON.stringify({
|
|
119
|
+
message: `Map ${output.failedCount === 0 ? 'completed' : 'completed with errors'}`,
|
|
120
|
+
assetId,
|
|
121
|
+
model: output.model,
|
|
122
|
+
segmentCount: output.segmentCount,
|
|
123
|
+
successCount: output.successCount,
|
|
124
|
+
failedCount: output.failedCount,
|
|
125
|
+
skippedCount: output.skippedCount,
|
|
126
|
+
totalInputTokens: output.costSummary.totalInputTokens,
|
|
127
|
+
totalOutputTokens: output.costSummary.totalOutputTokens,
|
|
128
|
+
estimatedCostUSD: output.costSummary.totalEstimatedUSD,
|
|
129
|
+
}, null, 2),
|
|
130
|
+
isError: false,
|
|
131
|
+
};
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const msg = (err as Error).message;
|
|
134
|
+
return { content: msg, isError: true };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { join, dirname } from 'node:path';
|
|
2
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
3
|
+
import { getMediaAssetById, getKeyframesForAsset } from '../../../../memory/media-store.js';
|
|
4
|
+
import { preprocessForAsset, type PreprocessOptions } from '../services/preprocess.js';
|
|
5
|
+
|
|
6
|
+
export { preprocessForAsset } from '../services/preprocess.js';
|
|
7
|
+
|
|
8
|
+
export async function run(
|
|
9
|
+
input: Record<string, unknown>,
|
|
10
|
+
context: ToolContext,
|
|
11
|
+
): Promise<ToolExecutionResult> {
|
|
12
|
+
const assetId = input.asset_id as string | undefined;
|
|
13
|
+
if (!assetId) {
|
|
14
|
+
return { content: 'asset_id is required.', isError: true };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const options: PreprocessOptions = {
|
|
18
|
+
intervalSeconds: (input.interval_seconds as number) || undefined,
|
|
19
|
+
segmentDuration: (input.segment_duration as number) || undefined,
|
|
20
|
+
deadTimeThreshold: (input.dead_time_threshold as number) || undefined,
|
|
21
|
+
sectionConfigPath: (input.section_config as string) || undefined,
|
|
22
|
+
skipDeadTime: input.skip_dead_time !== undefined ? Boolean(input.skip_dead_time) : undefined,
|
|
23
|
+
shortEdge: (input.short_edge as number) || undefined,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const manifest = await preprocessForAsset(assetId, options, context.onOutput);
|
|
28
|
+
|
|
29
|
+
const asset = getMediaAssetById(assetId);
|
|
30
|
+
const pipelineDir = join(dirname(asset!.filePath), 'pipeline', assetId);
|
|
31
|
+
const keyframes = getKeyframesForAsset(assetId);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
content: JSON.stringify({
|
|
35
|
+
message: `Preprocessed video: ${manifest.segments.length} segments, ${keyframes.length} keyframes`,
|
|
36
|
+
assetId,
|
|
37
|
+
segmentCount: manifest.segments.length,
|
|
38
|
+
keyframeCount: keyframes.length,
|
|
39
|
+
deadTimeRanges: manifest.deadTimeRanges.length,
|
|
40
|
+
subjectGroups: manifest.subjectRegistry.groups.length,
|
|
41
|
+
manifestPath: join(pipelineDir, 'manifest.json'),
|
|
42
|
+
config: manifest.config,
|
|
43
|
+
}, null, 2),
|
|
44
|
+
isError: false,
|
|
45
|
+
};
|
|
46
|
+
} catch (err) {
|
|
47
|
+
const msg = (err as Error).message;
|
|
48
|
+
if (
|
|
49
|
+
msg.startsWith('Media asset not found:') ||
|
|
50
|
+
msg.startsWith('Preprocess requires a video asset.') ||
|
|
51
|
+
msg.startsWith('Video asset has no duration') ||
|
|
52
|
+
msg.startsWith('ffmpeg failed:') ||
|
|
53
|
+
msg === 'No frames were extracted from the video.'
|
|
54
|
+
) {
|
|
55
|
+
return { content: msg, isError: true };
|
|
56
|
+
}
|
|
57
|
+
return { content: `Preprocess failed: ${msg}`, isError: true };
|
|
58
|
+
}
|
|
59
|
+
}
|