@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
@@ -92,6 +92,19 @@ export interface HistorySurface {
92
92
  completionSummary?: string;
93
93
  }
94
94
 
95
+ /**
96
+ * Positional reference to a file attachment captured while walking the
97
+ * content array. The index of an entry in `RenderedHistoryContent.attachments`
98
+ * is what `contentOrder` references as `attachment:N`.
99
+ */
100
+ export interface HistoryAttachmentRef {
101
+ /** Stable DB attachment id when persisted on the file block (`_attachmentId`). */
102
+ attachmentId?: string;
103
+ filename: string;
104
+ mimeType: string;
105
+ sizeBytes: number;
106
+ }
107
+
95
108
  export interface RenderedHistoryContent {
96
109
  text: string;
97
110
  toolCalls: HistoryToolCall[];
@@ -99,12 +112,18 @@ export interface RenderedHistoryContent {
99
112
  toolCallsBeforeText: boolean;
100
113
  /** Text segments split by tool-call boundaries. */
101
114
  textSegments: string[];
102
- /** Content block ordering using "text:N", "tool:N", "surface:N" encoding. */
115
+ /** Content block ordering using "text:N", "tool:N", "surface:N", "attachment:N" encoding. */
103
116
  contentOrder: string[];
104
117
  /** UI surfaces (widgets) embedded in the message. */
105
118
  surfaces: HistorySurface[];
106
119
  /** Thinking segments extracted from thinking blocks. */
107
120
  thinkingSegments: string[];
121
+ /**
122
+ * File attachments captured in content order. Index `N` matches an
123
+ * `attachment:N` entry in `contentOrder`. Callers align their DB-sourced
124
+ * attachment metadata to this ordering for inline placement.
125
+ */
126
+ attachments: HistoryAttachmentRef[];
108
127
  }
109
128
 
110
129
  /**
@@ -115,6 +134,8 @@ export interface RenderedHistoryContent {
115
134
  export interface SlackInboundMessageMetadata {
116
135
  /** Slack channel id (conversation external id) — recorded as `channelId`. */
117
136
  channelId: string;
137
+ /** Human-readable Slack channel name, when the gateway supplied it. */
138
+ channelName?: string;
118
139
  /** Slack `ts` for this message — required so persistence can record `channelTs`. */
119
140
  channelTs: string;
120
141
  /** Parent `thread_ts` when the message lives inside a thread; absent for top-level. */
@@ -123,6 +144,18 @@ export interface SlackInboundMessageMetadata {
123
144
  displayName?: string;
124
145
  /** Canonical Slack external user id for the sender, when available. */
125
146
  actorExternalUserId?: string;
147
+ /** Raw Slack profile timezone for the sender, when supplied. */
148
+ actorTimezone?: string;
149
+ /** Compact Slack profile timezone label for the sender, when supplied. */
150
+ actorTimezoneLabel?: string;
151
+ /** Raw Slack profile timezone offset in seconds, when supplied. */
152
+ actorTimezoneOffsetSeconds?: number;
153
+ /** Timezone used to render this message's timestamp. */
154
+ timestampTimezone?: string;
155
+ /** Compact label for the rendered timestamp timezone. */
156
+ timestampTimezoneLabel?: string;
157
+ /** Compact timezone label appended to the rendered speaker name. */
158
+ speakerTimezoneLabel?: string;
126
159
  }
127
160
 
128
161
  /**
@@ -144,8 +177,6 @@ export interface ConversationCreateOptions {
144
177
  authContext?: AuthContext;
145
178
  /** Whether this turn can block on interactive approval prompts. */
146
179
  isInteractive?: boolean;
147
- /** Slack-only non-persisted notice injected into the active model turn. */
148
- slackRuntimeContextNotice?: string;
149
180
  /**
150
181
  * Persisted user-facing content. When present, storage/UI use this value
151
182
  * while the model-facing turn continues to use `content`.
@@ -192,22 +223,42 @@ function clampAttachmentText(text: string): string {
192
223
  return `${text.slice(0, HISTORY_ATTACHMENT_TEXT_LIMIT)}<truncated />`;
193
224
  }
194
225
 
195
- function renderFileBlockForHistory(block: Record<string, unknown>): string {
226
+ interface FileBlockMetadata {
227
+ mediaType: string;
228
+ filename: string;
229
+ sizeBytes: number;
230
+ }
231
+
232
+ function extractFileBlockMetadata(
233
+ block: Record<string, unknown>,
234
+ ): FileBlockMetadata {
196
235
  const source = isRecord(block.source) ? block.source : null;
197
- const mediaType =
198
- source && typeof source.media_type === "string"
199
- ? source.media_type
200
- : "application/octet-stream";
201
- const filename =
202
- source && typeof source.filename === "string"
203
- ? source.filename
204
- : "attachment";
205
- const sizeBytes =
206
- source && typeof source.data === "string"
207
- ? estimateBase64Bytes(source.data)
208
- : 0;
209
- const summaryParts = [`[File attachment] ${filename}`, `type=${mediaType}`];
210
- if (sizeBytes > 0) summaryParts.push(`size=${formatBytes(sizeBytes)}`);
236
+ return {
237
+ mediaType:
238
+ source && typeof source.media_type === "string"
239
+ ? source.media_type
240
+ : "application/octet-stream",
241
+ filename:
242
+ source && typeof source.filename === "string"
243
+ ? source.filename
244
+ : "attachment",
245
+ sizeBytes:
246
+ source && typeof source.data === "string"
247
+ ? estimateBase64Bytes(source.data)
248
+ : 0,
249
+ };
250
+ }
251
+
252
+ function renderFileBlockForHistory(
253
+ block: Record<string, unknown>,
254
+ meta: FileBlockMetadata,
255
+ ): string {
256
+ const summaryParts = [
257
+ `[File attachment] ${meta.filename}`,
258
+ `type=${meta.mediaType}`,
259
+ ];
260
+ if (meta.sizeBytes > 0)
261
+ summaryParts.push(`size=${formatBytes(meta.sizeBytes)}`);
211
262
 
212
263
  const extractedText =
213
264
  typeof block.extracted_text === "string" ? block.extracted_text.trim() : "";
@@ -237,11 +288,13 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
237
288
  contentOrder: text ? ["text:0"] : [],
238
289
  surfaces: [],
239
290
  thinkingSegments: [],
291
+ attachments: [],
240
292
  };
241
293
  }
242
294
 
243
295
  const textParts: string[] = [];
244
296
  const attachmentParts: string[] = [];
297
+ const attachments: HistoryAttachmentRef[] = [];
245
298
  const toolCalls: HistoryToolCall[] = [];
246
299
  const surfaces: HistorySurface[] = [];
247
300
  const thinkingSegments: string[] = [];
@@ -351,7 +404,19 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
351
404
  continue;
352
405
  }
353
406
  if (block.type === "file") {
354
- attachmentParts.push(renderFileBlockForHistory(block));
407
+ const meta = extractFileBlockMetadata(block);
408
+ attachmentParts.push(renderFileBlockForHistory(block, meta));
409
+ finalizeSegment();
410
+ const ref: HistoryAttachmentRef = {
411
+ filename: meta.filename,
412
+ mimeType: meta.mediaType,
413
+ sizeBytes: meta.sizeBytes,
414
+ };
415
+ if (typeof block._attachmentId === "string" && block._attachmentId) {
416
+ ref.attachmentId = block._attachmentId;
417
+ }
418
+ attachments.push(ref);
419
+ contentOrder.push(`attachment:${attachments.length - 1}`);
355
420
  continue;
356
421
  }
357
422
  if (block.type === "image") {
@@ -495,17 +560,14 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
495
560
  matched.imageData = imageDataList[0];
496
561
  matched.imageDataList = imageDataList;
497
562
  }
498
- } else {
499
- toolCalls.push({
500
- name: "unknown",
501
- input: {},
502
- result: resultContent,
503
- isError,
504
- ...(imageDataList.length > 0
505
- ? { imageData: imageDataList[0], imageDataList }
506
- : {}),
507
- });
508
563
  }
564
+ // Orphan tool_result with no matching tool_use — drop it. Synthesizing
565
+ // a "name: 'unknown'" phantom entry rendered in chat as "Used unknown"
566
+ // / "Completed 1 step" with no context, with a timestamp later than
567
+ // the assistant's final answer. Most commonly orphans appear when
568
+ // context-window compaction trims the parent tool_use block while
569
+ // leaving the paired tool_result. Losing the orphan's result content
570
+ // is correct: without the parent we can't tell the user what tool ran.
509
571
  continue;
510
572
  }
511
573
  }
@@ -541,6 +603,7 @@ export function renderHistoryContent(content: unknown): RenderedHistoryContent {
541
603
  contentOrder,
542
604
  surfaces,
543
605
  thinkingSegments,
606
+ attachments,
544
607
  };
545
608
  }
546
609
 
@@ -22,10 +22,11 @@
22
22
  * `running`; the rollback path on a failed `start` restores from the
23
23
  * current confirmed pointer (not from a per-call snapshot of a sibling
24
24
  * optimistic write), so two overlapping starts that both fail cannot
25
- * leave a phantom lock and a late-arriving `running` for an older
26
- * overlapping start still updates the confirmed pointer so the lock
27
- * survives a subsequent rollback of the newer start. The lock is
28
- * released outright when the owning proxy's `dispose()` fires.
25
+ * leave a phantom lock. Each session carries a monotonic `dispatchedAt`
26
+ * counter so out-of-order `running` responses promote in dispatch order:
27
+ * the latest-dispatched start that the host confirms becomes the
28
+ * confirmed baseline, regardless of which response arrived last. The
29
+ * lock is released outright when the owning proxy's `dispose()` fires.
29
30
  *
30
31
  * `app_control_start` is the only tool that can acquire the lock — the
31
32
  * user's medium-risk approval at start time is the consent boundary. All
@@ -84,8 +85,22 @@ export interface ActiveAppControlSession {
84
85
  * the `app` of subsequent non-start tool calls.
85
86
  */
86
87
  app: string;
88
+ /**
89
+ * Strictly monotonic counter assigned when the session is created (in
90
+ * `request()` for a `start`). Used by {@link promoteStartIfCurrent} to
91
+ * tell which of two confirmations from overlapping starts is newer when
92
+ * host responses arrive out of order. Larger values are newer.
93
+ */
94
+ dispatchedAt: number;
87
95
  }
88
96
 
97
+ /**
98
+ * Monotonic counter that stamps each `start`'s {@link
99
+ * ActiveAppControlSession.dispatchedAt}. Process-lifetime monotonic; the
100
+ * absolute value is meaningless — only ordering matters.
101
+ */
102
+ let nextDispatchedAt = 1;
103
+
89
104
  /**
90
105
  * Currently active session, or `undefined` when no session is held. This
91
106
  * is the optimistic value: it is set the moment a `start` is dispatched
@@ -112,6 +127,13 @@ export function _getActiveAppControlSession():
112
127
  return activeAppControlSession;
113
128
  }
114
129
 
130
+ /** Test-only helper: read the last host-confirmed session. */
131
+ export function _getConfirmedAppControlSession():
132
+ | ActiveAppControlSession
133
+ | undefined {
134
+ return confirmedAppControlSession;
135
+ }
136
+
115
137
  /** Test-only helper: clear both session pointers between test cases. */
116
138
  export function _resetActiveAppControlSession(): void {
117
139
  activeAppControlSession = undefined;
@@ -123,11 +145,18 @@ export function _resetActiveAppControlSession(): void {
123
145
  * round-trip. Useful for tests that exercise non-start tool paths and
124
146
  * don't need to verify the start flow itself.
125
147
  */
126
- export function _setActiveAppControlSession(
127
- session: ActiveAppControlSession,
128
- ): void {
129
- activeAppControlSession = session;
130
- confirmedAppControlSession = session;
148
+ export function _setActiveAppControlSession(session: {
149
+ conversationId: string;
150
+ app: string;
151
+ dispatchedAt?: number;
152
+ }): void {
153
+ const full: ActiveAppControlSession = {
154
+ conversationId: session.conversationId,
155
+ app: session.app,
156
+ dispatchedAt: session.dispatchedAt ?? nextDispatchedAt++,
157
+ };
158
+ activeAppControlSession = full;
159
+ confirmedAppControlSession = full;
131
160
  }
132
161
 
133
162
  /**
@@ -280,6 +309,7 @@ export class HostAppControlProxy extends HostProxyBase<
280
309
  attemptedSession = {
281
310
  conversationId: this.conversationId,
282
311
  app: input.app,
312
+ dispatchedAt: nextDispatchedAt++,
283
313
  };
284
314
  activeAppControlSession = attemptedSession;
285
315
  } else {
@@ -356,15 +386,27 @@ export class HostAppControlProxy extends HostProxyBase<
356
386
  }
357
387
 
358
388
  /**
359
- * Promote this start's optimistic write to the confirmed pointer when
360
- * the host returns `running`. Gated on conversation ownership rather
361
- * than object identity: a newer overlapping start in the same
362
- * conversation may have superseded our optimistic write while we were
363
- * waiting on the host, but the host's `running` response for our
364
- * `attempted` is still ground-truth that the lock should be held.
365
- * The conversation-ownership check ensures we don't resurrect a session
366
- * after `dispose()` cleared the lock or after another conversation
367
- * acquired it.
389
+ * Promote this start's session to the confirmed pointer when the host
390
+ * returns `running`. Two gates:
391
+ *
392
+ * 1. The live optimistic write must still belong to this conversation —
393
+ * if `dispose()` cleared the lock or another conversation acquired
394
+ * it, this confirmation must not resurrect a stale session.
395
+ * 2. The confirming session must be at least as recent as the currently
396
+ * confirmed one, compared via {@link
397
+ * ActiveAppControlSession.dispatchedAt}. The dispatch counter is
398
+ * assigned synchronously in `request()`, so it captures dispatch
399
+ * order even when host responses arrive out of order. The latest
400
+ * dispatched start that confirms wins, which is the right baseline
401
+ * for the rollback path: if a newer start later fails, rollback
402
+ * restores the most recently confirmed session, not an older one.
403
+ *
404
+ * Also advance the active pointer when it is strictly older than the
405
+ * newly-confirmed session. This handles the case where an even newer
406
+ * optimistic write has already failed and rolled active back to the
407
+ * previous confirmed session; without this, observe/actions for the
408
+ * newly-confirmed session would target the older app. A newer
409
+ * in-flight optimistic write (higher `dispatchedAt`) is preserved.
368
410
  */
369
411
  private promoteStartIfCurrent(
370
412
  attempted: ActiveAppControlSession | undefined,
@@ -373,7 +415,16 @@ export class HostAppControlProxy extends HostProxyBase<
373
415
  if (activeAppControlSession?.conversationId !== attempted.conversationId) {
374
416
  return;
375
417
  }
418
+ if (
419
+ confirmedAppControlSession != null &&
420
+ attempted.dispatchedAt <= confirmedAppControlSession.dispatchedAt
421
+ ) {
422
+ return;
423
+ }
376
424
  confirmedAppControlSession = attempted;
425
+ if (activeAppControlSession.dispatchedAt < attempted.dispatchedAt) {
426
+ activeAppControlSession = attempted;
427
+ }
377
428
  }
378
429
 
379
430
  /**
@@ -185,7 +185,7 @@ export class HostBashProxy extends HostProxyBase<
185
185
  timedOut: boolean;
186
186
  },
187
187
  ): void {
188
- pendingInteractions.resolve(requestId);
188
+ pendingInteractions.resolve(requestId, "answered");
189
189
  this.resolve(requestId, response);
190
190
  }
191
191
  }
@@ -280,7 +280,7 @@ export class HostCuProxy {
280
280
  observation: CuObservationResult,
281
281
  ): ToolExecutionResult | undefined {
282
282
  this._ownedRequests.delete(requestId);
283
- const interaction = pendingInteractions.resolve(requestId);
283
+ const interaction = pendingInteractions.resolve(requestId, "answered");
284
284
  if (!interaction?.rpcResolve) {
285
285
  log.warn({ requestId }, "No pending host CU request for response");
286
286
  return undefined;
@@ -227,7 +227,7 @@ export class HostFileProxy {
227
227
  requestId: string,
228
228
  response: { content: string; isError: boolean; imageData?: string },
229
229
  ): void {
230
- const interaction = pendingInteractions.resolve(requestId);
230
+ const interaction = pendingInteractions.resolve(requestId, "answered");
231
231
  if (!interaction?.rpcResolve) {
232
232
  log.warn({ requestId }, "No pending host file request for response");
233
233
  return;
@@ -29,6 +29,9 @@
29
29
  import type { HostProxyCapability, InterfaceId } from "../channels/types.js";
30
30
  import { supportsHostProxy } from "../channels/types.js";
31
31
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
32
+ import { getLogger } from "../util/logger.js";
33
+
34
+ const log = getLogger("host-proxy-preactivation");
32
35
 
33
36
  /**
34
37
  * Subset of Conversation/ProcessConversationContext that
@@ -36,9 +39,29 @@ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
36
39
  * `ProcessConversationContext` satisfy this structurally.
37
40
  */
38
41
  export interface HostProxyPreactivationTarget {
42
+ readonly conversationId: string;
39
43
  addPreactivatedSkillId(id: string): void;
40
44
  }
41
45
 
46
+ /**
47
+ * Why an attachment decision went the way it did. Logged per turn so that
48
+ * silent-gate failures (e.g. ATL-609: computer-use never reaches the LLM
49
+ * surface for a macOS user) can be diagnosed from production logs without
50
+ * extra instrumentation.
51
+ */
52
+ export type HostProxyAttachmentReason =
53
+ | "native_support"
54
+ | "cross_client"
55
+ | "denied_no_interface"
56
+ | "denied_chrome_extension"
57
+ | "denied_no_clients";
58
+
59
+ export interface HostProxyAttachmentDecision {
60
+ shouldAttach: boolean;
61
+ reason: HostProxyAttachmentReason;
62
+ clientCount?: number;
63
+ }
64
+
42
65
  /**
43
66
  * Registry mapping each host-proxy capability to the skill that must be
44
67
  * preactivated when that capability is supported by the source interface.
@@ -62,45 +85,89 @@ export const HOST_PROXY_SKILL_PREACTIVATIONS: ReadonlyArray<{
62
85
  ];
63
86
 
64
87
  /**
65
- * Returns true when a host-proxy for the given capability should be attached
66
- * (instantiated and preactivated) for the current turn. Two cases qualify:
88
+ * Returns the full attachment decision for a host-proxy capability used both
89
+ * to gate proxy instantiation and to feed the structured preactivation log so
90
+ * silent gates can be diagnosed without re-instrumenting after the fact.
67
91
  *
68
- * 1. The source interface natively supports the capability (e.g. macOS host_cu).
69
- * 2. The source interface doesn't support the capability natively but at least
70
- * one connected client does cross-client routing. `chrome-extension` is
71
- * excluded as a security boundary: it is its own executor context and cannot
72
- * broker cross-client routing to a macOS client.
92
+ * 1. No source interface → `denied_no_interface`.
93
+ * 2. Source interface natively supports the capability `native_support`.
94
+ * 3. `chrome-extension` source can never broker cross-client routing to a
95
+ * macOS client (security boundary) `denied_chrome_extension`.
96
+ * 4. At least one connected client advertises the capability
97
+ * `cross_client` with `clientCount`.
98
+ * 5. Otherwise → `denied_no_clients` with `clientCount: 0`.
73
99
  *
74
- * This is the single source of truth for both preactivation and proxy
75
- * instantiation, so the two decisions stay in sync.
100
+ * Single source of truth for preactivation and proxy instantiation.
101
+ */
102
+ export function evaluateHostProxyAttachment(
103
+ capability: HostProxyCapability,
104
+ sourceInterface: InterfaceId | undefined,
105
+ ): HostProxyAttachmentDecision {
106
+ if (!sourceInterface) {
107
+ return { shouldAttach: false, reason: "denied_no_interface" };
108
+ }
109
+ if (supportsHostProxy(sourceInterface, capability)) {
110
+ return { shouldAttach: true, reason: "native_support" };
111
+ }
112
+ if (sourceInterface === "chrome-extension") {
113
+ return { shouldAttach: false, reason: "denied_chrome_extension" };
114
+ }
115
+ const clientCount =
116
+ assistantEventHub.listClientsByCapability(capability).length;
117
+ if (clientCount > 0) {
118
+ return { shouldAttach: true, reason: "cross_client", clientCount };
119
+ }
120
+ return { shouldAttach: false, reason: "denied_no_clients", clientCount: 0 };
121
+ }
122
+
123
+ /**
124
+ * Boolean wrapper retained for the proxy-instantiation call sites that only
125
+ * need the gate result. Prefer `evaluateHostProxyAttachment` when the reason
126
+ * is also useful (e.g. for logging or telemetry).
76
127
  */
77
128
  export function shouldAttachHostProxyForCapability(
78
129
  capability: HostProxyCapability,
79
130
  sourceInterface: InterfaceId | undefined,
80
131
  ): boolean {
81
- if (!sourceInterface) return false;
82
- if (supportsHostProxy(sourceInterface, capability)) return true;
83
- if (sourceInterface === "chrome-extension") return false;
84
- return assistantEventHub.listClientsByCapability(capability).length > 0;
132
+ return evaluateHostProxyAttachment(capability, sourceInterface).shouldAttach;
85
133
  }
86
134
 
87
135
  /**
88
136
  * Preactivate every host-proxy-backed skill that the given source interface
89
- * supports. No-op when `sourceInterface` is undefined.
137
+ * supports, and emit one structured `log.info` line per turn capturing each
138
+ * capability's decision + the final preactivated skill IDs.
139
+ *
140
+ * The log line fires unconditionally — even when `sourceInterface` is
141
+ * undefined — because "preactivation never ran because no interface" is
142
+ * itself the diagnostic signal we want visible in production.
90
143
  *
91
144
  * Callers are responsible for any additional gating (e.g. only preactivating
92
145
  * when the conversation is idle vs. when re-adding after dequeue), since
93
- * those constraints differ across create vs. drain paths. This helper just
94
- * iterates the registry and dispatches.
146
+ * those constraints differ across create vs. drain paths.
95
147
  */
96
148
  export function preactivateHostProxySkills(
97
149
  conversation: HostProxyPreactivationTarget,
98
150
  sourceInterface: InterfaceId | undefined,
99
151
  ): void {
100
- if (!sourceInterface) return;
152
+ const decisions: Record<string, HostProxyAttachmentDecision> = {};
153
+ const preactivatedSkillIds: string[] = [];
154
+
101
155
  for (const { capability, skillId } of HOST_PROXY_SKILL_PREACTIVATIONS) {
102
- if (shouldAttachHostProxyForCapability(capability, sourceInterface)) {
156
+ const decision = evaluateHostProxyAttachment(capability, sourceInterface);
157
+ decisions[capability] = decision;
158
+ if (decision.shouldAttach) {
103
159
  conversation.addPreactivatedSkillId(skillId);
160
+ preactivatedSkillIds.push(skillId);
104
161
  }
105
162
  }
163
+
164
+ log.info(
165
+ {
166
+ conversationId: conversation.conversationId,
167
+ sourceInterface,
168
+ decisions,
169
+ preactivatedSkillIds,
170
+ },
171
+ "host-proxy preactivation decision",
172
+ );
106
173
  }
@@ -508,7 +508,7 @@ export class HostTransferProxy {
508
508
  errorMessage?: string;
509
509
  },
510
510
  ): void {
511
- const interaction = pendingInteractions.resolve(requestId);
511
+ const interaction = pendingInteractions.resolve(requestId, "answered");
512
512
  if (!interaction?.rpcResolve) {
513
513
  log.warn({ requestId }, "No pending host transfer request for response");
514
514
  return;