@vellumai/assistant 0.8.7 → 0.8.8

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 (387) hide show
  1. package/Dockerfile +20 -4
  2. package/docker-entrypoint.sh +4 -2
  3. package/docker-init-apt-root.sh +3 -1
  4. package/docker-kata-apt-env.sh +3 -1
  5. package/docker-kata-runtime-family.sh +12 -0
  6. package/docs/architecture/memory.md +1 -1
  7. package/docs/plugins.md +75 -79
  8. package/examples/plugins/echo/README.md +6 -12
  9. package/examples/plugins/echo/register.ts +0 -41
  10. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  11. package/openapi.yaml +3381 -348
  12. package/package.json +1 -1
  13. package/scripts/generate-openapi.ts +68 -41
  14. package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
  15. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  16. package/src/__tests__/agent-loop.test.ts +37 -87
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  18. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  19. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  20. package/src/__tests__/anthropic-provider.test.ts +95 -2
  21. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  22. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  23. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  24. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  25. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  26. package/src/__tests__/btw-routes.test.ts +62 -3
  27. package/src/__tests__/build-persisted-content.test.ts +184 -0
  28. package/src/__tests__/catalog-files.test.ts +1 -1
  29. package/src/__tests__/clawhub-files.test.ts +1 -1
  30. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  32. package/src/__tests__/config-watcher.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  34. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
  35. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
  37. package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
  38. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  39. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  40. package/src/__tests__/conversation-pairing.test.ts +4 -31
  41. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
  43. package/src/__tests__/conversation-queue.test.ts +2 -0
  44. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
  47. package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
  48. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  49. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  50. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  51. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  52. package/src/__tests__/conversation-title-service.test.ts +135 -2
  53. package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
  54. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  55. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  56. package/src/__tests__/dm-persistence.test.ts +5 -1
  57. package/src/__tests__/empty-response-hook.test.ts +304 -0
  58. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  59. package/src/__tests__/gemini-image-service.test.ts +13 -0
  60. package/src/__tests__/helpers/mock-provider.ts +110 -0
  61. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  62. package/src/__tests__/history-repair-hook.test.ts +1 -0
  63. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  64. package/src/__tests__/identity-routes.test.ts +248 -7
  65. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  66. package/src/__tests__/injector-background-turn.test.ts +2 -8
  67. package/src/__tests__/injector-chain.test.ts +106 -270
  68. package/src/__tests__/injector-disk-pressure.test.ts +3 -12
  69. package/src/__tests__/injector-document-comments.test.ts +2 -2
  70. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  71. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  72. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  73. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  74. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  75. package/src/__tests__/llm-usage-store.test.ts +223 -1
  76. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  77. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  78. package/src/__tests__/native-web-search.test.ts +191 -0
  79. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  80. package/src/__tests__/openai-image-service.test.ts +17 -0
  81. package/src/__tests__/openai-provider.test.ts +31 -1
  82. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  83. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  84. package/src/__tests__/pipeline-runner.test.ts +29 -39
  85. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  86. package/src/__tests__/plugin-bootstrap.test.ts +13 -28
  87. package/src/__tests__/plugin-registry.test.ts +0 -27
  88. package/src/__tests__/plugin-types.test.ts +2 -125
  89. package/src/__tests__/process-message-display-content.test.ts +6 -2
  90. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  91. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  92. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  93. package/src/__tests__/schedule-routes.test.ts +603 -2
  94. package/src/__tests__/schedule-store.test.ts +41 -0
  95. package/src/__tests__/schedule-tools.test.ts +35 -0
  96. package/src/__tests__/server-history-render.test.ts +314 -1
  97. package/src/__tests__/skillssh-files.test.ts +1 -1
  98. package/src/__tests__/system-prompt.test.ts +20 -0
  99. package/src/__tests__/task-scheduler.test.ts +162 -1
  100. package/src/__tests__/terminal-tools.test.ts +6 -1
  101. package/src/__tests__/title-generate-hook.test.ts +319 -0
  102. package/src/__tests__/tool-error-hook.test.ts +278 -0
  103. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  104. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  106. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  107. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  108. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  109. package/src/__tests__/usage-routes.test.ts +285 -1
  110. package/src/__tests__/user-plugin-loader.test.ts +2 -2
  111. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  112. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  113. package/src/agent/loop.ts +346 -442
  114. package/src/api/events/assistant-thinking-delta.ts +33 -0
  115. package/src/api/events/tool-output-chunk.ts +45 -0
  116. package/src/api/events/tool-use-preview-start.ts +32 -0
  117. package/src/api/events/trace-event.ts +69 -0
  118. package/src/api/index.ts +48 -13
  119. package/src/api/responses/conversation-message.ts +368 -0
  120. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  121. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  122. package/src/cli/commands/notifications.ts +112 -60
  123. package/src/config/assistant-feature-flags.ts +22 -11
  124. package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
  125. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  126. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  127. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  128. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  129. package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
  130. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  131. package/src/config/feature-flag-cache.ts +3 -3
  132. package/src/config/feature-flag-registry.json +35 -3
  133. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  134. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  135. package/src/config/schemas/llm.ts +1 -0
  136. package/src/config/schemas/memory-v2.ts +8 -0
  137. package/src/config/schemas/memory-v3.ts +8 -0
  138. package/src/config/schemas/platform.ts +8 -0
  139. package/src/config/seed-inference-profiles.ts +2 -2
  140. package/src/config/skills.ts +13 -0
  141. package/src/context/compactor.ts +1 -1
  142. package/src/context/strip-injections.ts +122 -0
  143. package/src/context/token-estimator.ts +23 -0
  144. package/src/context/tool-result-truncation.ts +0 -23
  145. package/src/context/window-manager.ts +3 -6
  146. package/src/credential-execution/executable-discovery.ts +16 -0
  147. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  148. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  149. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  150. package/src/daemon/assistant-attachments.ts +1 -1
  151. package/src/daemon/config-watcher.ts +2 -2
  152. package/src/daemon/context-overflow-reducer.ts +0 -1
  153. package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
  154. package/src/daemon/conversation-agent-loop.ts +281 -760
  155. package/src/daemon/conversation-history.ts +5 -4
  156. package/src/daemon/conversation-lifecycle.ts +3 -4
  157. package/src/daemon/conversation-messaging.ts +7 -6
  158. package/src/daemon/conversation-process.ts +11 -16
  159. package/src/daemon/conversation-runtime-assembly.ts +130 -347
  160. package/src/daemon/conversation-slash.ts +6 -25
  161. package/src/daemon/conversation-surfaces.ts +222 -4
  162. package/src/daemon/conversation-tool-setup.ts +2 -29
  163. package/src/daemon/conversation.ts +32 -14
  164. package/src/daemon/external-plugins-bootstrap.ts +9 -10
  165. package/src/daemon/handlers/config-a2a.ts +51 -36
  166. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  167. package/src/daemon/handlers/config-telegram.ts +16 -2
  168. package/src/daemon/handlers/shared.ts +156 -84
  169. package/src/daemon/handlers/skills.ts +39 -10
  170. package/src/daemon/lifecycle.ts +4 -0
  171. package/src/daemon/message-types/apps.ts +1 -29
  172. package/src/daemon/message-types/messages.ts +9 -57
  173. package/src/daemon/message-types/skills.ts +2 -0
  174. package/src/daemon/message-types/surfaces.ts +136 -3
  175. package/src/daemon/now-scratchpad.ts +21 -0
  176. package/src/daemon/orphan-reaper.test.ts +210 -0
  177. package/src/daemon/orphan-reaper.ts +240 -0
  178. package/src/daemon/persist-unsendable-image.ts +117 -0
  179. package/src/daemon/process-message.ts +1 -3
  180. package/src/daemon/trace-emitter.ts +6 -4
  181. package/src/daemon/trust-context.ts +19 -0
  182. package/src/daemon/wake-target-adapter.ts +3 -1
  183. package/src/home/home-greeting-cache.ts +24 -1
  184. package/src/ipc/gateway-client.test.ts +2 -2
  185. package/src/ipc/gateway-client.ts +3 -3
  186. package/src/media/gemini-image-service.ts +15 -0
  187. package/src/media/openai-image-service.ts +14 -0
  188. package/src/media/types.ts +34 -0
  189. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  190. package/src/memory/auth-fallback-events-store.ts +94 -0
  191. package/src/memory/conversation-title-service.ts +65 -41
  192. package/src/memory/db-init.ts +4 -0
  193. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  194. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  195. package/src/memory/jobs-store.ts +33 -0
  196. package/src/memory/jobs-worker.ts +31 -4
  197. package/src/memory/llm-usage-store.ts +224 -50
  198. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  199. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  200. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  201. package/src/memory/migrations/index.ts +2 -0
  202. package/src/memory/pkb/autoinject.ts +61 -0
  203. package/src/memory/pkb/context.ts +50 -0
  204. package/src/memory/pkb/types.ts +14 -0
  205. package/src/memory/schedule-attribution-sql.ts +104 -0
  206. package/src/memory/schema/infrastructure.ts +16 -0
  207. package/src/memory/usage-grouped-buckets.ts +6 -1
  208. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
  209. package/src/memory/v2/consolidation-job.ts +1 -1
  210. package/src/memory/v3/__tests__/health.test.ts +16 -0
  211. package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
  212. package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
  213. package/src/memory/v3/__tests__/router.test.ts +101 -29
  214. package/src/memory/v3/__tests__/selector.test.ts +93 -27
  215. package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
  216. package/src/memory/v3/health.ts +0 -0
  217. package/src/memory/v3/llm-retry.ts +32 -0
  218. package/src/memory/v3/orchestrate.ts +26 -14
  219. package/src/memory/v3/provider-blocks.ts +15 -5
  220. package/src/memory/v3/router.ts +48 -42
  221. package/src/memory/v3/selector.ts +57 -42
  222. package/src/memory/v3/shadow-plugin.ts +47 -15
  223. package/src/memory/v3/types.ts +8 -0
  224. package/src/notifications/conversation-pairing.ts +8 -15
  225. package/src/notifications/decision-engine.ts +6 -3
  226. package/src/notifications/home-feed-side-effect.ts +12 -1
  227. package/src/permissions/prompter.ts +4 -0
  228. package/src/plugin-api/constants.ts +4 -0
  229. package/src/plugin-api/index.ts +8 -1
  230. package/src/plugin-api/types.ts +151 -1
  231. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  232. package/src/plugins/defaults/empty-response/register.ts +8 -13
  233. package/src/plugins/defaults/index.ts +1 -15
  234. package/src/plugins/defaults/injectors/register.ts +243 -74
  235. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
  236. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  237. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  238. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  239. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  240. package/src/plugins/defaults/title-generate/package.json +1 -1
  241. package/src/plugins/defaults/title-generate/register.ts +18 -18
  242. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  243. package/src/plugins/defaults/tool-error/package.json +1 -1
  244. package/src/plugins/defaults/tool-error/register.ts +9 -21
  245. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  246. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  247. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  248. package/src/plugins/pipeline.ts +6 -18
  249. package/src/plugins/registry.ts +8 -25
  250. package/src/plugins/types.ts +43 -474
  251. package/src/proactive-artifact/aux-message-injector.ts +3 -3
  252. package/src/proactive-artifact/job.test.ts +7 -12
  253. package/src/prompts/__tests__/system-prompt.test.ts +36 -0
  254. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
  255. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  256. package/src/prompts/templates/system-sections.ts +15 -0
  257. package/src/providers/anthropic/client.ts +37 -29
  258. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  259. package/src/providers/openai/chat-completions-provider.ts +44 -0
  260. package/src/providers/openrouter/client.ts +1 -0
  261. package/src/providers/placeholder-sentinels.ts +35 -0
  262. package/src/runtime/__tests__/agent-wake.test.ts +5 -1
  263. package/src/runtime/agent-wake.ts +2 -2
  264. package/src/runtime/assistant-event-hub.ts +36 -6
  265. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  266. package/src/runtime/http-router.ts +16 -21
  267. package/src/runtime/http-types.ts +16 -70
  268. package/src/runtime/pending-interactions.ts +1 -0
  269. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  270. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  271. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  272. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  273. package/src/runtime/routes/app-management-routes.ts +6 -117
  274. package/src/runtime/routes/app-routes.ts +13 -15
  275. package/src/runtime/routes/attachment-routes.ts +26 -15
  276. package/src/runtime/routes/avatar-routes.ts +26 -0
  277. package/src/runtime/routes/btw-routes.ts +29 -23
  278. package/src/runtime/routes/consolidation-routes.ts +120 -20
  279. package/src/runtime/routes/conversation-query-routes.ts +2 -0
  280. package/src/runtime/routes/conversation-routes.ts +358 -184
  281. package/src/runtime/routes/documents-routes.ts +4 -0
  282. package/src/runtime/routes/domain-routes.ts +51 -37
  283. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  284. package/src/runtime/routes/events-routes.ts +28 -34
  285. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  286. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  287. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  288. package/src/runtime/routes/identity-routes.ts +208 -17
  289. package/src/runtime/routes/image-generation-routes.ts +40 -2
  290. package/src/runtime/routes/index.ts +2 -0
  291. package/src/runtime/routes/integrations/a2a.ts +12 -10
  292. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  293. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  294. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  295. package/src/runtime/routes/integrations/telegram.ts +6 -0
  296. package/src/runtime/routes/integrations/twilio.ts +42 -0
  297. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  298. package/src/runtime/routes/log-export-routes.ts +8 -0
  299. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  300. package/src/runtime/routes/memory-v3-routes.ts +50 -28
  301. package/src/runtime/routes/oauth-apps.ts +66 -12
  302. package/src/runtime/routes/oauth-providers.ts +44 -5
  303. package/src/runtime/routes/platform-routes.ts +81 -5
  304. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  305. package/src/runtime/routes/playground/force-compact.ts +1 -1
  306. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  307. package/src/runtime/routes/schedule-routes.ts +152 -42
  308. package/src/runtime/routes/secret-routes.ts +14 -2
  309. package/src/runtime/routes/skills-routes.ts +43 -14
  310. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  311. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  312. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  313. package/src/runtime/routes/tts-routes.ts +35 -0
  314. package/src/runtime/routes/types.ts +66 -8
  315. package/src/runtime/routes/usage-routes.ts +47 -39
  316. package/src/runtime/routes/webhook-routes.ts +41 -2
  317. package/src/runtime/routes/workspace-routes.ts +4 -0
  318. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  319. package/src/runtime/services/analyze-conversation.ts +2 -2
  320. package/src/schedule/schedule-store.ts +20 -1
  321. package/src/schedule/schedule-usage-store.ts +83 -0
  322. package/src/schedule/scheduler.ts +12 -5
  323. package/src/skills/catalog-files.ts +2 -2
  324. package/src/skills/catalog-install.ts +3 -0
  325. package/src/skills/categories-cache.ts +118 -0
  326. package/src/skills/clawhub-files.ts +1 -2
  327. package/src/skills/skillssh-files.ts +1 -2
  328. package/src/telemetry/types.ts +29 -1
  329. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  330. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  331. package/src/tools/executor.ts +1 -53
  332. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  333. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  334. package/src/tools/network/web-search-error.test.ts +248 -0
  335. package/src/tools/network/web-search-error.ts +267 -0
  336. package/src/tools/network/web-search.ts +207 -48
  337. package/src/tools/schedule/create.ts +2 -0
  338. package/src/tools/terminal/safe-env.ts +10 -1
  339. package/src/tools/ui-surface/definitions.ts +9 -1
  340. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  341. package/src/tts/provider-catalog.ts +76 -1
  342. package/src/util/mutex.ts +47 -0
  343. package/src/workspace/git-service.ts +1 -42
  344. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  345. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  346. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
  347. package/src/workspace/migrations/registry.ts +6 -0
  348. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  349. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  350. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  351. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  352. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  353. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  354. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  355. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  356. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  357. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  358. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  359. package/src/gallery/default-gallery.ts +0 -1359
  360. package/src/gallery/gallery-manifest.ts +0 -28
  361. package/src/home/feature-gate.ts +0 -22
  362. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  363. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  364. package/src/plugins/defaults/injectors/package.json +0 -15
  365. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  366. package/src/plugins/defaults/llm-call/package.json +0 -15
  367. package/src/plugins/defaults/llm-call/register.ts +0 -45
  368. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  369. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  370. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  371. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  372. package/src/plugins/defaults/persistence/package.json +0 -15
  373. package/src/plugins/defaults/persistence/register.ts +0 -38
  374. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  375. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  376. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  377. package/src/plugins/defaults/token-estimate/package.json +0 -15
  378. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  379. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  380. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  381. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  382. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  383. package/src/plugins/defaults/tool-execute/package.json +0 -15
  384. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  385. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  386. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  387. package/src/skills/category-inference.ts +0 -111
@@ -1,3 +1,5 @@
1
+ import { z } from "zod";
2
+
1
3
  import {
2
4
  getConfig,
3
5
  invalidateConfigCache,
@@ -26,20 +28,24 @@ import { log as _log } from "./shared.js";
26
28
 
27
29
  // -- Result type --
28
30
 
29
- export interface SlackChannelConfigResult {
30
- success: boolean;
31
- hasBotToken: boolean;
32
- hasAppToken: boolean;
33
- hasUserToken: boolean;
34
- connected: boolean;
35
- teamId?: string;
36
- teamName?: string;
37
- teamUrl?: string;
38
- botUserId?: string;
39
- botUsername?: string;
40
- error?: string;
41
- warning?: string;
42
- }
31
+ export const SlackChannelConfigResultSchema = z.object({
32
+ success: z.boolean(),
33
+ hasBotToken: z.boolean(),
34
+ hasAppToken: z.boolean(),
35
+ hasUserToken: z.boolean(),
36
+ connected: z.boolean(),
37
+ teamId: z.string().optional(),
38
+ teamName: z.string().optional(),
39
+ teamUrl: z.string().optional(),
40
+ botUserId: z.string().optional(),
41
+ botUsername: z.string().optional(),
42
+ error: z.string().optional(),
43
+ warning: z.string().optional(),
44
+ });
45
+
46
+ export type SlackChannelConfigResult = z.infer<
47
+ typeof SlackChannelConfigResultSchema
48
+ >;
43
49
 
44
50
  // -- Helpers --
45
51
 
@@ -1,3 +1,5 @@
1
+ import { z } from "zod";
2
+
1
3
  import { getIsPlatform } from "../../config/env-registry.js";
2
4
  import {
3
5
  invalidateConfigCache,
@@ -26,7 +28,6 @@ import {
26
28
  deleteCredentialMetadata,
27
29
  upsertCredentialMetadata,
28
30
  } from "../../tools/credentials/metadata-store.js";
29
- import type { TelegramConfigResponse } from "../message-protocol.js";
30
31
  import { log } from "./shared.js";
31
32
 
32
33
  const TELEGRAM_BOT_TOKEN_IN_URL_PATTERN =
@@ -60,7 +61,20 @@ function summarizeTelegramError(err: unknown): string {
60
61
 
61
62
  // -- Transport-agnostic result type (omits the `type` discriminant) --
62
63
 
63
- export type TelegramConfigResult = Omit<TelegramConfigResponse, "type">;
64
+ export const TelegramConfigResultSchema = z.object({
65
+ success: z.boolean(),
66
+ hasBotToken: z.boolean(),
67
+ botId: z.string().optional(),
68
+ botUsername: z.string().optional(),
69
+ connected: z.boolean(),
70
+ hasWebhookSecret: z.boolean(),
71
+ lastError: z.string().optional(),
72
+ error: z.string().optional(),
73
+ commandsRegistered: z.array(z.string()).optional(),
74
+ warning: z.string().optional(),
75
+ });
76
+
77
+ export type TelegramConfigResult = z.infer<typeof TelegramConfigResultSchema>;
64
78
 
65
79
  // -- Extracted business logic functions --
66
80
 
@@ -1,9 +1,16 @@
1
1
  import { v4 as uuid } from "uuid";
2
2
 
3
+ import type {
4
+ ConversationContentBlock,
5
+ ConversationMessageAttachment,
6
+ ConversationMessageSurface,
7
+ ConversationMessageToolCall,
8
+ } from "../../api/responses/conversation-message.js";
9
+ import { ConfirmationDecisionSchema } from "../../api/responses/conversation-message.js";
3
10
  import { getConfig } from "../../config/loader.js";
4
11
  import type { LLMCallSite, Speed } from "../../config/schemas/llm.js";
5
12
  import type { SecretPromptResult } from "../../permissions/secret-prompter.js";
6
- import { isPlaceholderSentinelText } from "../../providers/anthropic/client.js";
13
+ import { isPlaceholderSentinelText } from "../../providers/placeholder-sentinels.js";
7
14
  import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
8
15
  import type { AuthContext } from "../../runtime/auth/types.js";
9
16
  import { unwrapExternalContentForDisplay } from "../../security/untrusted-content.js";
@@ -30,74 +37,18 @@ const pendingStandaloneSecrets = new Map<
30
37
  }
31
38
  >();
32
39
 
33
- export interface HistoryToolCall {
34
- name: string;
35
- input: Record<string, unknown>;
36
- result?: string;
37
- isError?: boolean;
38
- /** Base64-encoded image data from tool contentBlocks (e.g. browser_screenshot). @deprecated Use imageDataList. */
39
- imageData?: string;
40
- /** Base64-encoded image data from tool contentBlocks (e.g. browser_screenshot, image generation). */
41
- imageDataList?: string[];
42
- /** Unix ms when the tool started executing. */
43
- startedAt?: number;
44
- /** Unix ms when the tool completed. */
45
- completedAt?: number;
46
- /** Confirmation decision for this tool call: "approved" | "denied" | "timed_out". */
47
- confirmationDecision?: string;
48
- /** Friendly label for the confirmation (e.g. "Edit File", "Run Command"). */
49
- confirmationLabel?: string;
50
- /** Risk level classification at invocation time ("low" | "medium" | "high" | "unknown"). */
51
- riskLevel?: string;
52
- /** Human-readable reason for the risk classification. */
53
- riskReason?: string;
54
- /** ID of the trust rule that matched this invocation (if any). */
55
- matchedTrustRuleId?: string;
56
- /**
57
- * @deprecated Use `approvalMode` and `approvalReason` instead.
58
- * Kept for backward compatibility during the migration window.
59
- */
60
- autoApproved?: boolean;
61
- /** How the approval decision was reached: prompted, auto, blocked, or unknown (legacy). */
62
- approvalMode?: string;
63
- /** Why the approval decision was reached (stable enum for client display). */
64
- approvalReason?: string;
65
- /** Snapshot of the auto-approve threshold at execution time. */
66
- riskThreshold?: string;
67
- /**
68
- * Display-only regex ladder for the rule editor (narrowest → broadest).
69
- * Persisted on tool_use blocks by `annotatePersistedAssistantMessage` so
70
- * historical chips render the same ladder as live tool_result events.
71
- */
72
- riskScopeOptions?: Array<{ pattern: string; label: string }>;
73
- /** Minimatch save patterns for the rule editor (narrowest → broadest). */
74
- riskAllowlistOptions?: Array<{
75
- label: string;
76
- description: string;
77
- pattern: string;
78
- }>;
79
- /** Directory scope ladder for the rule editor. */
80
- riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
81
- }
40
+ /**
41
+ * A single tool call rendered into a history row. Alias of the canonical
42
+ * wire-contract type so `renderHistoryContent` (the producer) cannot drift
43
+ * from what the messages endpoint serializes.
44
+ */
45
+ export type HistoryToolCall = ConversationMessageToolCall;
82
46
 
83
- export interface HistorySurface {
84
- surfaceId: string;
85
- surfaceType: string;
86
- title?: string;
87
- data: Record<string, unknown>;
88
- actions?: Array<{
89
- id: string;
90
- label: string;
91
- style?: string;
92
- data?: Record<string, unknown>;
93
- }>;
94
- display?: string;
95
- persistent?: boolean;
96
- completed?: boolean;
97
- completionSummary?: string;
98
- /** Id of the tool call that produced this surface (the `ui_show` proxy tool). Lets the client gate app previews on the tool result's arrival rather than whole-turn streaming state. */
99
- toolCallId?: string;
100
- }
47
+ /**
48
+ * A UI surface (widget) embedded in a history row. Alias of the canonical
49
+ * wire-contract type so the producer matches the serialized shape.
50
+ */
51
+ export type HistorySurface = ConversationMessageSurface;
101
52
 
102
53
  /**
103
54
  * Positional reference to a file attachment captured while walking the
@@ -131,6 +82,16 @@ export interface RenderedHistoryContent {
131
82
  * attachment metadata to this ordering for inline placement.
132
83
  */
133
84
  attachments: HistoryAttachmentRef[];
85
+ /**
86
+ * Unified ordered content blocks built directly from the model-native
87
+ * content during the single walk — the wire `contentBlocks` projection.
88
+ * `attachment` blocks are inlined for file blocks whose DB-hydrated metadata
89
+ * the caller supplies via the `attachmentBlocks` argument (matched by
90
+ * attachment-ref order); a file block with no supplied metadata produces no
91
+ * block. Every other block type is always complete, so the serializer ships
92
+ * this array as-is with no post-processing.
93
+ */
94
+ contentBlocks: ConversationContentBlock[];
134
95
  }
135
96
 
136
97
  /**
@@ -256,6 +217,44 @@ function extractFileBlockMetadata(
256
217
  };
257
218
  }
258
219
 
220
+ /**
221
+ * Build the positional attachment reference for a `file` content block:
222
+ * filename/mime/size from the block's source plus the persisted
223
+ * `_attachmentId` when present (user-uploaded files).
224
+ */
225
+ function fileBlockToAttachmentRef(
226
+ block: Record<string, unknown>,
227
+ meta: FileBlockMetadata,
228
+ ): HistoryAttachmentRef {
229
+ const ref: HistoryAttachmentRef = {
230
+ filename: meta.filename,
231
+ mimeType: meta.mediaType,
232
+ sizeBytes: meta.sizeBytes,
233
+ };
234
+ if (typeof block._attachmentId === "string" && block._attachmentId) {
235
+ ref.attachmentId = block._attachmentId;
236
+ }
237
+ return ref;
238
+ }
239
+
240
+ /**
241
+ * Collect file-block attachment references in content-walk order without
242
+ * building the full history projection. The serializer aligns its DB-hydrated
243
+ * attachment rows against this ordering, then feeds the resolved metadata back
244
+ * into `renderHistoryContent` so it inlines `attachment` blocks during the walk.
245
+ */
246
+ export function collectAttachmentRefs(
247
+ content: unknown,
248
+ ): HistoryAttachmentRef[] {
249
+ if (!Array.isArray(content)) return [];
250
+ const refs: HistoryAttachmentRef[] = [];
251
+ for (const block of content) {
252
+ if (!isRecord(block) || block.type !== "file") continue;
253
+ refs.push(fileBlockToAttachmentRef(block, extractFileBlockMetadata(block)));
254
+ }
255
+ return refs;
256
+ }
257
+
259
258
  function renderFileBlockForHistory(
260
259
  block: Record<string, unknown>,
261
260
  meta: FileBlockMetadata,
@@ -277,7 +276,13 @@ function renderFileBlockForHistory(
277
276
  )}`;
278
277
  }
279
278
 
280
- export function renderHistoryContent(content: unknown): RenderedHistoryContent {
279
+ export function renderHistoryContent(
280
+ content: unknown,
281
+ attachmentBlocks?: ReadonlyArray<
282
+ ConversationMessageAttachment | null | undefined
283
+ >,
284
+ messageId?: string,
285
+ ): RenderedHistoryContent {
281
286
  if (!Array.isArray(content)) {
282
287
  let text: string;
283
288
  if (content == null) {
@@ -296,6 +301,7 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
296
301
  surfaces: [],
297
302
  thinkingSegments: [],
298
303
  attachments: [],
304
+ contentBlocks: text ? [{ type: "text", text }] : [],
299
305
  };
300
306
  }
301
307
 
@@ -316,6 +322,13 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
316
322
  let currentSegmentParts: string[] = [];
317
323
  let hasOpenSegment = false;
318
324
 
325
+ // Unified content blocks built in lockstep with the positional arrays as we
326
+ // walk the model-native content. `attachment` blocks are inlined here when
327
+ // the caller supplied DB-hydrated metadata in `attachmentBlocks`, matched by
328
+ // attachment-ref order; otherwise the file block contributes no block.
329
+ const contentBlocks: ConversationContentBlock[] = [];
330
+ let currentTextBlock: { type: "text"; text: string } | null = null;
331
+
319
332
  function joinWithSpacing(parts: string[]): string {
320
333
  let result = parts[0] ?? "";
321
334
  for (let i = 1; i < parts.length; i++) {
@@ -341,18 +354,42 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
341
354
 
342
355
  function finalizeSegment(): void {
343
356
  if (hasOpenSegment) {
344
- textSegments[textSegments.length - 1] =
345
- joinWithSpacing(currentSegmentParts);
357
+ const joined = joinWithSpacing(currentSegmentParts);
358
+ textSegments[textSegments.length - 1] = joined;
359
+ if (currentTextBlock) {
360
+ currentTextBlock.text = joined;
361
+ currentTextBlock = null;
362
+ }
346
363
  currentSegmentParts = [];
347
364
  hasOpenSegment = false;
348
365
  }
349
366
  }
350
367
 
351
- function ensureSegment(): void {
368
+ // Flush the open text segment into its tracked block and stop tracking it,
369
+ // without closing the segment. Used before folding the synthetic attachment
370
+ // description into the trailing segment: it stays in the legacy
371
+ // `textSegments`/`text` body but must not pollute the clean contentBlocks,
372
+ // since `attachment` blocks already carry that metadata.
373
+ function detachTextBlock(): void {
374
+ if (currentTextBlock) {
375
+ currentTextBlock.text = joinWithSpacing(currentSegmentParts);
376
+ currentTextBlock = null;
377
+ }
378
+ }
379
+
380
+ // `trackBlock` mirrors the segment into `contentBlocks`. The trailing
381
+ // attachment-description segment (legacy `message.text` for clients without
382
+ // attachment UI) sets it false so it isn't duplicated as a text block —
383
+ // attachments surface as `attachment` blocks instead.
384
+ function ensureSegment(trackBlock = true): void {
352
385
  if (!hasOpenSegment) {
353
386
  textSegments.push("");
354
387
  contentOrder.push(`text:${textSegments.length - 1}`);
355
388
  hasOpenSegment = true;
389
+ if (trackBlock) {
390
+ currentTextBlock = { type: "text", text: "" };
391
+ contentBlocks.push(currentTextBlock);
392
+ }
356
393
  }
357
394
  }
358
395
 
@@ -383,6 +420,7 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
383
420
  };
384
421
  surfaces.push(surface);
385
422
  contentOrder.push(`surface:${surfaces.length - 1}`);
423
+ contentBlocks.push({ type: "surface", surface });
386
424
  continue;
387
425
  }
388
426
 
@@ -390,6 +428,7 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
390
428
  finalizeSegment();
391
429
  thinkingSegments.push(block.thinking);
392
430
  contentOrder.push(`thinking:${thinkingSegments.length - 1}`);
431
+ contentBlocks.push({ type: "thinking", thinking: block.thinking });
393
432
  continue;
394
433
  }
395
434
 
@@ -416,16 +455,13 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
416
455
  const meta = extractFileBlockMetadata(block);
417
456
  attachmentParts.push(renderFileBlockForHistory(block, meta));
418
457
  finalizeSegment();
419
- const ref: HistoryAttachmentRef = {
420
- filename: meta.filename,
421
- mimeType: meta.mediaType,
422
- sizeBytes: meta.sizeBytes,
423
- };
424
- if (typeof block._attachmentId === "string" && block._attachmentId) {
425
- ref.attachmentId = block._attachmentId;
458
+ attachments.push(fileBlockToAttachmentRef(block, meta));
459
+ const refIndex = attachments.length - 1;
460
+ contentOrder.push(`attachment:${refIndex}`);
461
+ const hydrated = attachmentBlocks?.[refIndex];
462
+ if (hydrated) {
463
+ contentBlocks.push({ type: "attachment", attachment: hydrated });
426
464
  }
427
- attachments.push(ref);
428
- contentOrder.push(`attachment:${attachments.length - 1}`);
429
465
  continue;
430
466
  }
431
467
  if (block.type === "image") {
@@ -442,13 +478,18 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
442
478
  : {};
443
479
  const id = typeof block.id === "string" ? block.id : "";
444
480
  const entry: HistoryToolCall = { name, input };
481
+ if (id) entry.id = id;
445
482
  // Extract persisted timing/confirmation metadata
446
483
  if (typeof block._startedAt === "number")
447
484
  entry.startedAt = block._startedAt;
448
485
  if (typeof block._completedAt === "number")
449
486
  entry.completedAt = block._completedAt;
450
- if (typeof block._confirmationDecision === "string")
451
- entry.confirmationDecision = block._confirmationDecision;
487
+ const confirmationDecision = ConfirmationDecisionSchema.safeParse(
488
+ block._confirmationDecision,
489
+ );
490
+ if (confirmationDecision.success) {
491
+ entry.confirmationDecision = confirmationDecision.data;
492
+ }
452
493
  if (typeof block._confirmationLabel === "string")
453
494
  entry.confirmationLabel = block._confirmationLabel;
454
495
  if (typeof block._riskLevel === "string")
@@ -477,9 +518,18 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
477
518
  if (Array.isArray(block._riskDirectoryScopeOptions))
478
519
  entry.riskDirectoryScopeOptions =
479
520
  block._riskDirectoryScopeOptions as HistoryToolCall["riskDirectoryScopeOptions"];
521
+ // Read back tool activity (web_search / web_fetch) persisted by
522
+ // `annotatePersistedAssistantMessage` so the activity card survives a
523
+ // history reopen instead of degrading to the plain result text.
524
+ if (isRecord(block._activityMetadata))
525
+ entry.activityMetadata =
526
+ block._activityMetadata as HistoryToolCall["activityMetadata"];
480
527
  toolCalls.push(entry);
481
528
  if (id) pendingToolUses.set(id, entry);
482
529
  contentOrder.push(`tool:${toolCalls.length - 1}`);
530
+ // Same `entry` reference the block carries: a later tool_result pairs its
531
+ // output onto `entry`, so the content block reflects it automatically.
532
+ contentBlocks.push({ type: "tool_use", toolCall: entry });
483
533
  if (!seenToolUse) {
484
534
  seenToolUse = true;
485
535
  if (!seenText) toolCallsBeforeText = true;
@@ -494,9 +544,16 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
494
544
  : {};
495
545
  const id = typeof block.id === "string" ? block.id : "";
496
546
  const entry: HistoryToolCall = { name, input };
547
+ if (id) entry.id = id;
548
+ // Native server tools (Anthropic web_search) persist their activity on
549
+ // the server_tool_use block, so read it back here too.
550
+ if (isRecord(block._activityMetadata))
551
+ entry.activityMetadata =
552
+ block._activityMetadata as HistoryToolCall["activityMetadata"];
497
553
  toolCalls.push(entry);
498
554
  if (id) pendingToolUses.set(id, entry);
499
555
  contentOrder.push(`tool:${toolCalls.length - 1}`);
556
+ contentBlocks.push({ type: "tool_use", toolCall: entry });
500
557
  if (!seenToolUse) {
501
558
  seenToolUse = true;
502
559
  if (!seenText) toolCallsBeforeText = true;
@@ -586,14 +643,28 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
586
643
  // The macOS client handles this by selecting the *first* non-empty text
587
644
  // segment in interleaved content, so trailing attachment segments are safe.
588
645
  if (attachmentParts.length > 0) {
646
+ detachTextBlock();
589
647
  const attachmentText = attachmentParts.join("\n");
590
648
  const prefix = textParts.length > 0 ? "\n" : "";
591
- ensureSegment();
649
+ ensureSegment(false);
592
650
  currentSegmentParts.push(prefix + attachmentText);
593
651
  }
594
652
 
595
653
  finalizeSegment();
596
654
 
655
+ // Default any tool call the provider left without an `id` to the same
656
+ // positional id the web client historically synthesized, so every wire tool
657
+ // call is self-identifying and snapshot/stream ids line up. `idx` indexes the
658
+ // final `toolCalls` array (the client keys off the same positions); the
659
+ // shared `entry` references mean `contentBlocks` reflect this for free.
660
+ if (messageId !== undefined) {
661
+ toolCalls.forEach((toolCall, idx) => {
662
+ if (toolCall.id === undefined) {
663
+ toolCall.id = `tool-history-${messageId}-${idx}`;
664
+ }
665
+ });
666
+ }
667
+
597
668
  const text = joinWithSpacing(textParts);
598
669
  let rendered: string;
599
670
  if (attachmentParts.length === 0) {
@@ -613,6 +684,7 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
613
684
  surfaces,
614
685
  thinkingSegments,
615
686
  attachments,
687
+ contentBlocks,
616
688
  };
617
689
  }
618
690
 
@@ -31,7 +31,7 @@ import {
31
31
  isTextMimeType as isTextMime,
32
32
  MAX_INLINE_TEXT_SIZE,
33
33
  } from "../../runtime/routes/workspace-utils.js";
34
- import { getCatalog } from "../../skills/catalog-cache.js";
34
+ import { getCachedCatalogSync, getCatalog } from "../../skills/catalog-cache.js";
35
35
  import type { SkillFileEntry } from "../../skills/catalog-files.js";
36
36
  import {
37
37
  catalogSkillToSlim,
@@ -48,7 +48,6 @@ import {
48
48
  installSkillLocally,
49
49
  } from "../../skills/catalog-install.js";
50
50
  import { filterByQuery } from "../../skills/catalog-search.js";
51
- import { inferCategory } from "../../skills/category-inference.js";
52
51
  import type { ClawhubInspectResult } from "../../skills/clawhub.js";
53
52
  import {
54
53
  clawhubCheckUpdates,
@@ -285,6 +284,25 @@ function postInstallSkill(skillId: string): void {
285
284
  refreshSkillCapabilityMemories(getConfig());
286
285
  }
287
286
 
287
+ // ─── Catalog category lookup ────────────────────────────────────────────────
288
+
289
+ let _catalogCategoryMap: Map<string, string> | null = null;
290
+ let _catalogCategoryRef: readonly CatalogSkill[] | null = null;
291
+
292
+ function getCatalogCategoryMap(): Map<string, string> {
293
+ const catalog = getCachedCatalogSync();
294
+ if (_catalogCategoryMap && _catalogCategoryRef === catalog) {
295
+ return _catalogCategoryMap;
296
+ }
297
+ _catalogCategoryMap = new Map();
298
+ for (const s of catalog) {
299
+ const cat = s.metadata?.vellum?.category;
300
+ if (cat) _catalogCategoryMap.set(s.id, cat);
301
+ }
302
+ _catalogCategoryRef = catalog;
303
+ return _catalogCategoryMap;
304
+ }
305
+
288
306
  // ─── Kind / origin / status derivation ───────────────────────────────────────
289
307
 
290
308
  /** Map the old `source` field to the new `kind` axis. */
@@ -328,11 +346,12 @@ function toSlimSkillResponse(
328
346
  const origin = deriveOrigin(kind, summary.directoryPath, installMeta);
329
347
  const status: SlimSkillResponse["status"] = state;
330
348
 
331
- const category = inferCategory(summary.displayName, summary.description);
349
+ const category = getCatalogCategoryMap().get(summary.id) ?? "system";
332
350
  const base = {
333
351
  id: summary.id,
334
352
  name: summary.displayName,
335
353
  description: summary.description,
354
+ icon: summary.icon,
336
355
  emoji: summary.emoji,
337
356
  kind,
338
357
  status,
@@ -395,14 +414,20 @@ export function listSkills(): SlimSkillResponse[] {
395
414
  * Installed skills take precedence when deduplicating by ID.
396
415
  */
397
416
  async function listSkillsWithCatalog(): Promise<SlimSkillResponse[]> {
398
- const installed = listSkills();
399
- const installedIds = new Set(installed.map((s) => s.id));
400
-
417
+ // Warm the catalog cache before converting installed skills so
418
+ // getCatalogCategoryMap() in toSlimSkillResponse() sees real categories
419
+ // instead of falling back to "system" on a cold cache.
401
420
  let catalogSkills: CatalogSkill[];
402
421
  try {
403
422
  catalogSkills = await getCatalog();
404
423
  } catch {
405
- // If catalog fetch fails, return installed-only
424
+ catalogSkills = [];
425
+ }
426
+
427
+ const installed = listSkills();
428
+ const installedIds = new Set(installed.map((s) => s.id));
429
+
430
+ if (catalogSkills.length === 0) {
406
431
  return installed;
407
432
  }
408
433
 
@@ -605,6 +630,7 @@ export async function getSkill(
605
630
  id: slim.id,
606
631
  name: slim.name,
607
632
  description: slim.description,
633
+ icon: slim.icon,
608
634
  emoji: slim.emoji,
609
635
  kind: slim.kind,
610
636
  origin: slim.origin,
@@ -645,6 +671,7 @@ export async function getSkill(
645
671
  id: slim.id,
646
672
  name: slim.name,
647
673
  description: slim.description,
674
+ icon: slim.icon,
648
675
  emoji: slim.emoji,
649
676
  kind: slim.kind,
650
677
  origin: slim.origin,
@@ -676,6 +703,7 @@ export async function getSkill(
676
703
  id: slim.id,
677
704
  name: slim.name,
678
705
  description: slim.description,
706
+ icon: slim.icon,
679
707
  emoji: slim.emoji,
680
708
  kind: slim.kind,
681
709
  origin: slim.origin,
@@ -1365,11 +1393,12 @@ export async function searchSkills(
1365
1393
  id: s.id,
1366
1394
  name: s.displayName,
1367
1395
  description: s.description,
1396
+ icon: s.icon,
1368
1397
  emoji: s.emoji,
1369
1398
  kind: "catalog" as const,
1370
1399
  origin: "vellum" as const,
1371
1400
  status: "available" as const,
1372
- category: inferCategory(s.displayName, s.description),
1401
+ category: getCatalogCategoryMap().get(s.id) ?? "system",
1373
1402
  };
1374
1403
  });
1375
1404
 
@@ -1388,7 +1417,7 @@ export async function searchSkills(
1388
1417
  kind: "catalog" as const,
1389
1418
  origin: "clawhub" as const,
1390
1419
  status: "available" as const,
1391
- category: inferCategory(s.name, s.description),
1420
+ category: "integrations",
1392
1421
  slug: s.slug,
1393
1422
  author: s.author,
1394
1423
  stars: s.stars,
@@ -1415,7 +1444,7 @@ export async function searchSkills(
1415
1444
  kind: "catalog" as const,
1416
1445
  origin: "skillssh" as const,
1417
1446
  status: "available" as const,
1418
- category: inferCategory(r.name, ""),
1447
+ category: "integrations",
1419
1448
  slug: r.id,
1420
1449
  sourceRepo: r.source,
1421
1450
  installs: r.installs,
@@ -117,6 +117,7 @@ import {
117
117
  maybeRebuildMemoryV2Concepts,
118
118
  rebuildBm25CorpusStatsAndReseedSkills,
119
119
  } from "./memory-v2-startup.js";
120
+ import { startOrphanReaper, stopOrphanReaper } from "./orphan-reaper.js";
120
121
  import { processMessage } from "./process-message.js";
121
122
  import { runProfilerSweep } from "./profiler-run-store.js";
122
123
  import {
@@ -728,6 +729,7 @@ export async function runDaemon(): Promise<void> {
728
729
  await server.start();
729
730
  log.info("Daemon startup: DaemonServer started");
730
731
  startDiskPressureGuardForLifecycle();
732
+ startOrphanReaper();
731
733
 
732
734
  // Kick off the update bulletin background job AFTER `server.start()`
733
735
  // resolves. The conversation store must be initialized before wake
@@ -1324,6 +1326,7 @@ export async function runDaemon(): Promise<void> {
1324
1326
  cleanupPidFile: () => {
1325
1327
  stopGatewayFlagListener();
1326
1328
  stopDiskPressureGuardForLifecycle();
1329
+ stopOrphanReaper();
1327
1330
  cleanupPidFile();
1328
1331
  },
1329
1332
  });
@@ -1338,6 +1341,7 @@ export async function runDaemon(): Promise<void> {
1338
1341
  } catch (err) {
1339
1342
  log.error({ err }, "Daemon startup failed — cleaning up");
1340
1343
  stopDiskPressureGuardForLifecycle();
1344
+ stopOrphanReaper();
1341
1345
  cleanupPidFileIfOwner(process.pid);
1342
1346
  throw err;
1343
1347
  }
@@ -1,6 +1,4 @@
1
- // App management, gallery, publishing, and sharing types.
2
-
3
- import type { GalleryManifest } from "../../gallery/gallery-manifest.js";
1
+ // App management, publishing, and sharing types.
4
2
 
5
3
  // === Client → Server ===
6
4
 
@@ -81,15 +79,6 @@ export interface GetSigningIdentityResponse {
81
79
  error?: string;
82
80
  }
83
81
 
84
- export interface GalleryListRequest {
85
- type: "gallery_list";
86
- }
87
-
88
- export interface GalleryInstallRequest {
89
- type: "gallery_install";
90
- galleryAppId: string;
91
- }
92
-
93
82
  export interface AppHistoryRequest {
94
83
  type: "app_history_request";
95
84
  appId: string;
@@ -271,19 +260,6 @@ export interface ShareAppCloudResponse {
271
260
  error?: string;
272
261
  }
273
262
 
274
- export interface GalleryListResponse {
275
- type: "gallery_list_response";
276
- gallery: GalleryManifest;
277
- }
278
-
279
- export interface GalleryInstallResponse {
280
- type: "gallery_install_response";
281
- success: boolean;
282
- appId?: string;
283
- name?: string;
284
- error?: string;
285
- }
286
-
287
263
  export interface AppHistoryResponse {
288
264
  type: "app_history_response";
289
265
  appId: string;
@@ -347,8 +323,6 @@ export type _AppsClientMessages =
347
323
  | OpenBundleRequest
348
324
  | SignBundlePayloadResponse
349
325
  | GetSigningIdentityResponse
350
- | GalleryListRequest
351
- | GalleryInstallRequest
352
326
  | AppHistoryRequest
353
327
  | AppDiffRequest
354
328
  | AppFileAtVersionRequest
@@ -371,8 +345,6 @@ export type _AppsServerMessages =
371
345
  | SignBundlePayloadRequest
372
346
  | GetSigningIdentityRequest
373
347
  | ShareAppCloudResponse
374
- | GalleryListResponse
375
- | GalleryInstallResponse
376
348
  | AppHistoryResponse
377
349
  | AppDiffResponse
378
350
  | AppFileAtVersionResponse