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
@@ -1,42 +1,28 @@
1
+ import { defineHandlers } from './shared.js';
1
2
  import type { HandlerContext } from './shared.js';
2
3
  import type { DocumentSaveRequest, DocumentLoadRequest, DocumentListRequest } from '../ipc-protocol.js';
3
4
  import type * as net from 'node:net';
4
5
  import type { Database } from 'bun:sqlite';
5
6
  import { getDb } from '../../memory/db.js';
7
+ import { getLogger } from '../../util/logger.js';
6
8
 
7
- import { writeFileSync } from 'node:fs';
9
+ const log = getLogger('documents');
8
10
 
9
11
  export function handleDocumentSave(msg: DocumentSaveRequest, socket: net.Socket, ctx: HandlerContext): void {
10
- const logMsg = `[${new Date().toISOString()}] handleDocumentSave called: ${JSON.stringify({
12
+ log.info({
11
13
  surfaceId: msg.surfaceId,
12
14
  conversationId: msg.conversationId,
13
15
  title: msg.title,
14
16
  contentLength: msg.content.length,
15
17
  wordCount: msg.wordCount,
16
- })}\n`;
18
+ }, 'Received save request');
17
19
 
18
20
  try {
19
- writeFileSync('/tmp/document-save-debug.log', logMsg, { flag: 'a' });
20
- } catch {
21
- // Ignore logging errors
22
- }
23
-
24
- console.log('💾 [handleDocumentSave] Received save request:', {
25
- surfaceId: msg.surfaceId,
26
- conversationId: msg.conversationId,
27
- title: msg.title,
28
- contentLength: msg.content.length,
29
- wordCount: msg.wordCount,
30
- });
31
-
32
- try {
33
- writeFileSync('/tmp/document-save-debug.log', `[${new Date().toISOString()}] Getting db...\n`, { flag: 'a' });
34
21
  const db = getDb();
35
22
  // Get the raw SQLite client from Drizzle
36
23
  const sqlite = (db as unknown as { $client: Database }).$client;
37
24
  const now = Date.now();
38
25
 
39
- writeFileSync('/tmp/document-save-debug.log', `[${new Date().toISOString()}] Running sqlite.run()...\n`, { flag: 'a' });
40
26
  // Upsert document (insert or update if exists)
41
27
  sqlite.run(
42
28
  `INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at)
@@ -49,18 +35,15 @@ export function handleDocumentSave(msg: DocumentSaveRequest, socket: net.Socket,
49
35
  [msg.surfaceId, msg.conversationId, msg.title, msg.content, msg.wordCount, now, now]
50
36
  );
51
37
 
52
- writeFileSync('/tmp/document-save-debug.log', `[${new Date().toISOString()}] db.run() completed, sending response...\n`, { flag: 'a' });
53
38
  ctx.send(socket, {
54
39
  type: 'document_save_response',
55
40
  surfaceId: msg.surfaceId,
56
41
  success: true,
57
42
  });
58
43
 
59
- writeFileSync('/tmp/document-save-debug.log', `[${new Date().toISOString()}] Response sent successfully\n`, { flag: 'a' });
60
-
61
- console.log(`[documents] Saved document: ${msg.surfaceId} - "${msg.title}"`);
44
+ log.info({ surfaceId: msg.surfaceId, title: msg.title }, 'Saved document');
62
45
  } catch (error) {
63
- console.error('[documents] Save error:', error);
46
+ log.error({ err: error, surfaceId: msg.surfaceId }, 'Save error');
64
47
  ctx.send(socket, {
65
48
  type: 'document_save_response',
66
49
  surfaceId: msg.surfaceId,
@@ -101,7 +84,7 @@ export function handleDocumentLoad(msg: DocumentLoadRequest, socket: net.Socket,
101
84
  updatedAt: result.updated_at,
102
85
  success: true,
103
86
  });
104
- console.log(`[documents] Loaded document: ${msg.surfaceId}`);
87
+ log.info({ surfaceId: msg.surfaceId }, 'Loaded document');
105
88
  } else {
106
89
  ctx.send(socket, {
107
90
  type: 'document_load_response',
@@ -115,10 +98,10 @@ export function handleDocumentLoad(msg: DocumentLoadRequest, socket: net.Socket,
115
98
  success: false,
116
99
  error: 'Document not found',
117
100
  });
118
- console.log(`[documents] Document not found: ${msg.surfaceId}`);
101
+ log.info({ surfaceId: msg.surfaceId }, 'Document not found');
119
102
  }
120
103
  } catch (error) {
121
- console.error('[documents] Load error:', error);
104
+ log.error({ err: error, surfaceId: msg.surfaceId }, 'Load error');
122
105
  ctx.send(socket, {
123
106
  type: 'document_load_response',
124
107
  surfaceId: msg.surfaceId,
@@ -173,12 +156,18 @@ export function handleDocumentList(msg: DocumentListRequest, socket: net.Socket,
173
156
  })),
174
157
  });
175
158
 
176
- console.log(`[documents] Listed ${results.length} documents`);
159
+ log.info({ count: results.length }, 'Listed documents');
177
160
  } catch (error) {
178
- console.error('[documents] List error:', error);
161
+ log.error({ err: error }, 'List error');
179
162
  ctx.send(socket, {
180
163
  type: 'document_list_response',
181
164
  documents: [],
182
165
  });
183
166
  }
184
167
  }
168
+
169
+ export const documentHandlers = defineHandlers({
170
+ document_save: handleDocumentSave,
171
+ document_load: handleDocumentLoad,
172
+ document_list: handleDocumentList,
173
+ });
@@ -7,7 +7,7 @@ import {
7
7
  } from '../../home-base/prebuilt/seed.js';
8
8
  import { getHomeBaseAppLink } from '../../home-base/app-link-store.js';
9
9
  import { getApp } from '../../memory/app-store.js';
10
- import { log, type HandlerContext } from './shared.js';
10
+ import { log, defineHandlers, type HandlerContext } from './shared.js';
11
11
 
12
12
  export function handleHomeBaseGet(
13
13
  msg: HomeBaseGetRequest,
@@ -71,3 +71,7 @@ export function handleHomeBaseGet(
71
71
  ctx.send(socket, { type: 'error', message: `Failed to resolve home base metadata: ${message}` });
72
72
  }
73
73
  }
74
+
75
+ export const homeBaseHandlers = defineHandlers({
76
+ home_base_get: handleHomeBaseGet,
77
+ });
@@ -3,117 +3,23 @@ import type { ClientMessage } from '../ipc-protocol.js';
3
3
  import { handleRideShotgunStart, handleRideShotgunStop } from '../ride-shotgun-handler.js';
4
4
  import { handleWatchObservation } from '../watch-handler.js';
5
5
  import { handleOpenBundle } from './open-bundle-handler.js';
6
- import { log, pendingSignBundlePayload, pendingSigningIdentity, type HandlerContext } from './shared.js';
7
- import { browserManager } from '../../tools/browser/browser-manager.js';
8
-
9
- import {
10
- handleUserMessage,
11
- handleConfirmationResponse,
12
- handleSecretResponse,
13
- handleSessionList,
14
- handleSessionsClear,
15
- handleSessionCreate,
16
- handleSessionSwitch,
17
- handleCancel,
18
- handleDeleteQueuedMessage,
19
- handleHistoryRequest,
20
- handleUndo,
21
- handleRegenerate,
22
- handleUsageRequest,
23
- handleSandboxSet,
24
- } from './sessions.js';
25
-
26
- import {
27
- handleSkillsList,
28
- handleSkillDetail,
29
- handleSkillsEnable,
30
- handleSkillsDisable,
31
- handleSkillsConfigure,
32
- handleSkillsInstall,
33
- handleSkillsUninstall,
34
- handleSkillsUpdate,
35
- handleSkillsCheckUpdates,
36
- handleSkillsSearch,
37
- handleSkillsInspect,
38
- } from './skills.js';
39
-
40
- import {
41
- handleAppDataRequest,
42
- handleAppOpenRequest,
43
- handleAppUpdatePreview,
44
- handleAppPreview,
45
- handleAppsList,
46
- handleSharedAppsList,
47
- handleSharedAppDelete,
48
- handleForkSharedApp,
49
- handleShareAppCloud,
50
- handleBundleApp,
51
- handleGalleryList,
52
- handleGalleryInstall,
53
- } from './apps.js';
54
-
55
- import {
56
- handleModelGet,
57
- handleModelSet,
58
- handleImageGenModelSet,
59
- handleAddTrustRule,
60
- handleTrustRulesList,
61
- handleRemoveTrustRule,
62
- handleUpdateTrustRule,
63
- handleAcceptStarterBundle,
64
- handleSchedulesList,
65
- handleScheduleToggle,
66
- handleScheduleRemove,
67
- handleRemindersList,
68
- handleReminderCancel,
69
- handleShareToSlack,
70
- handleSlackWebhookConfig,
71
- handleVercelApiConfig,
72
- handleEnvVarsRequest,
73
- } from './config.js';
74
-
75
- import {
76
- handleCuSessionCreate,
77
- handleCuSessionAbort,
78
- handleCuObservation,
79
- } from './computer-use.js';
80
-
81
- import {
82
- handlePublishPage,
83
- handleUnpublishPage,
84
- } from './publish.js';
85
- import { handleHomeBaseGet } from './home-base.js';
86
- import { handleDiagnosticsExport } from './diagnostics.js';
87
-
88
- import {
89
- handleTaskSubmit,
90
- handleSuggestionRequest,
91
- handleLinkOpenRequest,
92
- handleIpcBlobProbe,
93
- } from './misc.js';
94
-
95
- import {
96
- handleDocumentSave,
97
- handleDocumentLoad,
98
- handleDocumentList,
99
- } from './documents.js';
100
-
101
- import {
102
- handleWorkItemsList,
103
- handleWorkItemGet,
104
- handleWorkItemCreate,
105
- handleWorkItemUpdate,
106
- handleWorkItemComplete,
107
- handleWorkItemDelete,
108
- handleWorkItemRunTask,
109
- handleWorkItemOutput,
110
- } from './work-items.js';
111
-
112
- import {
113
- handleSubagentAbort,
114
- handleSubagentStatus,
115
- handleSubagentMessage,
116
- } from './subagents.js';
6
+ import { log, defineHandlers, type HandlerContext, type DispatchMap } from './shared.js';
7
+
8
+ import { sessionHandlers } from './sessions.js';
9
+ import { skillHandlers } from './skills.js';
10
+ import { appHandlers } from './apps.js';
11
+ import { configHandlers } from './config.js';
12
+ import { computerUseHandlers } from './computer-use.js';
13
+ import { publishHandlers } from './publish.js';
14
+ import { homeBaseHandlers } from './home-base.js';
15
+ import { diagnosticsHandlers } from './diagnostics.js';
16
+ import { miscHandlers } from './misc.js';
17
+ import { documentHandlers } from './documents.js';
18
+ import { workItemHandlers } from './work-items.js';
19
+ import { subagentHandlers } from './subagents.js';
20
+ import { browserHandlers } from './browser.js';
21
+ import { signingHandlers } from './signing.js';
22
+ import { twitterAuthHandlers } from './twitter-auth.js';
117
23
 
118
24
  // Re-export types and utilities for backwards compatibility
119
25
  export type {
@@ -132,106 +38,13 @@ export {
132
38
 
133
39
  // ─── Typed dispatch ──────────────────────────────────────────────────────────
134
40
 
135
- type MessageType = ClientMessage['type'];
136
- // 'auth' is handled at the transport layer (server.ts) and never reaches dispatch.
137
- type DispatchableType = Exclude<MessageType, 'auth'>;
138
- type MessageOfType<T extends MessageType> = Extract<ClientMessage, { type: T }>;
139
- type MessageHandler<T extends MessageType> = (
140
- msg: MessageOfType<T>,
141
- socket: net.Socket,
142
- ctx: HandlerContext,
143
- ) => void | Promise<void>;
144
- type DispatchMap = { [T in DispatchableType]: MessageHandler<T> };
145
-
146
- const handlers: DispatchMap = {
147
- user_message: handleUserMessage,
148
- confirmation_response: handleConfirmationResponse,
149
- secret_response: handleSecretResponse,
150
- session_list: (_msg, socket, ctx) => handleSessionList(socket, ctx),
151
- session_create: handleSessionCreate,
152
- sessions_clear: (_msg, socket, ctx) => handleSessionsClear(socket, ctx),
153
- session_switch: handleSessionSwitch,
154
- cancel: handleCancel,
155
- delete_queued_message: handleDeleteQueuedMessage,
156
- model_get: (_msg, socket, ctx) => handleModelGet(socket, ctx),
157
- model_set: handleModelSet,
158
- image_gen_model_set: handleImageGenModelSet,
159
- history_request: handleHistoryRequest,
160
- undo: handleUndo,
161
- regenerate: handleRegenerate,
162
- usage_request: handleUsageRequest,
163
- sandbox_set: handleSandboxSet,
164
- cu_session_create: handleCuSessionCreate,
165
- cu_session_abort: handleCuSessionAbort,
166
- cu_observation: handleCuObservation,
41
+ // Inline handlers for messages not owned by any feature group
42
+ const inlineHandlers = defineHandlers({
167
43
  ride_shotgun_start: handleRideShotgunStart,
168
44
  ride_shotgun_stop: handleRideShotgunStop,
169
45
  watch_observation: handleWatchObservation,
170
- task_submit: handleTaskSubmit,
171
- app_data_request: handleAppDataRequest,
172
- skills_list: (_msg, socket, ctx) => handleSkillsList(socket, ctx),
173
- skill_detail: handleSkillDetail,
174
- skills_enable: handleSkillsEnable,
175
- skills_disable: handleSkillsDisable,
176
- skills_configure: handleSkillsConfigure,
177
- skills_install: handleSkillsInstall,
178
- skills_uninstall: handleSkillsUninstall,
179
- skills_update: handleSkillsUpdate,
180
- skills_check_updates: handleSkillsCheckUpdates,
181
- skills_search: handleSkillsSearch,
182
- skills_inspect: handleSkillsInspect,
183
- suggestion_request: handleSuggestionRequest,
184
- add_trust_rule: handleAddTrustRule,
185
- trust_rules_list: (_msg, socket, ctx) => handleTrustRulesList(socket, ctx),
186
- remove_trust_rule: handleRemoveTrustRule,
187
- update_trust_rule: handleUpdateTrustRule,
188
- accept_starter_bundle: (_msg, socket, ctx) => handleAcceptStarterBundle(socket, ctx),
189
- schedules_list: (_msg, socket, ctx) => handleSchedulesList(socket, ctx),
190
- schedule_toggle: handleScheduleToggle,
191
- schedule_remove: handleScheduleRemove,
192
- reminders_list: (_msg, socket, ctx) => handleRemindersList(socket, ctx),
193
- reminder_cancel: handleReminderCancel,
194
- share_app_cloud: handleShareAppCloud,
195
- bundle_app: handleBundleApp,
196
46
  open_bundle: handleOpenBundle,
197
- app_open_request: (msg, socket, ctx) => handleAppOpenRequest(msg, socket, ctx),
198
- app_update_preview: handleAppUpdatePreview,
199
- apps_list: (_msg, socket, ctx) => handleAppsList(socket, ctx),
200
- app_preview_request: handleAppPreview,
201
- home_base_get: handleHomeBaseGet,
202
- shared_apps_list: (_msg, socket, ctx) => handleSharedAppsList(socket, ctx),
203
- shared_app_delete: handleSharedAppDelete,
204
- fork_shared_app: handleForkSharedApp,
205
- sign_bundle_payload_response: (msg) => {
206
- const pending = pendingSignBundlePayload.get(msg.requestId);
207
- if (pending) {
208
- clearTimeout(pending.timer);
209
- pendingSignBundlePayload.delete(msg.requestId);
210
- pending.resolve({ signature: msg.signature, keyId: msg.keyId, publicKey: msg.publicKey });
211
- } else {
212
- log.warn({ requestId: msg.requestId }, 'Received sign_bundle_payload_response with no pending request');
213
- }
214
- },
215
- get_signing_identity_response: (msg) => {
216
- const pending = pendingSigningIdentity.get(msg.requestId);
217
- if (pending) {
218
- clearTimeout(pending.timer);
219
- pendingSigningIdentity.delete(msg.requestId);
220
- pending.resolve({ keyId: msg.keyId, publicKey: msg.publicKey });
221
- } else {
222
- log.warn({ requestId: msg.requestId }, 'Received get_signing_identity_response with no pending request');
223
- }
224
- },
225
- gallery_list: (_msg, socket, ctx) => handleGalleryList(socket, ctx),
226
- gallery_install: handleGalleryInstall,
227
- share_to_slack: handleShareToSlack,
228
- slack_webhook_config: handleSlackWebhookConfig,
229
- vercel_api_config: handleVercelApiConfig,
230
- publish_page: handlePublishPage,
231
- unpublish_page: handleUnpublishPage,
232
- ping: (_msg, socket, ctx) => { ctx.send(socket, { type: 'pong' }); },
233
- link_open_request: handleLinkOpenRequest,
234
- ipc_blob_probe: handleIpcBlobProbe,
47
+
235
48
  ui_surface_action: (msg, _socket, ctx) => {
236
49
  const cuSession = ctx.cuSessions.get(msg.sessionId);
237
50
  if (cuSession) {
@@ -255,15 +68,6 @@ const handlers: DispatchMap = {
255
68
  }
256
69
  log.warn({ sessionId: msg.sessionId, surfaceId: msg.surfaceId }, 'No session found for surface undo');
257
70
  },
258
- diagnostics_export_request: handleDiagnosticsExport,
259
- env_vars_request: (_msg, socket, ctx) => handleEnvVarsRequest(socket, ctx),
260
- document_save: handleDocumentSave,
261
- document_load: handleDocumentLoad,
262
- document_list: handleDocumentList,
263
-
264
- browser_cdp_response: (msg) => {
265
- browserManager.resolveCDPResponse(msg.sessionId, msg.success, msg.declined);
266
- },
267
71
 
268
72
  // Stub handlers: the integration registry was removed but the Swift client
269
73
  // still sends these messages. Return safe no-op responses so the client
@@ -279,68 +83,27 @@ const handlers: DispatchMap = {
279
83
  error: 'Please use chat to connect integrations.',
280
84
  });
281
85
  },
282
-
283
- browser_user_click: async (msg) => {
284
- try {
285
- const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
286
- const viewport = await page.evaluate('(() => ({ vw: window.innerWidth, vh: window.innerHeight }))()') as { vw: number; vh: number };
287
- const scale = Math.min(1280 / viewport.vw, 960 / viewport.vh);
288
- const pageX = msg.x / scale;
289
- const pageY = msg.y / scale;
290
- const options: Record<string, unknown> = {};
291
- if (msg.button === 'right') options.button = 'right';
292
- if (msg.doubleClick) options.clickCount = 2;
293
- await page.mouse.click(pageX, pageY, options);
294
- } catch (err) {
295
- log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user click');
296
- }
297
- },
298
-
299
- browser_user_scroll: async (msg) => {
300
- try {
301
- const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
302
- await page.mouse.wheel(msg.deltaX, msg.deltaY);
303
- } catch (err) {
304
- log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user scroll');
305
- }
306
- },
307
-
308
- browser_user_keypress: async (msg) => {
309
- try {
310
- const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
311
- const combo = msg.modifiers?.length ? [...msg.modifiers, msg.key].join('+') : msg.key;
312
- await page.keyboard.press(combo);
313
- } catch (err) {
314
- log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user keypress');
315
- }
316
- },
317
-
318
- browser_interactive_mode: (msg, socket, ctx) => {
319
- log.info({ sessionId: msg.sessionId, enabled: msg.enabled }, 'Interactive mode toggled');
320
- browserManager.setInteractiveMode(msg.sessionId, msg.enabled);
321
- ctx.send(socket, {
322
- type: 'browser_interactive_mode_changed',
323
- sessionId: msg.sessionId,
324
- surfaceId: msg.surfaceId,
325
- enabled: msg.enabled,
326
- });
327
- },
328
-
329
86
  integration_disconnect: () => { /* no-op — integration registry removed */ },
330
-
331
- work_items_list: handleWorkItemsList,
332
- work_item_get: handleWorkItemGet,
333
- work_item_create: handleWorkItemCreate,
334
- work_item_update: handleWorkItemUpdate,
335
- work_item_complete: handleWorkItemComplete,
336
- work_item_delete: handleWorkItemDelete,
337
- work_item_run_task: handleWorkItemRunTask,
338
- work_item_output: handleWorkItemOutput,
339
-
340
- subagent_abort: handleSubagentAbort,
341
- subagent_status: handleSubagentStatus,
342
- subagent_message: handleSubagentMessage,
343
- };
87
+ });
88
+
89
+ const handlers = {
90
+ ...sessionHandlers,
91
+ ...skillHandlers,
92
+ ...appHandlers,
93
+ ...configHandlers,
94
+ ...computerUseHandlers,
95
+ ...publishHandlers,
96
+ ...homeBaseHandlers,
97
+ ...diagnosticsHandlers,
98
+ ...miscHandlers,
99
+ ...documentHandlers,
100
+ ...workItemHandlers,
101
+ ...subagentHandlers,
102
+ ...browserHandlers,
103
+ ...signingHandlers,
104
+ ...twitterAuthHandlers,
105
+ ...inlineHandlers,
106
+ } satisfies DispatchMap;
344
107
 
345
108
  export function handleMessage(
346
109
  msg: ClientMessage,
@@ -18,7 +18,7 @@ import type {
18
18
  IpcBlobProbe,
19
19
  CuSessionCreate,
20
20
  } from '../ipc-protocol.js';
21
- import { log, wireEscalationHandler, renderHistoryContent, type HandlerContext } from './shared.js';
21
+ import { log, wireEscalationHandler, renderHistoryContent, defineHandlers, type HandlerContext } from './shared.js';
22
22
  import { handleCuSessionCreate } from './computer-use.js';
23
23
 
24
24
  // ─── Task submit handler ────────────────────────────────────────────────────
@@ -321,3 +321,11 @@ export function handleIpcBlobProbe(
321
321
  observedNonceSha256: observedHash,
322
322
  });
323
323
  }
324
+
325
+ export const miscHandlers = defineHandlers({
326
+ task_submit: handleTaskSubmit,
327
+ suggestion_request: handleSuggestionRequest,
328
+ link_open_request: handleLinkOpenRequest,
329
+ ipc_blob_probe: handleIpcBlobProbe,
330
+ ping: (_msg, socket, ctx) => { ctx.send(socket, { type: 'pong' }); },
331
+ });
@@ -10,7 +10,7 @@ import type {
10
10
  PublishPageRequest,
11
11
  UnpublishPageRequest,
12
12
  } from '../ipc-protocol.js';
13
- import { log, requestSecretStandalone, type HandlerContext } from './shared.js';
13
+ import { log, requestSecretStandalone, defineHandlers, type HandlerContext } from './shared.js';
14
14
 
15
15
  export async function handlePublishPage(
16
16
  msg: PublishPageRequest,
@@ -180,3 +180,8 @@ export async function handleUnpublishPage(
180
180
  });
181
181
  }
182
182
  }
183
+
184
+ export const publishHandlers = defineHandlers({
185
+ publish_page: handlePublishPage,
186
+ unpublish_page: handleUnpublishPage,
187
+ });
@@ -3,7 +3,8 @@ import { v4 as uuid } from 'uuid';
3
3
  import * as conversationStore from '../../memory/conversation-store.js';
4
4
  import { checkIngressForSecrets } from '../../security/secret-ingress.js';
5
5
  import { classifySessionError, buildSessionErrorMessage } from '../session-error.js';
6
- import { getAttachmentsForMessageUnscoped } from '../../memory/attachments-store.js';
6
+ import { getAttachmentsForMessage, setAttachmentThumbnail } from '../../memory/attachments-store.js';
7
+ import { generateVideoThumbnail } from '../video-thumbnail.js';
7
8
  import type { UserMessageAttachment } from '../ipc-contract.js';
8
9
  import { normalizeThreadType } from '../ipc-protocol.js';
9
10
  import type {
@@ -30,10 +31,12 @@ import {
30
31
  mergeToolResults,
31
32
  pendingStandaloneSecrets,
32
33
  type HandlerContext,
34
+ defineHandlers,
33
35
  type HistoryToolCall,
34
36
  type HistorySurface,
35
37
  type ParsedHistoryMessage,
36
38
  } from './shared.js';
39
+ import { truncate } from '../../util/truncate.js';
37
40
 
38
41
  export async function handleUserMessage(
39
42
  msg: UserMessage,
@@ -271,14 +274,27 @@ export async function handleSessionSwitch(
271
274
  ctx.send(socket, { type: 'error', message: `Session ${msg.sessionId} not found` });
272
275
  return;
273
276
  }
277
+
278
+ // If the target session is headless-locked (actively executing a task run),
279
+ // skip rebinding the socket so tool confirmations stay suppressed.
280
+ const existingSession = ctx.sessions.get(msg.sessionId);
281
+ const isHeadlessLocked = existingSession && (existingSession as unknown as { headlessLock?: boolean }).headlessLock;
282
+
274
283
  ctx.socketToSession.set(socket, msg.sessionId);
275
- const session = await ctx.getOrCreateSession(msg.sessionId, socket, true);
276
- // Only wire the escalation handler if one isn't already set — handleTaskSubmit
277
- // sets a handler with the client's actual screen dimensions, and overwriting it
278
- // here would replace those dimensions with the daemon's defaults.
279
- if (!session.hasEscalationHandler()) {
280
- wireEscalationHandler(session, socket, ctx);
284
+
285
+ if (isHeadlessLocked) {
286
+ // Load the session without rebinding the client the session stays headless
287
+ await ctx.getOrCreateSession(msg.sessionId, socket, false);
288
+ } else {
289
+ const session = await ctx.getOrCreateSession(msg.sessionId, socket, true);
290
+ // Only wire the escalation handler if one isn't already set — handleTaskSubmit
291
+ // sets a handler with the client's actual screen dimensions, and overwriting it
292
+ // here would replace those dimensions with the daemon's defaults.
293
+ if (!session.hasEscalationHandler()) {
294
+ wireEscalationHandler(session, socket, ctx);
295
+ }
281
296
  }
297
+
282
298
  ctx.send(socket, {
283
299
  type: 'session_info',
284
300
  sessionId: conversation.id,
@@ -326,7 +342,7 @@ export function handleHistoryRequest(
326
342
  contentOrder = rendered.contentOrder;
327
343
  surfaces = rendered.surfaces;
328
344
  if (m.role === 'assistant' && toolCalls.length > 0) {
329
- log.info({ messageId: m.id, toolCallCount: toolCalls.length, text: text.substring(0, 100) }, 'History message with tool calls');
345
+ log.info({ messageId: m.id, toolCallCount: toolCalls.length, text: truncate(text, 100, '') }, 'History message with tool calls');
330
346
  }
331
347
  } catch (err) {
332
348
  log.debug({ err, messageId: m.id }, 'Failed to parse message content as JSON, using raw text');
@@ -335,7 +351,15 @@ export function handleHistoryRequest(
335
351
  contentOrder = text ? ['text:0'] : [];
336
352
  surfaces = [];
337
353
  }
338
- return { id: m.id, role: m.role, text, timestamp: m.createdAt, toolCalls, toolCallsBeforeText, textSegments, contentOrder, surfaces };
354
+ let subagentNotification: ParsedHistoryMessage['subagentNotification'];
355
+ if (m.metadata) {
356
+ try {
357
+ subagentNotification = (JSON.parse(m.metadata) as { subagentNotification?: ParsedHistoryMessage['subagentNotification'] }).subagentNotification;
358
+ } catch (err) {
359
+ log.debug({ err, messageId: m.id }, 'Failed to parse message metadata as JSON, ignoring');
360
+ }
361
+ }
362
+ return { id: m.id, role: m.role, text, timestamp: m.createdAt, toolCalls, toolCallsBeforeText, textSegments, contentOrder, surfaces, ...(subagentNotification ? { subagentNotification } : {}) };
339
363
  });
340
364
 
341
365
  // Merge tool_result data from user messages into the preceding assistant
@@ -346,7 +370,7 @@ export function handleHistoryRequest(
346
370
  const historyMessages = merged.map((m) => {
347
371
  let attachments: UserMessageAttachment[] | undefined;
348
372
  if (m.role === 'assistant' && m.id) {
349
- const linked = getAttachmentsForMessageUnscoped(m.id);
373
+ const linked = getAttachmentsForMessage(m.id);
350
374
  if (linked.length > 0) {
351
375
  // Skip embedding base64 data for large video attachments to keep the
352
376
  // history_response payload small. Only videos have a lazy-fetch path on
@@ -354,12 +378,23 @@ export function handleHistoryRequest(
354
378
  const MAX_INLINE_B64_SIZE = 512 * 1024;
355
379
  attachments = linked.map((a) => {
356
380
  const omit = a.mimeType.startsWith('video/') && a.dataBase64.length > MAX_INLINE_B64_SIZE;
381
+
382
+ // Lazily generate thumbnails for existing video attachments on first history load.
383
+ if (a.mimeType.startsWith('video/') && !a.thumbnailBase64) {
384
+ const attachmentId = a.id;
385
+ const base64 = a.dataBase64;
386
+ generateVideoThumbnail(base64).then((thumb) => {
387
+ if (thumb) setAttachmentThumbnail(attachmentId, thumb);
388
+ }).catch(() => {});
389
+ }
390
+
357
391
  return {
358
392
  id: a.id,
359
393
  filename: a.originalFilename,
360
394
  mimeType: a.mimeType,
361
395
  data: omit ? '' : a.dataBase64,
362
396
  ...(omit ? { sizeBytes: a.sizeBytes } : {}),
397
+ ...(a.thumbnailBase64 ? { thumbnailData: a.thumbnailBase64 } : {}),
363
398
  };
364
399
  });
365
400
  }
@@ -374,6 +409,7 @@ export function handleHistoryRequest(
374
409
  ...(m.textSegments.length > 0 ? { textSegments: m.textSegments } : {}),
375
410
  ...(m.contentOrder.length > 0 ? { contentOrder: m.contentOrder } : {}),
376
411
  ...(m.surfaces.length > 0 ? { surfaces: m.surfaces } : {}),
412
+ ...(m.subagentNotification ? { subagentNotification: m.subagentNotification } : {}),
377
413
  };
378
414
  });
379
415
  ctx.send(socket, { type: 'history_response', sessionId: msg.sessionId, messages: historyMessages });
@@ -421,10 +457,10 @@ export async function handleRegenerate(
421
457
  } catch (err) {
422
458
  const message = err instanceof Error ? err.message : String(err);
423
459
  log.error({ err, sessionId: msg.sessionId }, 'Error regenerating message');
424
- session.traceEmitter.emit('request_error', message.slice(0, 200), {
460
+ session.traceEmitter.emit('request_error', truncate(message, 200, ''), {
425
461
  requestId,
426
462
  status: 'error',
427
- attributes: { errorClass: err instanceof Error ? err.constructor.name : 'Error', message: message.slice(0, 500) },
463
+ attributes: { errorClass: err instanceof Error ? err.constructor.name : 'Error', message: truncate(message, 500, '') },
428
464
  });
429
465
  ctx.send(socket, { type: 'error', message: `Failed to regenerate: ${message}` });
430
466
  const classified = classifySessionError(err, { phase: 'regenerate' });
@@ -484,3 +520,20 @@ export function handleDeleteQueuedMessage(
484
520
  log.warn({ sessionId: msg.sessionId, requestId: msg.requestId }, 'Queued message not found for deletion');
485
521
  }
486
522
  }
523
+
524
+ export const sessionHandlers = defineHandlers({
525
+ user_message: handleUserMessage,
526
+ confirmation_response: handleConfirmationResponse,
527
+ secret_response: handleSecretResponse,
528
+ session_list: (_msg, socket, ctx) => handleSessionList(socket, ctx),
529
+ session_create: handleSessionCreate,
530
+ sessions_clear: (_msg, socket, ctx) => handleSessionsClear(socket, ctx),
531
+ session_switch: handleSessionSwitch,
532
+ cancel: handleCancel,
533
+ delete_queued_message: handleDeleteQueuedMessage,
534
+ history_request: handleHistoryRequest,
535
+ undo: handleUndo,
536
+ regenerate: handleRegenerate,
537
+ usage_request: handleUsageRequest,
538
+ sandbox_set: handleSandboxSet,
539
+ });