@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,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clip generation tool — extract a video segment from a media asset.
|
|
3
|
+
*
|
|
4
|
+
* Uses ffmpeg to cut a segment with configurable pre/post-roll padding,
|
|
5
|
+
* then registers the resulting clip as an attachment for in-chat delivery.
|
|
6
|
+
* This is a generic media-processing primitive with no domain-specific logic.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { mkdir, unlink, stat, rmdir } from 'node:fs/promises';
|
|
12
|
+
import { randomUUID } from 'node:crypto';
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
15
|
+
import { getMediaAssetById } from '../../../../memory/media-store.js';
|
|
16
|
+
import { uploadAttachment } from '../../../../memory/attachments-store.js';
|
|
17
|
+
|
|
18
|
+
const FFMPEG_TIMEOUT_MS = 300_000;
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Helpers
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
function spawnWithTimeout(
|
|
25
|
+
cmd: string[],
|
|
26
|
+
timeoutMs: number,
|
|
27
|
+
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const proc = Bun.spawn(cmd, { stdout: 'pipe', stderr: 'pipe' });
|
|
30
|
+
const timer = setTimeout(() => {
|
|
31
|
+
proc.kill();
|
|
32
|
+
reject(new Error(`Process timed out after ${timeoutMs}ms: ${cmd[0]}`));
|
|
33
|
+
}, timeoutMs);
|
|
34
|
+
proc.exited.then(async (exitCode) => {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
const stdout = await new Response(proc.stdout).text();
|
|
37
|
+
const stderr = await new Response(proc.stderr).text();
|
|
38
|
+
resolve({ exitCode, stdout, stderr });
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the duration of a media file in seconds via ffprobe.
|
|
45
|
+
*/
|
|
46
|
+
async function getMediaDuration(filePath: string): Promise<number> {
|
|
47
|
+
const result = await spawnWithTimeout([
|
|
48
|
+
'ffprobe', '-v', 'error',
|
|
49
|
+
'-show_entries', 'format=duration',
|
|
50
|
+
'-of', 'csv=p=0',
|
|
51
|
+
filePath,
|
|
52
|
+
], 10_000);
|
|
53
|
+
if (result.exitCode !== 0) return 0;
|
|
54
|
+
return parseFloat(result.stdout.trim()) || 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function formatTimestamp(seconds: number): string {
|
|
58
|
+
const hrs = Math.floor(seconds / 3600);
|
|
59
|
+
const mins = Math.floor((seconds % 3600) / 60);
|
|
60
|
+
const secs = Math.floor(seconds % 60);
|
|
61
|
+
return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const MIME_BY_FORMAT: Record<string, string> = {
|
|
65
|
+
mp4: 'video/mp4',
|
|
66
|
+
webm: 'video/webm',
|
|
67
|
+
mov: 'video/quicktime',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Tool entry point
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
export async function run(
|
|
75
|
+
input: Record<string, unknown>,
|
|
76
|
+
context: ToolContext,
|
|
77
|
+
): Promise<ToolExecutionResult> {
|
|
78
|
+
const assetId = input.asset_id as string | undefined;
|
|
79
|
+
if (!assetId) {
|
|
80
|
+
return { content: 'asset_id is required.', isError: true };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const startTime = input.start_time as number | undefined;
|
|
84
|
+
if (startTime == null) {
|
|
85
|
+
return { content: 'start_time is required (seconds).', isError: true };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const endTime = input.end_time as number | undefined;
|
|
89
|
+
if (endTime == null) {
|
|
90
|
+
return { content: 'end_time is required (seconds).', isError: true };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (endTime <= startTime) {
|
|
94
|
+
return { content: 'end_time must be greater than start_time.', isError: true };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const preRoll = (input.pre_roll as number) ?? 3;
|
|
98
|
+
const postRoll = (input.post_roll as number) ?? 2;
|
|
99
|
+
const outputFormat = (input.output_format as string) ?? 'mp4';
|
|
100
|
+
|
|
101
|
+
const asset = getMediaAssetById(assetId);
|
|
102
|
+
if (!asset) {
|
|
103
|
+
return { content: `Media asset not found: ${assetId}`, isError: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (asset.mediaType !== 'video') {
|
|
107
|
+
return { content: `Clip generation requires a video asset. Got: ${asset.mediaType}`, isError: true };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Get the file duration so we can clamp pre/post-roll to file boundaries
|
|
111
|
+
const fileDuration = asset.durationSeconds ?? await getMediaDuration(asset.filePath);
|
|
112
|
+
|
|
113
|
+
// Calculate actual clip boundaries with pre/post-roll, clamped to file
|
|
114
|
+
const clipStart = Math.max(0, startTime - preRoll);
|
|
115
|
+
const clipEnd = fileDuration > 0 ? Math.min(fileDuration, endTime + postRoll) : endTime + postRoll;
|
|
116
|
+
const clipDuration = clipEnd - clipStart;
|
|
117
|
+
|
|
118
|
+
// Prepare output path
|
|
119
|
+
const clipDir = join(tmpdir(), `vellum-clips-${randomUUID()}`);
|
|
120
|
+
await mkdir(clipDir, { recursive: true });
|
|
121
|
+
|
|
122
|
+
const clipFilename = `clip-${formatTimestamp(startTime).replace(/:/g, '')}-${formatTimestamp(endTime).replace(/:/g, '')}.${outputFormat}`;
|
|
123
|
+
const clipPath = join(clipDir, clipFilename);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
context.onOutput?.(`Extracting clip ${formatTimestamp(clipStart)} – ${formatTimestamp(clipEnd)} from ${asset.title}...\n`);
|
|
127
|
+
|
|
128
|
+
// Use ffmpeg to extract the segment
|
|
129
|
+
const ffmpegArgs = [
|
|
130
|
+
'ffmpeg', '-y',
|
|
131
|
+
'-ss', String(clipStart),
|
|
132
|
+
'-i', asset.filePath,
|
|
133
|
+
'-t', String(clipDuration),
|
|
134
|
+
'-c', 'copy',
|
|
135
|
+
'-avoid_negative_ts', 'make_zero',
|
|
136
|
+
clipPath,
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
const result = await spawnWithTimeout(ffmpegArgs, FFMPEG_TIMEOUT_MS);
|
|
140
|
+
|
|
141
|
+
if (result.exitCode !== 0) {
|
|
142
|
+
return {
|
|
143
|
+
content: `ffmpeg clip extraction failed: ${result.stderr.slice(0, 500)}`,
|
|
144
|
+
isError: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Verify the output file exists and has content
|
|
149
|
+
const clipStat = await stat(clipPath);
|
|
150
|
+
if (clipStat.size === 0) {
|
|
151
|
+
return { content: 'Clip extraction produced an empty file.', isError: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
context.onOutput?.(`Clip extracted (${(clipStat.size / 1024 / 1024).toFixed(1)} MB). Registering as attachment...\n`);
|
|
155
|
+
|
|
156
|
+
// Read clip file and register as attachment
|
|
157
|
+
const clipData = await readFile(clipPath);
|
|
158
|
+
const clipBase64 = clipData.toString('base64');
|
|
159
|
+
const mimeType = MIME_BY_FORMAT[outputFormat] ?? 'video/mp4';
|
|
160
|
+
|
|
161
|
+
const attachment = uploadAttachment(clipFilename, mimeType, clipBase64);
|
|
162
|
+
|
|
163
|
+
context.onOutput?.(`Clip registered as attachment ${attachment.id}.\n`);
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
content: JSON.stringify({
|
|
167
|
+
message: `Clip extracted successfully`,
|
|
168
|
+
attachmentId: attachment.id,
|
|
169
|
+
filename: clipFilename,
|
|
170
|
+
mimeType,
|
|
171
|
+
sizeBytes: attachment.sizeBytes,
|
|
172
|
+
clipStart,
|
|
173
|
+
clipEnd,
|
|
174
|
+
clipDuration,
|
|
175
|
+
requestedRange: {
|
|
176
|
+
startTime,
|
|
177
|
+
endTime,
|
|
178
|
+
preRoll,
|
|
179
|
+
postRoll,
|
|
180
|
+
},
|
|
181
|
+
assetId,
|
|
182
|
+
}, null, 2),
|
|
183
|
+
isError: false,
|
|
184
|
+
};
|
|
185
|
+
} catch (err) {
|
|
186
|
+
return {
|
|
187
|
+
content: `Clip generation failed: ${(err as Error).message}`,
|
|
188
|
+
isError: true,
|
|
189
|
+
};
|
|
190
|
+
} finally {
|
|
191
|
+
// Clean up temp file and directory
|
|
192
|
+
try { await unlink(clipPath); } catch { /* ignore */ }
|
|
193
|
+
try { await rmdir(clipDir); } catch { /* ignore */ }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { basename, extname } from 'node:path';
|
|
2
|
+
import { access } from 'node:fs/promises';
|
|
3
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
4
|
+
import {
|
|
5
|
+
registerMediaAsset,
|
|
6
|
+
getMediaAssetByHash,
|
|
7
|
+
createProcessingStage,
|
|
8
|
+
updateMediaAssetStatus,
|
|
9
|
+
computeFileHash,
|
|
10
|
+
type MediaType,
|
|
11
|
+
} from '../../../../memory/media-store.js';
|
|
12
|
+
import { enqueueMemoryJob } from '../../../../memory/jobs-store.js';
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// MIME detection
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
const MIME_MAP: Record<string, string> = {
|
|
19
|
+
// Video
|
|
20
|
+
'.mp4': 'video/mp4', '.mov': 'video/quicktime', '.avi': 'video/x-msvideo',
|
|
21
|
+
'.mkv': 'video/x-matroska', '.webm': 'video/webm', '.m4v': 'video/x-m4v',
|
|
22
|
+
'.mpeg': 'video/mpeg', '.mpg': 'video/mpeg', '.ts': 'video/mp2t',
|
|
23
|
+
'.flv': 'video/x-flv', '.wmv': 'video/x-ms-wmv',
|
|
24
|
+
// Audio
|
|
25
|
+
'.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.m4a': 'audio/x-m4a',
|
|
26
|
+
'.aac': 'audio/aac', '.ogg': 'audio/ogg', '.flac': 'audio/flac',
|
|
27
|
+
'.aiff': 'audio/aiff', '.wma': 'audio/x-ms-wma',
|
|
28
|
+
// Image
|
|
29
|
+
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
30
|
+
'.gif': 'image/gif', '.webp': 'image/webp', '.bmp': 'image/bmp',
|
|
31
|
+
'.tiff': 'image/tiff', '.svg': 'image/svg+xml', '.heic': 'image/heic',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function detectMimeType(filePath: string): string | null {
|
|
35
|
+
const ext = extname(filePath).toLowerCase();
|
|
36
|
+
return MIME_MAP[ext] ?? null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function classifyMediaType(mimeType: string): MediaType | null {
|
|
40
|
+
if (mimeType.startsWith('video/')) return 'video';
|
|
41
|
+
if (mimeType.startsWith('audio/')) return 'audio';
|
|
42
|
+
if (mimeType.startsWith('image/')) return 'image';
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// ffprobe duration extraction
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
function spawnWithTimeout(
|
|
51
|
+
cmd: string[],
|
|
52
|
+
timeoutMs: number,
|
|
53
|
+
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const proc = Bun.spawn(cmd, { stdout: 'pipe', stderr: 'pipe' });
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
proc.kill();
|
|
58
|
+
reject(new Error(`Process timed out after ${timeoutMs}ms: ${cmd[0]}`));
|
|
59
|
+
}, timeoutMs);
|
|
60
|
+
proc.exited.then(async (exitCode) => {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
const stdout = await new Response(proc.stdout).text();
|
|
63
|
+
const stderr = await new Response(proc.stderr).text();
|
|
64
|
+
resolve({ exitCode, stdout, stderr });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function extractDuration(filePath: string): Promise<number | null> {
|
|
70
|
+
try {
|
|
71
|
+
const result = await spawnWithTimeout([
|
|
72
|
+
'ffprobe', '-v', 'error',
|
|
73
|
+
'-show_entries', 'format=duration',
|
|
74
|
+
'-of', 'csv=p=0',
|
|
75
|
+
filePath,
|
|
76
|
+
], 15_000);
|
|
77
|
+
if (result.exitCode !== 0) return null;
|
|
78
|
+
const duration = parseFloat(result.stdout.trim());
|
|
79
|
+
return Number.isFinite(duration) ? duration : null;
|
|
80
|
+
} catch {
|
|
81
|
+
// ffprobe not available or timed out
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Main entry point
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
export async function run(
|
|
91
|
+
input: Record<string, unknown>,
|
|
92
|
+
context: ToolContext,
|
|
93
|
+
): Promise<ToolExecutionResult> {
|
|
94
|
+
const filePath = input.file_path as string | undefined;
|
|
95
|
+
if (!filePath) {
|
|
96
|
+
return { content: 'file_path is required. Provide an absolute path to a local media file.', isError: true };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Validate file exists
|
|
100
|
+
try {
|
|
101
|
+
await access(filePath);
|
|
102
|
+
} catch {
|
|
103
|
+
return { content: `File not found: ${filePath}`, isError: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Detect MIME type
|
|
107
|
+
const mimeType = detectMimeType(filePath);
|
|
108
|
+
if (!mimeType) {
|
|
109
|
+
return {
|
|
110
|
+
content: `Unsupported file type: ${extname(filePath)}. Supported: video (mp4, mov, avi, mkv, webm, etc.), audio (mp3, wav, m4a, etc.), image (png, jpg, gif, webp, etc.).`,
|
|
111
|
+
isError: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const mediaType = classifyMediaType(mimeType);
|
|
116
|
+
if (!mediaType) {
|
|
117
|
+
return { content: `Could not classify media type for MIME: ${mimeType}`, isError: true };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Compute content hash for dedup – uses the same Bun.hash (wyhash) as
|
|
121
|
+
// media-store.ts so that hashes are consistent across ingest and lookup.
|
|
122
|
+
context.onOutput?.('Computing content hash...\n');
|
|
123
|
+
const fileBytes = await Bun.file(filePath).arrayBuffer();
|
|
124
|
+
const fileHash = computeFileHash(new Uint8Array(fileBytes));
|
|
125
|
+
|
|
126
|
+
// Check for existing asset with same hash
|
|
127
|
+
const existingAsset = getMediaAssetByHash(fileHash);
|
|
128
|
+
if (existingAsset) {
|
|
129
|
+
return {
|
|
130
|
+
content: JSON.stringify({
|
|
131
|
+
message: 'Media asset already registered (duplicate detected by content hash)',
|
|
132
|
+
asset: existingAsset,
|
|
133
|
+
deduplicated: true,
|
|
134
|
+
}, null, 2),
|
|
135
|
+
isError: false,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Extract duration for video/audio
|
|
140
|
+
let durationSeconds: number | null = null;
|
|
141
|
+
if (mediaType === 'video' || mediaType === 'audio') {
|
|
142
|
+
context.onOutput?.('Extracting duration via ffprobe...\n');
|
|
143
|
+
durationSeconds = await extractDuration(filePath);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Determine title
|
|
147
|
+
const title = (input.title as string) || basename(filePath);
|
|
148
|
+
|
|
149
|
+
// Parse optional metadata
|
|
150
|
+
let metadata: Record<string, unknown> | undefined;
|
|
151
|
+
if (input.metadata && typeof input.metadata === 'object') {
|
|
152
|
+
metadata = input.metadata as Record<string, unknown>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Register the asset
|
|
156
|
+
const asset = registerMediaAsset({
|
|
157
|
+
title,
|
|
158
|
+
filePath,
|
|
159
|
+
mimeType,
|
|
160
|
+
durationSeconds,
|
|
161
|
+
fileHash,
|
|
162
|
+
mediaType,
|
|
163
|
+
metadata,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Create an initial processing stage
|
|
167
|
+
createProcessingStage({
|
|
168
|
+
assetId: asset.id,
|
|
169
|
+
stage: 'ingest',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Update status to processing
|
|
173
|
+
updateMediaAssetStatus(asset.id, 'processing');
|
|
174
|
+
|
|
175
|
+
// Enqueue a processing job via the existing jobs framework
|
|
176
|
+
enqueueMemoryJob('media_processing', {
|
|
177
|
+
mediaAssetId: asset.id,
|
|
178
|
+
stage: 'ingest',
|
|
179
|
+
filePath,
|
|
180
|
+
mimeType,
|
|
181
|
+
mediaType,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
context.onOutput?.(`Registered media asset: ${asset.id}\n`);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
content: JSON.stringify({
|
|
188
|
+
message: 'Media asset registered and processing enqueued',
|
|
189
|
+
asset: {
|
|
190
|
+
...asset,
|
|
191
|
+
status: 'processing',
|
|
192
|
+
},
|
|
193
|
+
deduplicated: false,
|
|
194
|
+
}, null, 2),
|
|
195
|
+
isError: false,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media diagnostics tool.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces processing stats, per-stage timing, failure reasons,
|
|
5
|
+
* and cost estimation for a media asset.
|
|
6
|
+
* All metrics are generic media-processing infrastructure.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join, dirname } from 'node:path';
|
|
10
|
+
import { readFile } from 'node:fs/promises';
|
|
11
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
12
|
+
import {
|
|
13
|
+
getMediaAssetById,
|
|
14
|
+
getProcessingStagesForAsset,
|
|
15
|
+
getKeyframesForAsset,
|
|
16
|
+
type ProcessingStage,
|
|
17
|
+
} from '../../../../memory/media-store.js';
|
|
18
|
+
import type { PreprocessManifest } from '../services/preprocess.js';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Cost estimation constants (Gemini 2.5 Flash pricing)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/** Estimated cost per segment Map call (Gemini 2.5 Flash with ~10 frames). */
|
|
24
|
+
const ESTIMATED_COST_PER_SEGMENT_USD = 0.001;
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Types
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
interface StageDiagnostic {
|
|
31
|
+
stage: string;
|
|
32
|
+
status: string;
|
|
33
|
+
progress: number;
|
|
34
|
+
durationMs: number | null;
|
|
35
|
+
lastError: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DiagnosticReport {
|
|
39
|
+
assetId: string;
|
|
40
|
+
assetTitle: string;
|
|
41
|
+
assetStatus: string;
|
|
42
|
+
mediaType: string;
|
|
43
|
+
durationSeconds: number | null;
|
|
44
|
+
processingStats: {
|
|
45
|
+
totalKeyframes: number;
|
|
46
|
+
};
|
|
47
|
+
stages: StageDiagnostic[];
|
|
48
|
+
costEstimate: {
|
|
49
|
+
keyframeCount: number;
|
|
50
|
+
estimatedSegments: number;
|
|
51
|
+
estimatedCostPerSegment: number;
|
|
52
|
+
estimatedTotalCost: number;
|
|
53
|
+
currency: string;
|
|
54
|
+
note: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Helpers
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
function computeStageDuration(stage: ProcessingStage): number | null {
|
|
63
|
+
if (stage.startedAt == null) return null;
|
|
64
|
+
if (stage.completedAt != null) return stage.completedAt - stage.startedAt;
|
|
65
|
+
// Only use Date.now() as a fallback for currently running stages
|
|
66
|
+
if (stage.status === 'running') return Date.now() - stage.startedAt;
|
|
67
|
+
// For failed/pending stages without completedAt, duration is unknown
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Main entry point
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
export async function run(
|
|
76
|
+
input: Record<string, unknown>,
|
|
77
|
+
_context: ToolContext,
|
|
78
|
+
): Promise<ToolExecutionResult> {
|
|
79
|
+
const assetId = input.asset_id as string | undefined;
|
|
80
|
+
if (!assetId) {
|
|
81
|
+
return { content: 'asset_id is required.', isError: true };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const asset = getMediaAssetById(assetId);
|
|
85
|
+
if (!asset) {
|
|
86
|
+
return { content: `Media asset not found: ${assetId}`, isError: true };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Gather processing stats
|
|
90
|
+
const keyframes = getKeyframesForAsset(assetId);
|
|
91
|
+
|
|
92
|
+
// Per-stage diagnostics (new pipeline: preprocess, map, reduce)
|
|
93
|
+
const stages = getProcessingStagesForAsset(assetId);
|
|
94
|
+
const stageDiagnostics: StageDiagnostic[] = stages.map((s) => ({
|
|
95
|
+
stage: s.stage,
|
|
96
|
+
status: s.status,
|
|
97
|
+
progress: s.progress,
|
|
98
|
+
durationMs: computeStageDuration(s),
|
|
99
|
+
lastError: s.lastError,
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
// Cost estimation: Gemini 2.5 Flash is ~$0.001 per segment (~10 frames each)
|
|
103
|
+
const keyframeCount = keyframes.length;
|
|
104
|
+
|
|
105
|
+
// Prefer actual segment count from preprocess manifest when available
|
|
106
|
+
let estimatedSegments: number;
|
|
107
|
+
const manifestPath = join(dirname(asset.filePath), 'pipeline', asset.id, 'manifest.json');
|
|
108
|
+
try {
|
|
109
|
+
const raw = await readFile(manifestPath, 'utf-8');
|
|
110
|
+
const manifest: PreprocessManifest = JSON.parse(raw);
|
|
111
|
+
estimatedSegments = manifest.segments.length;
|
|
112
|
+
} catch {
|
|
113
|
+
// Manifest doesn't exist yet (preprocess hasn't run) — fall back to estimation
|
|
114
|
+
estimatedSegments = Math.ceil(keyframeCount / 10);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const estimatedTotalCost = estimatedSegments * ESTIMATED_COST_PER_SEGMENT_USD;
|
|
118
|
+
|
|
119
|
+
const report: DiagnosticReport = {
|
|
120
|
+
assetId: asset.id,
|
|
121
|
+
assetTitle: asset.title,
|
|
122
|
+
assetStatus: asset.status,
|
|
123
|
+
mediaType: asset.mediaType,
|
|
124
|
+
durationSeconds: asset.durationSeconds,
|
|
125
|
+
processingStats: {
|
|
126
|
+
totalKeyframes: keyframeCount,
|
|
127
|
+
},
|
|
128
|
+
stages: stageDiagnostics,
|
|
129
|
+
costEstimate: {
|
|
130
|
+
keyframeCount,
|
|
131
|
+
estimatedSegments,
|
|
132
|
+
estimatedCostPerSegment: ESTIMATED_COST_PER_SEGMENT_USD,
|
|
133
|
+
estimatedTotalCost: Math.round(estimatedTotalCost * 1000) / 1000,
|
|
134
|
+
currency: 'USD',
|
|
135
|
+
note: 'Gemini 2.5 Flash for Map phase; Claude for Reduce phase (additional cost).',
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
content: JSON.stringify(report, null, 2),
|
|
141
|
+
isError: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
2
|
+
import {
|
|
3
|
+
getMediaAssetById,
|
|
4
|
+
getMediaAssetByFilePath,
|
|
5
|
+
getMediaAssetsByStatus,
|
|
6
|
+
getProcessingStagesForAsset,
|
|
7
|
+
type MediaAssetStatus,
|
|
8
|
+
} from '../../../../memory/media-store.js';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Main entry point
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export async function run(
|
|
15
|
+
input: Record<string, unknown>,
|
|
16
|
+
_context: ToolContext,
|
|
17
|
+
): Promise<ToolExecutionResult> {
|
|
18
|
+
const assetId = input.asset_id as string | undefined;
|
|
19
|
+
const filePath = input.file_path as string | undefined;
|
|
20
|
+
const statusFilter = input.status_filter as MediaAssetStatus | undefined;
|
|
21
|
+
|
|
22
|
+
// Query by asset ID
|
|
23
|
+
if (assetId) {
|
|
24
|
+
const asset = getMediaAssetById(assetId);
|
|
25
|
+
if (!asset) {
|
|
26
|
+
return { content: `Media asset not found: ${assetId}`, isError: true };
|
|
27
|
+
}
|
|
28
|
+
const stages = getProcessingStagesForAsset(asset.id);
|
|
29
|
+
return {
|
|
30
|
+
content: JSON.stringify({ asset, stages }, null, 2),
|
|
31
|
+
isError: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Query by file path
|
|
36
|
+
if (filePath) {
|
|
37
|
+
const asset = getMediaAssetByFilePath(filePath);
|
|
38
|
+
if (!asset) {
|
|
39
|
+
return { content: `No media asset found for path: ${filePath}`, isError: true };
|
|
40
|
+
}
|
|
41
|
+
const stages = getProcessingStagesForAsset(asset.id);
|
|
42
|
+
return {
|
|
43
|
+
content: JSON.stringify({ asset, stages }, null, 2),
|
|
44
|
+
isError: false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Query by status filter
|
|
49
|
+
if (statusFilter) {
|
|
50
|
+
const validStatuses: MediaAssetStatus[] = ['registered', 'processing', 'indexed', 'failed'];
|
|
51
|
+
if (!validStatuses.includes(statusFilter)) {
|
|
52
|
+
return {
|
|
53
|
+
content: `Invalid status filter: ${statusFilter}. Valid values: ${validStatuses.join(', ')}`,
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const assets = getMediaAssetsByStatus(statusFilter);
|
|
58
|
+
const results = assets.map((asset) => ({
|
|
59
|
+
asset,
|
|
60
|
+
stages: getProcessingStagesForAsset(asset.id),
|
|
61
|
+
}));
|
|
62
|
+
return {
|
|
63
|
+
content: JSON.stringify({
|
|
64
|
+
count: results.length,
|
|
65
|
+
assets: results,
|
|
66
|
+
}, null, 2),
|
|
67
|
+
isError: false,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
content: 'Provide at least one query parameter: asset_id, file_path, or status_filter.',
|
|
73
|
+
isError: true,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query media tool — sends natural language queries against video
|
|
3
|
+
* analysis data (map output) via Claude for intelligent answers.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the old keyword-matching approach with an LLM-powered
|
|
6
|
+
* reduce/query step that can answer arbitrary questions about the
|
|
7
|
+
* video content.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
|
|
11
|
+
import { reduceForAsset, type ReduceOptions, type ReduceResult } from '../services/reduce.js';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Exported function for job handler use (one-shot merge mode)
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export { reduceForAsset } from '../services/reduce.js';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Tool entry point
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export async function run(
|
|
24
|
+
input: Record<string, unknown>,
|
|
25
|
+
context: ToolContext,
|
|
26
|
+
): Promise<ToolExecutionResult> {
|
|
27
|
+
const assetId = input.asset_id as string | undefined;
|
|
28
|
+
if (!assetId) {
|
|
29
|
+
return { content: 'asset_id is required.', isError: true };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const query = input.query as string | undefined;
|
|
33
|
+
if (!query) {
|
|
34
|
+
return { content: 'query is required.', isError: true };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const systemPrompt = input.system_prompt as string | undefined;
|
|
38
|
+
const model = input.model as string | undefined;
|
|
39
|
+
|
|
40
|
+
const options: ReduceOptions = {
|
|
41
|
+
query,
|
|
42
|
+
systemPrompt,
|
|
43
|
+
model,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result: ReduceResult = await reduceForAsset(assetId, options, context.onOutput);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
content: JSON.stringify({
|
|
51
|
+
query,
|
|
52
|
+
answer: result.answer,
|
|
53
|
+
model: result.model,
|
|
54
|
+
usage: {
|
|
55
|
+
inputTokens: result.inputTokens,
|
|
56
|
+
outputTokens: result.outputTokens,
|
|
57
|
+
},
|
|
58
|
+
}, null, 2),
|
|
59
|
+
isError: false,
|
|
60
|
+
};
|
|
61
|
+
} catch (err) {
|
|
62
|
+
const msg = (err as Error).message;
|
|
63
|
+
return { content: msg, isError: true };
|
|
64
|
+
}
|
|
65
|
+
}
|