@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
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Personalized home-page greeting generator.
3
+ *
4
+ * Produces a short greeting in the assistant's tone/persona for the
5
+ * Home page header. Uses the BTW side-chain for LLM generation and
6
+ * caches the result for 4 hours (busted when identity files change).
7
+ *
8
+ * The GET handler reads only from cache (`getPersonalizedGreeting`).
9
+ * Generation runs in the background via `refreshPersonalizedGreeting`,
10
+ * called at daemon startup and periodically by the home-content
11
+ * refresh timer.
12
+ */
13
+
14
+ import { resolveCallSiteConfig } from "../config/llm-resolver.js";
15
+ import { getConfig } from "../config/loader.js";
16
+ import { resolvePersonaContext } from "../prompts/persona-resolver.js";
17
+ import { buildSystemPrompt } from "../prompts/system-prompt.js";
18
+ import { getConfiguredProvider } from "../providers/provider-send-message.js";
19
+ import { runBtwSidechain } from "../runtime/btw-sidechain.js";
20
+ import { getLogger } from "../util/logger.js";
21
+ import {
22
+ getCachedHomeGreeting,
23
+ setCachedHomeGreeting,
24
+ } from "./home-greeting-cache.js";
25
+
26
+ const log = getLogger("home-greeting");
27
+
28
+ const GENERATION_TIMEOUT_MS = 5_000;
29
+
30
+ /**
31
+ * Return the cached personalized greeting, or `null` when none is
32
+ * available yet. This is a synchronous cache read — safe for GET
33
+ * handlers.
34
+ */
35
+ export function getPersonalizedGreeting(): string | null {
36
+ return getCachedHomeGreeting();
37
+ }
38
+
39
+ /**
40
+ * Generate a personalized greeting via LLM and write it to cache.
41
+ * No-ops when the cache is still fresh. Intended for background
42
+ * invocation (daemon startup / periodic refresh), not the GET path.
43
+ */
44
+ export async function refreshPersonalizedGreeting(): Promise<void> {
45
+ const cached = getCachedHomeGreeting();
46
+ if (cached) {
47
+ return;
48
+ }
49
+
50
+ try {
51
+ const config = getConfig();
52
+ const resolved = resolveCallSiteConfig("homeGreeting", config.llm);
53
+
54
+ const provider = await getConfiguredProvider("homeGreeting");
55
+ if (!provider) {
56
+ return;
57
+ }
58
+
59
+ const { userPersona, userSlug, channelPersona } = resolvePersonaContext(
60
+ undefined,
61
+ undefined,
62
+ );
63
+
64
+ const systemPrompt = buildSystemPrompt({
65
+ excludeBootstrap: true,
66
+ excludeCustomPrefix: true,
67
+ userPersona,
68
+ channelPersona,
69
+ userSlug,
70
+ });
71
+
72
+ const result = await runBtwSidechain({
73
+ content:
74
+ "Generate a short, casual greeting for the home page (max 10 words). " +
75
+ "It should convey the meaning of 'here's what's been going on' but in " +
76
+ "your unique tone and personality. Just output the greeting text, nothing else. " +
77
+ "No quotes, no preamble.",
78
+ provider,
79
+ systemPrompt,
80
+ messages: [],
81
+ tools: [],
82
+ callSite: "homeGreeting",
83
+ maxTokens: resolved.maxTokens,
84
+ timeoutMs: GENERATION_TIMEOUT_MS,
85
+ });
86
+
87
+ const text = result.text.trim();
88
+ if (text) {
89
+ setCachedHomeGreeting(text);
90
+ }
91
+ } catch (err) {
92
+ log.warn({ err }, "Failed to generate personalized home greeting");
93
+ }
94
+ }
@@ -6,11 +6,23 @@
6
6
  *
7
7
  * Two sources of prompts:
8
8
  * - **Deterministic** — derived from missing OAuth connections.
9
+ * Computed inline (read-only, safe for GET).
9
10
  * - **Assistant-generated** — contextual suggestions from the LLM
10
- * (placeholder; not yet implemented).
11
+ * based on what's relevant to the user. Read from an in-memory
12
+ * cache in the GET path; generation runs in the background via
13
+ * `refreshAssistantSuggestedPrompts`.
11
14
  */
12
15
 
13
- import { isProviderConnected, listProviders } from "../oauth/oauth-store.js";
16
+ import { resolveCallSiteConfig } from "../config/llm-resolver.js";
17
+ import { getConfig } from "../config/loader.js";
18
+ import { listProviders } from "../oauth/oauth-store.js";
19
+ import { resolvePersonaContext } from "../prompts/persona-resolver.js";
20
+ import { buildSystemPrompt } from "../prompts/system-prompt.js";
21
+ import { getConfiguredProvider } from "../providers/provider-send-message.js";
22
+ import { buildAssistantEvent } from "../runtime/assistant-event.js";
23
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
24
+ import { runBtwSidechain } from "../runtime/btw-sidechain.js";
25
+ import { isOAuthProviderConnected } from "../schedule/integration-status.js";
14
26
  import { getLogger } from "../util/logger.js";
15
27
  import type { SuggestedPrompt } from "./feed-types.js";
16
28
 
@@ -72,27 +84,88 @@ const CONNECT_PROMPT_META: Record<
72
84
  },
73
85
  };
74
86
 
87
+ const LLM_SUGGESTIONS_TIMEOUT_MS = 5_000;
88
+ const LLM_CACHE_TTL_MS = 30 * 60 * 1000; // 30 minutes
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // In-memory cache for LLM-generated suggestions
92
+ // ---------------------------------------------------------------------------
93
+
94
+ let cachedLLMPrompts: SuggestedPrompt[] = [];
95
+ let cachedLLMPromptsAt = 0;
96
+
75
97
  /**
76
- * Produce deterministic suggested prompts based on missing OAuth
77
- * connections and (in the future) assistant-generated conversation
78
- * starters.
98
+ * Produce suggested prompts from both deterministic and cached LLM sources.
99
+ * Deterministic prompts always come first; cached LLM-generated prompts are
100
+ * appended when available. No LLM calls happen in this path — safe for GET.
79
101
  */
80
102
  export async function getSuggestedPrompts(): Promise<SuggestedPrompt[]> {
81
103
  const prompts: SuggestedPrompt[] = [];
82
104
 
105
+ let deterministicPrompts: SuggestedPrompt[] = [];
83
106
  try {
84
- const deterministicPrompts = await getDeterministicPrompts();
107
+ deterministicPrompts = await getDeterministicPrompts();
85
108
  prompts.push(...deterministicPrompts);
86
109
  } catch (err) {
87
110
  log.warn({ err }, "Failed to compute deterministic suggested prompts");
88
111
  }
89
112
 
90
- // Placeholder: assistant-generated prompts will be added here once
91
- // the LLM producer is implemented.
113
+ if (Date.now() - cachedLLMPromptsAt < LLM_CACHE_TTL_MS) {
114
+ prompts.push(...cachedLLMPrompts);
115
+ }
92
116
 
93
117
  return prompts;
94
118
  }
95
119
 
120
+ /**
121
+ * Drops the in-memory LLM suggestion cache so the next call to
122
+ * `getSuggestedPrompts()` returns only the fresh deterministic list (and
123
+ * a follow-up background refresh repopulates the LLM half).
124
+ *
125
+ * Called from OAuth connect/disconnect paths so a freshly-connected
126
+ * provider stops surfacing as a "Connect X" pill within one reload — the
127
+ * 30-minute TTL would otherwise pin a stale suggestion until the next
128
+ * periodic refresh.
129
+ */
130
+ export function invalidateAssistantSuggestedPromptsCache(): void {
131
+ cachedLLMPrompts = [];
132
+ cachedLLMPromptsAt = 0;
133
+ assistantEventHub
134
+ .publish(
135
+ buildAssistantEvent({
136
+ type: "home_feed_updated",
137
+ updatedAt: new Date().toISOString(),
138
+ newItemCount: 0,
139
+ }),
140
+ )
141
+ .catch((err) => {
142
+ log.warn(
143
+ { err },
144
+ "Failed to publish home_feed_updated after prompt cache invalidation",
145
+ );
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Generate LLM-based suggestion prompts and write them to the in-memory
151
+ * cache. No-ops when the cache is still fresh. Intended for background
152
+ * invocation (daemon startup / periodic refresh), not the GET path.
153
+ */
154
+ export async function refreshAssistantSuggestedPrompts(): Promise<void> {
155
+ if (Date.now() - cachedLLMPromptsAt < LLM_CACHE_TTL_MS) {
156
+ return;
157
+ }
158
+
159
+ try {
160
+ const deterministicPrompts = await getDeterministicPrompts();
161
+ const llmPrompts = await generateAssistantPrompts(deterministicPrompts);
162
+ cachedLLMPrompts = llmPrompts;
163
+ cachedLLMPromptsAt = Date.now();
164
+ } catch (err) {
165
+ log.warn({ err }, "Failed to refresh assistant suggested prompts");
166
+ }
167
+ }
168
+
96
169
  /**
97
170
  * Check which well-known OAuth providers are not connected and return
98
171
  * a "Connect X" prompt for each. For connected providers that have
@@ -107,7 +180,7 @@ async function getDeterministicPrompts(): Promise<SuggestedPrompt[]> {
107
180
  const meta = CONNECT_PROMPT_META[provider.provider];
108
181
  if (!meta) continue;
109
182
 
110
- const connected = await isProviderConnected(provider.provider);
183
+ const connected = await isOAuthProviderConnected(provider.provider);
111
184
 
112
185
  if (!connected) {
113
186
  prompts.push({
@@ -135,3 +208,98 @@ async function getDeterministicPrompts(): Promise<SuggestedPrompt[]> {
135
208
 
136
209
  return prompts;
137
210
  }
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // LLM-generated suggestions
214
+ // ---------------------------------------------------------------------------
215
+
216
+ interface LLMSuggestion {
217
+ label: string;
218
+ prompt: string;
219
+ }
220
+
221
+ /**
222
+ * Ask the LLM to generate contextual conversation-starter suggestions
223
+ * based on the assistant's persona and the user's connected services.
224
+ * Returns an empty array on failure so deterministic prompts still show.
225
+ */
226
+ async function generateAssistantPrompts(
227
+ deterministicPrompts: SuggestedPrompt[],
228
+ ): Promise<SuggestedPrompt[]> {
229
+ const config = getConfig();
230
+ const resolved = resolveCallSiteConfig("homeSuggestedPrompts", config.llm);
231
+
232
+ const provider = await getConfiguredProvider("homeSuggestedPrompts");
233
+ if (!provider) {
234
+ return [];
235
+ }
236
+
237
+ const { userPersona, userSlug, channelPersona } = resolvePersonaContext(
238
+ undefined,
239
+ undefined,
240
+ );
241
+
242
+ const systemPrompt = buildSystemPrompt({
243
+ excludeBootstrap: true,
244
+ excludeCustomPrefix: true,
245
+ userPersona,
246
+ channelPersona,
247
+ userSlug,
248
+ });
249
+
250
+ const existingLabels = deterministicPrompts.map((p) => p.label).join(", ");
251
+ const contextNote = existingLabels
252
+ ? `The user already has these suggestions: ${existingLabels}. Do NOT duplicate them.`
253
+ : "";
254
+
255
+ const result = await runBtwSidechain({
256
+ content:
257
+ "Suggest 2-3 short, actionable conversation starters for the home page. " +
258
+ "Each should be something specific and helpful you can do for the user right now. " +
259
+ `${contextNote} ` +
260
+ 'Return ONLY a JSON array of objects with "label" (max 5 words) and "prompt" (the full message to send). ' +
261
+ "No markdown fences, no explanation.",
262
+ provider,
263
+ systemPrompt,
264
+ messages: [],
265
+ tools: [],
266
+ callSite: "homeSuggestedPrompts",
267
+ maxTokens: resolved.maxTokens,
268
+ timeoutMs: LLM_SUGGESTIONS_TIMEOUT_MS,
269
+ });
270
+
271
+ const text = result.text.trim();
272
+ if (!text) {
273
+ return [];
274
+ }
275
+
276
+ const parsed = parseLLMSuggestions(text);
277
+ return parsed.map((s, i) => ({
278
+ id: `assistant-${i}-${s.label.toLowerCase().replace(/\s+/g, "-")}`,
279
+ label: s.label,
280
+ prompt: s.prompt,
281
+ source: "assistant" as const,
282
+ }));
283
+ }
284
+
285
+ function parseLLMSuggestions(text: string): LLMSuggestion[] {
286
+ try {
287
+ const cleaned = text
288
+ .replace(/^```(?:json)?\n?/m, "")
289
+ .replace(/\n?```$/m, "");
290
+ const parsed = JSON.parse(cleaned);
291
+ if (!Array.isArray(parsed)) {
292
+ return [];
293
+ }
294
+ return parsed.filter(
295
+ (item): item is LLMSuggestion =>
296
+ typeof item === "object" &&
297
+ item !== null &&
298
+ typeof item.label === "string" &&
299
+ typeof item.prompt === "string",
300
+ );
301
+ } catch {
302
+ log.warn("Failed to parse LLM suggestions response");
303
+ return [];
304
+ }
305
+ }
@@ -135,19 +135,26 @@ export async function cliIpcCall<T = unknown>(
135
135
 
136
136
  const reqId = crypto.randomUUID();
137
137
 
138
- opts?.signal?.addEventListener("abort", () => {
139
- finish({ ok: false, error: "Request aborted" });
140
- }, { once: true });
138
+ opts?.signal?.addEventListener(
139
+ "abort",
140
+ () => {
141
+ finish({ ok: false, error: "Request aborted" });
142
+ },
143
+ { once: true },
144
+ );
141
145
 
142
146
  const reader = new IpcFrameReader(
143
147
  (envelope) => {
144
148
  if (envelope.id !== reqId) return;
145
149
  const msg = envelope as IpcResponse;
146
150
  if (msg.error) {
147
- finish({ ok: false, error: msg.error,
151
+ finish({
152
+ ok: false,
153
+ error: msg.error,
148
154
  ...(msg.statusCode != null && { statusCode: msg.statusCode }),
149
155
  ...(msg.errorCode != null && { errorCode: msg.errorCode }),
150
- ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }) });
156
+ ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }),
157
+ });
151
158
  } else {
152
159
  finish({ ok: true, result: msg.result as T });
153
160
  }
@@ -199,7 +206,13 @@ export async function cliIpcCallBinary(
199
206
  opts?: { timeoutMs?: number; signal?: AbortSignal },
200
207
  ): Promise<
201
208
  | { ok: true; headers: Record<string, string>; bytes: Uint8Array }
202
- | { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown }
209
+ | {
210
+ ok: false;
211
+ error: string;
212
+ statusCode?: number;
213
+ errorCode?: string;
214
+ errorDetails?: unknown;
215
+ }
203
216
  > {
204
217
  if (opts?.signal?.aborted) {
205
218
  throw opts.signal.reason ?? new DOMException("Aborted", "AbortError");
@@ -215,7 +228,13 @@ export async function cliIpcCallBinary(
215
228
  const finish = (
216
229
  result:
217
230
  | { ok: true; headers: Record<string, string>; bytes: Uint8Array }
218
- | { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown },
231
+ | {
232
+ ok: false;
233
+ error: string;
234
+ statusCode?: number;
235
+ errorCode?: string;
236
+ errorDetails?: unknown;
237
+ },
219
238
  ) => {
220
239
  if (settled) return;
221
240
  settled = true;
@@ -226,8 +245,14 @@ export async function cliIpcCallBinary(
226
245
  };
227
246
 
228
247
  const connectTimer = setTimeout(() => {
229
- log.debug({ method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS }, "CLI IPC binary connect timed out");
230
- finish({ ok: false, error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.` });
248
+ log.debug(
249
+ { method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS },
250
+ "CLI IPC binary connect timed out",
251
+ );
252
+ finish({
253
+ ok: false,
254
+ error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.`,
255
+ });
231
256
  }, CONNECT_TIMEOUT_MS);
232
257
 
233
258
  const socket = new Socket();
@@ -235,7 +260,10 @@ export async function cliIpcCallBinary(
235
260
 
236
261
  socket.on("error", (err) => {
237
262
  const code = (err as NodeJS.ErrnoException).code;
238
- log.debug({ err, code, method, socketPath }, "CLI IPC binary socket error");
263
+ log.debug(
264
+ { err, code, method, socketPath },
265
+ "CLI IPC binary socket error",
266
+ );
239
267
  finish({
240
268
  ok: false,
241
269
  error:
@@ -258,23 +286,37 @@ export async function cliIpcCallBinary(
258
286
 
259
287
  const reqId = crypto.randomUUID();
260
288
 
261
- opts?.signal?.addEventListener("abort", () => {
262
- finish({ ok: false, error: "Request aborted" });
263
- }, { once: true });
289
+ opts?.signal?.addEventListener(
290
+ "abort",
291
+ () => {
292
+ finish({ ok: false, error: "Request aborted" });
293
+ },
294
+ { once: true },
295
+ );
264
296
 
265
297
  const reader = new IpcFrameReader(
266
298
  (envelope, binary) => {
267
299
  if (envelope.id !== reqId) return;
268
300
  const msg = envelope as IpcResponse;
269
301
  if (msg.error) {
270
- finish({ ok: false, error: msg.error,
302
+ finish({
303
+ ok: false,
304
+ error: msg.error,
271
305
  ...(msg.statusCode != null && { statusCode: msg.statusCode }),
272
306
  ...(msg.errorCode != null && { errorCode: msg.errorCode }),
273
- ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }) });
307
+ ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }),
308
+ });
274
309
  } else if (binary === undefined) {
275
- finish({ ok: false, error: "Expected binary frame but received JSON-only response" });
310
+ finish({
311
+ ok: false,
312
+ error: "Expected binary frame but received JSON-only response",
313
+ });
276
314
  } else {
277
- finish({ ok: true, headers: (envelope.headers ?? {}) as Record<string, string>, bytes: binary });
315
+ finish({
316
+ ok: true,
317
+ headers: (envelope.headers ?? {}) as Record<string, string>,
318
+ bytes: binary,
319
+ });
278
320
  }
279
321
  },
280
322
  (err) => finish({ ok: false, error: err.message }),
@@ -285,7 +327,10 @@ export async function cliIpcCallBinary(
285
327
  writeMessage(socket, { id: reqId, method, params });
286
328
 
287
329
  callTimer = setTimeout(() => {
288
- log.debug({ method, socketPath, timeoutMs: callTimeoutMs }, "CLI IPC binary call timed out");
330
+ log.debug(
331
+ { method, socketPath, timeoutMs: callTimeoutMs },
332
+ "CLI IPC binary call timed out",
333
+ );
289
334
  finish({ ok: false, error: "Request timed out" });
290
335
  }, callTimeoutMs);
291
336
 
@@ -323,24 +368,42 @@ export async function cliIpcCallStream(
323
368
  params?: Record<string, unknown>,
324
369
  opts?: { firstByteTimeoutMs?: number; signal?: AbortSignal },
325
370
  ): Promise<
326
- | { ok: true; headers: Record<string, string>; body: ReadableStream<Uint8Array>; abort: () => void }
327
- | { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown }
371
+ | {
372
+ ok: true;
373
+ headers: Record<string, string>;
374
+ body: ReadableStream<Uint8Array>;
375
+ abort: () => void;
376
+ }
377
+ | {
378
+ ok: false;
379
+ error: string;
380
+ statusCode?: number;
381
+ errorCode?: string;
382
+ errorDetails?: unknown;
383
+ }
328
384
  > {
329
385
  if (opts?.signal?.aborted) {
330
386
  throw opts.signal.reason ?? new DOMException("Aborted", "AbortError");
331
387
  }
332
388
 
333
389
  const socketPath = getAssistantSocketPath();
334
- const firstByteTimeoutMs = opts?.firstByteTimeoutMs ?? DEFAULT_FIRST_BYTE_TIMEOUT_MS;
390
+ const firstByteTimeoutMs =
391
+ opts?.firstByteTimeoutMs ?? DEFAULT_FIRST_BYTE_TIMEOUT_MS;
335
392
 
336
393
  return new Promise((resolve) => {
337
394
  let settled = false;
338
395
  let firstByteTimer: ReturnType<typeof setTimeout> | undefined;
339
- let streamController: ReadableStreamDefaultController<Uint8Array> | undefined;
340
-
341
- const finishError = (
342
- result: { ok: false; error: string; statusCode?: number; errorCode?: string; errorDetails?: unknown },
343
- ) => {
396
+ let streamController:
397
+ | ReadableStreamDefaultController<Uint8Array>
398
+ | undefined;
399
+
400
+ const finishError = (result: {
401
+ ok: false;
402
+ error: string;
403
+ statusCode?: number;
404
+ errorCode?: string;
405
+ errorDetails?: unknown;
406
+ }) => {
344
407
  if (settled) return;
345
408
  settled = true;
346
409
  clearTimeout(connectTimer);
@@ -366,8 +429,14 @@ export async function cliIpcCallStream(
366
429
  };
367
430
 
368
431
  const connectTimer = setTimeout(() => {
369
- log.debug({ method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS }, "CLI IPC stream connect timed out");
370
- finishError({ ok: false, error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.` });
432
+ log.debug(
433
+ { method, socketPath, timeoutMs: CONNECT_TIMEOUT_MS },
434
+ "CLI IPC stream connect timed out",
435
+ );
436
+ finishError({
437
+ ok: false,
438
+ error: `Could not connect to the assistant at ${socketPath}.\nRun \`assistant status\` to check, or \`assistant gateway start\` to start it.`,
439
+ });
371
440
  }, CONNECT_TIMEOUT_MS);
372
441
 
373
442
  const socket = new Socket();
@@ -375,7 +444,10 @@ export async function cliIpcCallStream(
375
444
 
376
445
  socket.on("error", (err) => {
377
446
  const code = (err as NodeJS.ErrnoException).code;
378
- log.debug({ err, code, method, socketPath }, "CLI IPC stream socket error");
447
+ log.debug(
448
+ { err, code, method, socketPath },
449
+ "CLI IPC stream socket error",
450
+ );
379
451
  if (!settled) {
380
452
  finishError({
381
453
  ok: false,
@@ -399,24 +471,35 @@ export async function cliIpcCallStream(
399
471
  : "Connection closed before response",
400
472
  });
401
473
  } else if (streamController) {
402
- streamController.error(new Error("Connection closed before stream ended"));
474
+ streamController.error(
475
+ new Error("Connection closed before stream ended"),
476
+ );
403
477
  streamController = undefined;
404
478
  }
405
479
  });
406
480
 
407
481
  const reqId = crypto.randomUUID();
408
482
 
409
- opts?.signal?.addEventListener("abort", () => { abort(); }, { once: true });
483
+ opts?.signal?.addEventListener(
484
+ "abort",
485
+ () => {
486
+ abort();
487
+ },
488
+ { once: true },
489
+ );
410
490
 
411
491
  const reader = new IpcFrameReader(
412
492
  (envelope) => {
413
493
  // Non-streaming envelope with error (e.g. method not found, auth failure)
414
494
  if (envelope.id !== reqId) return;
415
495
  const msg = envelope as IpcResponse;
416
- finishError({ ok: false, error: msg.error ?? "Unexpected non-streaming response",
496
+ finishError({
497
+ ok: false,
498
+ error: msg.error ?? "Unexpected non-streaming response",
417
499
  ...(msg.statusCode != null && { statusCode: msg.statusCode }),
418
500
  ...(msg.errorCode != null && { errorCode: msg.errorCode }),
419
- ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }) });
501
+ ...(msg.errorDetails != null && { errorDetails: msg.errorDetails }),
502
+ });
420
503
  },
421
504
  (err) => finishError({ ok: false, error: err.message }),
422
505
  {
@@ -437,7 +520,12 @@ export async function cliIpcCallStream(
437
520
  });
438
521
  settled = true;
439
522
  clearTimeout(connectTimer);
440
- resolve({ ok: true, headers: (envelope.headers ?? {}) as Record<string, string>, body, abort });
523
+ resolve({
524
+ ok: true,
525
+ headers: (envelope.headers ?? {}) as Record<string, string>,
526
+ body,
527
+ abort,
528
+ });
441
529
  },
442
530
  onStreamChunk: (chunk) => {
443
531
  streamController?.enqueue(chunk);
@@ -455,8 +543,14 @@ export async function cliIpcCallStream(
455
543
  writeMessage(socket, { id: reqId, method, params });
456
544
 
457
545
  firstByteTimer = setTimeout(() => {
458
- log.debug({ method, socketPath, timeoutMs: firstByteTimeoutMs }, "CLI IPC stream first-byte timeout");
459
- finishError({ ok: false, error: "Stream timed out waiting for first byte" });
546
+ log.debug(
547
+ { method, socketPath, timeoutMs: firstByteTimeoutMs },
548
+ "CLI IPC stream first-byte timeout",
549
+ );
550
+ finishError({
551
+ ok: false,
552
+ error: "Stream timed out waiting for first byte",
553
+ });
460
554
  }, firstByteTimeoutMs);
461
555
 
462
556
  socket.on("data", (chunk) => {
@@ -491,13 +585,21 @@ export function exitFromIpcResult(
491
585
  _cmd?: unknown,
492
586
  ): never {
493
587
  process.stderr.write((r.error ?? "Unknown error") + "\n");
494
- if (r.statusCode === undefined) {
495
- process.exit(10);
496
- } else if (r.statusCode >= 500) {
497
- process.exit(3);
498
- } else if (r.statusCode >= 400) {
499
- process.exit(2);
500
- } else {
501
- process.exit(1);
502
- }
588
+ process.exit(exitCodeFromIpcResult(r));
589
+ }
590
+
591
+ /**
592
+ * Map an IPC error result to its CLI process exit code without exiting.
593
+ *
594
+ * Use this when callers want to emit a structured response (e.g. a JSON
595
+ * error envelope in `--json` mode) before terminating with the same status
596
+ * code that {@link exitFromIpcResult} would produce.
597
+ *
598
+ * Exit code matrix matches {@link exitFromIpcResult}.
599
+ */
600
+ export function exitCodeFromIpcResult(r: { statusCode?: number }): number {
601
+ if (r.statusCode === undefined) return 10;
602
+ if (r.statusCode >= 500) return 3;
603
+ if (r.statusCode >= 400) return 2;
604
+ return 1;
503
605
  }