@vellumai/assistant 0.8.2 → 0.8.4

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 (503) hide show
  1. package/ARCHITECTURE.md +11 -12
  2. package/docker-entrypoint.sh +13 -2
  3. package/docker-init-apt-root.sh +79 -6
  4. package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
  5. package/openapi.yaml +945 -36
  6. package/package.json +1 -1
  7. package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
  8. package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
  9. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  10. package/src/__tests__/agent-loop.test.ts +88 -3
  11. package/src/__tests__/anthropic-provider.test.ts +272 -0
  12. package/src/__tests__/approval-cascade.test.ts +1 -1
  13. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
  14. package/src/__tests__/channel-delivery-store.test.ts +193 -0
  15. package/src/__tests__/channel-reply-delivery.test.ts +284 -5
  16. package/src/__tests__/channel-retry-sweep.test.ts +274 -1
  17. package/src/__tests__/compaction-events.test.ts +1 -1
  18. package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
  19. package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
  20. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  22. package/src/__tests__/config-watcher.test.ts +1 -1
  23. package/src/__tests__/context-token-estimator.test.ts +112 -57
  24. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
  27. package/src/__tests__/conversation-agent-loop.test.ts +77 -3
  28. package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
  29. package/src/__tests__/conversation-clean-command.test.ts +137 -0
  30. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +161 -0
  32. package/src/__tests__/conversation-lifecycle.test.ts +1 -1
  33. package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
  34. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  35. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  36. package/src/__tests__/conversation-pairing.test.ts +2 -2
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -1
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
  39. package/src/__tests__/conversation-queue.test.ts +1 -1
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
  41. package/src/__tests__/conversation-seed-composer.test.ts +66 -4
  42. package/src/__tests__/conversation-slash-commands.test.ts +36 -8
  43. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  44. package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
  45. package/src/__tests__/conversation-speed-override.test.ts +1 -1
  46. package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
  47. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
  48. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  49. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  50. package/src/__tests__/credential-security-invariants.test.ts +6 -0
  51. package/src/__tests__/cu-unified-flow.test.ts +10 -1
  52. package/src/__tests__/date-context.test.ts +45 -0
  53. package/src/__tests__/dm-backfill.test.ts +64 -0
  54. package/src/__tests__/dm-persistence.test.ts +33 -0
  55. package/src/__tests__/document-find-replace.test.ts +501 -0
  56. package/src/__tests__/external-plugin-loader.test.ts +91 -19
  57. package/src/__tests__/first-greeting.test.ts +23 -2
  58. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  59. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  60. package/src/__tests__/headless-browser-navigate.test.ts +172 -0
  61. package/src/__tests__/heartbeat-service.test.ts +24 -164
  62. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  63. package/src/__tests__/host-app-control-proxy.test.ts +241 -0
  64. package/src/__tests__/host-bash-proxy.test.ts +6 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +10 -0
  66. package/src/__tests__/host-cu-proxy.test.ts +8 -1
  67. package/src/__tests__/host-file-proxy.test.ts +8 -1
  68. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  69. package/src/__tests__/host-transfer-proxy.test.ts +8 -1
  70. package/src/__tests__/identity-routes.test.ts +57 -0
  71. package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
  72. package/src/__tests__/injector-background-turn.test.ts +153 -0
  73. package/src/__tests__/injector-chain.test.ts +7 -0
  74. package/src/__tests__/injector-document-comments.test.ts +378 -0
  75. package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
  76. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
  77. package/src/__tests__/list-messages-attachments.test.ts +21 -17
  78. package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
  79. package/src/__tests__/list-messages-page-latest.test.ts +130 -14
  80. package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
  81. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  82. package/src/__tests__/llm-catalog-parity.test.ts +3 -0
  83. package/src/__tests__/llm-context-normalization.test.ts +0 -2
  84. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  85. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  86. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
  87. package/src/__tests__/llm-resolver.test.ts +340 -3
  88. package/src/__tests__/log-export-routes.test.ts +99 -2
  89. package/src/__tests__/managed-profile-guard.test.ts +10 -0
  90. package/src/__tests__/message-queue-steer.test.ts +114 -0
  91. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  92. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  93. package/src/__tests__/notification-deep-link.test.ts +15 -0
  94. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  95. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  96. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  97. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  98. package/src/__tests__/openai-provider.test.ts +323 -3
  99. package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
  100. package/src/__tests__/openai-responses-provider.test.ts +4 -4
  101. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  102. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  103. package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
  104. package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
  105. package/src/__tests__/platform-proxy-context.test.ts +6 -1
  106. package/src/__tests__/platform.test.ts +0 -3
  107. package/src/__tests__/plugin-source-watcher.test.ts +302 -0
  108. package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
  109. package/src/__tests__/plugin-types.test.ts +2 -2
  110. package/src/__tests__/process-message-background-slack.test.ts +1 -51
  111. package/src/__tests__/process-message-display-content.test.ts +21 -16
  112. package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
  113. package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
  115. package/src/__tests__/server-history-render.test.ts +83 -4
  116. package/src/__tests__/steer-tool-repair.test.ts +249 -0
  117. package/src/__tests__/system-prompt.test.ts +57 -101
  118. package/src/__tests__/terminal-tools.test.ts +11 -1
  119. package/src/__tests__/thinking-block-replay.test.ts +113 -0
  120. package/src/__tests__/thread-backfill.test.ts +370 -22
  121. package/src/__tests__/tool-executor.test.ts +90 -1
  122. package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
  123. package/src/__tests__/twilio-routes.test.ts +1 -1
  124. package/src/__tests__/web-fetch.test.ts +2 -2
  125. package/src/__tests__/workspace-git-service.test.ts +88 -5
  126. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  127. package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
  128. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  129. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  130. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  131. package/src/a2a/__tests__/task-store.test.ts +246 -0
  132. package/src/a2a/agent-card.ts +58 -0
  133. package/src/a2a/feature-gate.ts +8 -0
  134. package/src/a2a/protocol-constants.ts +21 -0
  135. package/src/a2a/protocol-errors.ts +50 -0
  136. package/src/a2a/protocol-types.ts +162 -0
  137. package/src/a2a/task-store.ts +168 -0
  138. package/src/agent/attachments.ts +1 -0
  139. package/src/agent/loop.ts +208 -22
  140. package/src/background-wake/next-wake.test.ts +289 -0
  141. package/src/background-wake/next-wake.ts +172 -0
  142. package/src/browser/operations.ts +15 -0
  143. package/src/channels/config.ts +9 -0
  144. package/src/channels/types.ts +14 -0
  145. package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
  146. package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
  147. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  148. package/src/cli/commands/__tests__/schedules.test.ts +469 -0
  149. package/src/cli/commands/conversations.ts +128 -1
  150. package/src/cli/commands/inference-providers.ts +147 -1
  151. package/src/cli/commands/memory-v2.ts +308 -0
  152. package/src/cli/commands/notifications.ts +89 -37
  153. package/src/cli/commands/plugins.ts +67 -0
  154. package/src/cli/commands/schedules.ts +297 -5
  155. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  156. package/src/cli/lib/install-from-github.ts +8 -9
  157. package/src/cli/lib/search-plugins.ts +163 -0
  158. package/src/cli/program.ts +14 -0
  159. package/src/cli/utils/conversation-id.ts +17 -5
  160. package/src/config/assistant-feature-flags.ts +24 -54
  161. package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
  162. package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
  163. package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
  164. package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
  165. package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
  166. package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
  167. package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
  168. package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
  169. package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
  170. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  171. package/src/config/bundled-skills/schedule/SKILL.md +8 -0
  172. package/src/config/bundled-tool-registry.ts +22 -12
  173. package/src/config/call-site-defaults.ts +124 -0
  174. package/src/config/feature-flag-registry.json +111 -23
  175. package/src/config/llm-resolver.ts +66 -1
  176. package/src/config/schema.ts +2 -0
  177. package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
  178. package/src/config/schemas/call-site-catalog.ts +21 -0
  179. package/src/config/schemas/channels.ts +9 -0
  180. package/src/config/schemas/conversations.ts +10 -0
  181. package/src/config/schemas/heartbeat.ts +14 -0
  182. package/src/config/schemas/llm.ts +4 -3
  183. package/src/config/schemas/memory-retrospective.ts +1 -1
  184. package/src/config/schemas/memory-v2.ts +51 -4
  185. package/src/config/schemas/memory.ts +3 -1
  186. package/src/config/seed-inference-profiles.ts +99 -29
  187. package/src/context/compactor.ts +80 -13
  188. package/src/context/token-estimator.ts +72 -31
  189. package/src/context/window-manager.ts +25 -0
  190. package/src/credential-health/credential-health-service.ts +34 -19
  191. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
  192. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
  193. package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
  194. package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
  195. package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
  196. package/src/daemon/conversation-agent-loop.ts +252 -56
  197. package/src/daemon/conversation-lifecycle.ts +142 -116
  198. package/src/daemon/conversation-messaging.ts +3 -0
  199. package/src/daemon/conversation-process.ts +273 -0
  200. package/src/daemon/conversation-queue-manager.ts +14 -0
  201. package/src/daemon/conversation-runtime-assembly.ts +144 -75
  202. package/src/daemon/conversation-slash.ts +37 -5
  203. package/src/daemon/conversation-surfaces.ts +45 -2
  204. package/src/daemon/conversation-tool-setup.ts +7 -0
  205. package/src/daemon/conversation.ts +42 -12
  206. package/src/daemon/date-context.ts +40 -0
  207. package/src/daemon/first-greeting.ts +10 -0
  208. package/src/daemon/guardian-action-generators.ts +1 -125
  209. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
  210. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  211. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  212. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  213. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  214. package/src/daemon/handlers/config-a2a.ts +449 -0
  215. package/src/daemon/handlers/config-model.test.ts +1 -0
  216. package/src/daemon/handlers/conversations.ts +80 -0
  217. package/src/daemon/handlers/shared.ts +92 -29
  218. package/src/daemon/host-app-control-proxy.ts +69 -18
  219. package/src/daemon/host-bash-proxy.ts +1 -1
  220. package/src/daemon/host-cu-proxy.ts +1 -1
  221. package/src/daemon/host-file-proxy.ts +1 -1
  222. package/src/daemon/host-proxy-preactivation.ts +85 -18
  223. package/src/daemon/host-transfer-proxy.ts +1 -1
  224. package/src/daemon/lifecycle.ts +67 -65
  225. package/src/daemon/memory-v2-startup.ts +49 -13
  226. package/src/daemon/message-protocol.ts +4 -0
  227. package/src/daemon/message-types/conversations.ts +8 -0
  228. package/src/daemon/message-types/document-comments.ts +50 -0
  229. package/src/daemon/message-types/messages.ts +68 -1
  230. package/src/daemon/message-types/notifications.ts +21 -0
  231. package/src/daemon/message-types/surfaces.ts +3 -1
  232. package/src/daemon/message-types/web-activity.ts +57 -0
  233. package/src/daemon/pkb-reminder-builder.test.ts +10 -53
  234. package/src/daemon/pkb-reminder-builder.ts +4 -19
  235. package/src/daemon/plugin-source-watcher.ts +135 -3
  236. package/src/daemon/process-message.ts +72 -12
  237. package/src/daemon/query-complexity-router.ts +75 -0
  238. package/src/daemon/skill-memory-refresh.ts +5 -1
  239. package/src/daemon/trust-context.ts +6 -0
  240. package/src/daemon/wake-target-adapter.ts +2 -0
  241. package/src/documents/document-comments-store.test.ts +338 -0
  242. package/src/documents/document-comments-store.ts +237 -0
  243. package/src/documents/document-store.ts +202 -0
  244. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  245. package/src/export/transcript-formatter.ts +54 -20
  246. package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
  247. package/src/heartbeat/heartbeat-service.ts +35 -191
  248. package/src/home/__tests__/feed-types.test.ts +40 -0
  249. package/src/home/__tests__/suggested-prompts.test.ts +33 -2
  250. package/src/home/feed-types.ts +20 -3
  251. package/src/home/home-content-refresh.ts +52 -0
  252. package/src/home/home-greeting-cache.ts +69 -0
  253. package/src/home/home-greeting.ts +94 -0
  254. package/src/home/suggested-prompts.ts +177 -9
  255. package/src/ipc/cli-client.ts +147 -45
  256. package/src/memory/__tests__/conversation-queries.test.ts +220 -0
  257. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
  258. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  259. package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
  260. package/src/memory/conversation-crud.ts +133 -43
  261. package/src/memory/conversation-queries.ts +87 -1
  262. package/src/memory/conversation-title-service.ts +26 -4
  263. package/src/memory/db-init.ts +22 -0
  264. package/src/memory/delivery-crud.ts +41 -0
  265. package/src/memory/delivery-status.ts +141 -15
  266. package/src/memory/external-conversation-store.ts +32 -1
  267. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
  268. package/src/memory/graph/conversation-graph-memory.ts +18 -6
  269. package/src/memory/graph/tools.ts +6 -37
  270. package/src/memory/invite-store.ts +53 -0
  271. package/src/memory/jobs-worker.ts +21 -1
  272. package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
  273. package/src/memory/llm-request-log-store.ts +92 -1
  274. package/src/memory/memory-retrospective-constants.ts +28 -0
  275. package/src/memory/memory-retrospective-enqueue.ts +4 -22
  276. package/src/memory/memory-retrospective-job.ts +438 -21
  277. package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
  278. package/src/memory/memory-v2-activation-log-store.ts +26 -8
  279. package/src/memory/migrations/100-core-tables.ts +1 -0
  280. package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
  281. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  282. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  283. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  284. package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
  285. package/src/memory/migrations/253-document-comments.ts +47 -0
  286. package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
  287. package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
  288. package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
  289. package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
  290. package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
  291. package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
  292. package/src/memory/migrations/index.ts +20 -0
  293. package/src/memory/migrations/registry.ts +33 -0
  294. package/src/memory/onboarding-events-store.ts +7 -0
  295. package/src/memory/schema/a2a.ts +15 -0
  296. package/src/memory/schema/calls.ts +1 -0
  297. package/src/memory/schema/conversations.ts +3 -0
  298. package/src/memory/schema/index.ts +1 -0
  299. package/src/memory/schema/inference.ts +2 -0
  300. package/src/memory/schema/infrastructure.ts +2 -0
  301. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  302. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  303. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  304. package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
  305. package/src/memory/v2/__tests__/injection.test.ts +221 -17
  306. package/src/memory/v2/__tests__/page-index.test.ts +365 -1
  307. package/src/memory/v2/__tests__/router.test.ts +489 -1
  308. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  309. package/src/memory/v2/activation-store.ts +14 -16
  310. package/src/memory/v2/cli-command-content.ts +19 -0
  311. package/src/memory/v2/cli-command-store.ts +304 -0
  312. package/src/memory/v2/consolidation-job.ts +14 -0
  313. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  314. package/src/memory/v2/injection-events.ts +101 -0
  315. package/src/memory/v2/injection.ts +69 -29
  316. package/src/memory/v2/page-index.ts +246 -19
  317. package/src/memory/v2/page-store.ts +18 -0
  318. package/src/memory/v2/router.ts +209 -55
  319. package/src/memory/v2/static-context.ts +4 -4
  320. package/src/memory/v2/types.ts +23 -0
  321. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  322. package/src/messaging/providers/a2a/deliver.ts +156 -0
  323. package/src/messaging/providers/gmail/client.ts +9 -2
  324. package/src/messaging/providers/index.ts +18 -3
  325. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
  327. package/src/messaging/providers/slack/adapter.ts +178 -25
  328. package/src/messaging/providers/slack/api.test.ts +54 -0
  329. package/src/messaging/providers/slack/api.ts +119 -3
  330. package/src/messaging/providers/slack/client.ts +12 -0
  331. package/src/messaging/providers/slack/deep-link.ts +20 -1
  332. package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
  333. package/src/messaging/providers/slack/message-metadata.ts +156 -0
  334. package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
  335. package/src/messaging/providers/slack/render-transcript.ts +176 -49
  336. package/src/messaging/providers/slack/send.test.ts +77 -0
  337. package/src/messaging/providers/slack/send.ts +8 -2
  338. package/src/messaging/providers/slack/types.ts +14 -0
  339. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  340. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  341. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  342. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
  343. package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
  344. package/src/notifications/adapters/macos.ts +12 -2
  345. package/src/notifications/broadcaster.ts +29 -4
  346. package/src/notifications/conversation-seed-composer.ts +14 -2
  347. package/src/notifications/copy-composer.ts +17 -64
  348. package/src/notifications/decision-engine.ts +111 -44
  349. package/src/notifications/deferred-emit.ts +135 -0
  350. package/src/notifications/deterministic-checks.ts +96 -0
  351. package/src/notifications/emit-signal.ts +10 -1
  352. package/src/notifications/home-feed-side-effect.ts +136 -27
  353. package/src/notifications/signal.ts +0 -4
  354. package/src/notifications/types.ts +8 -0
  355. package/src/oauth/connect-orchestrator.ts +3 -0
  356. package/src/oauth/credential-token-resolver.ts +2 -0
  357. package/src/oauth/manual-token-connection.ts +19 -0
  358. package/src/oauth/oauth-store.ts +12 -0
  359. package/src/oauth/platform-connection.test.ts +43 -3
  360. package/src/oauth/platform-connection.ts +13 -4
  361. package/src/oauth/seed-providers.ts +22 -0
  362. package/src/permissions/prompter.ts +5 -2
  363. package/src/permissions/secret-prompter.ts +4 -1
  364. package/src/plugins/defaults/injectors.ts +118 -26
  365. package/src/plugins/external-plugin-loader.ts +82 -10
  366. package/src/plugins/types.ts +16 -7
  367. package/src/prompts/__tests__/system-prompt.test.ts +44 -45
  368. package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
  369. package/src/prompts/normalize-onboarding.ts +40 -0
  370. package/src/prompts/sections.ts +32 -14
  371. package/src/prompts/system-prompt.ts +105 -76
  372. package/src/prompts/template-detection.ts +37 -0
  373. package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
  374. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  375. package/src/prompts/templates/VOICE.md +3 -0
  376. package/src/prompts/templates/system-sections.ts +51 -10
  377. package/src/providers/__tests__/inference.test.ts +2 -0
  378. package/src/providers/anthropic/client.ts +132 -5
  379. package/src/providers/call-site-routing.ts +24 -6
  380. package/src/providers/connection-resolution.ts +63 -13
  381. package/src/providers/fireworks/client.ts +20 -2
  382. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  383. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
  384. package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
  385. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
  386. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  387. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  388. package/src/providers/inference/adapter-factory.ts +24 -21
  389. package/src/providers/inference/auth.ts +15 -3
  390. package/src/providers/inference/backfill.ts +14 -1
  391. package/src/providers/inference/codex-token-refresh.ts +128 -0
  392. package/src/providers/inference/connections.ts +85 -5
  393. package/src/providers/inference/resolve-auth.ts +50 -5
  394. package/src/providers/model-catalog.ts +244 -242
  395. package/src/providers/model-intents.ts +3 -3
  396. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  397. package/src/providers/openai/chat-completions-provider.ts +215 -25
  398. package/src/providers/openai/responses-provider.ts +9 -3
  399. package/src/providers/openrouter/client.ts +46 -4
  400. package/src/providers/platform-proxy/constants.ts +3 -4
  401. package/src/providers/provider-catalog-visibility.ts +3 -1
  402. package/src/providers/provider-send-message.ts +27 -12
  403. package/src/providers/registry.ts +30 -1
  404. package/src/providers/types.ts +25 -0
  405. package/src/runtime/__tests__/agent-wake.test.ts +214 -0
  406. package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
  407. package/src/runtime/agent-wake.ts +212 -57
  408. package/src/runtime/auth/route-policy.ts +20 -3
  409. package/src/runtime/background-job-runner.ts +26 -0
  410. package/src/runtime/channel-reply-delivery.ts +182 -47
  411. package/src/runtime/channel-retry-sweep.ts +141 -16
  412. package/src/runtime/http-server.ts +7 -16
  413. package/src/runtime/http-types.ts +7 -51
  414. package/src/runtime/pending-interactions.ts +51 -8
  415. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  416. package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
  417. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
  418. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  419. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  420. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
  421. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
  422. package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
  423. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
  424. package/src/runtime/routes/approval-routes.ts +4 -1
  425. package/src/runtime/routes/channel-availability-routes.ts +5 -0
  426. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
  427. package/src/runtime/routes/consolidation-routes.ts +100 -0
  428. package/src/runtime/routes/content-source-routes.ts +78 -0
  429. package/src/runtime/routes/conversation-cli-routes.ts +146 -1
  430. package/src/runtime/routes/conversation-query-routes.ts +130 -12
  431. package/src/runtime/routes/conversation-routes.ts +288 -76
  432. package/src/runtime/routes/document-comments-routes.ts +287 -0
  433. package/src/runtime/routes/documents-routes.ts +33 -0
  434. package/src/runtime/routes/home-feed-routes.ts +6 -3
  435. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  436. package/src/runtime/routes/host-browser-routes.ts +8 -1
  437. package/src/runtime/routes/identity-routes.ts +21 -0
  438. package/src/runtime/routes/inbound-message-handler.ts +288 -58
  439. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
  440. package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
  441. package/src/runtime/routes/index.ts +14 -4
  442. package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
  443. package/src/runtime/routes/integrations/a2a.ts +294 -0
  444. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  445. package/src/runtime/routes/log-export-routes.ts +39 -0
  446. package/src/runtime/routes/memory-v2-routes.ts +217 -0
  447. package/src/runtime/routes/notification-routes.ts +19 -2
  448. package/src/runtime/routes/question-routes.ts +4 -1
  449. package/src/runtime/routes/sanity-routes.ts +159 -0
  450. package/src/runtime/routes/slack-channel-routes.ts +187 -0
  451. package/src/runtime/routes/subagents-routes.ts +41 -0
  452. package/src/runtime/services/conversation-serializer.ts +30 -4
  453. package/src/schedule/integration-status.ts +3 -1
  454. package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
  455. package/src/security/oauth2-device-code.ts +307 -0
  456. package/src/security/oauth2.ts +26 -9
  457. package/src/security/secure-keys.ts +5 -0
  458. package/src/skills/catalog-install.ts +6 -2
  459. package/src/subagent/manager.ts +2 -0
  460. package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
  461. package/src/tools/browser/browser-execution.ts +93 -0
  462. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
  463. package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
  464. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
  465. package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
  466. package/src/tools/browser/cdp-client/factory.ts +87 -3
  467. package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
  468. package/src/tools/browser/cdp-client/types.ts +36 -0
  469. package/src/tools/browser/pinned-tabs.ts +90 -0
  470. package/src/tools/document/document-comment-tool.test.ts +379 -0
  471. package/src/tools/document/document-comment-tool.ts +156 -0
  472. package/src/tools/document/document-tool.ts +128 -2
  473. package/src/tools/memory/register.ts +1 -9
  474. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
  475. package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
  476. package/src/tools/network/domain-normalize.ts +17 -0
  477. package/src/tools/network/web-fetch.ts +213 -64
  478. package/src/tools/network/web-search.ts +191 -66
  479. package/src/tools/registry.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +3 -2
  481. package/src/tools/tool-approval-handler.ts +19 -12
  482. package/src/tools/types.ts +41 -2
  483. package/src/tools/ui-surface/definitions.ts +3 -1
  484. package/src/types/onboarding-context.ts +4 -0
  485. package/src/util/__tests__/favicon.test.ts +84 -0
  486. package/src/util/favicon.ts +40 -0
  487. package/src/util/platform.ts +0 -5
  488. package/src/workspace/git-service.ts +75 -4
  489. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  490. package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
  491. package/src/workspace/migrations/registry.ts +4 -0
  492. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  493. package/src/config/bundled-skills/document/SKILL.md +0 -54
  494. package/src/config/bundled-skills/document/TOOLS.json +0 -106
  495. package/src/daemon/seed-files.ts +0 -18
  496. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  497. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
  498. package/src/runtime/routes/interface-routes.ts +0 -43
  499. /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
  500. /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
  501. /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
  502. /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
  503. /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
@@ -8,6 +8,8 @@ import { join } from "node:path";
8
8
  import { z } from "zod";
9
9
 
10
10
  import { loadConfig } from "../../config/loader.js";
11
+ import type { AssistantConfig } from "../../config/types.js";
12
+ import { getDb } from "../../memory/db-connection.js";
11
13
  import {
12
14
  enqueueMemoryJob,
13
15
  type MemoryJobType,
@@ -21,12 +23,16 @@ import {
21
23
  totalEdgeCount,
22
24
  validateEdgeTargets,
23
25
  } from "../../memory/v2/edge-index.js";
26
+ import { computeInjectionScores } from "../../memory/v2/injection-events.js";
27
+ import { loadNowText } from "../../memory/v2/now-text.js";
28
+ import { getPageIndex } from "../../memory/v2/page-index.js";
24
29
  import {
25
30
  getConceptsDir,
26
31
  listPages,
27
32
  readPage,
28
33
  renderPageContent,
29
34
  } from "../../memory/v2/page-store.js";
35
+ import { type RouterSource, runRouter } from "../../memory/v2/router.js";
30
36
  import { seedV2SkillEntries } from "../../memory/v2/skill-store.js";
31
37
  import { getLogger } from "../../util/logger.js";
32
38
  import { getWorkspaceDir } from "../../util/platform.js";
@@ -290,6 +296,195 @@ async function handleConceptFrequency({
290
296
  return getConceptFrequencySummary(workspaceDir, { conversationId, sinceMs });
291
297
  }
292
298
 
299
+ // ── EMA scores ──────────────────────────────────────────────────────────
300
+
301
+ const MemoryV2EmaScoresParams = z.object({}).strict();
302
+
303
+ export interface MemoryV2EmaScoresEntry {
304
+ slug: string;
305
+ /** Time-decayed injection frequency; 0 when no events in the read window. */
306
+ score: number;
307
+ /** File mtime in epoch ms; 0 for synthetic entries (skills, CLI commands). */
308
+ modifiedAt: number;
309
+ }
310
+
311
+ export interface MemoryV2EmaScoresResult {
312
+ /** Every page index entry, sorted by score descending then slug ASCII. */
313
+ entries: MemoryV2EmaScoresEntry[];
314
+ }
315
+
316
+ async function handleEmaScores({
317
+ body = {},
318
+ }: RouteHandlerArgs): Promise<MemoryV2EmaScoresResult> {
319
+ // Intentionally NOT gated on `memory.v2.enabled` — operators inspecting
320
+ // EMA data before flipping tier-2 routing on is a legitimate dry-run
321
+ // use case, mirroring `memory_v2_validate`.
322
+ MemoryV2EmaScoresParams.parse(body);
323
+
324
+ const pageIndex = await getPageIndex(getWorkspaceDir());
325
+ const slugs = pageIndex.entries.map((e) => e.slug);
326
+ const scores = computeInjectionScores(getDb(), slugs, Date.now());
327
+
328
+ const entries: MemoryV2EmaScoresEntry[] = pageIndex.entries.map((entry) => ({
329
+ slug: entry.slug,
330
+ score: scores.get(entry.slug) ?? 0,
331
+ modifiedAt: entry.modifiedAt,
332
+ }));
333
+ entries.sort((a, b) => {
334
+ if (a.score !== b.score) return b.score - a.score;
335
+ return a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0;
336
+ });
337
+ return { entries };
338
+ }
339
+
340
+ // ── Simulate router (dry-run playground) ────────────────────────────────
341
+
342
+ const SimulateRouterOverridesSchema = z
343
+ .object({
344
+ tier1_size: z.number().int().min(1).nullable().optional(),
345
+ tier2_size: z.number().int().min(1).nullable().optional(),
346
+ batch_size: z.number().int().min(1).nullable().optional(),
347
+ })
348
+ .strict();
349
+
350
+ const MemoryV2SimulateRouterParams = z
351
+ .object({
352
+ query: z.string().min(1, "query must be non-empty"),
353
+ configOverrides: SimulateRouterOverridesSchema.optional(),
354
+ })
355
+ .strict();
356
+
357
+ export interface MemoryV2SimulateRouterEffectiveConfig {
358
+ tier1_size: number | null;
359
+ tier2_size: number | null;
360
+ batch_size: number | null;
361
+ max_page_ids: number;
362
+ }
363
+
364
+ export interface MemoryV2SimulateRouterResult {
365
+ /** Slugs the router would select, in model-returned order. */
366
+ selectedSlugs: string[];
367
+ /** Per-slug provenance: `"tier1"`, `"tier2"`, or `"tier3:<bucket>"`. */
368
+ sourceBySlug: Record<string, RouterSource>;
369
+ /** EMA scores for the selected slugs (0 when the slug has no events). */
370
+ scores: Record<string, number>;
371
+ /** `null` on success; otherwise one of the router failure reasons. */
372
+ failureReason: string | null;
373
+ /** The router config that actually ran (live merged with overrides). */
374
+ effectiveConfig: MemoryV2SimulateRouterEffectiveConfig;
375
+ /** The overrides the caller submitted, for display. */
376
+ overrides: {
377
+ tier1_size?: number | null;
378
+ tier2_size?: number | null;
379
+ batch_size?: number | null;
380
+ };
381
+ /** Page index size the router was given (post-tier-carve, all batches). */
382
+ totalCandidatePages: number;
383
+ }
384
+
385
+ /**
386
+ * Build the config the router will see by overlaying override values on top
387
+ * of the live workspace config. Only the three new tier knobs are exposed —
388
+ * everything else (provider, prompts, weights) stays exactly as it would on
389
+ * a real turn. `undefined` means "inherit live"; `null` is a valid override
390
+ * value (meaning "disable this tier").
391
+ */
392
+ function applySimulateOverrides(
393
+ live: AssistantConfig,
394
+ overrides: z.infer<typeof SimulateRouterOverridesSchema> | undefined,
395
+ ): AssistantConfig {
396
+ if (!overrides) return live;
397
+ const liveRouter = live.memory.v2.router;
398
+ const mergedRouter = {
399
+ ...liveRouter,
400
+ ...("tier1_size" in overrides && overrides.tier1_size !== undefined
401
+ ? { tier1_size: overrides.tier1_size }
402
+ : {}),
403
+ ...("tier2_size" in overrides && overrides.tier2_size !== undefined
404
+ ? { tier2_size: overrides.tier2_size }
405
+ : {}),
406
+ ...("batch_size" in overrides && overrides.batch_size !== undefined
407
+ ? { batch_size: overrides.batch_size }
408
+ : {}),
409
+ };
410
+ return {
411
+ ...live,
412
+ memory: {
413
+ ...live.memory,
414
+ v2: {
415
+ ...live.memory.v2,
416
+ router: mergedRouter,
417
+ },
418
+ },
419
+ };
420
+ }
421
+
422
+ export async function handleSimulateRouter({
423
+ body = {},
424
+ }: RouteHandlerArgs): Promise<MemoryV2SimulateRouterResult> {
425
+ requireMemoryV2Enabled();
426
+ const { query, configOverrides } = MemoryV2SimulateRouterParams.parse(body);
427
+
428
+ const liveConfig = loadConfig();
429
+ const mergedConfig = applySimulateOverrides(liveConfig, configOverrides);
430
+ const effectiveRouter = mergedConfig.memory.v2.router;
431
+
432
+ const workspaceDir = getWorkspaceDir();
433
+ const nowText = await loadNowText(workspaceDir);
434
+
435
+ const routerResult = await runRouter({
436
+ workspaceDir,
437
+ userMessage: query,
438
+ assistantMessage: "",
439
+ nowText,
440
+ priorEverInjected: [],
441
+ config: mergedConfig,
442
+ database: getDb(),
443
+ });
444
+
445
+ const pageIndex = await getPageIndex(workspaceDir);
446
+ const scores = computeInjectionScores(
447
+ getDb(),
448
+ routerResult.selectedSlugs,
449
+ Date.now(),
450
+ );
451
+
452
+ const sourceBySlug: Record<string, RouterSource> = {};
453
+ for (const [slug, source] of routerResult.sourceBySlug.entries()) {
454
+ sourceBySlug[slug] = source;
455
+ }
456
+
457
+ const scoresOut: Record<string, number> = {};
458
+ for (const slug of routerResult.selectedSlugs) {
459
+ scoresOut[slug] = scores.get(slug) ?? 0;
460
+ }
461
+
462
+ return {
463
+ selectedSlugs: routerResult.selectedSlugs,
464
+ sourceBySlug,
465
+ scores: scoresOut,
466
+ failureReason: routerResult.failureReason,
467
+ effectiveConfig: {
468
+ tier1_size: effectiveRouter.tier1_size,
469
+ tier2_size: effectiveRouter.tier2_size,
470
+ batch_size: effectiveRouter.batch_size,
471
+ max_page_ids: effectiveRouter.max_page_ids,
472
+ },
473
+ overrides: {
474
+ ...(configOverrides?.tier1_size !== undefined
475
+ ? { tier1_size: configOverrides.tier1_size }
476
+ : {}),
477
+ ...(configOverrides?.tier2_size !== undefined
478
+ ? { tier2_size: configOverrides.tier2_size }
479
+ : {}),
480
+ ...(configOverrides?.batch_size !== undefined
481
+ ? { batch_size: configOverrides.batch_size }
482
+ : {}),
483
+ },
484
+ totalCandidatePages: pageIndex.entries.length,
485
+ };
486
+ }
487
+
293
488
  // ── Route definitions ───────────────────────────────────────────────────
294
489
 
295
490
  export const ROUTES: RouteDefinition[] = [
@@ -359,4 +554,26 @@ export const ROUTES: RouteDefinition[] = [
359
554
  tags: ["memory"],
360
555
  requestBody: MemoryV2ConceptFrequencyParams,
361
556
  },
557
+ {
558
+ operationId: "memory_v2_ema_scores",
559
+ method: "POST",
560
+ endpoint: "memory/v2/ema-scores",
561
+ handler: handleEmaScores,
562
+ summary: "List every concept page with its injection-frequency EMA score",
563
+ description:
564
+ "Computes the time-decayed injection frequency (3-day half-life) for every entry in the current page index by reading memory_v2_injection_events. Returns entries sorted by score descending then slug ASCII, including zero-score pages so callers can decide whether to filter. Read-only; tier 2 of the v4 router uses the same computation to pick its top-M.",
565
+ tags: ["memory"],
566
+ requestBody: MemoryV2EmaScoresParams,
567
+ },
568
+ {
569
+ operationId: "memory_v2_simulate_router",
570
+ method: "POST",
571
+ endpoint: "memory/v2/simulate-router",
572
+ handler: handleSimulateRouter,
573
+ summary: "Dry-run the v4 router with config overrides (read-only)",
574
+ description:
575
+ "Runs the memory router against the live page index + EMA scores with optional tier_size / batch_size overrides, without recording an injection event or writing an activation log. Returns the slugs that would have been selected, per-slug tier provenance, EMA scores, and the effective router config so operators can validate knob changes before flipping them in workspace config.",
576
+ tags: ["memory"],
577
+ requestBody: MemoryV2SimulateRouterParams,
578
+ },
362
579
  ];
@@ -7,6 +7,7 @@ import { z } from "zod";
7
7
 
8
8
  import { getDb } from "../../memory/db-connection.js";
9
9
  import { notificationDeliveries } from "../../memory/schema.js";
10
+ import { bufferIfDeferred } from "../../notifications/deferred-emit.js";
10
11
  import { emitNotificationSignal } from "../../notifications/emit-signal.js";
11
12
  import { listEvents } from "../../notifications/events-store.js";
12
13
  import type { AttentionHints } from "../../notifications/signal.js";
@@ -78,6 +79,9 @@ const EmitSignalParams = z.object({
78
79
  conversationAffinityHint: z.record(z.string(), z.string()).optional(),
79
80
  dedupeKey: z.string().optional(),
80
81
  throwOnError: z.boolean().optional(),
82
+ // Conversation that originated this signal — used by `deferred-emit` to
83
+ // buffer notifications during in-band background-job tool calls.
84
+ originatingConversationId: z.string().optional(),
81
85
  });
82
86
 
83
87
  const ListNotificationEventsParams = z.object({
@@ -89,7 +93,7 @@ const ListNotificationEventsParams = z.object({
89
93
 
90
94
  async function handleEmitSignal({ body = {} }: RouteHandlerArgs) {
91
95
  const validated = EmitSignalParams.parse(body);
92
- const result = await emitNotificationSignal({
96
+ const params = {
93
97
  sourceEventName: validated.sourceEventName,
94
98
  sourceChannel: validated.sourceChannel,
95
99
  sourceContextId: validated.sourceContextId,
@@ -99,7 +103,20 @@ async function handleEmitSignal({ body = {} }: RouteHandlerArgs) {
99
103
  conversationAffinityHint: validated.conversationAffinityHint,
100
104
  dedupeKey: validated.dedupeKey,
101
105
  throwOnError: validated.throwOnError,
102
- });
106
+ };
107
+ const buffered = bufferIfDeferred(
108
+ validated.originatingConversationId,
109
+ params,
110
+ );
111
+ if (buffered) {
112
+ return {
113
+ signalId: buffered.signalId,
114
+ dispatched: buffered.dispatched,
115
+ deduplicated: buffered.deduplicated,
116
+ reason: buffered.reason,
117
+ };
118
+ }
119
+ const result = await emitNotificationSignal(params);
103
120
  return {
104
121
  signalId: result.signalId,
105
122
  dispatched: result.dispatched,
@@ -148,7 +148,10 @@ function handleQuestionResponse({ body }: RouteHandlerArgs) {
148
148
 
149
149
  // Validation passed — deregister now to clear the prompter timer, then
150
150
  // hand the result to the prompter's caller via rpcResolve.
151
- pendingInteractions.resolve(requestId);
151
+ pendingInteractions.resolve(
152
+ requestId,
153
+ response.kind === "close" ? "cancelled" : "answered",
154
+ );
152
155
 
153
156
  log.info(
154
157
  {
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Route definitions for Sanity CMS connection management.
3
+ *
4
+ * POST /v1/sanity/discover — project/dataset discovery using the stored API token
5
+ * POST /v1/sanity/connect — finalise connection by writing the sidecar file
6
+ *
7
+ * Both routes use policyKey: "secrets" — they read credentials and write
8
+ * workspace data, the same policy tier as the existing secrets routes.
9
+ */
10
+
11
+ import { mkdirSync, writeFileSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+
14
+ import { z } from "zod";
15
+
16
+ import { credentialKey } from "../../security/credential-key.js";
17
+ import { getSecureKeyAsync } from "../../security/secure-keys.js";
18
+ import { getWorkspaceDir } from "../../util/platform.js";
19
+ import { BadRequestError } from "./errors.js";
20
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ async function getStoredToken(): Promise<string | undefined> {
27
+ return getSecureKeyAsync(credentialKey("sanity", "api_token"));
28
+ }
29
+
30
+ function writeSidecar(relPath: string, data: Record<string, unknown>): void {
31
+ const workspaceDir = getWorkspaceDir();
32
+ const absPath = join(workspaceDir, relPath);
33
+ mkdirSync(dirname(absPath), { recursive: true });
34
+ writeFileSync(absPath, JSON.stringify(data, null, 2), "utf-8");
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // POST /v1/sanity/discover
39
+ // ---------------------------------------------------------------------------
40
+
41
+ async function handleDiscover(
42
+ args: RouteHandlerArgs,
43
+ ): Promise<Record<string, unknown>> {
44
+ const token = await getStoredToken();
45
+ if (!token) {
46
+ return { error: "no_token" };
47
+ }
48
+
49
+ const projectId =
50
+ typeof args.body?.projectId === "string" ? args.body.projectId : undefined;
51
+
52
+ if (!projectId) {
53
+ // List all projects this token has access to
54
+ const response = await fetch("https://api.sanity.io/v2021-06-07/projects", {
55
+ headers: { Authorization: `Bearer ${token}` },
56
+ });
57
+
58
+ if (response.status === 401 || response.status === 403) {
59
+ return { error: "token_scope_limited" };
60
+ }
61
+
62
+ if (!response.ok) {
63
+ return { error: "discovery_failed" };
64
+ }
65
+
66
+ const raw = (await response.json()) as Array<{
67
+ id: string;
68
+ displayName?: string;
69
+ }>;
70
+ const projects = raw.map((p) => ({
71
+ id: p.id,
72
+ displayName: p.displayName ?? p.id,
73
+ }));
74
+ return { projects };
75
+ }
76
+
77
+ // List datasets for a specific project
78
+ const response = await fetch(
79
+ `https://api.sanity.io/v2021-06-07/projects/${projectId}/datasets`,
80
+ {
81
+ headers: { Authorization: `Bearer ${token}` },
82
+ },
83
+ );
84
+
85
+ if (response.status === 401 || response.status === 403) {
86
+ return { error: "token_scope_limited" };
87
+ }
88
+
89
+ if (!response.ok) {
90
+ return { error: "discovery_failed" };
91
+ }
92
+
93
+ const raw = (await response.json()) as Array<{ name: string }>;
94
+ const datasets = raw.map((d) => d.name);
95
+ return { projectId, datasets };
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // POST /v1/sanity/connect
100
+ // ---------------------------------------------------------------------------
101
+
102
+ async function handleConnect(
103
+ args: RouteHandlerArgs,
104
+ ): Promise<Record<string, unknown>> {
105
+ const token = await getStoredToken();
106
+ if (!token) {
107
+ throw new BadRequestError(
108
+ "Sanity API token not found. Store it first via POST /v1/secrets.",
109
+ );
110
+ }
111
+
112
+ const projectId =
113
+ typeof args.body?.projectId === "string" ? args.body.projectId.trim() : "";
114
+ const dataset =
115
+ typeof args.body?.dataset === "string" ? args.body.dataset.trim() : "";
116
+
117
+ if (!projectId) {
118
+ throw new BadRequestError(
119
+ "projectId is required and must be a non-empty string",
120
+ );
121
+ }
122
+ if (!dataset) {
123
+ throw new BadRequestError(
124
+ "dataset is required and must be a non-empty string",
125
+ );
126
+ }
127
+
128
+ writeSidecar("data/sanity-connection.json", { projectId, dataset });
129
+
130
+ return { ok: true };
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Route definitions
135
+ // ---------------------------------------------------------------------------
136
+
137
+ export const ROUTES: RouteDefinition[] = [
138
+ {
139
+ operationId: "sanity_discover",
140
+ endpoint: "sanity/discover",
141
+ method: "POST",
142
+ policyKey: "secrets",
143
+ summary: "Discover Sanity projects and datasets using the stored API token",
144
+ requestBody: z.object({ projectId: z.string().optional() }).optional(),
145
+ handler: handleDiscover,
146
+ },
147
+ {
148
+ operationId: "sanity_connect",
149
+ endpoint: "sanity/connect",
150
+ method: "POST",
151
+ policyKey: "secrets",
152
+ summary: "Finalise Sanity connection and write sidecar file",
153
+ requestBody: z.object({
154
+ projectId: z.string(),
155
+ dataset: z.string(),
156
+ }),
157
+ handler: handleConnect,
158
+ },
159
+ ];
@@ -0,0 +1,187 @@
1
+ import { z } from "zod";
2
+
3
+ import {
4
+ conversationMetadataSyncTag,
5
+ SYNC_TAGS,
6
+ } from "../../daemon/message-types/sync.js";
7
+ import {
8
+ getBindingByConversation,
9
+ updateExternalChatName,
10
+ } from "../../memory/external-conversation-store.js";
11
+ import {
12
+ getSlackConversationInfo,
13
+ SlackApiError,
14
+ } from "../../messaging/providers/slack/api.js";
15
+ import { publishSyncInvalidation } from "../sync/sync-publisher.js";
16
+ import { NotFoundError } from "./errors.js";
17
+ import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
18
+
19
+ type ResolveReason =
20
+ | "auth"
21
+ | "dm"
22
+ | "no_name"
23
+ | "not_found"
24
+ | "permission"
25
+ | "rate_limit"
26
+ | "slack_error";
27
+
28
+ interface SlackChannelResolveResponse {
29
+ channelId: string;
30
+ channelName?: string;
31
+ cached: boolean;
32
+ resolved: boolean;
33
+ reason?: ResolveReason;
34
+ }
35
+
36
+ const SlackChannelResolveResponseSchema = z.object({
37
+ channelId: z.string(),
38
+ channelName: z.string().optional(),
39
+ cached: z.boolean(),
40
+ resolved: z.boolean(),
41
+ reason: z
42
+ .enum([
43
+ "auth",
44
+ "dm",
45
+ "no_name",
46
+ "not_found",
47
+ "permission",
48
+ "rate_limit",
49
+ "slack_error",
50
+ ])
51
+ .optional(),
52
+ });
53
+
54
+ function friendlyCachedName(
55
+ externalChatId: string,
56
+ externalChatName?: string | null,
57
+ ): string | undefined {
58
+ const trimmed = externalChatName?.trim();
59
+ if (!trimmed || trimmed === externalChatId) return undefined;
60
+ return trimmed;
61
+ }
62
+
63
+ function usableResolvedName(
64
+ externalChatId: string,
65
+ name?: string,
66
+ nameNormalized?: string,
67
+ ): string | undefined {
68
+ for (const candidate of [name, nameNormalized]) {
69
+ const trimmed = candidate?.trim();
70
+ if (trimmed && trimmed !== externalChatId) return trimmed;
71
+ }
72
+ return undefined;
73
+ }
74
+
75
+ function reasonForSlackError(err: unknown): ResolveReason {
76
+ if (err instanceof SlackApiError) {
77
+ switch (err.category) {
78
+ case "auth":
79
+ return "auth";
80
+ case "channel_not_found":
81
+ case "not_found":
82
+ return "not_found";
83
+ case "permission":
84
+ return "permission";
85
+ case "rate_limit":
86
+ return "rate_limit";
87
+ default:
88
+ return "slack_error";
89
+ }
90
+ }
91
+ return "slack_error";
92
+ }
93
+
94
+ async function handleSlackChannelNameResolve({
95
+ pathParams = {},
96
+ }: RouteHandlerArgs): Promise<SlackChannelResolveResponse> {
97
+ const conversationId = pathParams.conversationId?.trim();
98
+ if (!conversationId) {
99
+ throw new NotFoundError("Conversation not found");
100
+ }
101
+
102
+ const binding = getBindingByConversation(conversationId);
103
+
104
+ if (!binding || binding.sourceChannel !== "slack") {
105
+ throw new NotFoundError("Conversation not found");
106
+ }
107
+
108
+ const channelId = binding.externalChatId;
109
+ const cachedName = friendlyCachedName(channelId, binding.externalChatName);
110
+ if (cachedName) {
111
+ return {
112
+ channelId,
113
+ channelName: cachedName,
114
+ cached: true,
115
+ resolved: true,
116
+ };
117
+ }
118
+
119
+ if (channelId.startsWith("D")) {
120
+ return {
121
+ channelId,
122
+ cached: false,
123
+ resolved: false,
124
+ reason: "dm",
125
+ };
126
+ }
127
+
128
+ let info;
129
+ try {
130
+ info = await getSlackConversationInfo(channelId);
131
+ } catch (err) {
132
+ return {
133
+ channelId,
134
+ cached: false,
135
+ resolved: false,
136
+ reason: reasonForSlackError(err),
137
+ };
138
+ }
139
+
140
+ const channelName = usableResolvedName(
141
+ channelId,
142
+ info?.name,
143
+ info?.nameNormalized,
144
+ );
145
+ if (!channelName) {
146
+ return {
147
+ channelId,
148
+ cached: false,
149
+ resolved: false,
150
+ reason: "no_name",
151
+ };
152
+ }
153
+
154
+ updateExternalChatName(conversationId, channelName);
155
+ await publishSyncInvalidation([
156
+ SYNC_TAGS.conversationsList,
157
+ conversationMetadataSyncTag(conversationId),
158
+ ]);
159
+
160
+ return {
161
+ channelId,
162
+ channelName,
163
+ cached: false,
164
+ resolved: true,
165
+ };
166
+ }
167
+
168
+ export const ROUTES: RouteDefinition[] = [
169
+ {
170
+ operationId: "slack_channel_name_resolve",
171
+ endpoint: "conversations/:conversationId/slack-channel/resolve",
172
+ method: "POST",
173
+ handler: handleSlackChannelNameResolve,
174
+ summary: "Resolve Slack channel name",
175
+ description:
176
+ "Resolve and persist a friendly Slack channel name for an external conversation binding.",
177
+ tags: ["conversations", "slack"],
178
+ pathParams: [
179
+ {
180
+ name: "conversationId",
181
+ type: "string",
182
+ description: "Conversation ID whose Slack channel name should resolve.",
183
+ },
184
+ ],
185
+ responseBody: SlackChannelResolveResponseSchema,
186
+ },
187
+ ];
@@ -188,6 +188,47 @@ function getSubagentDetail(
188
188
  // ---------------------------------------------------------------------------
189
189
 
190
190
  export const ROUTES: RouteDefinition[] = [
191
+ {
192
+ operationId: "reconcileSubagents",
193
+ endpoint: "subagents/reconcile",
194
+ method: "GET",
195
+ policyKey: "subagents",
196
+ summary: "Reconcile subagent live status",
197
+ description:
198
+ "Returns the live in-memory status of all subagents known to the daemon for a given parent conversation. Subagents not in the response are orphaned.",
199
+ tags: ["subagents"],
200
+ queryParams: [
201
+ {
202
+ name: "parentConversationId",
203
+ schema: { type: "string" },
204
+ description: "Parent conversation ID",
205
+ },
206
+ ],
207
+ responseBody: z.object({
208
+ subagents: z.record(
209
+ z.string(),
210
+ z.object({
211
+ status: z.string(),
212
+ }),
213
+ ),
214
+ }),
215
+ handler: ({ queryParams }) => {
216
+ const parentConversationId = queryParams?.parentConversationId;
217
+ if (!parentConversationId) {
218
+ throw new BadRequestError(
219
+ "parentConversationId query parameter is required",
220
+ );
221
+ }
222
+ const manager = getSubagentManager();
223
+ const children = manager.getChildrenOf(parentConversationId);
224
+ const subagents: Record<string, { status: string }> = {};
225
+ for (const child of children) {
226
+ subagents[child.config.id] = { status: child.status };
227
+ }
228
+ return { subagents };
229
+ },
230
+ },
231
+
191
232
  {
192
233
  operationId: "getSubagentDetail",
193
234
  endpoint: "subagents/:id",