@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
@@ -10,6 +10,8 @@
10
10
  * with the platform for this assistant.
11
11
  */
12
12
 
13
+ import { z } from "zod";
14
+
13
15
  import { getIsPlatform } from "../../config/env-registry.js";
14
16
  import { getConfig } from "../../config/loader.js";
15
17
  import {
@@ -25,6 +27,38 @@ import {
25
27
  } from "./errors.js";
26
28
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
27
29
 
30
+ // ---------------------------------------------------------------------------
31
+ // Schemas
32
+ // ---------------------------------------------------------------------------
33
+
34
+ const WebhooksRegisterRequestSchema = z.object({
35
+ type: z.string(),
36
+ path: z.string().optional(),
37
+ source: z.string().optional(),
38
+ });
39
+
40
+ const WebhooksRegisterResponseSchema = z.object({
41
+ callbackUrl: z.string(),
42
+ type: z.string(),
43
+ path: z.string(),
44
+ mode: z.enum(["platform", "self-hosted"]),
45
+ });
46
+ type WebhooksRegisterResponse = z.infer<typeof WebhooksRegisterResponseSchema>;
47
+
48
+ const WebhookCallbackRouteSchema = z.object({
49
+ id: z.string(),
50
+ assistant_id: z.string(),
51
+ type: z.string(),
52
+ callback_path: z.string(),
53
+ callback_url: z.string(),
54
+ source_identifier: z.string().nullable(),
55
+ });
56
+
57
+ const WebhooksListResponseSchema = z.object({
58
+ routes: z.array(WebhookCallbackRouteSchema),
59
+ });
60
+ type WebhooksListResponse = z.infer<typeof WebhooksListResponseSchema>;
61
+
28
62
  // ---------------------------------------------------------------------------
29
63
  // Helpers
30
64
  // ---------------------------------------------------------------------------
@@ -49,7 +83,7 @@ function deriveWebhookPath(type: string): string {
49
83
 
50
84
  async function handleWebhooksRegister(
51
85
  args: RouteHandlerArgs,
52
- ): Promise<unknown> {
86
+ ): Promise<WebhooksRegisterResponse> {
53
87
  const { type, path: pathOverride, source } = args.body ?? {};
54
88
 
55
89
  if (!type || typeof type !== "string") {
@@ -93,7 +127,9 @@ async function handleWebhooksRegister(
93
127
  };
94
128
  }
95
129
 
96
- async function handleWebhooksList(_args: RouteHandlerArgs): Promise<unknown> {
130
+ async function handleWebhooksList(
131
+ _args: RouteHandlerArgs,
132
+ ): Promise<WebhooksListResponse> {
97
133
  const context = await resolvePlatformCallbackRegistrationContext();
98
134
 
99
135
  if (!context.platformBaseUrl || !context.authHeader) {
@@ -155,6 +191,8 @@ export const ROUTES: RouteDefinition[] = [
155
191
  description:
156
192
  "Resolves a stable callback URL for a webhook type. On platform-managed assistants, registers the route with the platform gateway. On self-hosted assistants, uses the configured ingress.publicBaseUrl.",
157
193
  tags: ["webhooks"],
194
+ requestBody: WebhooksRegisterRequestSchema,
195
+ responseBody: WebhooksRegisterResponseSchema,
158
196
  handler: handleWebhooksRegister,
159
197
  },
160
198
  {
@@ -169,6 +207,7 @@ export const ROUTES: RouteDefinition[] = [
169
207
  description:
170
208
  "Lists all webhook callback routes registered with the platform for this assistant.",
171
209
  tags: ["webhooks"],
210
+ responseBody: WebhooksListResponseSchema,
172
211
  handler: handleWebhooksList,
173
212
  },
174
213
  ];
@@ -663,6 +663,10 @@ export const ROUTES: RouteDefinition[] = [
663
663
  description: "Allow hidden files (true/false)",
664
664
  },
665
665
  ],
666
+ responseBody: {
667
+ contentType: "application/octet-stream",
668
+ schema: { type: "string", format: "binary" },
669
+ },
666
670
  responseStatus: ({ headers }) => (headers?.["range"] ? "206" : "200"),
667
671
  additionalResponses: {
668
672
  "416": { description: "Range Not Satisfiable" },
@@ -61,6 +61,12 @@ function makeConversation() {
61
61
  setSubagentAllowedTools: mock(() => {}),
62
62
  updateClient: mock(() => {}),
63
63
  processing: false,
64
+ isProcessing(this: { processing: boolean }) {
65
+ return this.processing;
66
+ },
67
+ setProcessing(this: { processing: boolean }, value: boolean) {
68
+ this.processing = value;
69
+ },
64
70
  abortController: null as AbortController | null,
65
71
  currentRequestId: null as string | null,
66
72
  loadedHistoryTrustClass: undefined as string | undefined,
@@ -198,7 +198,7 @@ export async function analyzeConversation(
198
198
  // `currentRequestId` and let two loops mutate the same Conversation
199
199
  // state. Skip this run instead — the next upstream trigger will
200
200
  // re-enqueue once the in-flight loop finishes.
201
- if (opts.trigger === "auto" && analysisConversation.processing) {
201
+ if (opts.trigger === "auto" && analysisConversation.isProcessing()) {
202
202
  log.info(
203
203
  {
204
204
  sourceConversationId: resolvedId,
@@ -247,7 +247,7 @@ export async function analyzeConversation(
247
247
  analysisConversation.updateClient(broadcastMessage, !hasLiveSubscriber);
248
248
 
249
249
  // k. Set up processing state (required by runAgentLoop guard)
250
- analysisConversation.processing = true;
250
+ analysisConversation.setProcessing(true);
251
251
  analysisConversation.abortController = new AbortController();
252
252
  analysisConversation.currentRequestId = crypto.randomUUID();
253
253
 
@@ -49,6 +49,7 @@ export interface ScheduleJob {
49
49
  retryBackoffMs: number;
50
50
  /** Script-mode execution timeout override (ms); null = use the default. */
51
51
  timeoutMs: number | null;
52
+ createdFromConversationId: string | null;
52
53
  createdBy: string;
53
54
  mode: ScheduleMode;
54
55
  routingIntent: RoutingIntent;
@@ -102,6 +103,7 @@ export function createSchedule(params: {
102
103
  maxRetries?: number;
103
104
  retryBackoffMs?: number;
104
105
  timeoutMs?: number | null;
106
+ createdFromConversationId?: string | null;
105
107
  }): ScheduleJob {
106
108
  const expression = params.expression ?? params.cronExpression ?? null;
107
109
  const isOneShot = expression == null;
@@ -138,6 +140,7 @@ export function createSchedule(params: {
138
140
  const maxRetries = params.maxRetries ?? 3;
139
141
  const retryBackoffMs = params.retryBackoffMs ?? 60000;
140
142
  const timeoutMs = params.timeoutMs ?? null;
143
+ const createdFromConversationId = params.createdFromConversationId ?? null;
141
144
 
142
145
  let nextRunAt: number;
143
146
  if (isOneShot) {
@@ -165,6 +168,7 @@ export function createSchedule(params: {
165
168
  maxRetries,
166
169
  retryBackoffMs,
167
170
  timeoutMs,
171
+ createdFromConversationId,
168
172
  createdBy: params.createdBy ?? "agent",
169
173
  mode,
170
174
  routingIntent,
@@ -264,6 +268,7 @@ export function updateSchedule(
264
268
  maxRetries?: number;
265
269
  retryBackoffMs?: number;
266
270
  timeoutMs?: number | null;
271
+ createdFromConversationId?: string | null;
267
272
  },
268
273
  ): ScheduleJob | null {
269
274
  const db = getDb();
@@ -329,6 +334,8 @@ export function updateSchedule(
329
334
  if (updates.retryBackoffMs !== undefined)
330
335
  set.retryBackoffMs = updates.retryBackoffMs;
331
336
  if (updates.timeoutMs !== undefined) set.timeoutMs = updates.timeoutMs;
337
+ if (updates.createdFromConversationId !== undefined)
338
+ set.createdFromConversationId = updates.createdFromConversationId;
332
339
 
333
340
  // Recompute nextRunAt if schedule timing may have changed (only for recurring)
334
341
  if (
@@ -591,7 +598,7 @@ export function cancelSchedule(id: string): boolean {
591
598
 
592
599
  export function createScheduleRun(
593
600
  jobId: string,
594
- conversationId: string,
601
+ conversationId: string | null,
595
602
  ): string {
596
603
  const db = getDb();
597
604
  const id = uuid();
@@ -613,6 +620,17 @@ export function createScheduleRun(
613
620
  return id;
614
621
  }
615
622
 
623
+ export function setScheduleRunConversationId(
624
+ runId: string,
625
+ conversationId: string,
626
+ ): void {
627
+ const db = getDb();
628
+ db.update(scheduleRuns)
629
+ .set({ conversationId })
630
+ .where(eq(scheduleRuns.id, runId))
631
+ .run();
632
+ }
633
+
616
634
  export function completeScheduleRun(
617
635
  runId: string,
618
636
  result: { status: "ok" | "error"; output?: string; error?: string },
@@ -977,6 +995,7 @@ function parseJobRow(row: typeof scheduleJobs.$inferSelect): ScheduleJob {
977
995
  maxRetries: row.maxRetries ?? 3,
978
996
  retryBackoffMs: row.retryBackoffMs ?? 60000,
979
997
  timeoutMs: row.timeoutMs ?? null,
998
+ createdFromConversationId: row.createdFromConversationId ?? null,
980
999
  createdBy: row.createdBy,
981
1000
  mode: (row.mode ?? "execute") as ScheduleMode,
982
1001
  routingIntent: (row.routingIntent ?? "all_channels") as RoutingIntent,
@@ -0,0 +1,83 @@
1
+ import { rawAll } from "../memory/raw-query.js";
2
+ import { buildScheduleAttributionSubquery } from "../memory/schedule-attribution-sql.js";
3
+
4
+ export interface ScheduleUsageSummary {
5
+ scheduleId: string;
6
+ runCount: number;
7
+ totalEstimatedCostUsd: number;
8
+ eventCount: number;
9
+ }
10
+
11
+ interface ScheduleUsageSummaryRow {
12
+ schedule_id: string;
13
+ run_count: number;
14
+ total_estimated_cost_usd: number | null;
15
+ event_count: number | null;
16
+ }
17
+
18
+ export function getScheduleUsageSummaries({
19
+ from,
20
+ to,
21
+ }: {
22
+ from: number;
23
+ to: number;
24
+ }): ScheduleUsageSummary[] {
25
+ const now = Date.now();
26
+ const scheduleAttribution = buildScheduleAttributionSubquery({
27
+ eventAlias: "e",
28
+ now,
29
+ selectExpression: "schedule_attr_runs.job_id",
30
+ });
31
+ const rows = rawAll<ScheduleUsageSummaryRow>(
32
+ /*sql*/ `
33
+ WITH run_counts AS (
34
+ SELECT
35
+ job_id AS schedule_id,
36
+ COUNT(*) AS run_count
37
+ FROM cron_runs
38
+ WHERE started_at >= ?
39
+ AND started_at <= ?
40
+ GROUP BY job_id
41
+ ),
42
+ attributed_usage AS (
43
+ SELECT
44
+ e.estimated_cost_usd,
45
+ COALESCE(e.llm_call_count, 1) AS event_count,
46
+ ${scheduleAttribution.sql} AS schedule_id
47
+ FROM llm_usage_events e
48
+ WHERE e.created_at >= ?
49
+ AND e.created_at <= ?
50
+ ),
51
+ usage_totals AS (
52
+ SELECT
53
+ schedule_id,
54
+ COALESCE(SUM(estimated_cost_usd), 0) AS total_estimated_cost_usd,
55
+ COALESCE(SUM(event_count), 0) AS event_count
56
+ FROM attributed_usage
57
+ WHERE schedule_id IS NOT NULL
58
+ GROUP BY schedule_id
59
+ )
60
+ SELECT
61
+ cron_jobs.id AS schedule_id,
62
+ COALESCE(run_counts.run_count, 0) AS run_count,
63
+ COALESCE(usage_totals.total_estimated_cost_usd, 0) AS total_estimated_cost_usd,
64
+ COALESCE(usage_totals.event_count, 0) AS event_count
65
+ FROM cron_jobs
66
+ LEFT JOIN run_counts ON run_counts.schedule_id = cron_jobs.id
67
+ LEFT JOIN usage_totals ON usage_totals.schedule_id = cron_jobs.id
68
+ ORDER BY cron_jobs.created_at ASC, cron_jobs.id ASC
69
+ `,
70
+ from,
71
+ to,
72
+ ...scheduleAttribution.params,
73
+ from,
74
+ to,
75
+ );
76
+
77
+ return rows.map((row) => ({
78
+ scheduleId: row.schedule_id,
79
+ runCount: row.run_count,
80
+ totalEstimatedCostUsd: row.total_estimated_cost_usd ?? 0,
81
+ eventCount: row.event_count ?? 0,
82
+ }));
83
+ }
@@ -28,6 +28,7 @@ import {
28
28
  type RoutingIntent,
29
29
  type ScheduleJob,
30
30
  scheduleRetry,
31
+ setScheduleRunConversationId,
31
32
  } from "./schedule-store.js";
32
33
 
33
34
  const log = getLogger("scheduler");
@@ -442,6 +443,7 @@ export async function runScheduleDueWorkOnce(
442
443
  job.syntax === "rrule" &&
443
444
  job.expression != null &&
444
445
  hasSetConstructs(job.expression);
446
+ const runId = createScheduleRun(job.id, null);
445
447
  let failed = false;
446
448
  try {
447
449
  log.info(
@@ -472,14 +474,13 @@ export async function runScheduleDueWorkOnce(
472
474
  },
473
475
  );
474
476
 
477
+ setScheduleRunConversationId(runId, result.conversationId);
475
478
  onScheduleConversationCreated?.({
476
479
  conversationId: result.conversationId,
477
480
  scheduleJobId: job.id,
478
481
  title: result.status === "failed" ? `${job.name}: Error` : job.name,
479
482
  });
480
483
 
481
- // Track the schedule run using the task's conversation
482
- const runId = createScheduleRun(job.id, result.conversationId);
483
484
  if (result.status === "failed") {
484
485
  const errorMessage = result.error ?? "Task run failed";
485
486
  completeScheduleRun(runId, {
@@ -530,7 +531,7 @@ export async function runScheduleDueWorkOnce(
530
531
  scheduleJobId: job.id,
531
532
  title: `${job.name}: Error`,
532
533
  });
533
- const runId = createScheduleRun(job.id, fallbackConversation.id);
534
+ setScheduleRunConversationId(runId, fallbackConversation.id);
534
535
  completeScheduleRun(runId, { status: "error", error: message });
535
536
  emitTaskActivityFailed({
536
537
  taskId,
@@ -584,6 +585,8 @@ export async function runScheduleDueWorkOnce(
584
585
  let ok: boolean;
585
586
  let errorMsg: string | undefined;
586
587
  const conversationReused = reusedConversationId != null;
588
+ let runConversationId = reusedConversationId;
589
+ const runId = createScheduleRun(job.id, reusedConversationId);
587
590
 
588
591
  if (reusedConversationId) {
589
592
  // Reuse path: keep using the injected `processMessage` callback so the
@@ -625,6 +628,8 @@ export async function runScheduleDueWorkOnce(
625
628
  scheduleJobId: job.id,
626
629
  suppressFailureNotifications: job.quiet === true,
627
630
  onConversationCreated: (newConversationId) => {
631
+ runConversationId = newConversationId;
632
+ setScheduleRunConversationId(runId, newConversationId);
628
633
  onScheduleConversationCreated?.({
629
634
  conversationId: newConversationId,
630
635
  scheduleJobId: job.id,
@@ -641,12 +646,14 @@ export async function runScheduleDueWorkOnce(
641
646
  !result.ok && result.conversationId === ""
642
647
  ? `bootstrap-error:${job.id}`
643
648
  : result.conversationId;
649
+ if (runConversationId !== conversationId) {
650
+ runConversationId = conversationId;
651
+ setScheduleRunConversationId(runId, conversationId);
652
+ }
644
653
  ok = result.ok;
645
654
  errorMsg = result.error?.message;
646
655
  }
647
656
 
648
- const runId = createScheduleRun(job.id, conversationId);
649
-
650
657
  if (ok) {
651
658
  completeScheduleRun(runId, { status: "ok" });
652
659
  if (isOneShot) completeOneShot(job.id);
@@ -37,7 +37,6 @@ import {
37
37
  import { getLogger } from "../util/logger.js";
38
38
  import { getCachedCatalogSync, getCatalog } from "./catalog-cache.js";
39
39
  import { type CatalogSkill, getRepoSkillsDir } from "./catalog-install.js";
40
- import { inferCategory } from "./category-inference.js";
41
40
  import type { SkillFileProvider } from "./skill-file-provider.js";
42
41
 
43
42
  const log = getLogger("catalog-files");
@@ -503,11 +502,12 @@ export function catalogSkillToSlim(cs: CatalogSkill): SlimSkillResponse {
503
502
  id: cs.id,
504
503
  name,
505
504
  description: cs.description,
505
+ icon: cs.icon ?? cs.metadata?.icon,
506
506
  emoji: cs.emoji ?? cs.metadata?.emoji,
507
507
  kind: "catalog",
508
508
  origin: "vellum",
509
509
  status: "available",
510
- category: inferCategory(name, cs.description),
510
+ category: cs.metadata?.vellum?.category ?? "system",
511
511
  };
512
512
  }
513
513
 
@@ -29,17 +29,20 @@ export interface CatalogSkill {
29
29
  id: string;
30
30
  name: string;
31
31
  description: string;
32
+ icon?: string;
32
33
  emoji?: string;
33
34
  includes?: string[];
34
35
  version?: string;
35
36
  updatedAt?: string;
36
37
  metadata?: {
38
+ icon?: string;
37
39
  emoji?: string;
38
40
  vellum?: {
39
41
  "display-name"?: string;
40
42
  "activation-hints"?: string[];
41
43
  "avoid-when"?: string[];
42
44
  "feature-flag"?: string;
45
+ category?: string;
43
46
  };
44
47
  };
45
48
  }
@@ -0,0 +1,118 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { parse as parseYaml } from "yaml";
5
+
6
+ import { getPlatformBaseUrl } from "../config/env.js";
7
+ import { getLogger } from "../util/logger.js";
8
+ import { getRepoSkillsDir } from "./catalog-install.js";
9
+
10
+ const log = getLogger("categories-cache");
11
+ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
12
+
13
+ export interface SkillCategoryDef {
14
+ slug: string;
15
+ label: string;
16
+ description: string;
17
+ icon: string;
18
+ }
19
+
20
+ let cachedCategories: SkillCategoryDef[] | null = null;
21
+ let cacheTimestamp = 0;
22
+
23
+ async function fetchCategories(): Promise<SkillCategoryDef[]> {
24
+ const platformUrl = getPlatformBaseUrl();
25
+ const url = `${platformUrl}/v1/skills/categories/`;
26
+ const response = await fetch(url, {
27
+ signal: AbortSignal.timeout(10000),
28
+ });
29
+
30
+ if (!response.ok) {
31
+ throw new Error(
32
+ `Platform API error ${response.status}: ${response.statusText}`,
33
+ );
34
+ }
35
+
36
+ const data = (await response.json()) as {
37
+ categories: SkillCategoryDef[];
38
+ };
39
+ if (!Array.isArray(data.categories)) {
40
+ throw new Error("Platform categories response has invalid categories array");
41
+ }
42
+ return data.categories.filter(
43
+ (c): c is SkillCategoryDef =>
44
+ !!c && typeof c.slug === "string" && typeof c.label === "string",
45
+ );
46
+ }
47
+
48
+ function readLocalCategories(repoSkillsDir: string): SkillCategoryDef[] {
49
+ try {
50
+ const raw = readFileSync(
51
+ join(repoSkillsDir, "skill-categories-catalog.yaml"),
52
+ "utf-8",
53
+ );
54
+ const parsed = parseYaml(raw) as { categories?: SkillCategoryDef[] };
55
+ if (!Array.isArray(parsed?.categories)) return [];
56
+ return parsed.categories.filter(
57
+ (c): c is SkillCategoryDef =>
58
+ !!c && typeof c.slug === "string" && typeof c.label === "string",
59
+ );
60
+ } catch {
61
+ return [];
62
+ }
63
+ }
64
+
65
+ export async function getCategories(): Promise<SkillCategoryDef[]> {
66
+ if (cachedCategories && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
67
+ return cachedCategories;
68
+ }
69
+
70
+ const repoSkillsDir = getRepoSkillsDir();
71
+ const local = repoSkillsDir ? readLocalCategories(repoSkillsDir) : [];
72
+ let categories: SkillCategoryDef[];
73
+
74
+ try {
75
+ const remote = await fetchCategories();
76
+ if (local.length > 0) {
77
+ const localSlugs = new Set(local.map((c) => c.slug));
78
+ categories = [
79
+ ...local,
80
+ ...remote.filter((c) => !localSlugs.has(c.slug)),
81
+ ];
82
+ } else {
83
+ categories = remote;
84
+ }
85
+ } catch (err) {
86
+ if (cachedCategories) {
87
+ log.warn(
88
+ { err },
89
+ "Failed to fetch skill categories, keeping stale cache",
90
+ );
91
+ cacheTimestamp = Date.now();
92
+ return cachedCategories;
93
+ }
94
+ if (local.length > 0) {
95
+ log.warn(
96
+ { err },
97
+ "Failed to fetch skill categories, falling back to bundled local catalog",
98
+ );
99
+ categories = local;
100
+ } else {
101
+ log.warn({ err }, "Failed to fetch skill categories, returning empty");
102
+ return [];
103
+ }
104
+ }
105
+
106
+ cachedCategories = categories;
107
+ cacheTimestamp = Date.now();
108
+ return categories;
109
+ }
110
+
111
+ export function getCachedCategoriesSync(): SkillCategoryDef[] {
112
+ return cachedCategories ?? [];
113
+ }
114
+
115
+ export function invalidateCategoriesCache(): void {
116
+ cachedCategories = null;
117
+ cacheTimestamp = 0;
118
+ }
@@ -17,7 +17,6 @@ import type { SlimSkillResponse } from "../daemon/message-types/skills.js";
17
17
  import { isTextMimeType as isTextMime } from "../runtime/routes/workspace-utils.js";
18
18
  import { getLogger } from "../util/logger.js";
19
19
  import type { SkillFileEntry } from "./catalog-files.js";
20
- import { inferCategory } from "./category-inference.js";
21
20
  import {
22
21
  clawhubInspect,
23
22
  clawhubInspectFile,
@@ -199,7 +198,7 @@ export function createClawhubProvider(): SkillFileProvider {
199
198
  kind: "catalog",
200
199
  status: "available",
201
200
  origin: "clawhub",
202
- category: inferCategory(data.skill.displayName, data.skill.summary),
201
+ category: "integrations",
203
202
  slug: data.skill.slug,
204
203
  author: data.owner?.handle ?? "",
205
204
  stars: data.stats?.stars ?? 0,
@@ -24,7 +24,6 @@ import {
24
24
  sanitizeRelativePath,
25
25
  SKIP_DIRS,
26
26
  } from "./catalog-files.js";
27
- import { inferCategory } from "./category-inference.js";
28
27
  import type { SkillFileProvider } from "./skill-file-provider.js";
29
28
  import type { GitHubContentsEntry } from "./skillssh-registry.js";
30
29
  import {
@@ -384,7 +383,7 @@ export function createSkillsShProvider(): SkillFileProvider {
384
383
  kind: "catalog",
385
384
  status: "available",
386
385
  origin: "skillssh",
387
- category: inferCategory(source.skillSlug, ""),
386
+ category: "integrations",
388
387
  slug: skillId,
389
388
  sourceRepo: `${source.owner}/${source.repo}`,
390
389
  installs: 0,
@@ -185,9 +185,37 @@ export interface OnboardingTelemetryEvent extends TelemetryEventBase {
185
185
  ab_variant?: string;
186
186
  }
187
187
 
188
+ /**
189
+ * Auth-fallback event — aggregated count of requests served via the legacy
190
+ * loopback auth fallback. One event per (guard, path, failure_kind) per flush
191
+ * window. Lets the platform see which deployments still rely on the loopback
192
+ * exemption instead of sending a bearer token.
193
+ */
194
+ export interface AuthFallbackTelemetryEvent extends TelemetryEventBase {
195
+ type: "auth_fallback";
196
+ /** Which auth guard fell back: `"edge"` | `"edge-scoped"` | `"edge-guardian"`. */
197
+ guard: string;
198
+ /** Request pathname that fell back. */
199
+ path: string;
200
+ /**
201
+ * Why the bearer-token check did not succeed before the fallback:
202
+ * `"missing_authorization"` | `"malformed_authorization"` |
203
+ * `"token_validation_failed"` | `"insufficient_scope"` |
204
+ * `"non_actor_principal"` | `"guardian_mismatch"`.
205
+ */
206
+ failure_kind: string;
207
+ /** Number of requests that fell back for this key during the window. */
208
+ count: number;
209
+ /** Window start (epoch ms) the count was accumulated over. */
210
+ window_start: number;
211
+ /** Window end (epoch ms) the count was accumulated over. */
212
+ window_end: number;
213
+ }
214
+
188
215
  /** Discriminated union of all telemetry event types. */
189
216
  export type TelemetryEvent =
190
217
  | LlmUsageTelemetryEvent
191
218
  | TurnTelemetryEvent
192
219
  | LifecycleTelemetryEvent
193
- | OnboardingTelemetryEvent;
220
+ | OnboardingTelemetryEvent
221
+ | AuthFallbackTelemetryEvent;