vellum 0.2.1 → 0.2.2

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 (349) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +5 -2
  3. package/package.json +4 -2
  4. package/scripts/capture-x-graphql.ts +562 -0
  5. package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
  6. package/scripts/test.sh +5 -0
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +133 -34
  8. package/src/__tests__/account-registry.test.ts +2 -1
  9. package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
  10. package/src/__tests__/asset-materialize-tool.test.ts +16 -15
  11. package/src/__tests__/asset-search-tool.test.ts +23 -22
  12. package/src/__tests__/attachments-store.test.ts +56 -127
  13. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  14. package/src/__tests__/browser-skill-endstate.test.ts +4 -3
  15. package/src/__tests__/call-bridge.test.ts +385 -0
  16. package/src/__tests__/call-constants.test.ts +40 -0
  17. package/src/__tests__/call-orchestrator.test.ts +130 -4
  18. package/src/__tests__/call-recovery.test.ts +518 -0
  19. package/src/__tests__/call-routes-http.test.ts +459 -0
  20. package/src/__tests__/call-state-machine.test.ts +143 -0
  21. package/src/__tests__/call-store.test.ts +216 -1
  22. package/src/__tests__/cli-discover.test.ts +1 -1
  23. package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
  24. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  25. package/src/__tests__/computer-use-tools.test.ts +250 -0
  26. package/src/__tests__/config-schema.test.ts +299 -3
  27. package/src/__tests__/conflict-store.test.ts +2 -1
  28. package/src/__tests__/contacts-tools.test.ts +331 -0
  29. package/src/__tests__/conversation-store.test.ts +30 -32
  30. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  31. package/src/__tests__/date-context.test.ts +373 -0
  32. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  34. package/src/__tests__/followup-tools.test.ts +303 -0
  35. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  36. package/src/__tests__/intent-routing.test.ts +64 -57
  37. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +62 -28
  39. package/src/__tests__/llm-usage-store.test.ts +3 -8
  40. package/src/__tests__/media-generate-image.test.ts +1 -1
  41. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  42. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  43. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  44. package/src/__tests__/playbook-tools.test.ts +342 -0
  45. package/src/__tests__/profile-compiler.test.ts +2 -1
  46. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  47. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  48. package/src/__tests__/recurrence-engine.test.ts +69 -0
  49. package/src/__tests__/recurrence-types.test.ts +71 -0
  50. package/src/__tests__/registry.test.ts +5 -3
  51. package/src/__tests__/relay-server.test.ts +633 -0
  52. package/src/__tests__/reminder-store.test.ts +6 -3
  53. package/src/__tests__/reminder.test.ts +43 -77
  54. package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
  55. package/src/__tests__/run-orchestrator.test.ts +4 -4
  56. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  57. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  58. package/src/__tests__/runtime-runs.test.ts +4 -4
  59. package/src/__tests__/schedule-store.test.ts +482 -0
  60. package/src/__tests__/schedule-tools.test.ts +700 -0
  61. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  62. package/src/__tests__/server-history-render.test.ts +14 -13
  63. package/src/__tests__/session-error.test.ts +28 -0
  64. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  65. package/src/__tests__/session-queue.test.ts +71 -48
  66. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  67. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  68. package/src/__tests__/signup-e2e.test.ts +2 -1
  69. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  70. package/src/__tests__/skill-script-runner.test.ts +159 -0
  71. package/src/__tests__/speaker-identification.test.ts +52 -0
  72. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  73. package/src/__tests__/subagent-tools.test.ts +141 -41
  74. package/src/__tests__/task-compiler.test.ts +2 -1
  75. package/src/__tests__/task-runner.test.ts +2 -1
  76. package/src/__tests__/task-scheduler.test.ts +2 -1
  77. package/src/__tests__/task-tools.test.ts +49 -56
  78. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  79. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  80. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  81. package/src/__tests__/tool-executor.test.ts +13 -17
  82. package/src/__tests__/turn-commit.test.ts +218 -3
  83. package/src/__tests__/twilio-provider.test.ts +143 -0
  84. package/src/__tests__/twilio-routes.test.ts +789 -0
  85. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  86. package/src/__tests__/view-image-tool.test.ts +217 -0
  87. package/src/__tests__/workspace-git-service.test.ts +186 -0
  88. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  89. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  90. package/src/bundler/app-bundler.ts +12 -8
  91. package/src/calls/call-bridge.ts +95 -0
  92. package/src/calls/call-constants.ts +43 -5
  93. package/src/calls/call-domain.ts +276 -0
  94. package/src/calls/call-orchestrator.ts +43 -17
  95. package/src/calls/call-recovery.ts +207 -0
  96. package/src/calls/call-state-machine.ts +68 -0
  97. package/src/calls/call-store.ts +192 -5
  98. package/src/calls/relay-server.ts +41 -4
  99. package/src/calls/speaker-identification.ts +213 -0
  100. package/src/calls/twilio-provider.ts +10 -6
  101. package/src/calls/twilio-routes.ts +90 -76
  102. package/src/calls/types.ts +1 -1
  103. package/src/cli/config-commands.ts +334 -0
  104. package/src/cli/core-commands.ts +776 -0
  105. package/src/cli/doordash.ts +251 -1
  106. package/src/cli/ipc-client.ts +82 -0
  107. package/src/cli/map.ts +246 -0
  108. package/src/cli/twitter.ts +575 -0
  109. package/src/cli.ts +7 -5
  110. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  111. package/src/commands/cc-command-registry.ts +209 -0
  112. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  113. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  114. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  115. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  116. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  117. package/src/config/bundled-skills/document/SKILL.md +18 -0
  118. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  119. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  120. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  121. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  122. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  123. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  124. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  125. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  126. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  127. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  128. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  129. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  130. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  131. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  132. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  133. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  134. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  135. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  136. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  137. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  138. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  139. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  141. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  142. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  143. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  144. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  145. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  146. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  147. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  148. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  149. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  150. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  151. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  152. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  153. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  154. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  155. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  156. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  157. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  158. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  159. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  160. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  161. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  162. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  163. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  164. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  165. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  166. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  167. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  168. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  169. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  170. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  171. package/src/config/defaults.ts +33 -0
  172. package/src/config/loader.ts +4 -1
  173. package/src/config/schema.ts +161 -1
  174. package/src/config/system-prompt.ts +61 -16
  175. package/src/config/templates/IDENTITY.md +7 -0
  176. package/src/config/types.ts +4 -0
  177. package/src/contacts/contact-store.ts +4 -4
  178. package/src/daemon/assistant-attachments.ts +10 -0
  179. package/src/daemon/classifier.ts +3 -1
  180. package/src/daemon/computer-use-session.ts +3 -1
  181. package/src/daemon/date-context.ts +136 -0
  182. package/src/daemon/handlers/apps.ts +16 -1
  183. package/src/daemon/handlers/browser.ts +54 -0
  184. package/src/daemon/handlers/computer-use.ts +7 -1
  185. package/src/daemon/handlers/config.ts +163 -5
  186. package/src/daemon/handlers/diagnostics.ts +5 -1
  187. package/src/daemon/handlers/documents.ts +18 -29
  188. package/src/daemon/handlers/home-base.ts +5 -1
  189. package/src/daemon/handlers/index.ts +40 -277
  190. package/src/daemon/handlers/misc.ts +9 -1
  191. package/src/daemon/handlers/publish.ts +6 -1
  192. package/src/daemon/handlers/sessions.ts +65 -12
  193. package/src/daemon/handlers/shared.ts +36 -1
  194. package/src/daemon/handlers/signing.ts +37 -0
  195. package/src/daemon/handlers/skills.ts +20 -6
  196. package/src/daemon/handlers/subagents.ts +8 -3
  197. package/src/daemon/handlers/twitter-auth.ts +169 -0
  198. package/src/daemon/handlers/work-items.ts +384 -68
  199. package/src/daemon/ipc-contract-inventory.json +28 -4
  200. package/src/daemon/ipc-contract.ts +133 -37
  201. package/src/daemon/ipc-protocol.ts +7 -2
  202. package/src/daemon/lifecycle.ts +21 -0
  203. package/src/daemon/main.ts +10 -4
  204. package/src/daemon/ride-shotgun-handler.ts +74 -10
  205. package/src/daemon/server.ts +143 -26
  206. package/src/daemon/session-agent-loop.ts +887 -0
  207. package/src/daemon/session-attachments.ts +28 -5
  208. package/src/daemon/session-error.ts +24 -3
  209. package/src/daemon/session-lifecycle.ts +147 -0
  210. package/src/daemon/session-media-retry.ts +147 -0
  211. package/src/daemon/session-messaging.ts +145 -0
  212. package/src/daemon/session-notifiers.ts +164 -0
  213. package/src/daemon/session-process.ts +2 -2
  214. package/src/daemon/session-queue-manager.ts +1 -0
  215. package/src/daemon/session-runtime-assembly.ts +52 -0
  216. package/src/daemon/session-skill-tools.ts +124 -5
  217. package/src/daemon/session-slash.ts +3 -0
  218. package/src/daemon/session-surfaces.ts +77 -2
  219. package/src/daemon/session-tool-setup.ts +216 -2
  220. package/src/daemon/session-usage.ts +0 -2
  221. package/src/daemon/session.ts +114 -1404
  222. package/src/daemon/video-thumbnail.ts +60 -0
  223. package/src/doordash/client.ts +121 -27
  224. package/src/doordash/queries.ts +1 -2
  225. package/src/export/formatter.ts +3 -1
  226. package/src/followups/followup-store.ts +4 -2
  227. package/src/followups/types.ts +6 -0
  228. package/src/hooks/templates.ts +1 -1
  229. package/src/index.ts +32 -1153
  230. package/src/memory/attachments-store.ts +28 -83
  231. package/src/memory/channel-delivery-store.ts +7 -21
  232. package/src/memory/clarification-resolver.ts +6 -5
  233. package/src/memory/contradiction-checker.ts +3 -2
  234. package/src/memory/conversation-key-store.ts +10 -29
  235. package/src/memory/conversation-store.ts +2 -1
  236. package/src/memory/db.ts +96 -2
  237. package/src/memory/entity-extractor.ts +6 -3
  238. package/src/memory/items-extractor.ts +5 -4
  239. package/src/memory/jobs-store.ts +3 -2
  240. package/src/memory/llm-usage-store.ts +1 -2
  241. package/src/memory/runs-store.ts +1 -2
  242. package/src/memory/schema.ts +23 -2
  243. package/src/messaging/style-analyzer.ts +3 -2
  244. package/src/messaging/thread-summarizer.ts +8 -12
  245. package/src/messaging/triage-engine.ts +4 -2
  246. package/src/providers/openrouter/client.ts +20 -0
  247. package/src/providers/registry.ts +8 -0
  248. package/src/runtime/http-server.ts +108 -20
  249. package/src/runtime/routes/attachment-routes.ts +2 -3
  250. package/src/runtime/routes/call-routes.ts +140 -0
  251. package/src/runtime/routes/channel-routes.ts +5 -10
  252. package/src/runtime/routes/conversation-routes.ts +5 -5
  253. package/src/runtime/routes/run-routes.ts +2 -2
  254. package/src/runtime/run-orchestrator.ts +9 -3
  255. package/src/schedule/recurrence-engine.ts +138 -0
  256. package/src/schedule/recurrence-types.ts +67 -0
  257. package/src/schedule/schedule-store.ts +102 -57
  258. package/src/schedule/scheduler.ts +9 -6
  259. package/src/security/oauth2.ts +29 -4
  260. package/src/security/secret-allowlist.ts +46 -0
  261. package/src/skills/clawhub.ts +1 -1
  262. package/src/subagent/manager.ts +40 -8
  263. package/src/swarm/backend-claude-code.ts +64 -9
  264. package/src/swarm/worker-prompts.ts +2 -1
  265. package/src/tasks/SPEC.md +34 -28
  266. package/src/tasks/ephemeral-permissions.ts +16 -7
  267. package/src/tasks/task-compiler.ts +5 -4
  268. package/src/tasks/task-runner.ts +10 -5
  269. package/src/tasks/task-scheduler.ts +1 -1
  270. package/src/tasks/tool-sanitizer.ts +36 -0
  271. package/src/tools/assets/search.ts +4 -4
  272. package/src/tools/browser/api-map.ts +220 -0
  273. package/src/tools/browser/auto-navigate.ts +270 -0
  274. package/src/tools/browser/browser-execution.ts +2 -1
  275. package/src/tools/browser/browser-manager.ts +2 -2
  276. package/src/tools/browser/network-recorder.ts +5 -4
  277. package/src/tools/browser/x-auto-navigate.ts +207 -0
  278. package/src/tools/calls/call-end.ts +17 -67
  279. package/src/tools/calls/call-start.ts +24 -85
  280. package/src/tools/calls/call-status.ts +35 -51
  281. package/src/tools/claude-code/claude-code.ts +77 -11
  282. package/src/tools/contacts/contact-merge.ts +46 -78
  283. package/src/tools/contacts/contact-search.ts +35 -79
  284. package/src/tools/contacts/contact-upsert.ts +35 -108
  285. package/src/tools/credentials/vault.ts +20 -4
  286. package/src/tools/document/document-tool.ts +71 -144
  287. package/src/tools/executor.ts +129 -10
  288. package/src/tools/followups/followup_create.ts +46 -88
  289. package/src/tools/followups/followup_list.ts +34 -74
  290. package/src/tools/followups/followup_resolve.ts +31 -66
  291. package/src/tools/host-terminal/cli-discover.ts +2 -1
  292. package/src/tools/host-terminal/host-shell.ts +10 -0
  293. package/src/tools/memory/handlers.ts +5 -4
  294. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  295. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  296. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  297. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  298. package/src/tools/network/web-fetch.ts +18 -6
  299. package/src/tools/playbooks/index.ts +4 -5
  300. package/src/tools/playbooks/playbook-create.ts +3 -47
  301. package/src/tools/playbooks/playbook-delete.ts +1 -25
  302. package/src/tools/playbooks/playbook-list.ts +1 -28
  303. package/src/tools/playbooks/playbook-update.ts +3 -51
  304. package/src/tools/reminder/reminder.ts +5 -78
  305. package/src/tools/schedule/create.ts +69 -74
  306. package/src/tools/schedule/delete.ts +21 -47
  307. package/src/tools/schedule/list.ts +55 -74
  308. package/src/tools/schedule/update.ts +77 -84
  309. package/src/tools/subagent/abort.ts +29 -58
  310. package/src/tools/subagent/message.ts +30 -63
  311. package/src/tools/subagent/read.ts +53 -84
  312. package/src/tools/subagent/spawn.ts +43 -82
  313. package/src/tools/subagent/status.ts +42 -71
  314. package/src/tools/swarm/delegate.ts +2 -1
  315. package/src/tools/tasks/index.ts +8 -8
  316. package/src/tools/tasks/task-delete.ts +60 -88
  317. package/src/tools/tasks/task-list.ts +31 -52
  318. package/src/tools/tasks/task-run.ts +72 -108
  319. package/src/tools/tasks/task-save.ts +33 -65
  320. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  321. package/src/tools/tasks/work-item-list.ts +33 -63
  322. package/src/tools/tasks/work-item-remove.ts +45 -97
  323. package/src/tools/tasks/work-item-update.ts +91 -163
  324. package/src/tools/terminal/backends/native.ts +3 -1
  325. package/src/tools/tool-manifest.ts +0 -62
  326. package/src/tools/types.ts +6 -0
  327. package/src/tools/ui-surface/definitions.ts +3 -1
  328. package/src/tools/watch/screen-watch.ts +3 -1
  329. package/src/tools/watcher/create.ts +52 -98
  330. package/src/tools/watcher/delete.ts +20 -46
  331. package/src/tools/watcher/digest.ts +36 -70
  332. package/src/tools/watcher/list.ts +49 -79
  333. package/src/tools/watcher/update.ts +45 -91
  334. package/src/twitter/client.ts +690 -0
  335. package/src/twitter/session.ts +91 -0
  336. package/src/usage/types.ts +0 -1
  337. package/src/util/truncate.ts +6 -0
  338. package/src/watcher/providers/slack.ts +2 -1
  339. package/src/watcher/watcher-store.ts +3 -2
  340. package/src/work-items/work-item-store.ts +27 -2
  341. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  342. package/src/workspace/git-service.ts +87 -22
  343. package/src/workspace/provider-commit-message-generator.ts +242 -0
  344. package/src/workspace/turn-commit.ts +62 -3
  345. package/src/tools/contacts/index.ts +0 -4
  346. package/src/tools/document/index.ts +0 -5
  347. package/src/tools/followups/index.ts +0 -3
  348. package/src/tools/subagent/index.ts +0 -5
  349. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
@@ -3,7 +3,8 @@ import type { UserMessageAttachment } from './ipc-protocol.js';
3
3
  import { check, classifyRisk, generateAllowlistOptions, generateScopeOptions } from '../permissions/checker.js';
4
4
  import { addRule } from '../permissions/trust-store.js';
5
5
  import type { PermissionPrompter } from '../permissions/prompter.js';
6
- import { uploadAttachment, linkAttachmentToMessage, AttachmentUploadError } from '../memory/attachments-store.js';
6
+ import { uploadAttachment, linkAttachmentToMessage, setAttachmentThumbnail, AttachmentUploadError } from '../memory/attachments-store.js';
7
+ import { generateVideoThumbnail } from './video-thumbnail.js';
7
8
  import {
8
9
  resolveDirectives,
9
10
  contentBlocksToDrafts,
@@ -92,7 +93,6 @@ export async function resolveAssistantAttachments(
92
93
  workingDir: string,
93
94
  approveHostRead: ApproveHostRead,
94
95
  lastAssistantMessageId: string | undefined,
95
- assistantScope: string,
96
96
  ): Promise<AttachmentResolutionResult> {
97
97
  let assistantAttachments: AssistantAttachmentDraft[] = [];
98
98
  const emittedAttachments: UserMessageAttachment[] = [];
@@ -131,14 +131,17 @@ export async function resolveAssistantAttachments(
131
131
  log.info('No directives or tool content blocks to resolve');
132
132
  }
133
133
 
134
- // Persist resolved attachments and link to the last assistant message
134
+ // Persist resolved attachments and link to the last assistant message.
135
+ // Large video attachments are omitted from the IPC payload and lazy-loaded
136
+ // by the client via the HTTP endpoint (same pattern as history_response).
137
+ const MAX_INLINE_B64_SIZE = 512 * 1024;
138
+
135
139
  if (assistantAttachments.length > 0 && lastAssistantMessageId) {
136
140
  for (let i = 0; i < assistantAttachments.length; i++) {
137
141
  const draft = assistantAttachments[i];
138
142
  let stored;
139
143
  try {
140
144
  stored = uploadAttachment(
141
- assistantScope,
142
145
  draft.filename,
143
146
  draft.mimeType,
144
147
  draft.dataBase64,
@@ -152,11 +155,31 @@ export async function resolveAssistantAttachments(
152
155
  throw err;
153
156
  }
154
157
  linkAttachmentToMessage(lastAssistantMessageId, stored.id, i);
158
+ const isVideo = draft.mimeType.startsWith('video/');
159
+ const omitData = isVideo && draft.dataBase64.length > MAX_INLINE_B64_SIZE;
160
+
161
+ // Generate and persist a thumbnail for video attachments.
162
+ let thumbnailData: string | undefined;
163
+ if (isVideo) {
164
+ const existing = stored.thumbnailBase64;
165
+ if (existing) {
166
+ thumbnailData = existing;
167
+ } else {
168
+ const generated = await generateVideoThumbnail(draft.dataBase64);
169
+ if (generated) {
170
+ setAttachmentThumbnail(stored.id, generated);
171
+ thumbnailData = generated;
172
+ }
173
+ }
174
+ }
175
+
155
176
  emittedAttachments.push({
156
177
  id: stored.id,
157
178
  filename: draft.filename,
158
179
  mimeType: draft.mimeType,
159
- data: draft.dataBase64,
180
+ data: omitData ? '' : draft.dataBase64,
181
+ ...(omitData ? { sizeBytes: draft.sizeBytes } : {}),
182
+ ...(thumbnailData ? { thumbnailData } : {}),
160
183
  });
161
184
  }
162
185
  } else if (assistantAttachments.length > 0) {
@@ -41,7 +41,16 @@ const CONTEXT_TOO_LARGE_PATTERNS = [
41
41
  /prompt.?is.?too.?long/i,
42
42
  /request too large/i,
43
43
  /too many.*input.*tokens/i,
44
- /max_tokens/i,
44
+ /max_tokens.*exceeded/i,
45
+ /exceeded.*max_tokens/i,
46
+ ];
47
+
48
+ // Generic timeout patterns — checked after NETWORK_PATTERNS and PROVIDER_API_PATTERNS
49
+ // so that "connection timeout" → PROVIDER_NETWORK and "gateway timeout" → PROVIDER_API
50
+ const TIMEOUT_PATTERNS = [
51
+ /\btimeout\b/i,
52
+ /deadline.?exceeded/i,
53
+ /request.?timed?.?out/i,
45
54
  ];
46
55
 
47
56
  // Provider API error patterns (5xx, server error, etc.)
@@ -210,7 +219,7 @@ function classifyByMessage(message: string): Omit<ClassifiedSessionError, 'debug
210
219
  }
211
220
  }
212
221
 
213
- // Network errors
222
+ // Network errors (before timeout so "connection timeout" is classified as network)
214
223
  for (const pattern of NETWORK_PATTERNS) {
215
224
  if (pattern.test(message)) {
216
225
  return {
@@ -221,7 +230,7 @@ function classifyByMessage(message: string): Omit<ClassifiedSessionError, 'debug
221
230
  }
222
231
  }
223
232
 
224
- // Provider API errors (5xx)
233
+ // Provider API errors (before timeout so "gateway timeout" keeps its specific message)
225
234
  for (const pattern of PROVIDER_API_PATTERNS) {
226
235
  if (pattern.test(message)) {
227
236
  return {
@@ -232,6 +241,18 @@ function classifyByMessage(message: string): Omit<ClassifiedSessionError, 'debug
232
241
  }
233
242
  }
234
243
 
244
+ // Generic timeout errors (checked after network and provider API patterns so
245
+ // specific timeouts like "connection timeout" and "gateway timeout" aren't misclassified)
246
+ for (const pattern of TIMEOUT_PATTERNS) {
247
+ if (pattern.test(message)) {
248
+ return {
249
+ code: 'PROVIDER_API',
250
+ userMessage: 'The request took too long. This is usually temporary — try again shortly.',
251
+ retryable: true,
252
+ };
253
+ }
254
+ }
255
+
235
256
  // Non-user abort/failure (e.g. AbortError from internal logic, not user cancel)
236
257
  for (const pattern of CANCEL_PATTERNS) {
237
258
  if (pattern.test(message)) {
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Session lifecycle methods extracted from Session: loadFromDb, abort,
3
+ * and dispose. Each operates on a context interface so the Session class
4
+ * can delegate without exposing its full surface.
5
+ */
6
+
7
+ import type { Message, ContentBlock } from '../providers/types.js';
8
+ import type { UsageStats, SurfaceType, SurfaceData } from './ipc-protocol.js';
9
+ import { repairHistory } from './history-repair.js';
10
+ import { createContextSummaryMessage } from '../context/window-manager.js';
11
+ import * as conversationStore from '../memory/conversation-store.js';
12
+ import type { PermissionPrompter } from '../permissions/prompter.js';
13
+ import type { SecretPrompter } from '../permissions/secret-prompter.js';
14
+ import type { ToolProfiler } from '../events/tool-profiling-listener.js';
15
+ import type { EventBus } from '../events/bus.js';
16
+ import type { AssistantDomainEvents } from '../events/domain-events.js';
17
+ import type { MessageQueue } from './session-queue-manager.js';
18
+ import { getHookManager } from '../hooks/manager.js';
19
+ import { getLogger } from '../util/logger.js';
20
+ import { unregisterWatchNotifiers, unregisterCallNotifiers } from './session-notifiers.js';
21
+ import { unregisterSessionSender } from '../tools/browser/browser-screencast.js';
22
+ import { resetSkillToolProjection } from './session-skill-tools.js';
23
+
24
+ const log = getLogger('session-lifecycle');
25
+
26
+ // ── Context Interfaces ───────────────────────────────────────────────
27
+
28
+ export interface LoadFromDbContext {
29
+ readonly conversationId: string;
30
+ messages: Message[];
31
+ usageStats: UsageStats;
32
+ contextCompactedMessageCount: number;
33
+ contextCompactedAt: number | null;
34
+ }
35
+
36
+ export interface AbortContext {
37
+ readonly conversationId: string;
38
+ processing: boolean;
39
+ abortController: AbortController | null;
40
+ prompter: PermissionPrompter;
41
+ secretPrompter: SecretPrompter;
42
+ pendingSurfaceActions: Map<string, { surfaceType: SurfaceType }>;
43
+ surfaceState: Map<string, { surfaceType: SurfaceType; data: SurfaceData }>;
44
+ readonly queue: MessageQueue;
45
+ }
46
+
47
+ export interface DisposeContext extends AbortContext {
48
+ eventBus: EventBus<AssistantDomainEvents>;
49
+ readonly skillProjectionState: Map<string, string>;
50
+ profiler: ToolProfiler;
51
+ messages: Message[];
52
+ surfaceUndoStacks: Map<string, string[]>;
53
+ currentTurnSurfaces: Array<unknown>;
54
+ lastSurfaceAction: Map<string, unknown>;
55
+ workspaceTopLevelContext: string | null;
56
+ abort(): void;
57
+ }
58
+
59
+ // ── loadFromDb ───────────────────────────────────────────────────────
60
+
61
+ export async function loadFromDb(ctx: LoadFromDbContext): Promise<void> {
62
+ const dbMessages = conversationStore.getMessages(ctx.conversationId);
63
+
64
+ const conv = conversationStore.getConversation(ctx.conversationId);
65
+ const contextSummary = conv?.contextSummary?.trim() || null;
66
+ ctx.contextCompactedMessageCount = Math.max(
67
+ 0,
68
+ Math.min(conv?.contextCompactedMessageCount ?? 0, dbMessages.length),
69
+ );
70
+ ctx.contextCompactedAt = conv?.contextCompactedAt ?? null;
71
+
72
+ const parsedMessages: Message[] = dbMessages
73
+ .slice(ctx.contextCompactedMessageCount)
74
+ .map((m) => {
75
+ const role = m.role as 'user' | 'assistant';
76
+ let content: ContentBlock[];
77
+ try {
78
+ const parsed = JSON.parse(m.content);
79
+ content = Array.isArray(parsed) ? parsed : [{ type: 'text', text: m.content }];
80
+ } catch {
81
+ log.warn({ conversationId: ctx.conversationId, messageId: m.id }, 'Invalid JSON in persisted message content, replacing with safe text block');
82
+ content = [{ type: 'text', text: m.content }];
83
+ }
84
+ return { role, content };
85
+ });
86
+
87
+ const { messages: repairedMessages, stats } = repairHistory(parsedMessages);
88
+ if (stats.assistantToolResultsMigrated > 0 || stats.missingToolResultsInserted > 0 || stats.orphanToolResultsDowngraded > 0 || stats.consecutiveSameRoleMerged > 0) {
89
+ log.warn({ conversationId: ctx.conversationId, phase: 'load', ...stats }, 'Repaired persisted history');
90
+ }
91
+ ctx.messages = repairedMessages;
92
+
93
+ if (contextSummary) {
94
+ ctx.messages.unshift(createContextSummaryMessage(contextSummary));
95
+ }
96
+
97
+ if (conv) {
98
+ ctx.usageStats = {
99
+ inputTokens: conv.totalInputTokens,
100
+ outputTokens: conv.totalOutputTokens,
101
+ estimatedCost: conv.totalEstimatedCost,
102
+ };
103
+ }
104
+
105
+ log.info({ conversationId: ctx.conversationId, count: ctx.messages.length }, 'Loaded messages from DB');
106
+ }
107
+
108
+ // ── abort ─────────────────────────────────────────────────────────────
109
+
110
+ export function abortSession(ctx: AbortContext): void {
111
+ if (ctx.processing) {
112
+ log.info({ conversationId: ctx.conversationId }, 'Aborting in-flight processing');
113
+ ctx.abortController?.abort();
114
+ ctx.prompter.dispose();
115
+ ctx.secretPrompter.dispose();
116
+ ctx.pendingSurfaceActions.clear();
117
+ ctx.surfaceState.clear();
118
+ unregisterWatchNotifiers(ctx.conversationId);
119
+ for (const queued of ctx.queue) {
120
+ queued.onEvent({ type: 'generation_cancelled', sessionId: ctx.conversationId });
121
+ }
122
+ ctx.queue.clear();
123
+ }
124
+ }
125
+
126
+ // ── dispose ──────────────────────────────────────────────────────────
127
+
128
+ export function disposeSession(ctx: DisposeContext): void {
129
+ void getHookManager().trigger('session-end', {
130
+ sessionId: ctx.conversationId,
131
+ });
132
+ ctx.abort();
133
+ unregisterCallNotifiers(ctx.conversationId);
134
+ unregisterSessionSender(ctx.conversationId);
135
+ resetSkillToolProjection(ctx.skillProjectionState);
136
+ ctx.eventBus.dispose();
137
+
138
+ // Release heavy in-memory data so GC can reclaim it
139
+ ctx.messages = [];
140
+ ctx.profiler.clear();
141
+ ctx.surfaceUndoStacks.clear();
142
+ ctx.currentTurnSurfaces = [];
143
+ ctx.pendingSurfaceActions.clear();
144
+ ctx.surfaceState.clear();
145
+ ctx.lastSurfaceAction.clear();
146
+ ctx.workspaceTopLevelContext = null;
147
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Media payload trimming for context-too-large retry scenarios.
3
+ *
4
+ * When the provider rejects a request because the context is too large,
5
+ * this module replaces older image and file content blocks with lightweight
6
+ * text stubs to shrink the payload before retrying.
7
+ */
8
+
9
+ import type { Message, ContentBlock } from '../providers/types.js';
10
+ import { getSummaryFromContextMessage } from '../context/window-manager.js';
11
+
12
+ const RETRY_KEEP_LATEST_MEDIA_BLOCKS = 3;
13
+ const MAX_MEDIA_STUB_TEXT = 2_000;
14
+
15
+ export function stripMediaPayloadsForRetry(messages: Message[]): { messages: Message[]; modified: boolean; replacedBlocks: number; latestUserIndex: number | null } {
16
+ let latestUserIndex: number | null = null;
17
+ for (let i = messages.length - 1; i >= 0; i--) {
18
+ const msg = messages[i];
19
+ if (msg.role !== 'user') continue;
20
+ if (getSummaryFromContextMessage(msg) !== null) continue;
21
+ if (isToolResultOnlyMessage(msg)) continue;
22
+ latestUserIndex = i;
23
+ break;
24
+ }
25
+
26
+ let modified = false;
27
+ let replacedBlocks = 0;
28
+ let keptLatestMediaBlocks = 0;
29
+
30
+ const nextMessages = messages.map((msg, msgIndex) => {
31
+ const nextContent: ContentBlock[] = [];
32
+ for (const block of msg.content) {
33
+ if (block.type === 'image') {
34
+ const keep = latestUserIndex === msgIndex && keptLatestMediaBlocks < RETRY_KEEP_LATEST_MEDIA_BLOCKS;
35
+ if (keep) {
36
+ keptLatestMediaBlocks += 1;
37
+ nextContent.push(block);
38
+ } else {
39
+ replacedBlocks += 1;
40
+ modified = true;
41
+ nextContent.push(imageBlockToStub(block));
42
+ }
43
+ continue;
44
+ }
45
+
46
+ if (block.type === 'file') {
47
+ const keep = latestUserIndex === msgIndex && keptLatestMediaBlocks < RETRY_KEEP_LATEST_MEDIA_BLOCKS;
48
+ if (keep) {
49
+ keptLatestMediaBlocks += 1;
50
+ nextContent.push(block);
51
+ } else {
52
+ replacedBlocks += 1;
53
+ modified = true;
54
+ nextContent.push(fileBlockToStub(block));
55
+ }
56
+ continue;
57
+ }
58
+
59
+ if (block.type === 'tool_result' && block.contentBlocks && block.contentBlocks.length > 0) {
60
+ let toolResultChanged = false;
61
+ const nextToolContentBlocks: ContentBlock[] = block.contentBlocks.map((cb) => {
62
+ if (cb.type === 'image') {
63
+ replacedBlocks += 1;
64
+ modified = true;
65
+ toolResultChanged = true;
66
+ return imageBlockToStub(cb);
67
+ }
68
+ if (cb.type === 'file') {
69
+ replacedBlocks += 1;
70
+ modified = true;
71
+ toolResultChanged = true;
72
+ return fileBlockToStub(cb);
73
+ }
74
+ return cb;
75
+ });
76
+ if (toolResultChanged) {
77
+ nextContent.push({ ...block, contentBlocks: nextToolContentBlocks });
78
+ } else {
79
+ nextContent.push(block);
80
+ }
81
+ continue;
82
+ }
83
+
84
+ nextContent.push(block);
85
+ }
86
+ return { ...msg, content: nextContent };
87
+ });
88
+
89
+ return {
90
+ messages: modified ? nextMessages : messages,
91
+ modified,
92
+ replacedBlocks,
93
+ latestUserIndex,
94
+ };
95
+ }
96
+
97
+ function imageBlockToStub(block: Extract<ContentBlock, { type: 'image' }>): Extract<ContentBlock, { type: 'text' }> {
98
+ const sizeBytes = Math.ceil(block.source.data.length / 4) * 3;
99
+ return {
100
+ type: 'text',
101
+ text: `[Image omitted from retry context: ${block.source.media_type}, ${sizeBytes} bytes]`,
102
+ };
103
+ }
104
+
105
+ function fileBlockToStub(block: Extract<ContentBlock, { type: 'file' }>): Extract<ContentBlock, { type: 'text' }> {
106
+ const sizeBytes = Math.ceil(block.source.data.length / 4) * 3;
107
+ const extracted = (block.extracted_text ?? '').trim();
108
+ const preview = extracted.length > MAX_MEDIA_STUB_TEXT
109
+ ? `${extracted.slice(0, MAX_MEDIA_STUB_TEXT)}...`
110
+ : extracted;
111
+ return {
112
+ type: 'text',
113
+ text: preview.length > 0
114
+ ? `[File omitted from retry context: ${block.source.filename} (${block.source.media_type}, ${sizeBytes} bytes)]\n${preview}`
115
+ : `[File omitted from retry context: ${block.source.filename} (${block.source.media_type}, ${sizeBytes} bytes)]`,
116
+ };
117
+ }
118
+
119
+ function isToolResultOnlyMessage(message: Message): boolean {
120
+ return message.content.length > 0
121
+ && message.content.every((block) => block.type === 'tool_result');
122
+ }
123
+
124
+ /**
125
+ * Race a promise against a timeout. Returns 'completed' if the promise
126
+ * resolves/rejects within the budget, or 'timed_out' if the timeout fires
127
+ * first. The timer is always cleared in `finally` to prevent handle leaks.
128
+ */
129
+ export async function raceWithTimeout<T>(
130
+ promise: Promise<T>,
131
+ timeoutMs: number,
132
+ ): Promise<'completed' | 'timed_out'> {
133
+ let timer: ReturnType<typeof setTimeout> | undefined;
134
+ try {
135
+ const result = await Promise.race([
136
+ promise.then(() => 'completed' as const, () => 'completed' as const),
137
+ new Promise<'timed_out'>((resolve) => {
138
+ timer = setTimeout(() => resolve('timed_out'), timeoutMs);
139
+ }),
140
+ ]);
141
+ return result;
142
+ } finally {
143
+ if (timer !== undefined) {
144
+ clearTimeout(timer);
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Session messaging methods: enqueue, persistUserMessage,
3
+ * redirectToSecurePrompt, and queue/confirmation helpers.
4
+ *
5
+ * Extracted from Session to keep the class focused on coordination.
6
+ */
7
+
8
+ import { v4 as uuid } from 'uuid';
9
+ import type { Message } from '../providers/types.js';
10
+ import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
11
+ import { createUserMessage } from '../agent/message-types.js';
12
+ import * as conversationStore from '../memory/conversation-store.js';
13
+ import type { SecretPrompter } from '../permissions/secret-prompter.js';
14
+ import type { MessageQueue } from './session-queue-manager.js';
15
+ import { getLogger } from '../util/logger.js';
16
+
17
+ const log = getLogger('session-messaging');
18
+
19
+ // ── Context Interface ────────────────────────────────────────────────
20
+
21
+ export interface MessagingSessionContext {
22
+ readonly conversationId: string;
23
+ messages: Message[];
24
+ processing: boolean;
25
+ abortController: AbortController | null;
26
+ currentRequestId?: string;
27
+ readonly queue: MessageQueue;
28
+ }
29
+
30
+ // ── enqueueMessage ───────────────────────────────────────────────────
31
+
32
+ export function enqueueMessage(
33
+ ctx: MessagingSessionContext,
34
+ content: string,
35
+ attachments: UserMessageAttachment[],
36
+ onEvent: (msg: ServerMessage) => void,
37
+ requestId: string,
38
+ activeSurfaceId?: string,
39
+ currentPage?: string,
40
+ metadata?: Record<string, unknown>,
41
+ ): { queued: boolean; rejected?: boolean; requestId: string } {
42
+ if (!ctx.processing) {
43
+ return { queued: false, requestId };
44
+ }
45
+
46
+ const pushed = ctx.queue.push({ content, attachments, requestId, onEvent, activeSurfaceId, currentPage, metadata });
47
+ if (!pushed) {
48
+ return { queued: false, rejected: true, requestId };
49
+ }
50
+ return { queued: true, requestId };
51
+ }
52
+
53
+ // ── persistUserMessage ───────────────────────────────────────────────
54
+
55
+ export function persistUserMessage(
56
+ ctx: MessagingSessionContext,
57
+ content: string,
58
+ attachments: UserMessageAttachment[],
59
+ requestId?: string,
60
+ metadata?: Record<string, unknown>,
61
+ ): string {
62
+ if (ctx.processing) {
63
+ throw new Error('Session is already processing a message');
64
+ }
65
+
66
+ if (!content.trim() && attachments.length === 0) {
67
+ throw new Error('Message content or attachments are required');
68
+ }
69
+
70
+ const reqId = requestId ?? uuid();
71
+ ctx.currentRequestId = reqId;
72
+ ctx.processing = true;
73
+ ctx.abortController = new AbortController();
74
+
75
+ const userMessage = createUserMessage(content, attachments.map((attachment) => ({
76
+ id: attachment.id,
77
+ filename: attachment.filename,
78
+ mimeType: attachment.mimeType,
79
+ data: attachment.data,
80
+ extractedText: attachment.extractedText,
81
+ })));
82
+ ctx.messages.push(userMessage);
83
+
84
+ try {
85
+ const persistedUserMessage = conversationStore.addMessage(
86
+ ctx.conversationId,
87
+ 'user',
88
+ JSON.stringify(userMessage.content),
89
+ metadata,
90
+ );
91
+
92
+ if (!persistedUserMessage.id) {
93
+ throw new Error('Failed to persist user message');
94
+ }
95
+
96
+ return persistedUserMessage.id;
97
+ } catch (err) {
98
+ ctx.messages.pop();
99
+ ctx.processing = false;
100
+ ctx.abortController = null;
101
+ ctx.currentRequestId = undefined;
102
+ throw err;
103
+ }
104
+ }
105
+
106
+ // ── redirectToSecurePrompt ───────────────────────────────────────────
107
+
108
+ export function redirectToSecurePrompt(
109
+ conversationId: string,
110
+ secretPrompter: SecretPrompter,
111
+ detectedTypes: string[],
112
+ onComplete?: () => void,
113
+ ): void {
114
+ const service = 'detected';
115
+ const field = detectedTypes.join(',');
116
+ secretPrompter.prompt(
117
+ service, field,
118
+ 'Secure Credential Entry',
119
+ 'Your message contained a secret. Please enter it here instead — it will be stored securely and never sent to the AI.',
120
+ undefined, conversationId,
121
+ ).then(async (result) => {
122
+ if (!result.value) return;
123
+
124
+ const { setSecureKey } = await import('../security/secure-keys.js');
125
+ const { upsertCredentialMetadata } = await import('../tools/credentials/metadata-store.js');
126
+
127
+ if (result.delivery === 'transient_send') {
128
+ const { credentialBroker } = await import('../tools/credentials/broker.js');
129
+ credentialBroker.injectTransient(service, field, result.value);
130
+ try { upsertCredentialMetadata(service, field, {}); } catch (e) { log.debug({ err: e, service, field }, 'Non-critical credential metadata upsert failed'); }
131
+ log.info({ service, field, delivery: 'transient_send' }, 'Ingress redirect: transient credential injected');
132
+ } else {
133
+ const key = `credential:${service}:${field}`;
134
+ const stored = setSecureKey(key, result.value);
135
+ if (stored) {
136
+ try { upsertCredentialMetadata(service, field, {}); } catch (e) { log.debug({ err: e, service, field }, 'Non-critical credential metadata upsert failed'); }
137
+ log.info({ service, field }, 'Ingress redirect: credential stored');
138
+ } else {
139
+ log.warn({ service, field }, 'Ingress redirect: secure storage write failed');
140
+ }
141
+ }
142
+ }).catch(() => { /* prompt timeout or cancel is fine */ }).finally(() => {
143
+ onComplete?.();
144
+ });
145
+ }