@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.
Files changed (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. 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
+ }