vellum 0.2.0 → 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 (361) 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 +161 -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__/app-bundler.test.ts +12 -33
  11. package/src/__tests__/asset-materialize-tool.test.ts +16 -15
  12. package/src/__tests__/asset-search-tool.test.ts +23 -22
  13. package/src/__tests__/attachments-store.test.ts +56 -127
  14. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  15. package/src/__tests__/browser-skill-endstate.test.ts +5 -8
  16. package/src/__tests__/call-bridge.test.ts +385 -0
  17. package/src/__tests__/call-constants.test.ts +40 -0
  18. package/src/__tests__/call-orchestrator.test.ts +454 -0
  19. package/src/__tests__/call-recovery.test.ts +518 -0
  20. package/src/__tests__/call-routes-http.test.ts +459 -0
  21. package/src/__tests__/call-state-machine.test.ts +143 -0
  22. package/src/__tests__/call-state.test.ts +133 -0
  23. package/src/__tests__/call-store.test.ts +691 -0
  24. package/src/__tests__/cli-discover.test.ts +1 -1
  25. package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
  26. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  27. package/src/__tests__/computer-use-tools.test.ts +250 -0
  28. package/src/__tests__/config-schema.test.ts +348 -3
  29. package/src/__tests__/conflict-store.test.ts +2 -1
  30. package/src/__tests__/contacts-tools.test.ts +331 -0
  31. package/src/__tests__/conversation-store.test.ts +30 -32
  32. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  33. package/src/__tests__/date-context.test.ts +373 -0
  34. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  35. package/src/__tests__/doordash-session.test.ts +9 -0
  36. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  37. package/src/__tests__/followup-tools.test.ts +303 -0
  38. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  39. package/src/__tests__/intent-routing.test.ts +64 -57
  40. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  41. package/src/__tests__/ipc-snapshot.test.ts +96 -28
  42. package/src/__tests__/llm-usage-store.test.ts +3 -8
  43. package/src/__tests__/media-generate-image.test.ts +1 -1
  44. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  46. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  47. package/src/__tests__/playbook-tools.test.ts +342 -0
  48. package/src/__tests__/profile-compiler.test.ts +2 -1
  49. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  50. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  51. package/src/__tests__/recurrence-engine.test.ts +69 -0
  52. package/src/__tests__/recurrence-types.test.ts +71 -0
  53. package/src/__tests__/registry.test.ts +17 -10
  54. package/src/__tests__/relay-server.test.ts +633 -0
  55. package/src/__tests__/reminder-store.test.ts +6 -3
  56. package/src/__tests__/reminder.test.ts +43 -77
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
  58. package/src/__tests__/run-orchestrator.test.ts +7 -7
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
  60. package/src/__tests__/runtime-runs-http.test.ts +5 -23
  61. package/src/__tests__/runtime-runs.test.ts +11 -11
  62. package/src/__tests__/schedule-store.test.ts +482 -0
  63. package/src/__tests__/schedule-tools.test.ts +700 -0
  64. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  65. package/src/__tests__/server-history-render.test.ts +14 -13
  66. package/src/__tests__/session-error.test.ts +28 -0
  67. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  68. package/src/__tests__/session-queue.test.ts +89 -16
  69. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  70. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  71. package/src/__tests__/signup-e2e.test.ts +2 -1
  72. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  73. package/src/__tests__/skill-script-runner.test.ts +159 -0
  74. package/src/__tests__/speaker-identification.test.ts +52 -0
  75. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  76. package/src/__tests__/subagent-tools.test.ts +141 -41
  77. package/src/__tests__/task-compiler.test.ts +2 -1
  78. package/src/__tests__/task-runner.test.ts +2 -1
  79. package/src/__tests__/task-scheduler.test.ts +2 -1
  80. package/src/__tests__/task-tools.test.ts +49 -56
  81. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  82. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  84. package/src/__tests__/tool-executor.test.ts +13 -17
  85. package/src/__tests__/turn-commit.test.ts +273 -2
  86. package/src/__tests__/twilio-provider.test.ts +143 -0
  87. package/src/__tests__/twilio-routes.test.ts +789 -0
  88. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  89. package/src/__tests__/view-image-tool.test.ts +217 -0
  90. package/src/__tests__/workspace-git-service.test.ts +403 -0
  91. package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  93. package/src/bundler/app-bundler.ts +35 -14
  94. package/src/calls/call-bridge.ts +95 -0
  95. package/src/calls/call-constants.ts +48 -0
  96. package/src/calls/call-domain.ts +276 -0
  97. package/src/calls/call-orchestrator.ts +390 -0
  98. package/src/calls/call-recovery.ts +207 -0
  99. package/src/calls/call-state-machine.ts +68 -0
  100. package/src/calls/call-state.ts +64 -0
  101. package/src/calls/call-store.ts +416 -0
  102. package/src/calls/relay-server.ts +335 -0
  103. package/src/calls/speaker-identification.ts +213 -0
  104. package/src/calls/twilio-config.ts +34 -0
  105. package/src/calls/twilio-provider.ts +173 -0
  106. package/src/calls/twilio-routes.ts +250 -0
  107. package/src/calls/types.ts +37 -0
  108. package/src/calls/voice-provider.ts +14 -0
  109. package/src/cli/config-commands.ts +334 -0
  110. package/src/cli/core-commands.ts +776 -0
  111. package/src/cli/doordash.ts +256 -25
  112. package/src/cli/ipc-client.ts +82 -0
  113. package/src/cli/map.ts +246 -0
  114. package/src/cli/twitter.ts +575 -0
  115. package/src/cli.ts +7 -5
  116. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  117. package/src/commands/cc-command-registry.ts +209 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  119. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  120. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  123. package/src/config/bundled-skills/document/SKILL.md +18 -0
  124. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  125. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  126. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  127. package/src/config/bundled-skills/doordash/SKILL.md +163 -0
  128. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  129. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  130. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  131. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  133. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
  135. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  136. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  137. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  138. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  139. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  140. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  141. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  142. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  143. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  144. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  145. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  146. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  149. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  150. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  151. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  152. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  153. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  154. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  155. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  156. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  157. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  158. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  159. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  160. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  161. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  162. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  163. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  164. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  165. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  166. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  167. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  168. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  169. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  170. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  171. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  172. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  173. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  174. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  175. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  176. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  177. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  178. package/src/config/defaults.ts +44 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +218 -1
  181. package/src/config/system-prompt.ts +100 -6
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +5 -0
  184. package/src/contacts/contact-store.ts +4 -4
  185. package/src/daemon/assistant-attachments.ts +10 -0
  186. package/src/daemon/classifier.ts +3 -1
  187. package/src/daemon/computer-use-session.ts +3 -1
  188. package/src/daemon/date-context.ts +136 -0
  189. package/src/daemon/handlers/apps.ts +16 -1
  190. package/src/daemon/handlers/browser.ts +54 -0
  191. package/src/daemon/handlers/computer-use.ts +7 -1
  192. package/src/daemon/handlers/config.ts +192 -4
  193. package/src/daemon/handlers/diagnostics.ts +5 -1
  194. package/src/daemon/handlers/documents.ts +18 -29
  195. package/src/daemon/handlers/home-base.ts +5 -1
  196. package/src/daemon/handlers/index.ts +40 -271
  197. package/src/daemon/handlers/misc.ts +9 -1
  198. package/src/daemon/handlers/publish.ts +6 -1
  199. package/src/daemon/handlers/sessions.ts +65 -12
  200. package/src/daemon/handlers/shared.ts +36 -1
  201. package/src/daemon/handlers/signing.ts +37 -0
  202. package/src/daemon/handlers/skills.ts +20 -6
  203. package/src/daemon/handlers/subagents.ts +8 -3
  204. package/src/daemon/handlers/twitter-auth.ts +169 -0
  205. package/src/daemon/handlers/work-items.ts +495 -39
  206. package/src/daemon/ipc-contract-inventory.json +40 -4
  207. package/src/daemon/ipc-contract.ts +185 -37
  208. package/src/daemon/ipc-protocol.ts +7 -2
  209. package/src/daemon/lifecycle.ts +48 -5
  210. package/src/daemon/main.ts +10 -4
  211. package/src/daemon/ride-shotgun-handler.ts +74 -10
  212. package/src/daemon/server.ts +144 -29
  213. package/src/daemon/session-agent-loop.ts +887 -0
  214. package/src/daemon/session-attachments.ts +28 -5
  215. package/src/daemon/session-error.ts +24 -3
  216. package/src/daemon/session-lifecycle.ts +147 -0
  217. package/src/daemon/session-media-retry.ts +147 -0
  218. package/src/daemon/session-messaging.ts +145 -0
  219. package/src/daemon/session-notifiers.ts +164 -0
  220. package/src/daemon/session-process.ts +2 -2
  221. package/src/daemon/session-queue-manager.ts +1 -0
  222. package/src/daemon/session-runtime-assembly.ts +52 -0
  223. package/src/daemon/session-skill-tools.ts +124 -5
  224. package/src/daemon/session-slash.ts +3 -0
  225. package/src/daemon/session-surfaces.ts +77 -2
  226. package/src/daemon/session-tool-setup.ts +222 -2
  227. package/src/daemon/session-usage.ts +0 -2
  228. package/src/daemon/session.ts +114 -1365
  229. package/src/daemon/video-thumbnail.ts +60 -0
  230. package/src/doordash/client.ts +121 -27
  231. package/src/doordash/queries.ts +1 -2
  232. package/src/export/formatter.ts +3 -1
  233. package/src/followups/followup-store.ts +4 -2
  234. package/src/followups/types.ts +6 -0
  235. package/src/hooks/templates.ts +1 -1
  236. package/src/index.ts +32 -1151
  237. package/src/media/gemini-image-service.ts +1 -1
  238. package/src/memory/attachments-store.ts +28 -83
  239. package/src/memory/channel-delivery-store.ts +7 -21
  240. package/src/memory/clarification-resolver.ts +6 -5
  241. package/src/memory/contradiction-checker.ts +3 -2
  242. package/src/memory/conversation-key-store.ts +10 -29
  243. package/src/memory/conversation-store.ts +2 -1
  244. package/src/memory/db.ts +362 -2
  245. package/src/memory/entity-extractor.ts +6 -3
  246. package/src/memory/items-extractor.ts +5 -4
  247. package/src/memory/jobs-store.ts +3 -2
  248. package/src/memory/llm-usage-store.ts +1 -2
  249. package/src/memory/runs-store.ts +1 -2
  250. package/src/memory/schema.ts +65 -2
  251. package/src/messaging/style-analyzer.ts +3 -2
  252. package/src/messaging/thread-summarizer.ts +8 -12
  253. package/src/messaging/triage-engine.ts +4 -2
  254. package/src/providers/openrouter/client.ts +20 -0
  255. package/src/providers/registry.ts +8 -0
  256. package/src/runtime/http-server.ts +277 -25
  257. package/src/runtime/http-types.ts +0 -2
  258. package/src/runtime/routes/attachment-routes.ts +5 -6
  259. package/src/runtime/routes/call-routes.ts +140 -0
  260. package/src/runtime/routes/channel-routes.ts +12 -19
  261. package/src/runtime/routes/conversation-routes.ts +5 -9
  262. package/src/runtime/routes/run-routes.ts +4 -8
  263. package/src/runtime/run-orchestrator.ts +39 -6
  264. package/src/schedule/recurrence-engine.ts +138 -0
  265. package/src/schedule/recurrence-types.ts +67 -0
  266. package/src/schedule/schedule-store.ts +102 -57
  267. package/src/schedule/scheduler.ts +9 -6
  268. package/src/security/oauth2.ts +29 -4
  269. package/src/security/secret-allowlist.ts +46 -0
  270. package/src/skills/clawhub.ts +1 -1
  271. package/src/subagent/manager.ts +40 -8
  272. package/src/swarm/backend-claude-code.ts +64 -9
  273. package/src/swarm/worker-prompts.ts +2 -1
  274. package/src/tasks/SPEC.md +34 -28
  275. package/src/tasks/ephemeral-permissions.ts +16 -7
  276. package/src/tasks/task-compiler.ts +5 -4
  277. package/src/tasks/task-runner.ts +10 -5
  278. package/src/tasks/task-scheduler.ts +1 -1
  279. package/src/tasks/tool-sanitizer.ts +36 -0
  280. package/src/tools/assets/search.ts +4 -4
  281. package/src/tools/browser/api-map.ts +220 -0
  282. package/src/tools/browser/auto-navigate.ts +270 -0
  283. package/src/tools/browser/browser-execution.ts +2 -1
  284. package/src/tools/browser/browser-manager.ts +2 -2
  285. package/src/tools/browser/network-recorder.ts +5 -4
  286. package/src/tools/browser/x-auto-navigate.ts +207 -0
  287. package/src/tools/calls/call-end.ts +67 -0
  288. package/src/tools/calls/call-start.ts +73 -0
  289. package/src/tools/calls/call-status.ts +81 -0
  290. package/src/tools/claude-code/claude-code.ts +77 -11
  291. package/src/tools/contacts/contact-merge.ts +46 -78
  292. package/src/tools/contacts/contact-search.ts +35 -79
  293. package/src/tools/contacts/contact-upsert.ts +35 -108
  294. package/src/tools/credentials/vault.ts +21 -5
  295. package/src/tools/document/document-tool.ts +71 -144
  296. package/src/tools/executor.ts +129 -10
  297. package/src/tools/followups/followup_create.ts +46 -88
  298. package/src/tools/followups/followup_list.ts +34 -74
  299. package/src/tools/followups/followup_resolve.ts +31 -66
  300. package/src/tools/host-terminal/cli-discover.ts +2 -1
  301. package/src/tools/host-terminal/host-shell.ts +10 -0
  302. package/src/tools/memory/handlers.ts +5 -4
  303. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  304. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  305. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  306. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  307. package/src/tools/network/web-fetch.ts +18 -6
  308. package/src/tools/playbooks/index.ts +4 -5
  309. package/src/tools/playbooks/playbook-create.ts +3 -47
  310. package/src/tools/playbooks/playbook-delete.ts +1 -25
  311. package/src/tools/playbooks/playbook-list.ts +1 -28
  312. package/src/tools/playbooks/playbook-update.ts +3 -51
  313. package/src/tools/registry.ts +2 -4
  314. package/src/tools/reminder/reminder.ts +5 -78
  315. package/src/tools/schedule/create.ts +69 -74
  316. package/src/tools/schedule/delete.ts +21 -47
  317. package/src/tools/schedule/list.ts +55 -74
  318. package/src/tools/schedule/update.ts +77 -84
  319. package/src/tools/subagent/abort.ts +29 -58
  320. package/src/tools/subagent/message.ts +30 -63
  321. package/src/tools/subagent/read.ts +53 -84
  322. package/src/tools/subagent/spawn.ts +43 -82
  323. package/src/tools/subagent/status.ts +42 -71
  324. package/src/tools/swarm/delegate.ts +2 -1
  325. package/src/tools/tasks/index.ts +8 -6
  326. package/src/tools/tasks/task-delete.ts +69 -56
  327. package/src/tools/tasks/task-list.ts +31 -52
  328. package/src/tools/tasks/task-run.ts +74 -102
  329. package/src/tools/tasks/task-save.ts +33 -65
  330. package/src/tools/tasks/work-item-enqueue.ts +192 -134
  331. package/src/tools/tasks/work-item-list.ts +33 -78
  332. package/src/tools/tasks/work-item-remove.ts +60 -0
  333. package/src/tools/tasks/work-item-update.ts +114 -0
  334. package/src/tools/terminal/backends/native.ts +3 -1
  335. package/src/tools/tool-manifest.ts +20 -74
  336. package/src/tools/types.ts +6 -0
  337. package/src/tools/ui-surface/definitions.ts +6 -1
  338. package/src/tools/watch/screen-watch.ts +3 -1
  339. package/src/tools/watcher/create.ts +52 -98
  340. package/src/tools/watcher/delete.ts +20 -46
  341. package/src/tools/watcher/digest.ts +36 -70
  342. package/src/tools/watcher/list.ts +49 -79
  343. package/src/tools/watcher/update.ts +45 -91
  344. package/src/twitter/client.ts +690 -0
  345. package/src/twitter/session.ts +91 -0
  346. package/src/usage/types.ts +0 -1
  347. package/src/util/truncate.ts +6 -0
  348. package/src/watcher/providers/slack.ts +2 -1
  349. package/src/watcher/watcher-store.ts +3 -2
  350. package/src/work-items/work-item-store.ts +236 -2
  351. package/src/workspace/commit-message-enrichment-service.ts +284 -0
  352. package/src/workspace/commit-message-provider.ts +95 -0
  353. package/src/workspace/git-service.ts +272 -52
  354. package/src/workspace/heartbeat-service.ts +70 -13
  355. package/src/workspace/provider-commit-message-generator.ts +242 -0
  356. package/src/workspace/turn-commit.ts +100 -51
  357. package/src/tools/contacts/index.ts +0 -4
  358. package/src/tools/document/index.ts +0 -5
  359. package/src/tools/followups/index.ts +0 -3
  360. package/src/tools/subagent/index.ts +0 -5
  361. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
@@ -3,114 +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
- handleAddTrustRule,
59
- handleTrustRulesList,
60
- handleRemoveTrustRule,
61
- handleUpdateTrustRule,
62
- handleAcceptStarterBundle,
63
- handleSchedulesList,
64
- handleScheduleToggle,
65
- handleScheduleRemove,
66
- handleRemindersList,
67
- handleReminderCancel,
68
- handleShareToSlack,
69
- handleSlackWebhookConfig,
70
- handleVercelApiConfig,
71
- handleEnvVarsRequest,
72
- } from './config.js';
73
-
74
- import {
75
- handleCuSessionCreate,
76
- handleCuSessionAbort,
77
- handleCuObservation,
78
- } from './computer-use.js';
79
-
80
- import {
81
- handlePublishPage,
82
- handleUnpublishPage,
83
- } from './publish.js';
84
- import { handleHomeBaseGet } from './home-base.js';
85
- import { handleDiagnosticsExport } from './diagnostics.js';
86
-
87
- import {
88
- handleTaskSubmit,
89
- handleSuggestionRequest,
90
- handleLinkOpenRequest,
91
- handleIpcBlobProbe,
92
- } from './misc.js';
93
-
94
- import {
95
- handleDocumentSave,
96
- handleDocumentLoad,
97
- handleDocumentList,
98
- } from './documents.js';
99
-
100
- import {
101
- handleWorkItemsList,
102
- handleWorkItemGet,
103
- handleWorkItemCreate,
104
- handleWorkItemUpdate,
105
- handleWorkItemComplete,
106
- handleWorkItemRunTask,
107
- } from './work-items.js';
108
-
109
- import {
110
- handleSubagentAbort,
111
- handleSubagentStatus,
112
- handleSubagentMessage,
113
- } 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';
114
23
 
115
24
  // Re-export types and utilities for backwards compatibility
116
25
  export type {
@@ -129,105 +38,13 @@ export {
129
38
 
130
39
  // ─── Typed dispatch ──────────────────────────────────────────────────────────
131
40
 
132
- type MessageType = ClientMessage['type'];
133
- // 'auth' is handled at the transport layer (server.ts) and never reaches dispatch.
134
- type DispatchableType = Exclude<MessageType, 'auth'>;
135
- type MessageOfType<T extends MessageType> = Extract<ClientMessage, { type: T }>;
136
- type MessageHandler<T extends MessageType> = (
137
- msg: MessageOfType<T>,
138
- socket: net.Socket,
139
- ctx: HandlerContext,
140
- ) => void | Promise<void>;
141
- type DispatchMap = { [T in DispatchableType]: MessageHandler<T> };
142
-
143
- const handlers: DispatchMap = {
144
- user_message: handleUserMessage,
145
- confirmation_response: handleConfirmationResponse,
146
- secret_response: handleSecretResponse,
147
- session_list: (_msg, socket, ctx) => handleSessionList(socket, ctx),
148
- session_create: handleSessionCreate,
149
- sessions_clear: (_msg, socket, ctx) => handleSessionsClear(socket, ctx),
150
- session_switch: handleSessionSwitch,
151
- cancel: handleCancel,
152
- delete_queued_message: handleDeleteQueuedMessage,
153
- model_get: (_msg, socket, ctx) => handleModelGet(socket, ctx),
154
- model_set: handleModelSet,
155
- history_request: handleHistoryRequest,
156
- undo: handleUndo,
157
- regenerate: handleRegenerate,
158
- usage_request: handleUsageRequest,
159
- sandbox_set: handleSandboxSet,
160
- cu_session_create: handleCuSessionCreate,
161
- cu_session_abort: handleCuSessionAbort,
162
- cu_observation: handleCuObservation,
41
+ // Inline handlers for messages not owned by any feature group
42
+ const inlineHandlers = defineHandlers({
163
43
  ride_shotgun_start: handleRideShotgunStart,
164
44
  ride_shotgun_stop: handleRideShotgunStop,
165
45
  watch_observation: handleWatchObservation,
166
- task_submit: handleTaskSubmit,
167
- app_data_request: handleAppDataRequest,
168
- skills_list: (_msg, socket, ctx) => handleSkillsList(socket, ctx),
169
- skill_detail: handleSkillDetail,
170
- skills_enable: handleSkillsEnable,
171
- skills_disable: handleSkillsDisable,
172
- skills_configure: handleSkillsConfigure,
173
- skills_install: handleSkillsInstall,
174
- skills_uninstall: handleSkillsUninstall,
175
- skills_update: handleSkillsUpdate,
176
- skills_check_updates: handleSkillsCheckUpdates,
177
- skills_search: handleSkillsSearch,
178
- skills_inspect: handleSkillsInspect,
179
- suggestion_request: handleSuggestionRequest,
180
- add_trust_rule: handleAddTrustRule,
181
- trust_rules_list: (_msg, socket, ctx) => handleTrustRulesList(socket, ctx),
182
- remove_trust_rule: handleRemoveTrustRule,
183
- update_trust_rule: handleUpdateTrustRule,
184
- accept_starter_bundle: (_msg, socket, ctx) => handleAcceptStarterBundle(socket, ctx),
185
- schedules_list: (_msg, socket, ctx) => handleSchedulesList(socket, ctx),
186
- schedule_toggle: handleScheduleToggle,
187
- schedule_remove: handleScheduleRemove,
188
- reminders_list: (_msg, socket, ctx) => handleRemindersList(socket, ctx),
189
- reminder_cancel: handleReminderCancel,
190
- share_app_cloud: handleShareAppCloud,
191
- bundle_app: handleBundleApp,
192
46
  open_bundle: handleOpenBundle,
193
- app_open_request: (msg, socket, ctx) => handleAppOpenRequest(msg, socket, ctx),
194
- app_update_preview: handleAppUpdatePreview,
195
- apps_list: (_msg, socket, ctx) => handleAppsList(socket, ctx),
196
- app_preview_request: handleAppPreview,
197
- home_base_get: handleHomeBaseGet,
198
- shared_apps_list: (_msg, socket, ctx) => handleSharedAppsList(socket, ctx),
199
- shared_app_delete: handleSharedAppDelete,
200
- fork_shared_app: handleForkSharedApp,
201
- sign_bundle_payload_response: (msg) => {
202
- const pending = pendingSignBundlePayload.get(msg.requestId);
203
- if (pending) {
204
- clearTimeout(pending.timer);
205
- pendingSignBundlePayload.delete(msg.requestId);
206
- pending.resolve({ signature: msg.signature, keyId: msg.keyId, publicKey: msg.publicKey });
207
- } else {
208
- log.warn({ requestId: msg.requestId }, 'Received sign_bundle_payload_response with no pending request');
209
- }
210
- },
211
- get_signing_identity_response: (msg) => {
212
- const pending = pendingSigningIdentity.get(msg.requestId);
213
- if (pending) {
214
- clearTimeout(pending.timer);
215
- pendingSigningIdentity.delete(msg.requestId);
216
- pending.resolve({ keyId: msg.keyId, publicKey: msg.publicKey });
217
- } else {
218
- log.warn({ requestId: msg.requestId }, 'Received get_signing_identity_response with no pending request');
219
- }
220
- },
221
- gallery_list: (_msg, socket, ctx) => handleGalleryList(socket, ctx),
222
- gallery_install: handleGalleryInstall,
223
- share_to_slack: handleShareToSlack,
224
- slack_webhook_config: handleSlackWebhookConfig,
225
- vercel_api_config: handleVercelApiConfig,
226
- publish_page: handlePublishPage,
227
- unpublish_page: handleUnpublishPage,
228
- ping: (_msg, socket, ctx) => { ctx.send(socket, { type: 'pong' }); },
229
- link_open_request: handleLinkOpenRequest,
230
- ipc_blob_probe: handleIpcBlobProbe,
47
+
231
48
  ui_surface_action: (msg, _socket, ctx) => {
232
49
  const cuSession = ctx.cuSessions.get(msg.sessionId);
233
50
  if (cuSession) {
@@ -251,15 +68,6 @@ const handlers: DispatchMap = {
251
68
  }
252
69
  log.warn({ sessionId: msg.sessionId, surfaceId: msg.surfaceId }, 'No session found for surface undo');
253
70
  },
254
- diagnostics_export_request: handleDiagnosticsExport,
255
- env_vars_request: (_msg, socket, ctx) => handleEnvVarsRequest(socket, ctx),
256
- document_save: handleDocumentSave,
257
- document_load: handleDocumentLoad,
258
- document_list: handleDocumentList,
259
-
260
- browser_cdp_response: (msg) => {
261
- browserManager.resolveCDPResponse(msg.sessionId, msg.success, msg.declined);
262
- },
263
71
 
264
72
  // Stub handlers: the integration registry was removed but the Swift client
265
73
  // still sends these messages. Return safe no-op responses so the client
@@ -275,66 +83,27 @@ const handlers: DispatchMap = {
275
83
  error: 'Please use chat to connect integrations.',
276
84
  });
277
85
  },
278
-
279
- browser_user_click: async (msg) => {
280
- try {
281
- const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
282
- const viewport = await page.evaluate('(() => ({ vw: window.innerWidth, vh: window.innerHeight }))()') as { vw: number; vh: number };
283
- const scale = Math.min(1280 / viewport.vw, 960 / viewport.vh);
284
- const pageX = msg.x / scale;
285
- const pageY = msg.y / scale;
286
- const options: Record<string, unknown> = {};
287
- if (msg.button === 'right') options.button = 'right';
288
- if (msg.doubleClick) options.clickCount = 2;
289
- await page.mouse.click(pageX, pageY, options);
290
- } catch (err) {
291
- log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user click');
292
- }
293
- },
294
-
295
- browser_user_scroll: async (msg) => {
296
- try {
297
- const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
298
- await page.mouse.wheel(msg.deltaX, msg.deltaY);
299
- } catch (err) {
300
- log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user scroll');
301
- }
302
- },
303
-
304
- browser_user_keypress: async (msg) => {
305
- try {
306
- const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
307
- const combo = msg.modifiers?.length ? [...msg.modifiers, msg.key].join('+') : msg.key;
308
- await page.keyboard.press(combo);
309
- } catch (err) {
310
- log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user keypress');
311
- }
312
- },
313
-
314
- browser_interactive_mode: (msg, socket, ctx) => {
315
- log.info({ sessionId: msg.sessionId, enabled: msg.enabled }, 'Interactive mode toggled');
316
- browserManager.setInteractiveMode(msg.sessionId, msg.enabled);
317
- ctx.send(socket, {
318
- type: 'browser_interactive_mode_changed',
319
- sessionId: msg.sessionId,
320
- surfaceId: msg.surfaceId,
321
- enabled: msg.enabled,
322
- });
323
- },
324
-
325
86
  integration_disconnect: () => { /* no-op — integration registry removed */ },
326
-
327
- work_items_list: handleWorkItemsList,
328
- work_item_get: handleWorkItemGet,
329
- work_item_create: handleWorkItemCreate,
330
- work_item_update: handleWorkItemUpdate,
331
- work_item_complete: handleWorkItemComplete,
332
- work_item_run_task: handleWorkItemRunTask,
333
-
334
- subagent_abort: handleSubagentAbort,
335
- subagent_status: handleSubagentStatus,
336
- subagent_message: handleSubagentMessage,
337
- };
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;
338
107
 
339
108
  export function handleMessage(
340
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
+ });
@@ -5,7 +5,7 @@ import { ComputerUseSession } from '../computer-use-session.js';
5
5
  import { getLogger } from '../../util/logger.js';
6
6
  import { execSync } from 'node:child_process';
7
7
  import { estimateBase64Bytes } from '../assistant-attachments.js';
8
- import type { CuSessionCreate, ServerMessage, SessionTransportMetadata } from '../ipc-protocol.js';
8
+ import type { ClientMessage, CuSessionCreate, ServerMessage, SessionTransportMetadata } from '../ipc-protocol.js';
9
9
  import type { SecretPromptResult } from '../../permissions/secret-prompter.js';
10
10
  import { getConfig } from '../../config/loader.js';
11
11
 
@@ -13,6 +13,9 @@ const log = getLogger('handlers');
13
13
 
14
14
  export { log };
15
15
 
16
+ /** Debounce window for suppressing file-watcher config reloads after programmatic saves. */
17
+ export const CONFIG_RELOAD_DEBOUNCE_MS = 300;
18
+
16
19
  const HISTORY_ATTACHMENT_TEXT_LIMIT = 500;
17
20
 
18
21
  export const FALLBACK_SCREEN = { width: 1920, height: 1080 };
@@ -67,6 +70,13 @@ export interface RenderedHistoryContent {
67
70
  surfaces: HistorySurface[];
68
71
  }
69
72
 
73
+ export interface SubagentNotificationData {
74
+ subagentId: string;
75
+ label: string;
76
+ status: 'completed' | 'failed' | 'aborted';
77
+ error?: string;
78
+ }
79
+
70
80
  export interface ParsedHistoryMessage {
71
81
  id?: string;
72
82
  role: string;
@@ -77,6 +87,7 @@ export interface ParsedHistoryMessage {
77
87
  textSegments: string[];
78
88
  contentOrder: string[];
79
89
  surfaces: HistorySurface[];
90
+ subagentNotification?: SubagentNotificationData;
80
91
  }
81
92
 
82
93
  /**
@@ -120,6 +131,30 @@ export interface HandlerContext {
120
131
  touchSession(sessionId: string): void;
121
132
  }
122
133
 
134
+ // ─── Typed dispatch ──────────────────────────────────────────────────────────
135
+
136
+ type MessageType = ClientMessage['type'];
137
+ // 'auth' is handled at the transport layer (server.ts) and never reaches dispatch.
138
+ export type DispatchableType = Exclude<MessageType, 'auth'>;
139
+ type MessageOfType<T extends MessageType> = Extract<ClientMessage, { type: T }>;
140
+ type MessageHandler<T extends MessageType> = (
141
+ msg: MessageOfType<T>,
142
+ socket: net.Socket,
143
+ ctx: HandlerContext,
144
+ ) => void | Promise<void>;
145
+ export type DispatchMap = { [T in DispatchableType]: MessageHandler<T> };
146
+
147
+ /**
148
+ * Type-safe handler group definition. Preserves exact key types so the
149
+ * combined spread in index.ts can be checked for exhaustiveness via
150
+ * `satisfies DispatchMap` instead of an unsafe `as DispatchMap` cast.
151
+ */
152
+ export function defineHandlers<K extends DispatchableType>(
153
+ handlers: Pick<DispatchMap, K>,
154
+ ): Pick<DispatchMap, K> {
155
+ return handlers;
156
+ }
157
+
123
158
  /**
124
159
  * Query the main display dimensions via CoreGraphics.
125
160
  * Cached after the first successful call; falls back to 1920x1080.
@@ -0,0 +1,37 @@
1
+ import { log, pendingSignBundlePayload, pendingSigningIdentity, defineHandlers } from './shared.js';
2
+
3
+ export const signingHandlers = defineHandlers({
4
+ sign_bundle_payload_response: (msg) => {
5
+ const pending = pendingSignBundlePayload.get(msg.requestId);
6
+ if (pending) {
7
+ clearTimeout(pending.timer);
8
+ pendingSignBundlePayload.delete(msg.requestId);
9
+ if (msg.error) {
10
+ pending.reject(new Error(msg.error));
11
+ } else if (msg.signature && msg.keyId && msg.publicKey) {
12
+ pending.resolve({ signature: msg.signature, keyId: msg.keyId, publicKey: msg.publicKey });
13
+ } else {
14
+ pending.reject(new Error('Missing required fields in sign_bundle_payload_response'));
15
+ }
16
+ } else {
17
+ log.warn({ requestId: msg.requestId }, 'Received sign_bundle_payload_response with no pending request');
18
+ }
19
+ },
20
+
21
+ get_signing_identity_response: (msg) => {
22
+ const pending = pendingSigningIdentity.get(msg.requestId);
23
+ if (pending) {
24
+ clearTimeout(pending.timer);
25
+ pendingSigningIdentity.delete(msg.requestId);
26
+ if (msg.error) {
27
+ pending.reject(new Error(msg.error));
28
+ } else if (msg.keyId && msg.publicKey) {
29
+ pending.resolve({ keyId: msg.keyId, publicKey: msg.publicKey });
30
+ } else {
31
+ pending.reject(new Error('Missing required fields in get_signing_identity_response'));
32
+ }
33
+ } else {
34
+ log.warn({ requestId: msg.requestId }, 'Received get_signing_identity_response with no pending request');
35
+ }
36
+ },
37
+ });