@vellumai/assistant 0.11.8 → 0.11.9-staging.1

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 (318) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/Dockerfile +8 -48
  3. package/docker-entrypoint.sh +5 -1
  4. package/docker-kata-apt-env.sh +3 -0
  5. package/docker-kata-apt-shims.sh +127 -0
  6. package/docker-kata-apt-wrapper.sh +45 -0
  7. package/docker-kata-chroot-exec.sh +35 -0
  8. package/docker-kata-pip.sh +8 -2
  9. package/docs/architecture/memory.md +8 -4
  10. package/docs/guardian-request-flow.md +18 -14
  11. package/docs/trusted-contact-access.md +1 -7
  12. package/node_modules/@vellumai/avatar-catalog/src/catalog.ts +7 -1
  13. package/node_modules/@vellumai/avatar-catalog/src/index.ts +1 -1
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +4 -1
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  24. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +0 -18
  25. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +110 -5
  26. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +5 -33
  27. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +3 -0
  28. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +100 -9
  29. package/node_modules/@vellumai/gateway-client/src/index.ts +1 -2
  30. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +9 -0
  31. package/node_modules/@vellumai/service-contracts/package.json +4 -1
  32. package/node_modules/@vellumai/service-contracts/src/guardian-requests.ts +18 -0
  33. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  34. package/node_modules/@vellumai/service-contracts/src/platform-credential.ts +41 -0
  35. package/node_modules/@vellumai/service-contracts/src/reactions.ts +60 -0
  36. package/openapi.yaml +193 -4
  37. package/package.json +2 -2
  38. package/src/__tests__/access-request-card-view.test.ts +6 -5
  39. package/src/__tests__/access-request-seed-content-blocks.test.ts +5 -2
  40. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +43 -82
  41. package/src/__tests__/always-loaded-tools-guard.test.ts +8 -2
  42. package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
  43. package/src/__tests__/attachments-store.test.ts +27 -0
  44. package/src/__tests__/attachments.test.ts +43 -0
  45. package/src/__tests__/canned-reply-release.test.ts +48 -5
  46. package/src/__tests__/channel-delivery-store.test.ts +129 -18
  47. package/src/__tests__/channel-reply-delivery.test.ts +553 -93
  48. package/src/__tests__/channel-retry-sweep.test.ts +199 -0
  49. package/src/__tests__/client-os-metadata-persistence.test.ts +32 -3
  50. package/src/__tests__/conversation-error.test.ts +15 -0
  51. package/src/__tests__/conversation-load-history-repair.test.ts +118 -0
  52. package/src/__tests__/conversation-pairing.test.ts +141 -1
  53. package/src/__tests__/conversation-routes-disk-view.test.ts +28 -1
  54. package/src/__tests__/conversation-routes-slash-commands.test.ts +125 -0
  55. package/src/__tests__/conversation-runtime-assembly.test.ts +79 -0
  56. package/src/__tests__/conversation-store-ephemeral.test.ts +323 -10
  57. package/src/__tests__/conversation-sync-tags.test.ts +2 -37
  58. package/src/__tests__/credential-health-service.test.ts +98 -10
  59. package/src/__tests__/delete-propagation.test.ts +469 -0
  60. package/src/__tests__/dm-persistence.test.ts +16 -0
  61. package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
  62. package/src/__tests__/forbidden-legacy-symbols.test.ts +12 -0
  63. package/src/__tests__/guardian-card-withdrawal.test.ts +0 -22
  64. package/src/__tests__/guardian-gateway-sim.ts +0 -23
  65. package/src/__tests__/guardian-question-mode.test.ts +108 -0
  66. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +0 -2
  67. package/src/__tests__/guardian-routing-invariants.test.ts +26 -12
  68. package/src/__tests__/host-proxy-interface.test.ts +10 -0
  69. package/src/__tests__/inbound-slack-persistence.test.ts +16 -0
  70. package/src/__tests__/injector-v3-suppression.test.ts +43 -9
  71. package/src/__tests__/list-messages-page-latest.test.ts +127 -0
  72. package/src/__tests__/list-messages-system-card.test.ts +103 -0
  73. package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
  74. package/src/__tests__/notification-decision-fallback.test.ts +199 -77
  75. package/src/__tests__/notification-decision-strategy.test.ts +198 -213
  76. package/src/__tests__/notification-discord-adapter.test.ts +25 -0
  77. package/src/__tests__/notification-slack-adapter.test.ts +133 -0
  78. package/src/__tests__/notification-telegram-adapter.test.ts +36 -0
  79. package/src/__tests__/openai-provider.test.ts +5 -5
  80. package/src/__tests__/outbound-slack-persistence.test.ts +85 -72
  81. package/src/__tests__/persist-user-message-set-processing-failure.test.ts +83 -65
  82. package/src/__tests__/platform-client-verify-credential.test.ts +100 -0
  83. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -1
  84. package/src/__tests__/process-message-display-content.test.ts +20 -9
  85. package/src/__tests__/processing-acquire-fenced-guard.test.ts +56 -0
  86. package/src/__tests__/provider-meta-persistence.test.ts +67 -0
  87. package/src/__tests__/reaction-persistence.test.ts +22 -198
  88. package/src/__tests__/run-conversation-turn-persistence.test.ts +5 -2
  89. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +16 -0
  90. package/src/__tests__/skill-load-tool.test.ts +27 -0
  91. package/src/__tests__/skills.test.ts +34 -1
  92. package/src/__tests__/strip-memory-injections.test.ts +3 -4
  93. package/src/__tests__/terminal-tools.test.ts +9 -0
  94. package/src/__tests__/thread-backfill.test.ts +5 -3
  95. package/src/__tests__/unified-turn-context-location.test.ts +76 -0
  96. package/src/__tests__/voice-session-bridge.test.ts +18 -0
  97. package/src/__tests__/watch-retro-report-payload.test.ts +185 -0
  98. package/src/__tests__/watch-retro-tool-availability.test.ts +82 -0
  99. package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +235 -0
  100. package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +233 -0
  101. package/src/agent/attachments.ts +13 -2
  102. package/src/agent/loop.ts +3 -19
  103. package/src/api/README.md +9 -5
  104. package/src/api/index.ts +9 -8
  105. package/src/api/package.json +1 -0
  106. package/src/api/responses/conversation-message.ts +8 -0
  107. package/src/api/responses/home.ts +7 -18
  108. package/src/api/surfaces.ts +114 -7
  109. package/src/approvals/AGENTS.md +1 -1
  110. package/src/channels/__tests__/gateway-guardian-requests.test.ts +1 -23
  111. package/src/channels/__tests__/types.test.ts +22 -1
  112. package/src/channels/gateway-guardian-requests.ts +0 -22
  113. package/src/channels/types.ts +8 -6
  114. package/src/cli/__tests__/catalog-search-help.test.ts +12 -0
  115. package/src/cli/commands/channels/__tests__/channels.test.ts +26 -0
  116. package/src/cli/commands/channels/index.help.ts +15 -1
  117. package/src/cli/commands/channels/index.ts +6 -2
  118. package/src/cli/commands/db/__tests__/status.test.ts +22 -0
  119. package/src/cli/commands/db/index.help.ts +1 -1
  120. package/src/cli/commands/db/status.ts +172 -1
  121. package/src/cli/commands/platform/__tests__/connect.test.ts +29 -0
  122. package/src/cli/commands/platform/connect.ts +14 -5
  123. package/src/cli/commands/plugins.help.ts +6 -5
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -8
  125. package/src/cli/lib/__tests__/install-from-platform.test.ts +72 -0
  126. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +33 -0
  127. package/src/cli/lib/bundled-marketplace.json +14 -0
  128. package/src/cli/lib/install-from-github.ts +9 -5
  129. package/src/cli/lib/install-from-platform.ts +12 -1
  130. package/src/config/__tests__/assistant-initiated-threads-gate.test.ts +61 -0
  131. package/src/config/assistant-initiated-threads-gate.ts +50 -0
  132. package/src/config/bundled-skills/acp/SKILL.md +10 -3
  133. package/src/config/bundled-skills/schedule/SKILL.md +25 -11
  134. package/src/config/bundled-skills/schedule/references/SCRIPT_MODE_PATTERNS.md +3 -1
  135. package/src/config/call-site-defaults.ts +4 -1
  136. package/src/config/feature-flag-registry.json +21 -4
  137. package/src/config/schemas/memory-v3.ts +4 -3
  138. package/src/context/strip-injections.ts +17 -56
  139. package/src/conversations/__tests__/message-consolidation.test.ts +39 -0
  140. package/src/conversations/message-consolidation.ts +4 -3
  141. package/src/credential-health/credential-health-service.ts +130 -0
  142. package/src/daemon/__tests__/conversation-tool-setup.test.ts +31 -0
  143. package/src/daemon/conversation-agent-loop-handlers.ts +55 -59
  144. package/src/daemon/conversation-error.ts +2 -0
  145. package/src/daemon/conversation-messaging.ts +195 -47
  146. package/src/daemon/conversation-process.ts +20 -5
  147. package/src/daemon/conversation-runtime-assembly.ts +48 -39
  148. package/src/daemon/conversation-store.ts +159 -11
  149. package/src/daemon/conversation-tool-setup.ts +9 -2
  150. package/src/daemon/conversation.ts +290 -19
  151. package/src/daemon/dictation-text-processing.ts +2 -7
  152. package/src/daemon/handlers/config-channels.ts +33 -3
  153. package/src/daemon/handlers/shared.ts +9 -0
  154. package/src/daemon/port-oversized-content.test.ts +117 -0
  155. package/src/daemon/port-oversized-content.ts +110 -0
  156. package/src/daemon/process-message.ts +87 -16
  157. package/src/daemon/reaction-record.test.ts +23 -11
  158. package/src/daemon/reaction-record.ts +6 -9
  159. package/src/documents/document-store.ts +1 -5
  160. package/src/home/conversation-starter-validation.ts +1 -5
  161. package/src/live-voice/__tests__/live-voice-photo.test.ts +910 -50
  162. package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +31 -25
  163. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +70 -1
  164. package/src/live-voice/live-voice-photo.ts +544 -61
  165. package/src/live-voice/live-voice-session.ts +6 -2
  166. package/src/live-voice/protocol.ts +20 -1
  167. package/src/messaging/provider-message-metadata.ts +40 -1
  168. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
  169. package/src/messaging/providers/channel-transport.ts +28 -0
  170. package/src/messaging/providers/discord/send.ts +3 -2
  171. package/src/messaging/providers/slack/api.ts +11 -1
  172. package/src/messaging/providers/slack/message-metadata.test.ts +133 -0
  173. package/src/messaging/providers/slack/message-metadata.ts +141 -1
  174. package/src/messaging/providers/slack/send.test.ts +58 -0
  175. package/src/messaging/providers/slack/send.ts +4 -4
  176. package/src/messaging/providers/slack/transport.ts +28 -2
  177. package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
  178. package/src/messaging/providers/telegram-bot/send.ts +93 -0
  179. package/src/messaging/providers/telegram-bot/transport.ts +71 -0
  180. package/src/messaging/reaction-envelopes.test.ts +73 -0
  181. package/src/messaging/reaction-envelopes.ts +33 -5
  182. package/src/messaging/read-provider-metadata.ts +4 -3
  183. package/src/monitoring/recovery/__tests__/stranded-delivery-events.test.ts +208 -0
  184. package/src/monitoring/recovery/db.ts +35 -0
  185. package/src/monitoring/recovery/orphaned-channel-events.ts +3 -22
  186. package/src/monitoring/recovery/run-recovery.ts +6 -2
  187. package/src/monitoring/recovery/stale-processing.ts +3 -20
  188. package/src/monitoring/recovery/stranded-delivery-events.ts +68 -0
  189. package/src/notifications/AGENTS.md +2 -2
  190. package/src/notifications/README.md +2 -2
  191. package/src/notifications/__tests__/assistant-reply-producer.test.ts +11 -9
  192. package/src/notifications/__tests__/broadcaster.test.ts +35 -4
  193. package/src/notifications/__tests__/notification-utils.test.ts +31 -0
  194. package/src/notifications/access-request-copy.ts +126 -114
  195. package/src/notifications/adapters/discord.ts +9 -5
  196. package/src/notifications/adapters/shared.ts +17 -4
  197. package/src/notifications/adapters/slack.ts +14 -4
  198. package/src/notifications/adapters/telegram.ts +8 -4
  199. package/src/notifications/approval-card-data.ts +5 -2
  200. package/src/notifications/assistant-reply-producer.ts +7 -7
  201. package/src/notifications/broadcaster.ts +57 -25
  202. package/src/notifications/conversation-pairing.ts +68 -2
  203. package/src/notifications/copy-composer.ts +13 -32
  204. package/src/notifications/decision-engine.ts +56 -237
  205. package/src/notifications/guardian-delivery-recorder.ts +5 -9
  206. package/src/notifications/guardian-feed-projection.ts +0 -15
  207. package/src/notifications/guardian-question-mode.ts +147 -6
  208. package/src/notifications/notification-utils.ts +117 -1
  209. package/src/permissions/confirmation-guardian-request.ts +2 -2
  210. package/src/permissions/prompter.ts +1 -1
  211. package/src/persistence/conversation-crud.ts +93 -14
  212. package/src/persistence/conversation-queries.ts +144 -17
  213. package/src/persistence/conversation-types.ts +39 -5
  214. package/src/persistence/delivery-crud.ts +224 -28
  215. package/src/persistence/delivery-status.ts +21 -2
  216. package/src/persistence/migrations/374-channel-inbound-message-id-index.ts +26 -0
  217. package/src/persistence/migrations/375-create-channel-outbound-posts.ts +52 -0
  218. package/src/persistence/migrations/__tests__/375-create-channel-outbound-posts.test.ts +84 -0
  219. package/src/persistence/schema/conversations.ts +71 -2
  220. package/src/persistence/schema-contract.test.ts +78 -0
  221. package/src/persistence/schema-contract.ts +96 -0
  222. package/src/persistence/steps.ts +4 -0
  223. package/src/platform/client.ts +55 -0
  224. package/src/plugins/__tests__/mcp-servers.test.ts +46 -0
  225. package/src/plugins/defaults/injector-order.ts +2 -1
  226. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +2 -2
  227. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +13 -15
  228. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +10 -0
  229. package/src/plugins/defaults/memory/injectors.ts +1 -1
  230. package/src/plugins/defaults/memory/memory-marker.ts +17 -7
  231. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +1 -5
  232. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +54 -41
  233. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +3 -3
  234. package/src/plugins/defaults/memory/v3/injector.ts +14 -15
  235. package/src/plugins/defaults/memory/v3/types.ts +15 -4
  236. package/src/plugins/defaults/turn-context/injectors.ts +3 -0
  237. package/src/plugins/defaults/turn-context/unified-turn-context.ts +24 -0
  238. package/src/plugins/mcp-servers.ts +14 -2
  239. package/src/providers/__tests__/dispatch-connection-routing.test.ts +50 -0
  240. package/src/providers/__tests__/registry-native-web-search.test.ts +49 -2
  241. package/src/providers/anthropic/client.ts +21 -24
  242. package/src/providers/call-site-routing.ts +5 -4
  243. package/src/providers/connection-resolution.ts +29 -1
  244. package/src/providers/content-block-size.test.ts +82 -0
  245. package/src/providers/content-block-size.ts +99 -0
  246. package/src/providers/file-block-text.test.ts +64 -0
  247. package/src/providers/file-block-text.ts +28 -0
  248. package/src/providers/gemini/client.ts +12 -7
  249. package/src/providers/inference/auth.ts +6 -6
  250. package/src/providers/media-resolve.ts +14 -0
  251. package/src/providers/model-catalog.ts +31 -34
  252. package/src/providers/openai/chat-completions-provider.ts +9 -25
  253. package/src/providers/openai/responses-provider.ts +11 -18
  254. package/src/providers/registry.ts +10 -7
  255. package/src/providers/routing-identity.ts +2 -1
  256. package/src/providers/types.ts +1 -4
  257. package/src/providers/vellum-model-routing.ts +3 -2
  258. package/src/runtime/AGENTS.md +40 -15
  259. package/src/runtime/approval-message-composer.ts +1 -6
  260. package/src/runtime/assistant-event-hub.ts +2 -39
  261. package/src/runtime/channel-approval-types.ts +1 -5
  262. package/src/runtime/channel-reply-delivery.ts +182 -115
  263. package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +294 -156
  264. package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +107 -86
  265. package/src/runtime/channel-retry-sweep.ts +102 -1
  266. package/src/runtime/finalize-event-delivery.ts +4 -4
  267. package/src/runtime/guardian-action-message-composer.ts +1 -4
  268. package/src/runtime/guardian-reply-router.ts +4 -75
  269. package/src/runtime/http-router.ts +1 -5
  270. package/src/runtime/question-request-guardian-bridge.ts +2 -3
  271. package/src/runtime/routes/__tests__/conversation-list-assistant-section.test.ts +359 -0
  272. package/src/runtime/routes/__tests__/dictation-command-mode.test.ts +120 -0
  273. package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +487 -0
  274. package/src/runtime/routes/acp-routes.ts +1 -1
  275. package/src/runtime/routes/canned-reply-release.ts +19 -9
  276. package/src/runtime/routes/channel-route-shared.ts +0 -39
  277. package/src/runtime/routes/channel-verification-routes.ts +4 -1
  278. package/src/runtime/routes/conversation-list-routes.ts +44 -3
  279. package/src/runtime/routes/conversation-management-routes.ts +2 -1
  280. package/src/runtime/routes/conversation-routes.ts +138 -59
  281. package/src/runtime/routes/diagnostics-routes.ts +111 -60
  282. package/src/runtime/routes/guardian-action-routes.ts +10 -14
  283. package/src/runtime/routes/guardian-approval-interception.ts +0 -5
  284. package/src/runtime/routes/inbound-message-handler.ts +170 -48
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +4 -3
  286. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +169 -8
  287. package/src/runtime/routes/inbound-stages/background-dispatch.ts +82 -14
  288. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +23 -48
  289. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +454 -94
  290. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +309 -102
  291. package/src/runtime/routes/index.ts +2 -0
  292. package/src/runtime/routes/platform-routes.ts +99 -10
  293. package/src/runtime/routes/sight-frame-routes.ts +136 -0
  294. package/src/runtime/sync/resource-sync-events.ts +14 -37
  295. package/src/runtime/sync/sync-publisher.test.ts +5 -4
  296. package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
  297. package/src/tools/client-os.ts +11 -1
  298. package/src/tools/host-filesystem/edit.ts +2 -2
  299. package/src/tools/host-filesystem/read.ts +2 -2
  300. package/src/tools/host-filesystem/transfer.ts +2 -2
  301. package/src/tools/host-filesystem/write.ts +2 -2
  302. package/src/tools/host-terminal/host-shell.ts +2 -2
  303. package/src/tools/skills/load.ts +1 -1
  304. package/src/tools/terminal/safe-env.ts +4 -0
  305. package/src/tools/tool-input-schemas.ts +2 -0
  306. package/src/tools/tool-manifest.ts +2 -0
  307. package/src/tools/ui-surface/definitions.ts +4 -3
  308. package/src/tools/watch/watch-retro-report.ts +205 -0
  309. package/src/watch/__tests__/watch-retro.test.ts +268 -40
  310. package/src/watch/watch-retro.ts +190 -77
  311. package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +195 -0
  312. package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +198 -0
  313. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +0 -10
  314. package/src/workspace/migrations/registry.ts +4 -0
  315. package/docker-kata-pip-chroot.sh +0 -22
  316. package/src/__tests__/slack-reaction-approvals.test.ts +0 -97
  317. package/src/__tests__/slack-reaction-guardian-approval.test.ts +0 -307
  318. package/src/api/events/conversation-list-invalidated.ts +0 -38
@@ -5,8 +5,9 @@
5
5
  * - platform_status (GET platform/status): aggregates platform context,
6
6
  * credentials, assistant ID, and webhook secret. (Velay tunnel status
7
7
  * lives on the gateway — see gateway_status.)
8
- * - platform_connect (POST platform/connect): checks existing credentials
9
- * and emits the show_platform_login signal to connected clients.
8
+ * - platform_connect (POST platform/connect): reports whether the stored
9
+ * credentials still connect, and when the stored key has been rejected,
10
+ * says so, so the caller can tell the user what replaces it.
10
11
  * - platform_disconnect (POST platform/disconnect): deletes stored platform
11
12
  * credentials and emits platform_disconnected signal.
12
13
  * - platform_callback_routes_register (POST platform/callback-routes/register):
@@ -24,6 +25,11 @@
24
25
  * invoice list and returns a single invoice by Stripe invoice ID.
25
26
  */
26
27
 
28
+ import {
29
+ type PlatformCredentialVerificationStatus,
30
+ type PlatformVerifyCredentialResponse,
31
+ PlatformVerifyCredentialResponseSchema,
32
+ } from "@vellumai/service-contracts/platform-credential";
27
33
  import { z } from "zod";
28
34
 
29
35
  import { isPlatformRemote } from "../../config/env-registry.js";
@@ -80,8 +86,16 @@ const PlatformConnectResponseSchema = z.object({
80
86
  alreadyConnected: z.boolean().optional(),
81
87
  baseUrl: z.string().optional(),
82
88
  showPlatformLogin: z.boolean().optional(),
89
+ /**
90
+ * The stored managed credential exists but the platform rejects it. Distinct
91
+ * from "nothing stored": the user is not setting up, they are recovering,
92
+ * and what replaces the key is a fresh sign-in, not a first one.
93
+ */
94
+ credentialRejected: z.boolean().optional(),
83
95
  });
84
- type PlatformConnectResponse = z.infer<typeof PlatformConnectResponseSchema>;
96
+ export type PlatformConnectResponse = z.infer<
97
+ typeof PlatformConnectResponseSchema
98
+ >;
85
99
 
86
100
  const PlatformDisconnectResponseSchema = z.object({
87
101
  disconnected: z.literal(true),
@@ -249,17 +263,72 @@ async function handlePlatformConnect(
249
263
  ),
250
264
  ]);
251
265
 
266
+ // Stored credentials only count as a connection while they still
267
+ // authenticate. Answering "already connected" for a key the platform has
268
+ // rejected would report nothing to do about the one thing that needs doing,
269
+ // and no client would get the signal to rotate it.
270
+ //
271
+ // Checked here rather than read from a cache: connect is a deliberate user
272
+ // action, so one request is affordable, and an answer computed now cannot go
273
+ // stale between the check and the answer. An unreachable platform leaves the
274
+ // stored credentials counting as a connection, so a network problem never
275
+ // strands a working install by reporting it disconnected.
276
+ let credentialRejected = false;
252
277
  if (existingUrl && existingApiKey) {
253
- return {
254
- alreadyConnected: true,
255
- baseUrl: existingUrl,
256
- };
278
+ const verified = await verifyStoredCredential();
279
+ if (verified !== "rejected") {
280
+ return {
281
+ alreadyConnected: true,
282
+ baseUrl: existingUrl,
283
+ };
284
+ }
285
+ credentialRejected = true;
257
286
  }
258
287
 
259
- // Emit signal for connected clients to show the platform login UI
288
+ // The outcome travels in the response, where the CLI reads it and tells the
289
+ // user what to do. Nothing shipped consumes the signal below, so the
290
+ // response is the only path the answer reaches anyone by.
260
291
  broadcastMessage({ type: "show_platform_login" });
261
292
 
262
- return { showPlatformLogin: true };
293
+ return credentialRejected
294
+ ? { showPlatformLogin: true, credentialRejected: true }
295
+ : { showPlatformLogin: true };
296
+ }
297
+
298
+ /**
299
+ * Ask the platform whether the stored managed credential authenticates.
300
+ * Nothing is stored: each caller that needs the answer asks, so there is no
301
+ * verdict to go stale.
302
+ *
303
+ * Shared by the verification route and by connect, so the two cannot disagree
304
+ * about what "connected" means. Never throws: an unsettled answer is
305
+ * `"unknown"`, which no caller treats as evidence of a dead credential.
306
+ */
307
+ async function verifyStoredCredential(): Promise<PlatformCredentialVerificationStatus> {
308
+ try {
309
+ const { checkAssistantApiKey } =
310
+ await import("../../credential-health/credential-health-service.js");
311
+ const result = await checkAssistantApiKey();
312
+ if (result === null) {
313
+ // No platform identity at all, so there is nothing to verify.
314
+ return "unknown";
315
+ }
316
+ if (result.status === "healthy") {
317
+ return "valid";
318
+ }
319
+ if (result.status === "revoked" || result.status === "missing_token") {
320
+ return "rejected";
321
+ }
322
+ return "unknown";
323
+ } catch {
324
+ return "unknown";
325
+ }
326
+ }
327
+
328
+ async function handlePlatformVerifyCredential(
329
+ _args: RouteHandlerArgs,
330
+ ): Promise<PlatformVerifyCredentialResponse> {
331
+ return { status: await verifyStoredCredential() };
263
332
  }
264
333
 
265
334
  async function handlePlatformDisconnect(
@@ -711,11 +780,31 @@ export const ROUTES: RouteDefinition[] = [
711
780
  },
712
781
  summary: "Connect to the Vellum Platform",
713
782
  description:
714
- "Checks existing credentials and emits the show_platform_login signal for connected clients to show a login UI.",
783
+ "Checks whether the stored platform credentials still connect. Reports a stored key the platform rejects as credentialRejected, so the caller can direct the user to sign in again and replace it.",
715
784
  tags: ["platform"],
716
785
  handler: handlePlatformConnect,
717
786
  responseBody: PlatformConnectResponseSchema,
718
787
  },
788
+ {
789
+ operationId: "platform_verify_credential",
790
+ endpoint: "platform/verify-credential",
791
+ method: "POST",
792
+ // The in-app repair calls this through the gateway with an actor token,
793
+ // the same caller shape as platform_status, so it takes that route's
794
+ // principal bundle. A local-only policy would 403 every browser repair
795
+ // after it had already rotated and stored the replacement. Read scope:
796
+ // the route performs a check and mutates nothing.
797
+ policy: {
798
+ requiredScopes: ["settings.read"],
799
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
800
+ },
801
+ summary: "Verify the Vellum-managed credential against the platform",
802
+ description:
803
+ "Asks the platform whether the stored assistant API key authenticates. POST because it performs a check; the result is returned, not stored.",
804
+ tags: ["platform"],
805
+ handler: handlePlatformVerifyCredential,
806
+ responseBody: PlatformVerifyCredentialResponseSchema,
807
+ },
719
808
  {
720
809
  operationId: "platform_disconnect",
721
810
  endpoint: "platform/disconnect",
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Transport-agnostic route definition for ambient camera keeps.
3
+ *
4
+ * POST /v1/conversations/:id/sight-frame persists one already-uploaded camera
5
+ * frame into a conversation as its own sight-tagged user message. It runs no
6
+ * turn and starts no agent loop: it is the same standalone-image persist the
7
+ * live-voice `sight_frame` socket frame performs, reached over HTTP by the
8
+ * clients that have a camera tile but no voice socket. The rationale for
9
+ * persisting a kept frame on its own rather than attaching it to a turn is at
10
+ * the top of `live-voice/live-voice-photo.ts`.
11
+ *
12
+ * The response is the client's per-frame acknowledgement, which is why the
13
+ * handler waits on the persist rather than accepting and reporting later.
14
+ */
15
+
16
+ import { z } from "zod";
17
+
18
+ import { persistAmbientSightFrame } from "../../live-voice/live-voice-photo.js";
19
+ import { getConversation } from "../../persistence/conversation-crud.js";
20
+ import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
21
+ import { resolveOrThrow } from "./conversation-management-routes.js";
22
+ import { BadRequestError, NotFoundError } from "./errors.js";
23
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
24
+ import { resolveVellumActorTrustContext } from "./vellum-actor-trust.js";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Handlers
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /**
31
+ * Persist a kept camera frame into an existing conversation.
32
+ *
33
+ * The DB existence check comes before the persist because the persist calls
34
+ * `getOrCreateConversation`, which mints a conversation for an id it has never
35
+ * seen. Without the gate a stale client id would silently create a phantom
36
+ * conversation holding nothing but camera frames.
37
+ *
38
+ * That read is also where the frame is accepted, so the row's `created_at` is
39
+ * taken from it and handed to the persist. Resolving the request's actor is
40
+ * awaited below, and a conversation deleted and recreated under this id inside
41
+ * that await leaves a row the gate never saw: the persist reading the id for
42
+ * itself afterwards would take the replacement for the conversation the client
43
+ * addressed. Nothing awaits between the check and the capture, so the two are
44
+ * one moment.
45
+ *
46
+ * The body is hand-validated because the HTTP adapter does not run a route's
47
+ * zod schema against the request and swallows a JSON parse failure, so a
48
+ * malformed send arrives here as an empty bag.
49
+ */
50
+ async function handleConversationSightFrame({
51
+ pathParams = {},
52
+ body = {},
53
+ headers,
54
+ }: RouteHandlerArgs) {
55
+ const rawId = pathParams.id!;
56
+ const conversationId = resolveOrThrow(rawId);
57
+ const accepted = getConversation(conversationId);
58
+ if (!accepted) {
59
+ throw new NotFoundError(`Conversation ${rawId} not found`);
60
+ }
61
+
62
+ const attachmentId = body.attachmentId;
63
+ if (typeof attachmentId !== "string" || attachmentId.length === 0) {
64
+ throw new BadRequestError("Missing attachmentId");
65
+ }
66
+
67
+ // The row is attributed to the actor this request was verified as, resolved
68
+ // the way every other vellum-channel route resolves it. Left to the
69
+ // conversation's resting trust, a frame would be stamped for whoever used
70
+ // the conversation last.
71
+ const trustContext = await resolveVellumActorTrustContext(
72
+ headers?.["x-vellum-actor-principal-id"],
73
+ { healResetDrift: true },
74
+ );
75
+
76
+ // Every non-fatal outcome the persist folds into `ok` is reported as a
77
+ // refusal rather than an error status: the frame is gone either way, the
78
+ // client's only move is to keep sampling, and the upload behind each one is
79
+ // the persist's to settle. It gives the bytes up as it refuses, or, where
80
+ // the store would not say whether a row landed, holds them until it answers
81
+ // and gives them up then. Either way the client is done with the frame,
82
+ // which is what the two-state response means.
83
+ const result = await persistAmbientSightFrame(
84
+ conversationId,
85
+ attachmentId,
86
+ "chat",
87
+ trustContext,
88
+ accepted.createdAt,
89
+ );
90
+ return {
91
+ persisted: result.ok,
92
+ ...(result.messageId === undefined ? {} : { messageId: result.messageId }),
93
+ };
94
+ }
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Route definitions
98
+ // ---------------------------------------------------------------------------
99
+
100
+ export const ROUTES: RouteDefinition[] = [
101
+ {
102
+ operationId: "conversationSightFrame",
103
+ endpoint: "conversations/:id/sight-frame",
104
+ method: "POST",
105
+ policy: {
106
+ requiredScopes: ["chat.write"],
107
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
108
+ },
109
+ summary: "Persist an ambient camera frame",
110
+ description:
111
+ "Add an already-uploaded camera frame to a conversation as its own " +
112
+ "message, tagged so retention can age the image out of the model's " +
113
+ "context while the transcript keeps it. No reply is generated. " +
114
+ "Returns persisted=false when the frame was dropped, in which case the " +
115
+ "assistant owns the upload and there is nothing to clean up.",
116
+ tags: ["conversations"],
117
+ pathParams: [{ name: "id", type: "uuid" }],
118
+ requestBody: z.object({
119
+ attachmentId: z
120
+ .string()
121
+ .describe("Id of the uploaded attachment holding the frame."),
122
+ }),
123
+ responseBody: z.object({
124
+ persisted: z.boolean(),
125
+ messageId: z
126
+ .string()
127
+ .optional()
128
+ .describe("The message the frame landed on, when one was written."),
129
+ }),
130
+ additionalResponses: {
131
+ "400": { description: "Missing or empty attachmentId" },
132
+ "404": { description: "Conversation not found" },
133
+ },
134
+ handler: handleConversationSightFrame,
135
+ },
136
+ ];
@@ -1,4 +1,3 @@
1
- import type { ConversationListInvalidatedReason } from "../../api/events/conversation-list-invalidated.js";
2
1
  import type { IdentityFields } from "../../daemon/handlers/identity.js";
3
2
  import {
4
3
  conversationMessagesSyncTag,
@@ -160,42 +159,23 @@ export function publishPluginsChanged(originClientId?: string): void {
160
159
  * delivered exclusively via the per-conversation `sync_changed` tag, which
161
160
  * web consumes by GET-and-patching the single row.
162
161
  */
163
- const SHAPE_CHANGING_REASONS: ReadonlySet<ConversationListInvalidatedReason> =
164
- new Set(["created", "deleted", "reordered"]);
162
+ type ConversationListReason =
163
+ | "created"
164
+ | "renamed"
165
+ | "deleted"
166
+ | "reordered"
167
+ | "seen_changed";
165
168
 
166
- /**
167
- * Publish the legacy `conversation_list_invalidated` broadcast to macOS
168
- * subscribers only.
169
- *
170
- * Web consumes `sync_changed` (`conversationsList` for shape changes,
171
- * `conversation:<id>:metadata` for content changes) directly and patches
172
- * the cached list in place — see `useConversationSync` for the consumer
173
- * side. macOS (`ConversationRestorer.swift`) still listens for the typed
174
- * broadcast.
175
- *
176
- * TODO(electron-cutover): remove this helper and all callers once macOS
177
- * migrates to the Electron client and consumes `sync_changed` directly.
178
- * At that point the `conversation_list_invalidated` message type can be
179
- * retired entirely.
180
- */
181
- function broadcastConversationListInvalidatedToMacos(
182
- reason: ConversationListInvalidatedReason,
183
- ): void {
184
- broadcastMessage(
185
- {
186
- type: "conversation_list_invalidated",
187
- reason,
188
- },
189
- undefined,
190
- { targetInterfaceId: "macos" },
191
- );
192
- }
169
+ const SHAPE_CHANGING_REASONS: ReadonlySet<ConversationListReason> = new Set([
170
+ "created",
171
+ "deleted",
172
+ "reordered",
173
+ ]);
193
174
 
194
175
  export function publishConversationListChanged(
195
- reason: ConversationListInvalidatedReason,
176
+ reason: ConversationListReason,
196
177
  originClientId?: string,
197
178
  ): void {
198
- broadcastConversationListInvalidatedToMacos(reason);
199
179
  void publishSyncInvalidation([SYNC_TAGS.conversationsList], originClientId);
200
180
  }
201
181
 
@@ -220,14 +200,13 @@ export function publishConversationMessagesChanged(
220
200
  }
221
201
 
222
202
  export function publishConversationListAndMetadataChanged(
223
- reason: ConversationListInvalidatedReason,
203
+ reason: ConversationListReason,
224
204
  conversationIds: string | string[],
225
205
  originClientId?: string,
226
206
  ): void {
227
207
  const ids = Array.isArray(conversationIds)
228
208
  ? conversationIds
229
209
  : [conversationIds];
230
- broadcastConversationListInvalidatedToMacos(reason);
231
210
 
232
211
  // Shape-changing reasons (`created`, `deleted`, `reordered`) add or
233
212
  // remove rows or change the order of the paginated window — web must
@@ -260,9 +239,7 @@ export function publishConversationTitleChanged(
260
239
  conversationId,
261
240
  );
262
241
  // Renames are content-only — the paired typed `conversation_title_updated`
263
- // event already carries the new title and patches the row in place on
264
- // web; macOS receives the per-interface `conversation_list_invalidated`
265
- // emitted from `broadcastMessage` (see `assistant-event-hub.ts`). The
242
+ // event already carries the new title and patches the row in place; the
266
243
  // `sync_changed` metadata tag is included as a belt-and-suspenders signal
267
244
  // for any sibling-tab consumer that missed the typed event.
268
245
  void publishSyncInvalidation(
@@ -78,7 +78,7 @@ describe("sync publisher", () => {
78
78
  const subscription = assistantEventHub.subscribe({
79
79
  type: "process",
80
80
  callback: async (event) => {
81
- if (event.message.type === "conversation_list_invalidated") {
81
+ if (event.message.type === "conversation_title_updated") {
82
82
  await new Promise((resolve) => setTimeout(resolve, 25));
83
83
  }
84
84
  receivedTypes.push(event.message.type);
@@ -87,15 +87,16 @@ describe("sync publisher", () => {
87
87
 
88
88
  try {
89
89
  broadcastMessage({
90
- type: "conversation_list_invalidated",
91
- reason: "created",
90
+ type: "conversation_title_updated",
91
+ conversationId: "conv-1",
92
+ title: "Test",
92
93
  });
93
94
  await publishSyncInvalidation([SYNC_TAGS.conversationsList]);
94
95
 
95
96
  await waitFor(() => receivedTypes.length === 2);
96
97
 
97
98
  expect(receivedTypes).toEqual([
98
- "conversation_list_invalidated",
99
+ "conversation_title_updated",
99
100
  "sync_changed",
100
101
  ]);
101
102
  } finally {
@@ -14,6 +14,7 @@ import { notifyParentTool } from "../subagent/notify-parent.js";
14
14
  import { requestSystemPermissionTool } from "../system/request-permission.js";
15
15
  import { shellTool } from "../terminal/shell.js";
16
16
  import { parseToolInput, TOOL_INPUT_SCHEMAS } from "../tool-input-schemas.js";
17
+ import { watchRetroReportTool } from "../watch/watch-retro-report.js";
17
18
 
18
19
  describe("parseToolInput", () => {
19
20
  test("passes input through unchanged for a tool with no registered schema", () => {
@@ -142,6 +143,7 @@ describe("derived input_schema", () => {
142
143
  requestSystemPermissionTool,
143
144
  notifyParentTool,
144
145
  reactToMessageTool,
146
+ watchRetroReportTool,
145
147
  ];
146
148
 
147
149
  test("every registered schema belongs to a tool whose input_schema is derived from it", () => {
@@ -13,6 +13,15 @@ interface HostClientContext {
13
13
  sourceActorPrincipalId?: string;
14
14
  }
15
15
 
16
+ export function desktopClientName(
17
+ context: HostClientContext,
18
+ ): "Windows" | "macOS" {
19
+ return context.clientOs === "windows" ||
20
+ context.transportInterface === "windows"
21
+ ? "Windows"
22
+ : "macOS";
23
+ }
24
+
16
25
  export function supportsClientOs(
17
26
  supportedClientOs: readonly ClientOs[] | undefined,
18
27
  clientOs: ClientOs | undefined,
@@ -43,7 +52,8 @@ export function getEligibleHostClientOs(
43
52
  const eligible = new Set<ClientOs>();
44
53
  const sourceClientOs =
45
54
  context.transportInterface === "macos" ||
46
- context.transportInterface === "windows"
55
+ context.transportInterface === "windows" ||
56
+ context.transportInterface === "linux"
47
57
  ? context.transportInterface
48
58
  : context.clientOs;
49
59
  if (
@@ -4,6 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
4
4
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
5
5
  import { RiskLevel } from "../../permissions/types.js";
6
6
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
7
+ import { desktopClientName } from "../client-os.js";
7
8
  import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
8
9
  import { formatEditDiff } from "../shared/filesystem/format-diff.js";
9
10
  import { hostPolicy } from "../shared/filesystem/path-policy.js";
@@ -114,8 +115,7 @@ export const hostFileEditTool = {
114
115
  !HostFileProxy.instance.isAvailable()
115
116
  ) {
116
117
  return {
117
- content:
118
- "Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
118
+ content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
119
119
  isError: true,
120
120
  };
121
121
  }
@@ -6,6 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
6
6
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
7
7
  import { RiskLevel } from "../../permissions/types.js";
8
8
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
9
+ import { desktopClientName } from "../client-os.js";
9
10
  import {
10
11
  AUDIO_EXTENSIONS,
11
12
  readAudioFile,
@@ -120,8 +121,7 @@ export const hostFileReadTool = {
120
121
  !HostFileProxy.instance.isAvailable()
121
122
  ) {
122
123
  return {
123
- content:
124
- "Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
124
+ content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
125
125
  isError: true,
126
126
  };
127
127
  }
@@ -6,6 +6,7 @@ import { supportsHostProxy } from "../../channels/types.js";
6
6
  import { HostTransferProxy } from "../../daemon/host-transfer-proxy.js";
7
7
  import { RiskLevel } from "../../permissions/types.js";
8
8
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
9
+ import { desktopClientName } from "../client-os.js";
9
10
  import { sandboxPolicy } from "../shared/filesystem/path-policy.js";
10
11
  import type {
11
12
  ToolContext,
@@ -124,8 +125,7 @@ export const hostFileTransferTool = {
124
125
  !HostTransferProxy.instance.isAvailable()
125
126
  ) {
126
127
  return {
127
- content:
128
- "Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
128
+ content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
129
129
  isError: true,
130
130
  };
131
131
  }
@@ -4,6 +4,7 @@ import { supportsHostProxy } from "../../channels/types.js";
4
4
  import { HostFileProxy } from "../../daemon/host-file-proxy.js";
5
5
  import { RiskLevel } from "../../permissions/types.js";
6
6
  import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
7
+ import { desktopClientName } from "../client-os.js";
7
8
  import { FileSystemOps } from "../shared/filesystem/file-ops-service.js";
8
9
  import { formatWriteSummary } from "../shared/filesystem/format-diff.js";
9
10
  import { hostPolicy } from "../shared/filesystem/path-policy.js";
@@ -89,8 +90,7 @@ export const hostFileWriteTool = {
89
90
  !HostFileProxy.instance.isAvailable()
90
91
  ) {
91
92
  return {
92
- content:
93
- "Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
93
+ content: `Error: no client with host_file capability is connected. Connect a ${desktopClientName(context)} client to use host_file from a non-desktop interface.`,
94
94
  isError: true,
95
95
  };
96
96
  }
@@ -39,6 +39,7 @@ import {
39
39
  registerBackgroundTool,
40
40
  removeBackgroundTool,
41
41
  } from "../background-tool-registry.js";
42
+ import { desktopClientName } from "../client-os.js";
42
43
  import {
43
44
  attachBoundedStdio,
44
45
  MAX_OUTPUT_LENGTH,
@@ -225,8 +226,7 @@ export const hostShellTool = {
225
226
  !HostBashProxy.instance.isAvailable()
226
227
  ) {
227
228
  return {
228
- content:
229
- "Error: no client with host_bash capability is connected. Connect a macOS client to use host_bash from a non-desktop interface.",
229
+ content: `Error: no client with host_bash capability is connected. Connect a ${desktopClientName(context)} client to use host_bash from a non-desktop interface.`,
230
230
  isError: true,
231
231
  };
232
232
  }
@@ -158,7 +158,7 @@ export const skillLoadTool = {
158
158
  name: "skill_load",
159
159
 
160
160
  description:
161
- 'Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills. Bundled/first-party skills (like `app-builder`) are already installed — loading only activates their instructions for the current conversation, since skills unload between turns; treat "load" as activation, not installation, and don\'t tell users a bundled skill needs installing. Loading can still fail (e.g. a feature-gated skill returns "currently unavailable", or the load errors) — if it does, relay that specific error rather than claiming the skill is not installed or offering to install it. For app, website, dashboard, game, calculator, tracker, visualization, or interactive tool requests, load `app-builder` with `skill: "app-builder"`.',
161
+ 'Load full instructions for a skill. Works for both bundled skills (listed in the catalog) and custom workspace skills. Bundled/first-party skills (like `app-builder`) are already installed: loading only activates their instructions for the current conversation, since skills unload between turns. Treat "load" as activation, not installation, and don\'t tell users a bundled skill needs installing. Loading can still fail (e.g. a feature-gated skill returns "currently unavailable", or the load errors). If it does, relay that specific error rather than claiming the skill is not installed or offering to install it. For app, website, dashboard, game, calculator, tracker, visualization, or interactive tool requests, load `app-builder` with `skill: "app-builder"`. For recurring monitoring of a page, dashboard, or status (check it later and notify), or for reminders and timed checks, load `schedule` with `skill: "schedule"`. When the user names Claude Code or Codex (including as a tool they will use elsewhere), load `acp` with `skill: "acp"`.',
162
162
 
163
163
  category: "skills",
164
164
 
@@ -116,6 +116,10 @@ function isKataFamilyRuntime(runtime: string | undefined): boolean {
116
116
 
117
117
  function kataAptPaths(dataRoot: string): string[] {
118
118
  return [
119
+ // Shims for chroot wrapper scripts with hardcoded absolute paths must
120
+ // shadow the broken originals in the chroot bin dirs below (see
121
+ // docker-kata-apt-shims.sh).
122
+ `${dataRoot}/.host-shims`,
119
123
  `${dataRoot}/bin`,
120
124
  `${dataRoot}/usr/local/sbin`,
121
125
  `${dataRoot}/usr/local/bin`,
@@ -14,6 +14,7 @@ import { formatToolInputError } from "./shared/zod-tool-schema.js";
14
14
  import { notifyParentInputSchema } from "./subagent/notify-parent.js";
15
15
  import { requestSystemPermissionInputSchema } from "./system/request-permission.js";
16
16
  import { shellInputSchema } from "./terminal/shell.js";
17
+ import { watchRetroReportInputSchema } from "./watch/watch-retro-report.js";
17
18
 
18
19
  /**
19
20
  * Per-tool Zod input schemas, keyed by tool name. Tool calls are a
@@ -57,6 +58,7 @@ export const TOOL_INPUT_SCHEMAS: Readonly<Record<string, z.ZodType>> = {
57
58
  host_file_read: hostFileReadInputSchema,
58
59
  host_file_write: hostFileWriteInputSchema,
59
60
  notify_parent: notifyParentInputSchema,
61
+ watch_retro_report: watchRetroReportInputSchema,
60
62
  react_to_message: reactToMessageInputSchema,
61
63
  request_system_permission: requestSystemPermissionInputSchema,
62
64
  };
@@ -36,6 +36,7 @@ import {
36
36
  uiShowTool,
37
37
  uiUpdateTool,
38
38
  } from "./ui-surface/definitions.js";
39
+ import { watchRetroReportTool } from "./watch/watch-retro-report.js";
39
40
 
40
41
  // ── Explicit tool instances ─────────────────────────────────────────
41
42
  // Core tools registered by initializeTools(). Tool modules only export
@@ -75,4 +76,5 @@ export const explicitTools: ToolDefinition[] = [
75
76
  uiShowTool,
76
77
  uiUpdateTool,
77
78
  uiDismissTool,
79
+ watchRetroReportTool,
78
80
  ];
@@ -2,7 +2,7 @@
2
2
  * UI surface tool definitions.
3
3
  *
4
4
  * These tools allow the model to show, update, and dismiss just-in-time UI
5
- * surfaces (cards, tables, forms, confirmations) on a connected macOS client.
5
+ * surfaces (cards, tables, forms, confirmations) on a connected desktop client.
6
6
  * They are proxy tools -- execution is forwarded to the client and never
7
7
  * handled locally by the daemon.
8
8
  */
@@ -13,6 +13,7 @@ import {
13
13
  } from "../../api/surfaces.js";
14
14
  import { RiskLevel } from "../../permissions/types.js";
15
15
  import { isWeakOpenModel } from "../../providers/weak-open-model.js";
16
+ import { desktopClientName } from "../client-os.js";
16
17
  import type {
17
18
  ToolContext,
18
19
  ToolDefinition,
@@ -37,7 +38,7 @@ const APP_BUILDER_BUILD_RE =
37
38
  /\b(build|building|built|create|creating|created|make|making|made|generate|generating|generated)\b/i;
38
39
 
39
40
  /**
40
- * Forward execution to the connected macOS client via the request-bound
41
+ * Forward execution to the connected desktop client via the request-bound
41
42
  * `proxyToolResolver`. Returns a structured error when no resolver is
42
43
  * configured (e.g. no client connected) so callers see a normal tool
43
44
  * failure rather than an unhandled throw.
@@ -88,7 +89,7 @@ function proxyExecute(toolName: string) {
88
89
 
89
90
  if (!context.proxyToolResolver) {
90
91
  return {
91
- content: `No proxy resolver configured for proxy tool "${toolName}". This tool requires an external resolver (e.g. a connected macOS client).`,
92
+ content: `No proxy resolver configured for proxy tool "${toolName}". This tool requires an external resolver (e.g. a connected ${desktopClientName(context)} client).`,
92
93
  isError: true,
93
94
  };
94
95
  }