@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,259 @@
1
+ /**
2
+ * Processing pipeline service.
3
+ *
4
+ * Orchestrates the full media processing pipeline with reliability features:
5
+ * - Sequential stage execution: preprocess -> map -> reduce
6
+ * - Stage-level retries with exponential backoff
7
+ * - Resumability: checks processing_stages to find last completed stage
8
+ * - Cancellation support: cooperative cancellation via asset status = 'cancelled'
9
+ * - Idempotency: respects content-hash dedup from media-store
10
+ * - Graceful degradation: saves partial results on failure
11
+ *
12
+ * All reliability infrastructure is generic media-processing, not domain-specific.
13
+ */
14
+
15
+ import {
16
+ getMediaAssetById,
17
+ getProcessingStagesForAsset,
18
+ createProcessingStage,
19
+ updateProcessingStage,
20
+ updateMediaAssetStatus,
21
+ type ProcessingStage,
22
+ } from '../../../../memory/media-store.js';
23
+ import { computeRetryDelay, sleep } from '../../../../util/retry.js';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Types
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export type PipelineStageName =
30
+ | 'preprocess'
31
+ | 'map'
32
+ | 'reduce';
33
+
34
+ export interface StageHandler {
35
+ /** Execute the stage. Throw on failure. */
36
+ execute: (assetId: string, onProgress?: (msg: string) => void) => Promise<void>;
37
+ }
38
+
39
+ export interface PipelineOptions {
40
+ /** Maximum retry attempts per stage (default: 3). */
41
+ maxRetries?: number;
42
+ /** Base delay in ms for exponential backoff between retries (default: 1000). */
43
+ baseDelayMs?: number;
44
+ /** Progress callback for streaming status updates. */
45
+ onProgress?: (message: string) => void;
46
+ }
47
+
48
+ export interface PipelineResult {
49
+ assetId: string;
50
+ completedStages: PipelineStageName[];
51
+ failedStage: PipelineStageName | null;
52
+ failureReason: string | null;
53
+ cancelled: boolean;
54
+ resumedFrom: PipelineStageName | null;
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Pipeline stage ordering
59
+ // ---------------------------------------------------------------------------
60
+
61
+ const STAGE_ORDER: PipelineStageName[] = [
62
+ 'preprocess',
63
+ 'map',
64
+ 'reduce',
65
+ ];
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Helpers
69
+ // ---------------------------------------------------------------------------
70
+
71
+ function findOrCreateStage(assetId: string, stageName: string): ProcessingStage {
72
+ const stages = getProcessingStagesForAsset(assetId);
73
+ const existing = stages.find((s) => s.stage === stageName);
74
+ if (existing) return existing;
75
+ return createProcessingStage({ assetId, stage: stageName });
76
+ }
77
+
78
+ function isStageCompleted(stage: ProcessingStage): boolean {
79
+ return stage.status === 'completed';
80
+ }
81
+
82
+ /**
83
+ * Check if the asset has been cancelled. Cooperative cancellation:
84
+ * the pipeline checks this between stages to allow graceful stopping.
85
+ */
86
+ function isAssetCancelled(assetId: string): boolean {
87
+ const asset = getMediaAssetById(assetId);
88
+ if (!asset) return true;
89
+ return (asset.status as string) === 'cancelled';
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Main pipeline
94
+ // ---------------------------------------------------------------------------
95
+
96
+ /**
97
+ * Run the full processing pipeline for a media asset.
98
+ *
99
+ * The pipeline is resumable: if previous stages are already completed,
100
+ * execution resumes from the first incomplete stage. Each stage is
101
+ * retried with exponential backoff on failure. If a stage exhausts
102
+ * its retries, partial results are preserved and the pipeline stops.
103
+ */
104
+ export async function runPipeline(
105
+ assetId: string,
106
+ handlers: Record<PipelineStageName, StageHandler>,
107
+ options?: PipelineOptions,
108
+ ): Promise<PipelineResult> {
109
+ const maxRetries = options?.maxRetries ?? 3;
110
+ const baseDelayMs = options?.baseDelayMs ?? 1000;
111
+ const onProgress = options?.onProgress;
112
+
113
+ const asset = getMediaAssetById(assetId);
114
+ if (!asset) {
115
+ throw new Error(`Media asset not found: ${assetId}`);
116
+ }
117
+
118
+ // Check if asset is already cancelled before forcing processing
119
+ if ((asset.status as string) === 'cancelled') {
120
+ return {
121
+ assetId,
122
+ completedStages: [],
123
+ failedStage: null,
124
+ failureReason: null,
125
+ cancelled: true,
126
+ resumedFrom: null,
127
+ };
128
+ }
129
+
130
+ // Mark asset as processing
131
+ updateMediaAssetStatus(assetId, 'processing');
132
+
133
+ const completedStages: PipelineStageName[] = [];
134
+ let failedStage: PipelineStageName | null = null;
135
+ let failureReason: string | null = null;
136
+ let cancelled = false;
137
+ let resumedFrom: PipelineStageName | null = null;
138
+
139
+ // Find where to resume from by checking existing stage records
140
+ let startIndex = 0;
141
+ for (let i = 0; i < STAGE_ORDER.length; i++) {
142
+ const stage = findOrCreateStage(assetId, STAGE_ORDER[i]);
143
+ if (isStageCompleted(stage)) {
144
+ completedStages.push(STAGE_ORDER[i]);
145
+ startIndex = i + 1;
146
+ } else {
147
+ break;
148
+ }
149
+ }
150
+
151
+ if (startIndex > 0 && startIndex < STAGE_ORDER.length) {
152
+ resumedFrom = STAGE_ORDER[startIndex];
153
+ onProgress?.(`Resuming pipeline from stage: ${resumedFrom}`);
154
+ } else if (startIndex >= STAGE_ORDER.length) {
155
+ // All stages already completed — idempotent no-op
156
+ onProgress?.('All pipeline stages already completed.');
157
+ updateMediaAssetStatus(assetId, 'indexed');
158
+ return {
159
+ assetId,
160
+ completedStages,
161
+ failedStage: null,
162
+ failureReason: null,
163
+ cancelled: false,
164
+ resumedFrom: null,
165
+ };
166
+ }
167
+
168
+ // Execute stages sequentially from the resume point
169
+ for (let i = startIndex; i < STAGE_ORDER.length; i++) {
170
+ const stageName = STAGE_ORDER[i];
171
+
172
+ // Cooperative cancellation check between stages
173
+ if (isAssetCancelled(assetId)) {
174
+ onProgress?.(`Pipeline cancelled before stage: ${stageName}`);
175
+ cancelled = true;
176
+ break;
177
+ }
178
+
179
+ const stageRecord = findOrCreateStage(assetId, stageName);
180
+ const handler = handlers[stageName];
181
+
182
+ onProgress?.(`Starting stage: ${stageName}`);
183
+ updateProcessingStage(stageRecord.id, {
184
+ status: 'running',
185
+ startedAt: Date.now(),
186
+ lastError: null,
187
+ });
188
+
189
+ let succeeded = false;
190
+
191
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
192
+ try {
193
+ if (attempt > 0) {
194
+ const delay = computeRetryDelay(attempt - 1, baseDelayMs);
195
+ onProgress?.(`Retrying stage ${stageName} (attempt ${attempt + 1}/${maxRetries + 1}) after ${Math.round(delay)}ms...`);
196
+ await sleep(delay);
197
+
198
+ // Re-check cancellation before retry
199
+ if (isAssetCancelled(assetId)) {
200
+ onProgress?.(`Pipeline cancelled during retry of stage: ${stageName}`);
201
+ cancelled = true;
202
+ break;
203
+ }
204
+ }
205
+
206
+ await handler.execute(assetId, onProgress);
207
+
208
+ // Mark stage as completed
209
+ updateProcessingStage(stageRecord.id, {
210
+ status: 'completed',
211
+ progress: 100,
212
+ completedAt: Date.now(),
213
+ });
214
+
215
+ completedStages.push(stageName);
216
+ succeeded = true;
217
+ onProgress?.(`Completed stage: ${stageName}`);
218
+ break;
219
+ } catch (err) {
220
+ const errorMsg = (err as Error).message.slice(0, 500);
221
+ onProgress?.(`Stage ${stageName} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${errorMsg}`);
222
+
223
+ // Save partial progress — the stage handler should have already
224
+ // persisted any partial results before throwing
225
+ updateProcessingStage(stageRecord.id, {
226
+ status: attempt >= maxRetries ? 'failed' : 'running',
227
+ lastError: errorMsg,
228
+ });
229
+ }
230
+ }
231
+
232
+ if (cancelled) break;
233
+
234
+ if (!succeeded) {
235
+ failedStage = stageName;
236
+ failureReason = `Stage ${stageName} failed after ${maxRetries + 1} attempts`;
237
+ onProgress?.(`Pipeline stopped: ${failureReason}`);
238
+ break;
239
+ }
240
+ }
241
+
242
+ // Update final asset status
243
+ if (cancelled) {
244
+ // Leave status as-is (already 'cancelled')
245
+ } else if (failedStage) {
246
+ updateMediaAssetStatus(assetId, 'failed');
247
+ } else {
248
+ updateMediaAssetStatus(assetId, 'indexed');
249
+ }
250
+
251
+ return {
252
+ assetId,
253
+ completedStages,
254
+ failedStage,
255
+ failureReason,
256
+ cancelled,
257
+ resumedFrom,
258
+ };
259
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Reduce service — sends Map output to Claude as text-only for analysis.
3
+ *
4
+ * Two modes:
5
+ * - One-shot merge: assembles all Map results into a single document,
6
+ * sends to Claude with the provided system prompt for analysis.
7
+ * - Interactive Q&A: loads existing map output + user query, sends to
8
+ * Claude, returns the answer.
9
+ *
10
+ * Uses the existing provider infrastructure (getConfiguredProvider) so
11
+ * it works with whatever LLM provider is configured.
12
+ */
13
+
14
+ import { readFile } from 'node:fs/promises';
15
+ import { join, dirname } from 'node:path';
16
+ import { getMediaAssetById } from '../../../../memory/media-store.js';
17
+ import {
18
+ getConfiguredProvider,
19
+ createTimeout,
20
+ extractAllText,
21
+ userMessage,
22
+ } from '../../../../providers/provider-send-message.js';
23
+ import type { MapOutput } from './gemini-map.js';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Types
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export interface ReduceOptions {
30
+ /** Natural language query about the video data. Optional for one-shot merge mode. */
31
+ query?: string;
32
+ /** Optional system prompt for Claude. */
33
+ systemPrompt?: string;
34
+ /** Model override. When omitted, the configured provider's default is used. */
35
+ model?: string;
36
+ }
37
+
38
+ export interface ReduceResult {
39
+ answer: string;
40
+ model: string;
41
+ inputTokens: number;
42
+ outputTokens: number;
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Helpers
47
+ // ---------------------------------------------------------------------------
48
+
49
+ const REDUCE_TIMEOUT_MS = 120_000;
50
+
51
+ /**
52
+ * Load map-output.json for an asset from its pipeline directory.
53
+ */
54
+ async function loadMapOutput(assetId: string): Promise<MapOutput> {
55
+ const asset = getMediaAssetById(assetId);
56
+ if (!asset) {
57
+ throw new Error(`Media asset not found: ${assetId}`);
58
+ }
59
+
60
+ const pipelineDir = join(dirname(asset.filePath), 'pipeline', assetId);
61
+ const mapOutputPath = join(pipelineDir, 'map-output.json');
62
+
63
+ let raw: string;
64
+ try {
65
+ raw = await readFile(mapOutputPath, 'utf-8');
66
+ } catch {
67
+ throw new Error(
68
+ 'No map output found. Run analyze_keyframes first to generate map-output.json.',
69
+ );
70
+ }
71
+
72
+ return JSON.parse(raw) as MapOutput;
73
+ }
74
+
75
+ /**
76
+ * Format map output segments into a text document for Claude.
77
+ * Strips image data — text only.
78
+ */
79
+ function formatMapOutputAsText(mapOutput: MapOutput): string {
80
+ const lines: string[] = [];
81
+
82
+ lines.push(`Video Analysis Data (asset: ${mapOutput.assetId})`);
83
+ lines.push(`Model: ${mapOutput.model}`);
84
+ lines.push(`Segments analyzed: ${mapOutput.successCount}/${mapOutput.segmentCount}`);
85
+ if (mapOutput.failedCount > 0) {
86
+ lines.push(`Failed segments: ${mapOutput.failedCount}`);
87
+ }
88
+ if (mapOutput.skippedCount > 0) {
89
+ lines.push(`Skipped segments: ${mapOutput.skippedCount}`);
90
+ }
91
+ lines.push('');
92
+ lines.push('--- Segment Results ---');
93
+ lines.push('');
94
+
95
+ for (const segment of mapOutput.segments) {
96
+ const startMin = Math.floor(segment.startSeconds / 60);
97
+ const startSec = Math.floor(segment.startSeconds % 60);
98
+ const endMin = Math.floor(segment.endSeconds / 60);
99
+ const endSec = Math.floor(segment.endSeconds % 60);
100
+ const timeRange = `${startMin}:${String(startSec).padStart(2, '0')} - ${endMin}:${String(endSec).padStart(2, '0')}`;
101
+
102
+ lines.push(`[Segment ${segment.segmentId}] ${timeRange}`);
103
+ lines.push(JSON.stringify(segment.result, null, 2));
104
+ lines.push('');
105
+ }
106
+
107
+ return lines.join('\n');
108
+ }
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // Core: send to Claude via provider infrastructure
112
+ // ---------------------------------------------------------------------------
113
+
114
+ async function sendToClaude(
115
+ mapText: string,
116
+ query: string,
117
+ systemPrompt?: string,
118
+ model?: string,
119
+ onProgress?: (msg: string) => void,
120
+ ): Promise<ReduceResult> {
121
+ const provider = getConfiguredProvider();
122
+ if (!provider) {
123
+ throw new Error('No LLM provider available. Please configure an API key.');
124
+ }
125
+
126
+ const effectiveSystemPrompt = systemPrompt
127
+ ?? 'You are an expert video analyst. You have been given structured analysis data extracted from a video. Answer the user\'s question based on this data. Be specific, reference timestamps when relevant, and provide clear, actionable insights.';
128
+
129
+ const userContent = `Here is the video analysis data:\n\n${mapText}\n\n---\n\nUser query: ${query}`;
130
+
131
+ onProgress?.('Sending map output to Claude for analysis...\n');
132
+
133
+ const { signal, cleanup } = createTimeout(REDUCE_TIMEOUT_MS);
134
+
135
+ try {
136
+ const response = await provider.sendMessage(
137
+ [userMessage(userContent)],
138
+ [],
139
+ effectiveSystemPrompt,
140
+ {
141
+ config: model ? { model } : {},
142
+ signal,
143
+ },
144
+ );
145
+ cleanup();
146
+
147
+ const answer = extractAllText(response);
148
+
149
+ onProgress?.(`Reduce complete (${response.usage.inputTokens} input + ${response.usage.outputTokens} output tokens).\n`);
150
+
151
+ return {
152
+ answer,
153
+ model: response.model,
154
+ inputTokens: response.usage.inputTokens,
155
+ outputTokens: response.usage.outputTokens,
156
+ };
157
+ } finally {
158
+ cleanup();
159
+ }
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // Public API
164
+ // ---------------------------------------------------------------------------
165
+
166
+ /**
167
+ * One-shot merge mode: load map output for an asset, send all data to
168
+ * Claude with a system prompt, and return the analysis.
169
+ */
170
+ export async function reduceForAsset(
171
+ assetId: string,
172
+ options: ReduceOptions,
173
+ onProgress?: (msg: string) => void,
174
+ ): Promise<ReduceResult> {
175
+ const mapOutput = await loadMapOutput(assetId);
176
+
177
+ if (mapOutput.segments.length === 0) {
178
+ throw new Error('Map output contains no segments. Run analyze_keyframes first.');
179
+ }
180
+
181
+ const mapText = formatMapOutputAsText(mapOutput);
182
+ const effectiveQuery = options.query ?? 'Summarize the video content.';
183
+ return sendToClaude(mapText, effectiveQuery, options.systemPrompt, options.model, onProgress);
184
+ }
185
+
186
+ /**
187
+ * Interactive Q&A mode: load existing map output + user query, send to
188
+ * Claude, return the answer. Functionally identical to reduceForAsset
189
+ * but named distinctly for clarity in call sites.
190
+ */
191
+ export async function queryMapOutput(
192
+ assetId: string,
193
+ options: ReduceOptions,
194
+ onProgress?: (msg: string) => void,
195
+ ): Promise<ReduceResult> {
196
+ return reduceForAsset(assetId, options, onProgress);
197
+ }
@@ -0,0 +1,136 @@
1
+ import { join, dirname } from 'node:path';
2
+ import { readFile } from 'node:fs/promises';
3
+ import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
4
+ import { getConfig } from '../../../../config/loader.js';
5
+ import {
6
+ getMediaAssetById,
7
+ } from '../../../../memory/media-store.js';
8
+ import { mapSegments, type MapOutput } from '../services/gemini-map.js';
9
+ import type { PreprocessManifest } from '../services/preprocess.js';
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Exported function for job handler use
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export interface MapSegmentsOptions {
16
+ systemPrompt: string;
17
+ outputSchema: Record<string, unknown>;
18
+ context?: Record<string, unknown>;
19
+ model?: string;
20
+ concurrency?: number;
21
+ maxRetries?: number;
22
+ }
23
+
24
+ export async function mapSegmentsForAsset(
25
+ assetId: string,
26
+ options: MapSegmentsOptions,
27
+ onProgress?: (msg: string) => void,
28
+ ): Promise<MapOutput> {
29
+ const config = getConfig();
30
+ const apiKey = config.apiKeys.gemini;
31
+
32
+ if (!apiKey) {
33
+ throw new Error('No Gemini API key configured. Please set your Gemini API key to use keyframe analysis.');
34
+ }
35
+
36
+ const asset = getMediaAssetById(assetId);
37
+ if (!asset) {
38
+ throw new Error(`Media asset not found: ${assetId}`);
39
+ }
40
+
41
+ // Load preprocess manifest
42
+ const pipelineDir = join(dirname(asset.filePath), 'pipeline', assetId);
43
+ const manifestPath = join(pipelineDir, 'manifest.json');
44
+
45
+ let manifest: PreprocessManifest;
46
+ try {
47
+ const raw = await readFile(manifestPath, 'utf-8');
48
+ manifest = JSON.parse(raw) as PreprocessManifest;
49
+ } catch {
50
+ throw new Error('No preprocess manifest found. Run extract_keyframes first.');
51
+ }
52
+
53
+ if (manifest.segments.length === 0) {
54
+ throw new Error('No segments found in preprocess manifest. Run extract_keyframes first.');
55
+ }
56
+
57
+ return mapSegments(assetId, pipelineDir, manifest.segments, {
58
+ apiKey,
59
+ systemPrompt: options.systemPrompt,
60
+ outputSchema: options.outputSchema,
61
+ context: options.context,
62
+ model: options.model,
63
+ concurrency: options.concurrency,
64
+ maxRetries: options.maxRetries,
65
+ }, onProgress);
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Tool entry point
70
+ // ---------------------------------------------------------------------------
71
+
72
+ export async function run(
73
+ input: Record<string, unknown>,
74
+ context: ToolContext,
75
+ ): Promise<ToolExecutionResult> {
76
+ const assetId = input.asset_id as string | undefined;
77
+ if (!assetId) {
78
+ return { content: 'asset_id is required.', isError: true };
79
+ }
80
+
81
+ const systemPrompt = input.system_prompt as string | undefined;
82
+ if (!systemPrompt) {
83
+ return { content: 'system_prompt is required.', isError: true };
84
+ }
85
+
86
+ const outputSchema = input.output_schema as Record<string, unknown> | undefined;
87
+ if (!outputSchema) {
88
+ return { content: 'output_schema is required.', isError: true };
89
+ }
90
+
91
+ const contextObj = input.context as Record<string, unknown> | undefined;
92
+ const model = input.model as string | undefined;
93
+ const concurrency = input.concurrency as number | undefined;
94
+ const maxRetries = input.max_retries as number | undefined;
95
+
96
+ if (concurrency !== undefined && concurrency < 1) {
97
+ return { content: 'concurrency must be at least 1.', isError: true };
98
+ }
99
+ if (maxRetries !== undefined && maxRetries < 0) {
100
+ return { content: 'max_retries must be non-negative.', isError: true };
101
+ }
102
+
103
+ try {
104
+ const output = await mapSegmentsForAsset(
105
+ assetId,
106
+ {
107
+ systemPrompt,
108
+ outputSchema,
109
+ context: contextObj,
110
+ model,
111
+ concurrency,
112
+ maxRetries,
113
+ },
114
+ context.onOutput,
115
+ );
116
+
117
+ return {
118
+ content: JSON.stringify({
119
+ message: `Map ${output.failedCount === 0 ? 'completed' : 'completed with errors'}`,
120
+ assetId,
121
+ model: output.model,
122
+ segmentCount: output.segmentCount,
123
+ successCount: output.successCount,
124
+ failedCount: output.failedCount,
125
+ skippedCount: output.skippedCount,
126
+ totalInputTokens: output.costSummary.totalInputTokens,
127
+ totalOutputTokens: output.costSummary.totalOutputTokens,
128
+ estimatedCostUSD: output.costSummary.totalEstimatedUSD,
129
+ }, null, 2),
130
+ isError: false,
131
+ };
132
+ } catch (err) {
133
+ const msg = (err as Error).message;
134
+ return { content: msg, isError: true };
135
+ }
136
+ }
@@ -0,0 +1,59 @@
1
+ import { join, dirname } from 'node:path';
2
+ import type { ToolContext, ToolExecutionResult } from '../../../../tools/types.js';
3
+ import { getMediaAssetById, getKeyframesForAsset } from '../../../../memory/media-store.js';
4
+ import { preprocessForAsset, type PreprocessOptions } from '../services/preprocess.js';
5
+
6
+ export { preprocessForAsset } from '../services/preprocess.js';
7
+
8
+ export async function run(
9
+ input: Record<string, unknown>,
10
+ context: ToolContext,
11
+ ): Promise<ToolExecutionResult> {
12
+ const assetId = input.asset_id as string | undefined;
13
+ if (!assetId) {
14
+ return { content: 'asset_id is required.', isError: true };
15
+ }
16
+
17
+ const options: PreprocessOptions = {
18
+ intervalSeconds: (input.interval_seconds as number) || undefined,
19
+ segmentDuration: (input.segment_duration as number) || undefined,
20
+ deadTimeThreshold: (input.dead_time_threshold as number) || undefined,
21
+ sectionConfigPath: (input.section_config as string) || undefined,
22
+ skipDeadTime: input.skip_dead_time !== undefined ? Boolean(input.skip_dead_time) : undefined,
23
+ shortEdge: (input.short_edge as number) || undefined,
24
+ };
25
+
26
+ try {
27
+ const manifest = await preprocessForAsset(assetId, options, context.onOutput);
28
+
29
+ const asset = getMediaAssetById(assetId);
30
+ const pipelineDir = join(dirname(asset!.filePath), 'pipeline', assetId);
31
+ const keyframes = getKeyframesForAsset(assetId);
32
+
33
+ return {
34
+ content: JSON.stringify({
35
+ message: `Preprocessed video: ${manifest.segments.length} segments, ${keyframes.length} keyframes`,
36
+ assetId,
37
+ segmentCount: manifest.segments.length,
38
+ keyframeCount: keyframes.length,
39
+ deadTimeRanges: manifest.deadTimeRanges.length,
40
+ subjectGroups: manifest.subjectRegistry.groups.length,
41
+ manifestPath: join(pipelineDir, 'manifest.json'),
42
+ config: manifest.config,
43
+ }, null, 2),
44
+ isError: false,
45
+ };
46
+ } catch (err) {
47
+ const msg = (err as Error).message;
48
+ if (
49
+ msg.startsWith('Media asset not found:') ||
50
+ msg.startsWith('Preprocess requires a video asset.') ||
51
+ msg.startsWith('Video asset has no duration') ||
52
+ msg.startsWith('ffmpeg failed:') ||
53
+ msg === 'No frames were extracted from the video.'
54
+ ) {
55
+ return { content: msg, isError: true };
56
+ }
57
+ return { content: `Preprocess failed: ${msg}`, isError: true };
58
+ }
59
+ }