vellum 0.2.1 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +71 -100
  3. package/package.json +5 -3
  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 +305 -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-twilio-config.test.ts +221 -0
  36. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  37. package/src/__tests__/intent-routing.test.ts +64 -57
  38. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  39. package/src/__tests__/ipc-snapshot.test.ts +71 -28
  40. package/src/__tests__/llm-usage-store.test.ts +3 -8
  41. package/src/__tests__/media-generate-image.test.ts +1 -1
  42. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  43. package/src/__tests__/memory-regressions.test.ts +100 -2
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  45. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  46. package/src/__tests__/playbook-tools.test.ts +342 -0
  47. package/src/__tests__/profile-compiler.test.ts +2 -1
  48. package/src/__tests__/provider-commit-message-generator.test.ts +303 -0
  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 +5 -3
  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 +8 -4
  58. package/src/__tests__/run-orchestrator.test.ts +4 -4
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  60. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  61. package/src/__tests__/runtime-runs.test.ts +4 -4
  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-conflict-gate.test.ts +28 -25
  67. package/src/__tests__/session-error.test.ts +28 -0
  68. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  69. package/src/__tests__/session-queue.test.ts +71 -48
  70. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  71. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  72. package/src/__tests__/signup-e2e.test.ts +2 -1
  73. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  74. package/src/__tests__/skill-script-runner.test.ts +159 -0
  75. package/src/__tests__/speaker-identification.test.ts +52 -0
  76. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  77. package/src/__tests__/subagent-tools.test.ts +141 -41
  78. package/src/__tests__/task-compiler.test.ts +2 -1
  79. package/src/__tests__/task-runner.test.ts +2 -1
  80. package/src/__tests__/task-scheduler.test.ts +2 -1
  81. package/src/__tests__/task-tools.test.ts +49 -56
  82. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  83. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  84. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  85. package/src/__tests__/tool-executor.test.ts +13 -17
  86. package/src/__tests__/turn-commit.test.ts +218 -3
  87. package/src/__tests__/twilio-provider.test.ts +143 -0
  88. package/src/__tests__/twilio-routes.test.ts +789 -0
  89. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  90. package/src/__tests__/view-image-tool.test.ts +217 -0
  91. package/src/__tests__/workspace-git-service.test.ts +186 -0
  92. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  93. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  94. package/src/bundler/app-bundler.ts +12 -8
  95. package/src/calls/__tests__/twilio-webhook-urls.test.ts +162 -0
  96. package/src/calls/call-bridge.ts +95 -0
  97. package/src/calls/call-constants.ts +43 -5
  98. package/src/calls/call-domain.ts +276 -0
  99. package/src/calls/call-orchestrator.ts +43 -17
  100. package/src/calls/call-recovery.ts +207 -0
  101. package/src/calls/call-state-machine.ts +68 -0
  102. package/src/calls/call-store.ts +192 -5
  103. package/src/calls/relay-server.ts +41 -4
  104. package/src/calls/speaker-identification.ts +213 -0
  105. package/src/calls/twilio-config.ts +8 -8
  106. package/src/calls/twilio-provider.ts +13 -9
  107. package/src/calls/twilio-routes.ts +90 -76
  108. package/src/calls/twilio-webhook-urls.ts +50 -0
  109. package/src/calls/types.ts +1 -1
  110. package/src/cli/config-commands.ts +334 -0
  111. package/src/cli/core-commands.ts +776 -0
  112. package/src/cli/doordash.ts +251 -1
  113. package/src/cli/ipc-client.ts +82 -0
  114. package/src/cli/map.ts +270 -0
  115. package/src/cli/twitter.ts +575 -0
  116. package/src/cli.ts +7 -5
  117. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  118. package/src/commands/cc-command-registry.ts +209 -0
  119. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  120. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  123. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  124. package/src/config/bundled-skills/document/SKILL.md +18 -0
  125. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  126. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  127. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  128. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  129. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  130. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  131. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  133. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  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 +34 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +165 -1
  181. package/src/config/system-prompt.ts +61 -16
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +4 -0
  184. package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -5
  185. package/src/contacts/contact-store.ts +4 -4
  186. package/src/daemon/assistant-attachments.ts +10 -0
  187. package/src/daemon/classifier.ts +3 -1
  188. package/src/daemon/computer-use-session.ts +3 -1
  189. package/src/daemon/date-context.ts +136 -0
  190. package/src/daemon/handlers/apps.ts +16 -1
  191. package/src/daemon/handlers/browser.ts +54 -0
  192. package/src/daemon/handlers/computer-use.ts +7 -1
  193. package/src/daemon/handlers/config.ts +205 -5
  194. package/src/daemon/handlers/diagnostics.ts +5 -1
  195. package/src/daemon/handlers/documents.ts +18 -29
  196. package/src/daemon/handlers/home-base.ts +5 -1
  197. package/src/daemon/handlers/index.ts +40 -277
  198. package/src/daemon/handlers/misc.ts +9 -1
  199. package/src/daemon/handlers/publish.ts +6 -1
  200. package/src/daemon/handlers/sessions.ts +65 -12
  201. package/src/daemon/handlers/shared.ts +36 -1
  202. package/src/daemon/handlers/signing.ts +37 -0
  203. package/src/daemon/handlers/skills.ts +20 -6
  204. package/src/daemon/handlers/subagents.ts +8 -3
  205. package/src/daemon/handlers/twitter-auth.ts +169 -0
  206. package/src/daemon/handlers/work-items.ts +384 -68
  207. package/src/daemon/ipc-contract-inventory.json +32 -4
  208. package/src/daemon/ipc-contract.ts +156 -37
  209. package/src/daemon/ipc-protocol.ts +7 -2
  210. package/src/daemon/lifecycle.ts +21 -0
  211. package/src/daemon/main.ts +10 -4
  212. package/src/daemon/ride-shotgun-handler.ts +75 -10
  213. package/src/daemon/server.ts +143 -26
  214. package/src/daemon/session-agent-loop.ts +922 -0
  215. package/src/daemon/session-attachments.ts +28 -5
  216. package/src/daemon/session-conflict-gate.ts +18 -109
  217. package/src/daemon/session-error.ts +24 -3
  218. package/src/daemon/session-lifecycle.ts +147 -0
  219. package/src/daemon/session-media-retry.ts +147 -0
  220. package/src/daemon/session-messaging.ts +145 -0
  221. package/src/daemon/session-notifiers.ts +164 -0
  222. package/src/daemon/session-process.ts +2 -2
  223. package/src/daemon/session-queue-manager.ts +1 -0
  224. package/src/daemon/session-runtime-assembly.ts +52 -0
  225. package/src/daemon/session-skill-tools.ts +124 -5
  226. package/src/daemon/session-slash.ts +3 -0
  227. package/src/daemon/session-surfaces.ts +77 -2
  228. package/src/daemon/session-tool-setup.ts +216 -2
  229. package/src/daemon/session-usage.ts +0 -2
  230. package/src/daemon/session.ts +114 -1404
  231. package/src/daemon/video-thumbnail.ts +60 -0
  232. package/src/doordash/client.ts +121 -27
  233. package/src/doordash/queries.ts +1 -2
  234. package/src/export/formatter.ts +3 -1
  235. package/src/followups/followup-store.ts +4 -2
  236. package/src/followups/types.ts +6 -0
  237. package/src/hooks/templates.ts +1 -1
  238. package/src/index.ts +32 -1153
  239. package/src/memory/attachments-store.ts +28 -83
  240. package/src/memory/channel-delivery-store.ts +7 -21
  241. package/src/memory/clarification-resolver.ts +6 -5
  242. package/src/memory/conflict-intent.ts +114 -0
  243. package/src/memory/contradiction-checker.ts +3 -2
  244. package/src/memory/conversation-key-store.ts +10 -29
  245. package/src/memory/conversation-store.ts +2 -1
  246. package/src/memory/db.ts +96 -2
  247. package/src/memory/entity-extractor.ts +6 -3
  248. package/src/memory/items-extractor.ts +5 -4
  249. package/src/memory/job-handlers/conflict.ts +23 -1
  250. package/src/memory/jobs-store.ts +3 -2
  251. package/src/memory/llm-usage-store.ts +1 -2
  252. package/src/memory/runs-store.ts +1 -2
  253. package/src/memory/schema.ts +23 -2
  254. package/src/messaging/style-analyzer.ts +3 -2
  255. package/src/messaging/thread-summarizer.ts +8 -12
  256. package/src/messaging/triage-engine.ts +4 -2
  257. package/src/providers/openrouter/client.ts +20 -0
  258. package/src/providers/registry.ts +8 -0
  259. package/src/runtime/gateway-client.ts +36 -0
  260. package/src/runtime/http-server.ts +166 -22
  261. package/src/runtime/routes/attachment-routes.ts +2 -3
  262. package/src/runtime/routes/call-routes.ts +140 -0
  263. package/src/runtime/routes/channel-routes.ts +125 -88
  264. package/src/runtime/routes/conversation-routes.ts +5 -5
  265. package/src/runtime/routes/run-routes.ts +2 -2
  266. package/src/runtime/run-orchestrator.ts +9 -3
  267. package/src/schedule/recurrence-engine.ts +138 -0
  268. package/src/schedule/recurrence-types.ts +67 -0
  269. package/src/schedule/schedule-store.ts +102 -57
  270. package/src/schedule/scheduler.ts +9 -6
  271. package/src/security/oauth2.ts +29 -4
  272. package/src/security/secret-allowlist.ts +46 -0
  273. package/src/skills/clawhub.ts +1 -1
  274. package/src/subagent/manager.ts +40 -8
  275. package/src/swarm/backend-claude-code.ts +64 -9
  276. package/src/swarm/worker-prompts.ts +2 -1
  277. package/src/tasks/SPEC.md +34 -28
  278. package/src/tasks/ephemeral-permissions.ts +16 -7
  279. package/src/tasks/task-compiler.ts +5 -4
  280. package/src/tasks/task-runner.ts +10 -5
  281. package/src/tasks/task-scheduler.ts +1 -1
  282. package/src/tasks/tool-sanitizer.ts +36 -0
  283. package/src/tools/assets/search.ts +4 -4
  284. package/src/tools/browser/api-map.ts +293 -0
  285. package/src/tools/browser/auto-navigate.ts +270 -0
  286. package/src/tools/browser/browser-execution.ts +2 -1
  287. package/src/tools/browser/browser-manager.ts +2 -2
  288. package/src/tools/browser/network-recorder.ts +5 -4
  289. package/src/tools/browser/x-auto-navigate.ts +207 -0
  290. package/src/tools/calls/call-end.ts +17 -67
  291. package/src/tools/calls/call-start.ts +24 -85
  292. package/src/tools/calls/call-status.ts +35 -51
  293. package/src/tools/claude-code/claude-code.ts +207 -11
  294. package/src/tools/contacts/contact-merge.ts +46 -78
  295. package/src/tools/contacts/contact-search.ts +35 -79
  296. package/src/tools/contacts/contact-upsert.ts +35 -108
  297. package/src/tools/credentials/vault.ts +20 -4
  298. package/src/tools/document/document-tool.ts +71 -144
  299. package/src/tools/executor.ts +129 -10
  300. package/src/tools/followups/followup_create.ts +46 -88
  301. package/src/tools/followups/followup_list.ts +34 -74
  302. package/src/tools/followups/followup_resolve.ts +31 -66
  303. package/src/tools/host-terminal/cli-discover.ts +2 -1
  304. package/src/tools/host-terminal/host-shell.ts +10 -0
  305. package/src/tools/memory/handlers.ts +5 -4
  306. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  307. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  308. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  309. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  310. package/src/tools/network/web-fetch.ts +18 -6
  311. package/src/tools/playbooks/index.ts +4 -5
  312. package/src/tools/playbooks/playbook-create.ts +3 -47
  313. package/src/tools/playbooks/playbook-delete.ts +1 -25
  314. package/src/tools/playbooks/playbook-list.ts +1 -28
  315. package/src/tools/playbooks/playbook-update.ts +3 -51
  316. package/src/tools/reminder/reminder.ts +5 -78
  317. package/src/tools/schedule/create.ts +69 -74
  318. package/src/tools/schedule/delete.ts +21 -47
  319. package/src/tools/schedule/list.ts +55 -74
  320. package/src/tools/schedule/update.ts +77 -84
  321. package/src/tools/subagent/abort.ts +29 -58
  322. package/src/tools/subagent/message.ts +30 -63
  323. package/src/tools/subagent/read.ts +53 -84
  324. package/src/tools/subagent/spawn.ts +43 -82
  325. package/src/tools/subagent/status.ts +42 -71
  326. package/src/tools/swarm/delegate.ts +2 -1
  327. package/src/tools/tasks/index.ts +8 -8
  328. package/src/tools/tasks/task-delete.ts +60 -88
  329. package/src/tools/tasks/task-list.ts +31 -52
  330. package/src/tools/tasks/task-run.ts +72 -108
  331. package/src/tools/tasks/task-save.ts +33 -65
  332. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  333. package/src/tools/tasks/work-item-list.ts +33 -63
  334. package/src/tools/tasks/work-item-remove.ts +45 -97
  335. package/src/tools/tasks/work-item-update.ts +91 -163
  336. package/src/tools/terminal/backends/native.ts +3 -1
  337. package/src/tools/tool-manifest.ts +0 -62
  338. package/src/tools/types.ts +6 -0
  339. package/src/tools/ui-surface/definitions.ts +3 -1
  340. package/src/tools/watch/screen-watch.ts +3 -1
  341. package/src/tools/watcher/create.ts +52 -98
  342. package/src/tools/watcher/delete.ts +20 -46
  343. package/src/tools/watcher/digest.ts +36 -70
  344. package/src/tools/watcher/list.ts +49 -79
  345. package/src/tools/watcher/update.ts +45 -91
  346. package/src/twitter/client.ts +690 -0
  347. package/src/twitter/session.ts +91 -0
  348. package/src/usage/types.ts +0 -1
  349. package/src/util/truncate.ts +6 -0
  350. package/src/watcher/providers/slack.ts +2 -1
  351. package/src/watcher/watcher-store.ts +3 -2
  352. package/src/work-items/work-item-store.ts +27 -2
  353. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  354. package/src/workspace/git-service.ts +87 -22
  355. package/src/workspace/provider-commit-message-generator.ts +269 -0
  356. package/src/workspace/turn-commit.ts +62 -3
  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
@@ -98,8 +98,4 @@ Summarize what was done:
98
98
  - Bot commands registered: /new
99
99
  - Credentials stored securely in the vault
100
100
 
101
- Remind the user that the gateway needs these environment variables set to match:
102
- - `TELEGRAM_BOT_TOKEN` — the bot token
103
- - `TELEGRAM_WEBHOOK_SECRET` — the generated secret
104
-
105
- The values are stored in the credential vault and can be retrieved for gateway configuration.
101
+ The gateway automatically detects credentials from the vault and will begin accepting Telegram webhooks shortly. No manual environment variable configuration is needed.
@@ -70,7 +70,7 @@ export function upsertContact(params: {
70
70
  responseExpectation?: string | null;
71
71
  preferredTone?: string | null;
72
72
  channels?: Array<{ type: string; address: string; isPrimary?: boolean }>;
73
- }): ContactWithChannels {
73
+ }): ContactWithChannels & { created: boolean } {
74
74
  const db = getDb();
75
75
  const now = Date.now();
76
76
 
@@ -96,7 +96,7 @@ export function upsertContact(params: {
96
96
  syncChannels(contactId, params.channels, now);
97
97
  }
98
98
 
99
- return getContact(contactId)!;
99
+ return { ...getContact(contactId)!, created: false };
100
100
  }
101
101
  }
102
102
 
@@ -124,7 +124,7 @@ export function upsertContact(params: {
124
124
  .run();
125
125
 
126
126
  syncChannels(contactId, params.channels, now);
127
- return getContact(contactId)!;
127
+ return { ...getContact(contactId)!, created: false };
128
128
  }
129
129
  }
130
130
  }
@@ -148,7 +148,7 @@ export function upsertContact(params: {
148
148
  syncChannels(contactId, params.channels, now);
149
149
  }
150
150
 
151
- return getContact(contactId)!;
151
+ return { ...getContact(contactId)!, created: true };
152
152
  }
153
153
 
154
154
  /**
@@ -313,6 +313,16 @@ export function drainDirectiveDisplayBuffer(buffer: string): DirectiveDisplayDra
313
313
 
314
314
  if (!isValidDirective) {
315
315
  emitText += tag;
316
+ } else {
317
+ // Only trim the trailing newline when the directive occupied its own
318
+ // line (preceded by \n and followed by \n or \r\n). We intentionally
319
+ // do NOT trim when nextChar is undefined (end-of-buffer) because in
320
+ // streaming mode more data may arrive in the next chunk — eagerly
321
+ // trimming would merge words across the directive boundary.
322
+ const nextChar = buffer[end + 2];
323
+ if (emitText.endsWith('\n') && (nextChar === '\n' || nextChar === '\r')) {
324
+ emitText = emitText.slice(0, -1);
325
+ }
316
326
  }
317
327
 
318
328
  cursor = end + 2;
@@ -4,6 +4,8 @@ import { getLogger } from '../util/logger.js';
4
4
 
5
5
  const log = getLogger('classifier');
6
6
 
7
+ const CLASSIFICATION_TIMEOUT_MS = 5000;
8
+
7
9
  export type InteractionType = 'computer_use' | 'text_qa';
8
10
 
9
11
  /**
@@ -53,7 +55,7 @@ export async function classifyInteraction(task: string, source?: 'voice' | 'text
53
55
  messages: [{ role: 'user' as const, content: task }],
54
56
  }),
55
57
  new Promise<never>((_, reject) =>
56
- setTimeout(() => reject(new Error('Classification timeout')), 5000),
58
+ setTimeout(() => reject(new Error('Classification timeout')), CLASSIFICATION_TIMEOUT_MS),
57
59
  ),
58
60
  ]);
59
61
 
@@ -21,7 +21,7 @@ import { registerSkillTools } from '../tools/registry.js';
21
21
  import { buildComputerUseSystemPrompt } from '../config/computer-use-prompt.js';
22
22
  import { getSandboxWorkingDir } from '../util/platform.js';
23
23
  import { getConfig } from '../config/loader.js';
24
- import { projectSkillTools, resetSkillToolProjection } from './session-skill-tools.js';
24
+ import { projectSkillTools, resetSkillToolProjection, type SkillProjectionCache } from './session-skill-tools.js';
25
25
  import { getLogger } from '../util/logger.js';
26
26
 
27
27
  const log = getLogger('computer-use-session');
@@ -60,6 +60,7 @@ export class ComputerUseSession {
60
60
  private readonly onTerminal?: (sessionId: string) => void;
61
61
  private readonly preactivatedSkillIds: string[];
62
62
  private readonly skillProjectionState = new Map<string, string>();
63
+ private readonly skillProjectionCache: SkillProjectionCache = {};
63
64
 
64
65
  private state: SessionState = 'idle';
65
66
  private stepCount = 0;
@@ -235,6 +236,7 @@ export class ComputerUseSession {
235
236
  const projection = projectSkillTools([], {
236
237
  preactivatedSkillIds: this.preactivatedSkillIds,
237
238
  previouslyActiveSkillIds: this.skillProjectionState,
239
+ cache: this.skillProjectionCache,
238
240
  });
239
241
 
240
242
  if (projection.toolDefinitions.length === 0) {
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Temporal context formatter for future weekday/weekend grounding.
3
+ *
4
+ * Produces a compact, deterministic payload describing the current date,
5
+ * upcoming weekend/work-week windows, and a short horizon of labelled
6
+ * future dates. Intended for runtime injection into the model context.
7
+ */
8
+
9
+ export interface TemporalContextOptions {
10
+ /** Override current time (epoch ms) for deterministic tests. */
11
+ nowMs?: number;
12
+ /** IANA timezone (e.g. "America/New_York"). Defaults to host timezone. */
13
+ timeZone?: string;
14
+ /** Number of future days to list (default 14, hard-capped at 14). */
15
+ horizonDays?: number;
16
+ }
17
+
18
+ const MAX_OUTPUT_CHARS = 1500;
19
+ const MAX_HORIZON_ENTRIES = 14;
20
+
21
+ const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] as const;
22
+
23
+ /**
24
+ * Get the local date parts for a given instant in the specified timezone.
25
+ */
26
+ function localDateParts(date: Date, timeZone: string): { year: number; month: number; day: number; weekday: number } {
27
+ const fmt = new Intl.DateTimeFormat('en-US', {
28
+ timeZone,
29
+ year: 'numeric',
30
+ month: '2-digit',
31
+ day: '2-digit',
32
+ weekday: 'short',
33
+ });
34
+ const parts = fmt.formatToParts(date);
35
+ const get = (t: string) => parts.find((p) => p.type === t)?.value ?? '';
36
+ // Weekday as 0-6 (Sun-Sat)
37
+ const weekdayShort = get('weekday');
38
+ const weekdayMap: Record<string, number> = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
39
+ return {
40
+ year: parseInt(get('year'), 10),
41
+ month: parseInt(get('month'), 10),
42
+ day: parseInt(get('day'), 10),
43
+ weekday: weekdayMap[weekdayShort] ?? 0,
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Format a Date as YYYY-MM-DD in the given timezone.
49
+ */
50
+ function formatLocalDate(date: Date, timeZone: string): string {
51
+ const p = localDateParts(date, timeZone);
52
+ return `${p.year}-${String(p.month).padStart(2, '0')}-${String(p.day).padStart(2, '0')}`;
53
+ }
54
+
55
+ /**
56
+ * Advance a date by `days` calendar days in the given timezone.
57
+ *
58
+ * Computes the local date, adds days to the day component, then anchors
59
+ * the result at noon local time to avoid DST-transition edge cases.
60
+ */
61
+ function addDays(date: Date, days: number, timeZone: string): Date {
62
+ const parts = localDateParts(date, timeZone);
63
+ // Use Date.UTC for calendar overflow (e.g. Jan 32 → Feb 1).
64
+ const ref = new Date(Date.UTC(parts.year, parts.month - 1, parts.day + days));
65
+ const tY = ref.getUTCFullYear();
66
+ const tM = ref.getUTCMonth() + 1;
67
+ const tD = ref.getUTCDate();
68
+ // Noon UTC covers UTC-12 through ~UTC+11. For far-east timezones
69
+ // (UTC+12/+13/+14) noon UTC is already the next local day, so fall
70
+ // back to midnight UTC which resolves correctly there.
71
+ const noonUTC = new Date(Date.UTC(tY, tM - 1, tD, 12, 0, 0));
72
+ const r = localDateParts(noonUTC, timeZone);
73
+ if (r.year === tY && r.month === tM && r.day === tD) {
74
+ return noonUTC;
75
+ }
76
+ return new Date(Date.UTC(tY, tM - 1, tD, 0, 0, 0));
77
+ }
78
+
79
+ /**
80
+ * Build a compact temporal context string for model injection.
81
+ *
82
+ * Output is hard-capped at {@link MAX_OUTPUT_CHARS} characters and
83
+ * {@link MAX_HORIZON_ENTRIES} horizon entries.
84
+ */
85
+ export function buildTemporalContext(options: TemporalContextOptions = {}): string {
86
+ const now = new Date(options.nowMs ?? Date.now());
87
+ const timeZone = options.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
88
+ const horizonDays = Math.min(options.horizonDays ?? MAX_HORIZON_ENTRIES, MAX_HORIZON_ENTRIES);
89
+
90
+ const todayParts = localDateParts(now, timeZone);
91
+ const todayStr = formatLocalDate(now, timeZone);
92
+ const todayWeekday = WEEKDAY_NAMES[todayParts.weekday];
93
+
94
+ // ── Next weekend (Saturday-Sunday) ──
95
+ const daysUntilSaturday = (6 - todayParts.weekday + 7) % 7 || 7;
96
+ const nextSaturday = addDays(now, daysUntilSaturday, timeZone);
97
+ const nextSunday = addDays(now, daysUntilSaturday + 1, timeZone);
98
+
99
+ // ── Next work week (Monday-Friday) ──
100
+ const daysUntilMonday = (1 - todayParts.weekday + 7) % 7 || 7;
101
+ const nextMonday = addDays(now, daysUntilMonday, timeZone);
102
+ const nextFriday = addDays(now, daysUntilMonday + 4, timeZone);
103
+
104
+ // ── Horizon list ──
105
+ const horizonLines: string[] = [];
106
+ for (let i = 1; i <= horizonDays; i++) {
107
+ const futureDate = addDays(now, i, timeZone);
108
+ const futureParts = localDateParts(futureDate, timeZone);
109
+ const label = WEEKDAY_NAMES[futureParts.weekday];
110
+ horizonLines.push(` ${formatLocalDate(futureDate, timeZone)} ${label}`);
111
+ }
112
+
113
+ const lines = [
114
+ `<temporal_context>`,
115
+ `Today: ${todayStr} (${todayWeekday})`,
116
+ `Timezone: ${timeZone}`,
117
+ ``,
118
+ `Week definitions: work week = Monday–Friday, weekend = Saturday–Sunday`,
119
+ ``,
120
+ `Next weekend: ${formatLocalDate(nextSaturday, timeZone)} – ${formatLocalDate(nextSunday, timeZone)}`,
121
+ `Next work week: ${formatLocalDate(nextMonday, timeZone)} – ${formatLocalDate(nextFriday, timeZone)}`,
122
+ ``,
123
+ `Upcoming dates:`,
124
+ ...horizonLines,
125
+ `</temporal_context>`,
126
+ ];
127
+
128
+ let output = lines.join('\n');
129
+
130
+ // Hard cap: truncate if somehow over budget (shouldn't happen with 14 entries).
131
+ if (output.length > MAX_OUTPUT_CHARS) {
132
+ output = output.slice(0, MAX_OUTPUT_CHARS - 25) + '\n</temporal_context>';
133
+ }
134
+
135
+ return output;
136
+ }
@@ -18,7 +18,7 @@ import type {
18
18
  AppUpdatePreviewRequest,
19
19
  UiSurfaceShow,
20
20
  } from '../ipc-protocol.js';
21
- import { log, compareSemver, createSigningCallback, type HandlerContext } from './shared.js';
21
+ import { log, compareSemver, createSigningCallback, defineHandlers, type HandlerContext } from './shared.js';
22
22
 
23
23
  export function handleAppDataRequest(
24
24
  msg: AppDataRequest,
@@ -444,3 +444,18 @@ export function handleGalleryInstall(
444
444
  });
445
445
  }
446
446
  }
447
+
448
+ export const appHandlers = defineHandlers({
449
+ app_data_request: handleAppDataRequest,
450
+ app_open_request: handleAppOpenRequest,
451
+ app_update_preview: handleAppUpdatePreview,
452
+ app_preview_request: handleAppPreview,
453
+ apps_list: (_msg, socket, ctx) => handleAppsList(socket, ctx),
454
+ shared_apps_list: (_msg, socket, ctx) => handleSharedAppsList(socket, ctx),
455
+ shared_app_delete: handleSharedAppDelete,
456
+ fork_shared_app: handleForkSharedApp,
457
+ share_app_cloud: handleShareAppCloud,
458
+ bundle_app: handleBundleApp,
459
+ gallery_list: (_msg, socket, ctx) => handleGalleryList(socket, ctx),
460
+ gallery_install: handleGalleryInstall,
461
+ });
@@ -0,0 +1,54 @@
1
+ import { browserManager } from '../../tools/browser/browser-manager.js';
2
+ import { log, defineHandlers } from './shared.js';
3
+
4
+ export const browserHandlers = defineHandlers({
5
+ browser_cdp_response: (msg) => {
6
+ browserManager.resolveCDPResponse(msg.sessionId, msg.success, msg.declined);
7
+ },
8
+
9
+ browser_user_click: async (msg) => {
10
+ try {
11
+ const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
12
+ const viewport = await page.evaluate('(() => ({ vw: window.innerWidth, vh: window.innerHeight }))()') as { vw: number; vh: number };
13
+ const scale = Math.min(1280 / viewport.vw, 960 / viewport.vh);
14
+ const pageX = msg.x / scale;
15
+ const pageY = msg.y / scale;
16
+ const options: Record<string, unknown> = {};
17
+ if (msg.button === 'right') options.button = 'right';
18
+ if (msg.doubleClick) options.clickCount = 2;
19
+ await page.mouse.click(pageX, pageY, options);
20
+ } catch (err) {
21
+ log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user click');
22
+ }
23
+ },
24
+
25
+ browser_user_scroll: async (msg) => {
26
+ try {
27
+ const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
28
+ await page.mouse.wheel(msg.deltaX, msg.deltaY);
29
+ } catch (err) {
30
+ log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user scroll');
31
+ }
32
+ },
33
+
34
+ browser_user_keypress: async (msg) => {
35
+ try {
36
+ const page = await browserManager.getOrCreateSessionPage(msg.sessionId);
37
+ const combo = msg.modifiers?.length ? [...msg.modifiers, msg.key].join('+') : msg.key;
38
+ await page.keyboard.press(combo);
39
+ } catch (err) {
40
+ log.warn({ err, sessionId: msg.sessionId }, 'Failed to forward user keypress');
41
+ }
42
+ },
43
+
44
+ browser_interactive_mode: (msg, socket, ctx) => {
45
+ log.info({ sessionId: msg.sessionId, enabled: msg.enabled }, 'Interactive mode toggled');
46
+ browserManager.setInteractiveMode(msg.sessionId, msg.enabled);
47
+ ctx.send(socket, {
48
+ type: 'browser_interactive_mode_changed',
49
+ sessionId: msg.sessionId,
50
+ surfaceId: msg.surfaceId,
51
+ enabled: msg.enabled,
52
+ });
53
+ },
54
+ });
@@ -10,7 +10,7 @@ import type {
10
10
  CuObservation,
11
11
  ServerMessage,
12
12
  } from '../ipc-protocol.js';
13
- import { log, type HandlerContext } from './shared.js';
13
+ import { log, defineHandlers, type HandlerContext } from './shared.js';
14
14
 
15
15
  const cuObservationSequenceBySession = new Map<string, number>();
16
16
 
@@ -179,3 +179,9 @@ export async function handleCuObservation(
179
179
  log.error({ err, sessionId: msg.sessionId }, 'Error handling CU observation');
180
180
  });
181
181
  }
182
+
183
+ export const computerUseHandlers = defineHandlers({
184
+ cu_session_create: handleCuSessionCreate,
185
+ cu_session_abort: handleCuSessionAbort,
186
+ cu_observation: handleCuObservation,
187
+ });
@@ -5,7 +5,7 @@ import { addRule, removeRule, updateRule, getAllRules, acceptStarterBundle } fro
5
5
  import { listSchedules, updateSchedule, deleteSchedule, describeCronExpression } from '../../schedule/schedule-store.js';
6
6
  import { listReminders, cancelReminder } from '../../tools/reminder/reminder-store.js';
7
7
  import { getSecureKey, setSecureKey, deleteSecureKey } from '../../security/secure-keys.js';
8
- import { upsertCredentialMetadata, deleteCredentialMetadata } from '../../tools/credentials/metadata-store.js';
8
+ import { upsertCredentialMetadata, deleteCredentialMetadata, getCredentialMetadata } from '../../tools/credentials/metadata-store.js';
9
9
  import { postToSlackWebhook } from '../../slack/slack-webhook.js';
10
10
  import { getApp } from '../../memory/app-store.js';
11
11
  import type {
@@ -19,9 +19,11 @@ import type {
19
19
  ReminderCancel,
20
20
  ShareToSlackRequest,
21
21
  SlackWebhookConfigRequest,
22
+ TwilioWebhookConfigRequest,
22
23
  VercelApiConfigRequest,
24
+ TwitterIntegrationConfigRequest,
23
25
  } from '../ipc-protocol.js';
24
- import { log, type HandlerContext } from './shared.js';
26
+ import { log, CONFIG_RELOAD_DEBOUNCE_MS, defineHandlers, type HandlerContext } from './shared.js';
25
27
  import { MODEL_TO_PROVIDER } from '../session-slash.js';
26
28
 
27
29
  export function handleModelGet(socket: net.Socket, ctx: HandlerContext): void {
@@ -96,7 +98,7 @@ export function handleModelSet(
96
98
  }
97
99
  const existingSuppressTimer = ctx.debounceTimers.get('__suppress_reset__');
98
100
  if (existingSuppressTimer) clearTimeout(existingSuppressTimer);
99
- const resetTimer = setTimeout(() => { ctx.setSuppressConfigReload(false); }, 300);
101
+ const resetTimer = setTimeout(() => { ctx.setSuppressConfigReload(false); }, CONFIG_RELOAD_DEBOUNCE_MS);
100
102
  ctx.debounceTimers.set('__suppress_reset__', resetTimer);
101
103
 
102
104
  // Re-initialize provider with the new model so LLM calls use it
@@ -145,7 +147,7 @@ export function handleImageGenModelSet(
145
147
  }
146
148
  const existingSuppressTimer = ctx.debounceTimers.get('__suppress_reset__');
147
149
  if (existingSuppressTimer) clearTimeout(existingSuppressTimer);
148
- const resetTimer = setTimeout(() => { ctx.setSuppressConfigReload(false); }, 300);
150
+ const resetTimer = setTimeout(() => { ctx.setSuppressConfigReload(false); }, CONFIG_RELOAD_DEBOUNCE_MS);
149
151
  ctx.debounceTimers.set('__suppress_reset__', resetTimer);
150
152
 
151
153
  ctx.updateConfigFingerprint();
@@ -237,13 +239,15 @@ export function handleSchedulesList(socket: net.Socket, ctx: HandlerContext): vo
237
239
  id: j.id,
238
240
  name: j.name,
239
241
  enabled: j.enabled,
242
+ syntax: j.syntax,
243
+ expression: j.expression,
240
244
  cronExpression: j.cronExpression,
241
245
  timezone: j.timezone,
242
246
  message: j.message,
243
247
  nextRunAt: j.nextRunAt,
244
248
  lastRunAt: j.lastRunAt,
245
249
  lastStatus: j.lastStatus,
246
- description: describeCronExpression(j.cronExpression),
250
+ description: j.syntax === 'cron' ? describeCronExpression(j.cronExpression) : j.expression,
247
251
  })),
248
252
  });
249
253
  }
@@ -393,6 +397,41 @@ export function handleSlackWebhookConfig(
393
397
  }
394
398
  }
395
399
 
400
+ export function handleTwilioWebhookConfig(
401
+ msg: TwilioWebhookConfigRequest,
402
+ socket: net.Socket,
403
+ ctx: HandlerContext,
404
+ ): void {
405
+ try {
406
+ if (msg.action === 'get') {
407
+ const raw = loadRawConfig();
408
+ const webhookBaseUrl = (raw?.calls as Record<string, unknown>)?.webhookBaseUrl as string ?? '';
409
+ ctx.send(socket, { type: 'twilio_webhook_config_response', webhookBaseUrl, success: true });
410
+ } else if (msg.action === 'set') {
411
+ const value = (msg.webhookBaseUrl ?? '').trim().replace(/\/+$/, '');
412
+ const raw = loadRawConfig();
413
+ const calls = (raw?.calls ?? {}) as Record<string, unknown>;
414
+ calls.webhookBaseUrl = value || undefined;
415
+ const wasSuppressed = ctx.suppressConfigReload;
416
+ ctx.setSuppressConfigReload(true);
417
+ try {
418
+ saveRawConfig({ ...raw, calls });
419
+ } catch (err) {
420
+ ctx.setSuppressConfigReload(wasSuppressed);
421
+ throw err;
422
+ }
423
+ const existingSuppressTimer = ctx.debounceTimers.get('__suppress_reset__');
424
+ if (existingSuppressTimer) clearTimeout(existingSuppressTimer);
425
+ const resetTimer = setTimeout(() => { ctx.setSuppressConfigReload(false); }, CONFIG_RELOAD_DEBOUNCE_MS);
426
+ ctx.debounceTimers.set('__suppress_reset__', resetTimer);
427
+ ctx.send(socket, { type: 'twilio_webhook_config_response', webhookBaseUrl: value, success: true });
428
+ }
429
+ } catch (err) {
430
+ const message = err instanceof Error ? err.message : String(err);
431
+ ctx.send(socket, { type: 'twilio_webhook_config_response', webhookBaseUrl: '', success: false, error: message });
432
+ }
433
+ }
434
+
396
435
  export function handleVercelApiConfig(
397
436
  msg: VercelApiConfigRequest,
398
437
  socket: net.Socket,
@@ -455,6 +494,145 @@ export function handleVercelApiConfig(
455
494
  }
456
495
  }
457
496
 
497
+ export function handleTwitterIntegrationConfig(
498
+ msg: TwitterIntegrationConfigRequest,
499
+ socket: net.Socket,
500
+ ctx: HandlerContext,
501
+ ): void {
502
+ try {
503
+ if (msg.action === 'get') {
504
+ const raw = loadRawConfig();
505
+ const mode = (raw.twitterIntegrationMode as 'local_byo' | 'managed' | undefined) ?? 'local_byo';
506
+ const localClientConfigured = !!getSecureKey('credential:integration:twitter:oauth_client_id');
507
+ const connected = !!getSecureKey('credential:integration:twitter:access_token');
508
+ const meta = getCredentialMetadata('integration:twitter', 'access_token');
509
+ ctx.send(socket, {
510
+ type: 'twitter_integration_config_response',
511
+ success: true,
512
+ mode,
513
+ managedAvailable: false,
514
+ localClientConfigured,
515
+ connected,
516
+ accountInfo: meta?.accountInfo ?? undefined,
517
+ });
518
+ } else if (msg.action === 'set_mode') {
519
+ const raw = loadRawConfig();
520
+ raw.twitterIntegrationMode = msg.mode ?? 'local_byo';
521
+ saveRawConfig(raw);
522
+ ctx.send(socket, {
523
+ type: 'twitter_integration_config_response',
524
+ success: true,
525
+ mode: msg.mode ?? 'local_byo',
526
+ managedAvailable: false,
527
+ localClientConfigured: !!getSecureKey('credential:integration:twitter:oauth_client_id'),
528
+ connected: !!getSecureKey('credential:integration:twitter:access_token'),
529
+ });
530
+ } else if (msg.action === 'set_local_client') {
531
+ if (!msg.clientId) {
532
+ ctx.send(socket, {
533
+ type: 'twitter_integration_config_response',
534
+ success: false,
535
+ managedAvailable: false,
536
+ localClientConfigured: false,
537
+ connected: false,
538
+ error: 'clientId is required for set_local_client action',
539
+ });
540
+ return;
541
+ }
542
+ const previousClientId = getSecureKey('credential:integration:twitter:oauth_client_id');
543
+ const storedId = setSecureKey('credential:integration:twitter:oauth_client_id', msg.clientId);
544
+ if (!storedId) {
545
+ ctx.send(socket, {
546
+ type: 'twitter_integration_config_response',
547
+ success: false,
548
+ managedAvailable: false,
549
+ localClientConfigured: false,
550
+ connected: false,
551
+ error: 'Failed to store client ID in secure storage',
552
+ });
553
+ return;
554
+ }
555
+ if (msg.clientSecret) {
556
+ const storedSecret = setSecureKey('credential:integration:twitter:oauth_client_secret', msg.clientSecret);
557
+ if (!storedSecret) {
558
+ // Roll back the client ID to its previous value to avoid inconsistent OAuth state
559
+ if (previousClientId) {
560
+ setSecureKey('credential:integration:twitter:oauth_client_id', previousClientId);
561
+ } else {
562
+ deleteSecureKey('credential:integration:twitter:oauth_client_id');
563
+ }
564
+ ctx.send(socket, {
565
+ type: 'twitter_integration_config_response',
566
+ success: false,
567
+ managedAvailable: false,
568
+ localClientConfigured: false,
569
+ connected: false,
570
+ error: 'Failed to store client secret in secure storage',
571
+ });
572
+ return;
573
+ }
574
+ } else {
575
+ // Clear any stale secret when updating client without a secret (e.g. switching to PKCE)
576
+ deleteSecureKey('credential:integration:twitter:oauth_client_secret');
577
+ }
578
+ ctx.send(socket, {
579
+ type: 'twitter_integration_config_response',
580
+ success: true,
581
+ managedAvailable: false,
582
+ localClientConfigured: true,
583
+ connected: !!getSecureKey('credential:integration:twitter:access_token'),
584
+ });
585
+ } else if (msg.action === 'clear_local_client') {
586
+ // If connected, disconnect first
587
+ if (getSecureKey('credential:integration:twitter:access_token')) {
588
+ deleteSecureKey('credential:integration:twitter:access_token');
589
+ deleteSecureKey('credential:integration:twitter:refresh_token');
590
+ deleteCredentialMetadata('integration:twitter', 'access_token');
591
+ }
592
+ deleteSecureKey('credential:integration:twitter:oauth_client_id');
593
+ deleteSecureKey('credential:integration:twitter:oauth_client_secret');
594
+ ctx.send(socket, {
595
+ type: 'twitter_integration_config_response',
596
+ success: true,
597
+ managedAvailable: false,
598
+ localClientConfigured: false,
599
+ connected: false,
600
+ });
601
+ } else if (msg.action === 'disconnect') {
602
+ deleteSecureKey('credential:integration:twitter:access_token');
603
+ deleteSecureKey('credential:integration:twitter:refresh_token');
604
+ deleteCredentialMetadata('integration:twitter', 'access_token');
605
+ ctx.send(socket, {
606
+ type: 'twitter_integration_config_response',
607
+ success: true,
608
+ managedAvailable: false,
609
+ localClientConfigured: !!getSecureKey('credential:integration:twitter:oauth_client_id'),
610
+ connected: false,
611
+ });
612
+ } else {
613
+ ctx.send(socket, {
614
+ type: 'twitter_integration_config_response',
615
+ success: false,
616
+ managedAvailable: false,
617
+ localClientConfigured: false,
618
+ connected: false,
619
+ error: `Unknown action: ${String((msg as unknown as Record<string, unknown>).action)}`,
620
+ });
621
+ }
622
+ } catch (err) {
623
+ const message = err instanceof Error ? err.message : String(err);
624
+ log.error({ err }, 'Failed to handle Twitter integration config');
625
+ ctx.send(socket, {
626
+ type: 'twitter_integration_config_response',
627
+ success: false,
628
+ managedAvailable: false,
629
+ localClientConfigured: false,
630
+ connected: false,
631
+ error: message,
632
+ });
633
+ }
634
+ }
635
+
458
636
  export function handleEnvVarsRequest(socket: net.Socket, ctx: HandlerContext): void {
459
637
  const vars: Record<string, string> = {};
460
638
  for (const [key, value] of Object.entries(process.env)) {
@@ -462,3 +640,25 @@ export function handleEnvVarsRequest(socket: net.Socket, ctx: HandlerContext): v
462
640
  }
463
641
  ctx.send(socket, { type: 'env_vars_response', vars });
464
642
  }
643
+
644
+ export const configHandlers = defineHandlers({
645
+ model_get: (_msg, socket, ctx) => handleModelGet(socket, ctx),
646
+ model_set: handleModelSet,
647
+ image_gen_model_set: handleImageGenModelSet,
648
+ add_trust_rule: handleAddTrustRule,
649
+ trust_rules_list: (_msg, socket, ctx) => handleTrustRulesList(socket, ctx),
650
+ remove_trust_rule: handleRemoveTrustRule,
651
+ update_trust_rule: handleUpdateTrustRule,
652
+ accept_starter_bundle: (_msg, socket, ctx) => handleAcceptStarterBundle(socket, ctx),
653
+ schedules_list: (_msg, socket, ctx) => handleSchedulesList(socket, ctx),
654
+ schedule_toggle: handleScheduleToggle,
655
+ schedule_remove: handleScheduleRemove,
656
+ reminders_list: (_msg, socket, ctx) => handleRemindersList(socket, ctx),
657
+ reminder_cancel: handleReminderCancel,
658
+ share_to_slack: handleShareToSlack,
659
+ slack_webhook_config: handleSlackWebhookConfig,
660
+ twilio_webhook_config: handleTwilioWebhookConfig,
661
+ vercel_api_config: handleVercelApiConfig,
662
+ twitter_integration_config: handleTwitterIntegrationConfig,
663
+ env_vars_request: (_msg, socket, ctx) => handleEnvVarsRequest(socket, ctx),
664
+ });
@@ -9,7 +9,7 @@ import archiver from 'archiver';
9
9
  import { getDb } from '../../memory/db.js';
10
10
  import { messages, toolInvocations, llmUsageEvents, llmRequestLogs } from '../../memory/schema.js';
11
11
  import type { DiagnosticsExportRequest } from '../ipc-protocol.js';
12
- import { log, type HandlerContext } from './shared.js';
12
+ import { log, defineHandlers, type HandlerContext } from './shared.js';
13
13
 
14
14
  const MAX_CONTENT_LENGTH = 500;
15
15
 
@@ -332,3 +332,7 @@ export async function handleDiagnosticsExport(
332
332
  });
333
333
  }
334
334
  }
335
+
336
+ export const diagnosticsHandlers = defineHandlers({
337
+ diagnostics_export_request: handleDiagnosticsExport,
338
+ });