@vellumai/assistant 0.3.5 → 0.3.7

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 (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,551 @@
1
+ import { join, dirname } from 'node:path';
2
+ import { mkdir, readdir, rename, rm, writeFile, readFile } from 'node:fs/promises';
3
+ import { randomUUID } from 'node:crypto';
4
+ import {
5
+ getMediaAssetById,
6
+ insertKeyframesBatch,
7
+ deleteKeyframesForAsset,
8
+ createProcessingStage,
9
+ updateProcessingStage,
10
+ getProcessingStagesForAsset,
11
+ type ProcessingStage,
12
+ } from '../../../../memory/media-store.js';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Constants
16
+ // ---------------------------------------------------------------------------
17
+
18
+ const FFMPEG_TIMEOUT_MS = 600_000;
19
+ const MIN_FRAMES_PER_SEGMENT = 4;
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Types
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export interface PreprocessConfig {
26
+ intervalSeconds: number;
27
+ shortEdge: number;
28
+ deadTimeThreshold: number;
29
+ segmentDuration: number;
30
+ }
31
+
32
+ export interface TimeRange {
33
+ start: number;
34
+ end: number;
35
+ }
36
+
37
+ export interface Segment {
38
+ id: string;
39
+ startSeconds: number;
40
+ endSeconds: number;
41
+ framePaths: string[];
42
+ frameTimestamps: number[];
43
+ }
44
+
45
+ export interface SubjectGroup {
46
+ label: string;
47
+ dominantColor: string;
48
+ identifiers: string[];
49
+ }
50
+
51
+ export interface SubjectRegistry {
52
+ groups: SubjectGroup[];
53
+ }
54
+
55
+ export interface SectionBoundary {
56
+ label: string;
57
+ startSeconds: number;
58
+ endSeconds: number;
59
+ }
60
+
61
+ export interface PreprocessManifest {
62
+ assetId: string;
63
+ videoPath: string;
64
+ durationSeconds: number;
65
+ segments: Segment[];
66
+ deadTimeRanges: TimeRange[];
67
+ subjectRegistry: SubjectRegistry;
68
+ sectionBoundaries: SectionBoundary[];
69
+ config: PreprocessConfig;
70
+ }
71
+
72
+ export interface PreprocessOptions {
73
+ intervalSeconds?: number;
74
+ segmentDuration?: number;
75
+ deadTimeThreshold?: number;
76
+ sectionConfigPath?: string;
77
+ skipDeadTime?: boolean;
78
+ shortEdge?: number;
79
+ }
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Spawn helper (same pattern as the existing extract-keyframes)
83
+ // ---------------------------------------------------------------------------
84
+
85
+ function spawnWithTimeout(
86
+ cmd: string[],
87
+ timeoutMs: number,
88
+ ): Promise<{ exitCode: number; stdout: string; stderr: string }> {
89
+ return new Promise((resolve, reject) => {
90
+ const proc = Bun.spawn(cmd, { stdout: 'pipe', stderr: 'pipe' });
91
+ const timer = setTimeout(() => {
92
+ proc.kill();
93
+ reject(new Error(`Process timed out after ${timeoutMs}ms: ${cmd[0]}`));
94
+ }, timeoutMs);
95
+ proc.exited.then(async (exitCode) => {
96
+ clearTimeout(timer);
97
+ const stdout = await new Response(proc.stdout).text();
98
+ const stderr = await new Response(proc.stderr).text();
99
+ resolve({ exitCode, stdout, stderr });
100
+ });
101
+ });
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Pure functions (exported for testing)
106
+ // ---------------------------------------------------------------------------
107
+
108
+ /**
109
+ * Parse mpdecimate filter stderr output to extract dropped frame timestamps.
110
+ * mpdecimate logs lines like:
111
+ * pts_time:12.34 ... drop
112
+ * We collect the pts_time values of dropped frames.
113
+ */
114
+ export function parseDroppedFrameTimestamps(stderr: string): number[] {
115
+ const timestamps: number[] = [];
116
+ const lines = stderr.split('\n');
117
+ for (const line of lines) {
118
+ // mpdecimate marks dropped frames with "drop" at the end of the line
119
+ if (!line.includes('drop')) continue;
120
+ const match = line.match(/pts_time:([\d.]+)/);
121
+ if (match) {
122
+ timestamps.push(parseFloat(match[1]));
123
+ }
124
+ }
125
+ return timestamps.sort((a, b) => a - b);
126
+ }
127
+
128
+ /**
129
+ * Build dead-time ranges from dropped frame timestamps.
130
+ * Groups consecutive dropped frames and merges ranges where the gap
131
+ * between dropped frames is small (within gapThresholdSeconds).
132
+ * Only ranges longer than minDurationSeconds are kept.
133
+ */
134
+ export function buildDeadTimeRanges(
135
+ droppedTimestamps: number[],
136
+ gapThresholdSeconds: number = 1.0,
137
+ minDurationSeconds: number = 5.0,
138
+ ): TimeRange[] {
139
+ if (droppedTimestamps.length === 0) return [];
140
+
141
+ const ranges: TimeRange[] = [];
142
+ let rangeStart = droppedTimestamps[0];
143
+ let rangeEnd = droppedTimestamps[0];
144
+
145
+ for (let i = 1; i < droppedTimestamps.length; i++) {
146
+ const ts = droppedTimestamps[i];
147
+ if (ts - rangeEnd <= gapThresholdSeconds) {
148
+ // Extend current range
149
+ rangeEnd = ts;
150
+ } else {
151
+ // Close current range if long enough
152
+ if (rangeEnd - rangeStart >= minDurationSeconds) {
153
+ ranges.push({ start: rangeStart, end: rangeEnd });
154
+ }
155
+ rangeStart = ts;
156
+ rangeEnd = ts;
157
+ }
158
+ }
159
+
160
+ // Close final range
161
+ if (rangeEnd - rangeStart >= minDurationSeconds) {
162
+ ranges.push({ start: rangeStart, end: rangeEnd });
163
+ }
164
+
165
+ return ranges;
166
+ }
167
+
168
+ /**
169
+ * Compute live time ranges by subtracting dead-time ranges from the total duration.
170
+ */
171
+ export function computeLiveRanges(
172
+ durationSeconds: number,
173
+ deadTimeRanges: TimeRange[],
174
+ ): TimeRange[] {
175
+ if (deadTimeRanges.length === 0) {
176
+ return [{ start: 0, end: durationSeconds }];
177
+ }
178
+
179
+ const sorted = [...deadTimeRanges].sort((a, b) => a.start - b.start);
180
+ const live: TimeRange[] = [];
181
+ let cursor = 0;
182
+
183
+ for (const dead of sorted) {
184
+ if (dead.start > cursor) {
185
+ live.push({ start: cursor, end: dead.start });
186
+ }
187
+ cursor = Math.max(cursor, dead.end);
188
+ }
189
+
190
+ if (cursor < durationSeconds) {
191
+ live.push({ start: cursor, end: durationSeconds });
192
+ }
193
+
194
+ return live;
195
+ }
196
+
197
+ /**
198
+ * Create non-overlapping segments of a given duration within live time ranges.
199
+ */
200
+ export function createSegments(
201
+ liveRanges: TimeRange[],
202
+ segmentDuration: number,
203
+ ): Array<{ id: string; startSeconds: number; endSeconds: number }> {
204
+ if (segmentDuration <= 0) {
205
+ throw new Error(`segmentDuration must be positive, got ${segmentDuration}`);
206
+ }
207
+
208
+ const segments: Array<{ id: string; startSeconds: number; endSeconds: number }> = [];
209
+ let segIndex = 0;
210
+
211
+ for (const range of liveRanges) {
212
+ let cursor = range.start;
213
+ while (cursor < range.end) {
214
+ const end = Math.min(cursor + segmentDuration, range.end);
215
+ segIndex++;
216
+ const id = `seg-${String(segIndex).padStart(3, '0')}`;
217
+ segments.push({ id, startSeconds: cursor, endSeconds: end });
218
+ cursor = end;
219
+ }
220
+ }
221
+
222
+ return segments;
223
+ }
224
+
225
+ /**
226
+ * Compute the effective frame extraction interval for a segment,
227
+ * ensuring at least MIN_FRAMES_PER_SEGMENT frames are produced.
228
+ */
229
+ export function computeEffectiveInterval(
230
+ segmentDurationSeconds: number,
231
+ requestedInterval: number,
232
+ ): number {
233
+ const framesAtRequestedInterval = Math.floor(segmentDurationSeconds / requestedInterval);
234
+ if (framesAtRequestedInterval >= MIN_FRAMES_PER_SEGMENT) {
235
+ return requestedInterval;
236
+ }
237
+ // Reduce interval to guarantee minimum frame count
238
+ return segmentDurationSeconds / MIN_FRAMES_PER_SEGMENT;
239
+ }
240
+
241
+ /**
242
+ * Generate frame timestamps for a segment based on effective interval.
243
+ */
244
+ export function generateFrameTimestamps(
245
+ segStart: number,
246
+ segEnd: number,
247
+ interval: number,
248
+ ): number[] {
249
+ const effectiveInterval = computeEffectiveInterval(segEnd - segStart, interval);
250
+ const timestamps: number[] = [];
251
+ let t = segStart;
252
+ while (t < segEnd) {
253
+ timestamps.push(parseFloat(t.toFixed(3)));
254
+ t += effectiveInterval;
255
+ }
256
+ return timestamps;
257
+ }
258
+
259
+ /**
260
+ * Create default section boundaries by splitting the duration into equal halves.
261
+ */
262
+ export function createDefaultSections(durationSeconds: number): SectionBoundary[] {
263
+ const mid = durationSeconds / 2;
264
+ return [
265
+ { label: 'section_1', startSeconds: 0, endSeconds: mid },
266
+ { label: 'section_2', startSeconds: mid, endSeconds: durationSeconds },
267
+ ];
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Subject registry (color sampling)
272
+ // ---------------------------------------------------------------------------
273
+
274
+ /**
275
+ * Sample ~10 frames evenly spread across the video for subject registry analysis.
276
+ * Returns indices into the total frame set.
277
+ */
278
+ export function sampleFrameIndices(totalFrames: number, sampleCount: number = 10): number[] {
279
+ if (totalFrames <= sampleCount) {
280
+ return Array.from({ length: totalFrames }, (_, i) => i);
281
+ }
282
+ const step = totalFrames / sampleCount;
283
+ const indices: number[] = [];
284
+ for (let i = 0; i < sampleCount; i++) {
285
+ indices.push(Math.min(Math.floor(i * step), totalFrames - 1));
286
+ }
287
+ return indices;
288
+ }
289
+
290
+ /**
291
+ * Extract dominant colors from a frame image using ffmpeg's thumbnail and showinfo filters.
292
+ * Returns hex color strings.
293
+ */
294
+ async function extractDominantColors(framePath: string): Promise<string[]> {
295
+ // Use ffmpeg to extract a palette from the frame
296
+ const result = await spawnWithTimeout([
297
+ 'ffmpeg', '-i', framePath,
298
+ '-vf', 'palettegen=max_colors=4:stats_mode=diff',
299
+ '-f', 'null', '-',
300
+ ], 30_000);
301
+
302
+ // Fallback: return empty if analysis fails
303
+ if (result.exitCode !== 0) return [];
304
+
305
+ // Parse palette info from stderr — look for color hex values
306
+ const colors: string[] = [];
307
+ const colorMatches = result.stderr.matchAll(/0x([0-9a-fA-F]{6})/g);
308
+ for (const m of colorMatches) {
309
+ colors.push(`#${m[1].toLowerCase()}`);
310
+ }
311
+ return colors.length > 0 ? colors.slice(0, 4) : [];
312
+ }
313
+
314
+ /**
315
+ * Build a subject registry by sampling frames and detecting dominant non-background colors.
316
+ */
317
+ async function buildSubjectRegistry(framePaths: string[]): Promise<SubjectRegistry> {
318
+ if (framePaths.length === 0) {
319
+ return { groups: [] };
320
+ }
321
+
322
+ const indices = sampleFrameIndices(framePaths.length);
323
+ const sampledPaths = indices.map((i) => framePaths[i]).filter(Boolean);
324
+
325
+ // Collect all dominant colors across sampled frames
326
+ const colorCounts = new Map<string, number>();
327
+ for (const path of sampledPaths) {
328
+ const colors = await extractDominantColors(path);
329
+ for (const c of colors) {
330
+ colorCounts.set(c, (colorCounts.get(c) || 0) + 1);
331
+ }
332
+ }
333
+
334
+ // Sort by frequency and pick top groups (skip very common colors likely to be court/background)
335
+ const sorted = [...colorCounts.entries()]
336
+ .sort((a, b) => b[1] - a[1])
337
+ .slice(0, 4);
338
+
339
+ const groups: SubjectGroup[] = sorted.map((entry, i) => ({
340
+ label: `group_${String.fromCharCode(97 + i)}`,
341
+ dominantColor: entry[0],
342
+ identifiers: [],
343
+ }));
344
+
345
+ return { groups };
346
+ }
347
+
348
+ // ---------------------------------------------------------------------------
349
+ // Main preprocess function
350
+ // ---------------------------------------------------------------------------
351
+
352
+ export async function preprocessForAsset(
353
+ assetId: string,
354
+ options: PreprocessOptions = {},
355
+ onProgress?: (msg: string) => void,
356
+ ): Promise<PreprocessManifest> {
357
+ const config: PreprocessConfig = {
358
+ intervalSeconds: options.intervalSeconds ?? 3,
359
+ segmentDuration: options.segmentDuration ?? 20,
360
+ deadTimeThreshold: options.deadTimeThreshold ?? 0.02,
361
+ shortEdge: options.shortEdge ?? 480,
362
+ };
363
+
364
+ const skipDeadTime = options.skipDeadTime ?? true;
365
+
366
+ const asset = getMediaAssetById(assetId);
367
+ if (!asset) {
368
+ throw new Error(`Media asset not found: ${assetId}`);
369
+ }
370
+ if (asset.mediaType !== 'video') {
371
+ throw new Error(`Preprocess requires a video asset. Got: ${asset.mediaType}`);
372
+ }
373
+
374
+ const durationSeconds = asset.durationSeconds ?? 0;
375
+ if (durationSeconds <= 0) {
376
+ throw new Error('Video asset has no duration information.');
377
+ }
378
+
379
+ // Find or create processing stage
380
+ let stage: ProcessingStage | undefined;
381
+ const existingStages = getProcessingStagesForAsset(assetId);
382
+ stage = existingStages.find((s) => s.stage === 'preprocess');
383
+ if (!stage) {
384
+ stage = createProcessingStage({ assetId, stage: 'preprocess' });
385
+ }
386
+ updateProcessingStage(stage.id, { status: 'running', startedAt: Date.now() });
387
+
388
+ const pipelineDir = join(dirname(asset.filePath), 'pipeline', assetId);
389
+ const framesDir = join(pipelineDir, 'frames');
390
+ const tempDir = framesDir + '-tmp-' + randomUUID();
391
+ await mkdir(tempDir, { recursive: true });
392
+
393
+ try {
394
+ // Step 1: Dead-time detection
395
+ onProgress?.('Detecting dead time with mpdecimate filter...\n');
396
+ let deadTimeRanges: TimeRange[] = [];
397
+
398
+ if (skipDeadTime) {
399
+ const mpdecimateResult = await spawnWithTimeout([
400
+ 'ffmpeg', '-i', asset.filePath,
401
+ '-vf', `mpdecimate=hi=64*${config.deadTimeThreshold}:lo=64*${config.deadTimeThreshold}:frac=1`,
402
+ '-loglevel', 'debug',
403
+ '-f', 'null', '-',
404
+ ], FFMPEG_TIMEOUT_MS);
405
+
406
+ const droppedTimestamps = parseDroppedFrameTimestamps(mpdecimateResult.stderr);
407
+ deadTimeRanges = buildDeadTimeRanges(droppedTimestamps);
408
+ onProgress?.(`Found ${deadTimeRanges.length} dead-time range(s).\n`);
409
+ }
410
+
411
+ // Step 2: Segmentation
412
+ onProgress?.('Creating segments...\n');
413
+ const liveRanges = computeLiveRanges(durationSeconds, deadTimeRanges);
414
+ const rawSegments = createSegments(liveRanges, config.segmentDuration);
415
+ onProgress?.(`Created ${rawSegments.length} segment(s) from ${liveRanges.length} live range(s).\n`);
416
+
417
+ // Step 3: Frame extraction per segment
418
+ onProgress?.('Extracting frames per segment...\n');
419
+ const segments: Segment[] = [];
420
+ const allFramePaths: string[] = [];
421
+
422
+ const scaleFilter = `scale='if(gt(iw,ih),-1,${config.shortEdge})':'if(gt(iw,ih),${config.shortEdge},-1)'`;
423
+
424
+ for (const seg of rawSegments) {
425
+ const segDuration = seg.endSeconds - seg.startSeconds;
426
+ const effectiveInterval = computeEffectiveInterval(segDuration, config.intervalSeconds);
427
+ const _frameTimestamps = generateFrameTimestamps(seg.startSeconds, seg.endSeconds, config.intervalSeconds);
428
+
429
+ const segTempDir = join(tempDir, seg.id);
430
+ await mkdir(segTempDir, { recursive: true });
431
+
432
+ const vfFilter = `fps=1/${effectiveInterval},${scaleFilter}`;
433
+
434
+ const result = await spawnWithTimeout([
435
+ 'ffmpeg', '-y',
436
+ '-ss', String(seg.startSeconds),
437
+ '-t', String(segDuration),
438
+ '-i', asset.filePath,
439
+ '-vf', vfFilter,
440
+ '-q:v', '2',
441
+ join(segTempDir, 'frame-%06d.jpg'),
442
+ ], FFMPEG_TIMEOUT_MS);
443
+
444
+ if (result.exitCode !== 0) {
445
+ onProgress?.(`Warning: frame extraction failed for ${seg.id}: ${result.stderr.slice(0, 200)}\n`);
446
+ continue;
447
+ }
448
+
449
+ const files = await readdir(segTempDir);
450
+ const frameFiles = files
451
+ .filter((f) => f.startsWith('frame-') && f.endsWith('.jpg'))
452
+ .sort();
453
+
454
+ const framePaths = frameFiles.map((f) => join(framesDir, seg.id, f));
455
+ allFramePaths.push(...framePaths.map((_, i) => join(segTempDir, frameFiles[i])));
456
+
457
+ // Use actual extracted frame count to recalculate timestamps
458
+ const actualTimestamps = frameFiles.map((_, i) =>
459
+ parseFloat((seg.startSeconds + i * effectiveInterval).toFixed(3))
460
+ );
461
+
462
+ segments.push({
463
+ id: seg.id,
464
+ startSeconds: seg.startSeconds,
465
+ endSeconds: seg.endSeconds,
466
+ framePaths,
467
+ frameTimestamps: actualTimestamps,
468
+ });
469
+ }
470
+
471
+ const totalFrames = segments.reduce((sum, s) => sum + s.framePaths.length, 0);
472
+ if (rawSegments.length > 0 && totalFrames === 0) {
473
+ throw new Error(
474
+ `All ${rawSegments.length} segment(s) failed frame extraction — zero usable frames produced.`,
475
+ );
476
+ }
477
+ onProgress?.(`Extracted ${totalFrames} total frames across ${segments.length} segments.\n`);
478
+
479
+ // Atomically swap temp dir to durable path
480
+ await rm(framesDir, { recursive: true, force: true });
481
+ await mkdir(dirname(framesDir), { recursive: true });
482
+ await rename(tempDir, framesDir);
483
+
484
+ // Step 4: Subject registry
485
+ onProgress?.('Building subject registry...\n');
486
+ const allExtractedPaths = segments.flatMap((s) => s.framePaths);
487
+ const subjectRegistry = await buildSubjectRegistry(allExtractedPaths);
488
+ onProgress?.(`Identified ${subjectRegistry.groups.length} subject group(s).\n`);
489
+
490
+ // Step 5: Section boundaries
491
+ let sectionBoundaries: SectionBoundary[];
492
+ if (options.sectionConfigPath) {
493
+ const raw = await readFile(options.sectionConfigPath, 'utf-8');
494
+ sectionBoundaries = JSON.parse(raw) as SectionBoundary[];
495
+ } else {
496
+ sectionBoundaries = createDefaultSections(durationSeconds);
497
+ }
498
+
499
+ // Step 6: Register keyframes in DB
500
+ onProgress?.('Registering keyframes in database...\n');
501
+ deleteKeyframesForAsset(assetId);
502
+
503
+ const keyframeRows = segments.flatMap((seg) =>
504
+ seg.framePaths.map((fp, i) => ({
505
+ assetId,
506
+ timestamp: seg.frameTimestamps[i] ?? seg.startSeconds + i * config.intervalSeconds,
507
+ filePath: fp,
508
+ metadata: { segmentId: seg.id, frameIndex: i, intervalSeconds: config.intervalSeconds },
509
+ }))
510
+ );
511
+
512
+ if (keyframeRows.length > 0) {
513
+ insertKeyframesBatch(keyframeRows);
514
+ }
515
+
516
+ // Step 7: Write manifest
517
+ const manifest: PreprocessManifest = {
518
+ assetId,
519
+ videoPath: asset.filePath,
520
+ durationSeconds,
521
+ segments,
522
+ deadTimeRanges,
523
+ subjectRegistry,
524
+ sectionBoundaries,
525
+ config,
526
+ };
527
+
528
+ const manifestPath = join(pipelineDir, 'manifest.json');
529
+ await mkdir(pipelineDir, { recursive: true });
530
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
531
+
532
+ // Update stage
533
+ updateProcessingStage(stage.id, {
534
+ status: 'completed',
535
+ progress: 100,
536
+ completedAt: Date.now(),
537
+ });
538
+
539
+ onProgress?.(`Preprocess complete. Manifest written to ${manifestPath}\n`);
540
+
541
+ return manifest;
542
+ } catch (err) {
543
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {});
544
+ const msg = (err as Error).message;
545
+ updateProcessingStage(stage.id, {
546
+ status: 'failed',
547
+ lastError: msg.slice(0, 500),
548
+ });
549
+ throw err;
550
+ }
551
+ }
@@ -2,7 +2,7 @@
2
2
  * Processing pipeline service.
3
3
  *
4
4
  * Orchestrates the full media processing pipeline with reliability features:
5
- * - Sequential stage execution: keyframe_extraction -> vision_analysis -> timeline_generation -> event_detection
5
+ * - Sequential stage execution: preprocess -> map -> reduce
6
6
  * - Stage-level retries with exponential backoff
7
7
  * - Resumability: checks processing_stages to find last completed stage
8
8
  * - Cancellation support: cooperative cancellation via asset status = 'cancelled'
@@ -27,10 +27,9 @@ import { computeRetryDelay, sleep } from '../../../../util/retry.js';
27
27
  // ---------------------------------------------------------------------------
28
28
 
29
29
  export type PipelineStageName =
30
- | 'keyframe_extraction'
31
- | 'vision_analysis'
32
- | 'timeline_generation'
33
- | 'event_detection';
30
+ | 'preprocess'
31
+ | 'map'
32
+ | 'reduce';
34
33
 
35
34
  export interface StageHandler {
36
35
  /** Execute the stage. Throw on failure. */
@@ -60,10 +59,9 @@ export interface PipelineResult {
60
59
  // ---------------------------------------------------------------------------
61
60
 
62
61
  const STAGE_ORDER: PipelineStageName[] = [
63
- 'keyframe_extraction',
64
- 'vision_analysis',
65
- 'timeline_generation',
66
- 'event_detection',
62
+ 'preprocess',
63
+ 'map',
64
+ 'reduce',
67
65
  ];
68
66
 
69
67
  // ---------------------------------------------------------------------------