@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5

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 (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Schedules the onboarding "Day 2 Check-in" calendar event server-side.
3
+ *
4
+ * Flow:
5
+ * 1. Resolve the Google OAuth connection, requiring the calendar.events scope.
6
+ * No connection / missing scope → a `scheduled: false` result (never an
7
+ * error): the web caller treats this as a best-effort skip.
8
+ * 2. List tomorrow's timed events in the 8am–8pm window (user's timezone) to
9
+ * derive busy intervals. Uses events.list rather than freeBusy.query
10
+ * because the onboarding grant is the narrow `calendar.events` scope, which
11
+ * authorizes events.list/insert but not freeBusy.query.
12
+ * 3. Choose the first open 15-minute slot (12pm–5pm, widening to 8am–8pm).
13
+ * 4. Create the event with the locked title + HTML description, sendUpdates=all.
14
+ *
15
+ * Authenticates via `resolveOAuthConnection("google")` + `connection.request()`,
16
+ * the same path the calendar watcher uses — no skill subprocess.
17
+ */
18
+
19
+ import { canonicalizeTimeZone } from "../daemon/date-context.js";
20
+ import type { OAuthConnection } from "../oauth/connection.js";
21
+ import { resolveOAuthConnection } from "../oauth/connection-resolver.js";
22
+ import { getLogger } from "../util/logger.js";
23
+ import {
24
+ buildCheckinDescription,
25
+ buildCheckinTitle,
26
+ checkinAvailabilityWindow,
27
+ type CheckinNames,
28
+ chooseCheckinSlot,
29
+ extractBusyFromEvents,
30
+ type GcalEvent,
31
+ } from "./checkin-event.js";
32
+
33
+ const log = getLogger("onboarding:schedule-checkin");
34
+
35
+ const GOOGLE_CALENDAR_BASE_URL = "https://www.googleapis.com/calendar/v3";
36
+ const GOOGLE_CALENDAR_EVENTS_SCOPE =
37
+ "https://www.googleapis.com/auth/calendar.events";
38
+ /** Calendar shares the Google OAuth connection with Gmail. */
39
+ const GOOGLE_PROVIDER = "google";
40
+ const PRIMARY_CALENDAR_ID = "primary";
41
+
42
+ export interface ScheduleCheckinInput extends CheckinNames {
43
+ /** IANA timezone reported by the client (e.g. "America/New_York"). */
44
+ timeZone?: string;
45
+ /** Override "now" for deterministic tests. Defaults to the current time. */
46
+ nowMs?: number;
47
+ }
48
+
49
+ export type ScheduleCheckinResult =
50
+ | {
51
+ scheduled: true;
52
+ eventId: string;
53
+ htmlLink: string | null;
54
+ /** Event start, ISO 8601 (UTC). */
55
+ start: string;
56
+ /** Event end, ISO 8601 (UTC). */
57
+ end: string;
58
+ timeZone: string;
59
+ }
60
+ | {
61
+ scheduled: false;
62
+ /**
63
+ * Why nothing was booked. `calendar_unavailable` covers both "not
64
+ * connected" and "calendar scope not granted" — the client surfaces a
65
+ * single best-effort skip either way.
66
+ */
67
+ reason: "calendar_unavailable";
68
+ };
69
+
70
+ interface EventsListResponse {
71
+ items?: GcalEvent[];
72
+ }
73
+
74
+ /** Subset of the events.insert response we surface back to the caller. */
75
+ interface CreatedEvent {
76
+ id?: string;
77
+ htmlLink?: string;
78
+ }
79
+
80
+ /**
81
+ * Schedule the Day 2 check-in. Resolves to a `scheduled: false` result when no
82
+ * calendar is connected; throws only on unexpected Calendar API failures (the
83
+ * route handler maps those to a 5xx, the web caller swallows them).
84
+ */
85
+ export async function scheduleOnboardingCheckin(
86
+ input: ScheduleCheckinInput,
87
+ ): Promise<ScheduleCheckinResult> {
88
+ const timeZone =
89
+ canonicalizeTimeZone(input.timeZone) ??
90
+ // Fall back to the daemon host timezone when the client didn't report one.
91
+ Intl.DateTimeFormat().resolvedOptions().timeZone ??
92
+ "UTC";
93
+ const nowMs = input.nowMs ?? Date.now();
94
+
95
+ let connection: OAuthConnection;
96
+ try {
97
+ connection = await resolveOAuthConnection(GOOGLE_PROVIDER, {
98
+ requiredScopes: [GOOGLE_CALENDAR_EVENTS_SCOPE],
99
+ });
100
+ } catch (err) {
101
+ // No active connection or the calendar scope wasn't granted — skip quietly.
102
+ log.info(
103
+ { err: err instanceof Error ? err.message : String(err) },
104
+ "Check-in skipped: Google Calendar not available",
105
+ );
106
+ return { scheduled: false, reason: "calendar_unavailable" };
107
+ }
108
+
109
+ const { timeMinMs, timeMaxMs } = checkinAvailabilityWindow(nowMs, timeZone);
110
+
111
+ const eventsResp = await connection.request({
112
+ method: "GET",
113
+ path: `/calendars/${encodeURIComponent(PRIMARY_CALENDAR_ID)}/events`,
114
+ query: {
115
+ timeMin: new Date(timeMinMs).toISOString(),
116
+ timeMax: new Date(timeMaxMs).toISOString(),
117
+ // Expand recurring events into instances so each occurrence is a concrete
118
+ // busy interval in the window.
119
+ singleEvents: "true",
120
+ orderBy: "startTime",
121
+ maxResults: "250",
122
+ },
123
+ baseUrl: GOOGLE_CALENDAR_BASE_URL,
124
+ headers: { "Content-Type": "application/json" },
125
+ });
126
+ if (eventsResp.status < 200 || eventsResp.status >= 300) {
127
+ throw new Error(
128
+ `Calendar events.list ${eventsResp.status}: ${stringifyBody(eventsResp.body)}`,
129
+ );
130
+ }
131
+
132
+ const busy = extractBusyFromEvents(
133
+ (eventsResp.body as EventsListResponse).items ?? [],
134
+ );
135
+ const slot = chooseCheckinSlot(nowMs, timeZone, busy);
136
+
137
+ const uuid = crypto.randomUUID();
138
+ const eventResp = await connection.request({
139
+ method: "POST",
140
+ path: `/calendars/${encodeURIComponent(PRIMARY_CALENDAR_ID)}/events`,
141
+ query: { sendUpdates: "all" },
142
+ baseUrl: GOOGLE_CALENDAR_BASE_URL,
143
+ headers: { "Content-Type": "application/json" },
144
+ body: {
145
+ summary: buildCheckinTitle(input),
146
+ description: buildCheckinDescription(uuid),
147
+ start: {
148
+ dateTime: new Date(slot.startMs).toISOString(),
149
+ timeZone,
150
+ },
151
+ end: {
152
+ dateTime: new Date(slot.endMs).toISOString(),
153
+ timeZone,
154
+ },
155
+ },
156
+ });
157
+ if (eventResp.status < 200 || eventResp.status >= 300) {
158
+ throw new Error(
159
+ `Calendar event create ${eventResp.status}: ${stringifyBody(eventResp.body)}`,
160
+ );
161
+ }
162
+
163
+ const event = eventResp.body as CreatedEvent;
164
+ if (!event.id) {
165
+ throw new Error("Calendar event create returned no event id");
166
+ }
167
+
168
+ log.info(
169
+ {
170
+ eventId: event.id,
171
+ start: new Date(slot.startMs).toISOString(),
172
+ window: slot.window,
173
+ timeZone,
174
+ },
175
+ "Scheduled onboarding Day 2 check-in",
176
+ );
177
+
178
+ return {
179
+ scheduled: true,
180
+ eventId: event.id,
181
+ htmlLink: event.htmlLink ?? null,
182
+ start: new Date(slot.startMs).toISOString(),
183
+ end: new Date(slot.endMs).toISOString(),
184
+ timeZone,
185
+ };
186
+ }
187
+
188
+ function stringifyBody(body: unknown): string {
189
+ return typeof body === "string" ? body : JSON.stringify(body ?? "");
190
+ }
@@ -9,7 +9,7 @@ import type {
9
9
 
10
10
  // Use a tiny timeout so the setTimeout branch fires quickly in tests
11
11
  const mockConfig = {
12
- timeouts: { permissionTimeoutSec: 0.05 },
12
+ timeouts: { questionResponseTimeoutSec: 0.05 },
13
13
  };
14
14
  // Preserve every other export from the real config/loader so other
15
15
  // tests in the same `bun test` run (which share module-level mocks)
@@ -156,9 +156,12 @@ export interface QuestionBatchMetadata {
156
156
  * the whole batch to `/v1/question-response` when the user is done — no
157
157
  * per-question accumulator, no partial state machine.
158
158
  *
159
- * Timeout reuses `getConfig().timeouts.permissionTimeoutSec` (default 5 min) —
160
- * questions are user-prompts in the same UX family as permission prompts and
161
- * secret prompts, so they share the same idle-timeout knob.
159
+ * The idle timeout is a backstop (`getConfig().timeouts.questionResponseTimeoutSec`,
160
+ * default 30 min), not the primary way a prompt is dismissed. An interactive
161
+ * user who moves on instead of answering enqueues another message, which
162
+ * supersedes the open prompt at the enqueue handler (see conversation-routes.ts);
163
+ * a non-interactive turn resolves immediately at the tool. The backstop only
164
+ * fires when a prompt is left open with no response and no follow-up message.
162
165
  */
163
166
  export class QuestionPrompter {
164
167
  async prompt(params: QuestionPromptParams): Promise<QuestionPromptResult> {
@@ -199,7 +202,7 @@ export class QuestionPrompter {
199
202
  const requestId = uuid();
200
203
 
201
204
  return new Promise<QuestionPromptResult>((resolve, reject) => {
202
- const timeoutMs = getConfig().timeouts.permissionTimeoutSec * 1000;
205
+ const timeoutMs = getConfig().timeouts.questionResponseTimeoutSec * 1000;
203
206
 
204
207
  // Closure-scoped idempotency guard. Every resolution path (timeout,
205
208
  // abort, route resolution via `rpcResolve`/`rpcReject`) routes through
@@ -37,8 +37,8 @@
37
37
  * (optionally overriding the profile) and run inference through the
38
38
  * workspace's configured profiles and credentials — no plugin-supplied API key
39
39
  *
40
- * - {@link PluginInitContext} — passed to `init` hook at bootstrap
41
- * - {@link PluginShutdownContext} — passed to `shutdown` hook at teardown
40
+ * - {@link InitContext} — passed to `init` hook at bootstrap
41
+ * - {@link ShutdownContext} — passed to `shutdown` hook at teardown
42
42
  * - {@link UserPromptSubmitContext} — passed to `user-prompt-submit` hook,
43
43
  * fired immediately before the agent loop receives a user's prompt
44
44
  * - {@link PostCompactContext} — passed to `post-compact` hook, fired after
@@ -55,7 +55,7 @@
55
55
  * - {@link PostModelCallContext} — passed to `post-model-call` hook, fired at
56
56
  * every model-call outcome (a finalized reply or a provider rejection) to
57
57
  * transform content and decide whether to retry
58
- * - {@link PluginHookFn} — signature every lifecycle hook implements
58
+ * - {@link HookFunction} — signature every lifecycle hook implements
59
59
  * - {@link PluginLogger} — pino-compatible logger shape on the contexts
60
60
  * - {@link ToolDefinition} — author-facing tool spec (default-export shape
61
61
  * for both plugin tool files and workspace tool files)
@@ -98,16 +98,16 @@ export type {
98
98
  export type { LLMCallSite } from "../config/schemas/llm.js";
99
99
  export type {
100
100
  AgentLoopExitReason,
101
+ HookFunction,
102
+ InitContext,
101
103
  ModelProfileInfo,
102
- PluginHookFn,
103
- PluginInitContext,
104
104
  PluginLogger,
105
- PluginShutdownContext,
106
105
  PostCompactContext,
107
106
  PostModelCallContext,
108
107
  PostModelCallDecision,
109
108
  PostToolUseContext,
110
109
  PreModelCallContext,
110
+ ShutdownContext,
111
111
  StopContext,
112
112
  ToolContext,
113
113
  ToolDefinition,
@@ -130,12 +130,12 @@ export type {
130
130
  } from "../runtime/assistant-event-hub.js";
131
131
  export { assistantEventHub } from "../runtime/assistant-event-hub.js";
132
132
  export { getModelProfiles } from "./model-profiles.js";
133
- // Check whether a profile's resolved model can process image input. Resolves
134
- // the effective (provider, model) by merging over the workspace default and
135
- // inferring the provider for model-only profiles, then looks up the model
136
- // catalog's `supportsVision` flag. Handles mix profiles (true if any arm
137
- // supports vision). Fail-open for unknown models. Pair with
138
- // `getModelProfiles()` to inspect the active or candidate profiles.
133
+ // Check whether a model or profile can process image input. Accepts a concrete
134
+ // model id, a profile key, or a `ModelProfileInfo`; a bare string is resolved
135
+ // as a model id first and then as a profile key. Profile resolution merges over
136
+ // the workspace default and infers the provider for model-only profiles, then
137
+ // looks up the model catalog's `supportsVision` flag (mix profiles are
138
+ // vision-capable if any arm is). Returns false when nothing resolves.
139
139
  export { doesSupportVision } from "./vision-support.js";
140
140
  // Resolve a provider for a call site (optionally overriding the profile) so a
141
141
  // plugin can run inference through the workspace's configured profiles and
@@ -57,30 +57,28 @@ export interface PluginLogger {
57
57
  * empty model that with `| null`, not `| undefined`.
58
58
  *
59
59
  * Each known hook key has a documented context shape:
60
- * - `init` — {@link PluginInitContext}
61
- * - `shutdown` — {@link PluginShutdownContext}
60
+ * - `init` — {@link InitContext}
61
+ * - `shutdown` — {@link ShutdownContext}
62
62
  * - `user-prompt-submit` — {@link UserPromptSubmitContext}
63
63
  * - `pre-model-call` — {@link PreModelCallContext}
64
64
  * - `post-tool-use` — {@link PostToolUseContext}
65
65
  * - `stop` — {@link StopContext}
66
66
  * - `post-model-call` — {@link PostModelCallContext}
67
67
  */
68
- export type PluginHookFn<TCtx = unknown> = (
68
+ export type HookFunction<TCtx = unknown> = (
69
69
  ctx: TCtx,
70
70
  ) => Promise<Partial<TCtx> | void>;
71
71
 
72
72
  // ─── Init context ────────────────────────────────────────────────────────────
73
73
 
74
74
  /**
75
- * Context passed to `Plugin.init()` during bootstrap. Carries resolved
76
- * config/credentials, a pino-compatible logger scoped to the plugin, a
77
- * per-plugin writable data directory, and the assistant's version metadata.
75
+ * Context passed to `Plugin.init()` during bootstrap. Carries the resolved
76
+ * config, a pino-compatible logger scoped to the plugin, a per-plugin
77
+ * writable data directory, and the assistant's version metadata.
78
78
  */
79
- export interface PluginInitContext {
79
+ export interface InitContext {
80
80
  /** Parsed config for this plugin (may be `unknown` until the manifest validates). */
81
81
  config: unknown;
82
- /** Resolved credential values keyed by the entries of `manifest.requiresCredential`. */
83
- credentials: Record<string, string>;
84
82
  /** Pino-compatible child logger bound to `{ plugin: <name> }`. */
85
83
  logger: PluginLogger;
86
84
  /** Absolute path to `<workspaceDir>/plugins-data/<plugin>/` (created by bootstrap). */
@@ -127,7 +125,7 @@ export interface ModelProfileInfo {
127
125
 
128
126
  /**
129
127
  * Context passed to the `shutdown` hook during daemon teardown. Kept
130
- * intentionally narrower than {@link PluginInitContext} — most teardown
128
+ * intentionally narrower than {@link InitContext} — most teardown
131
129
  * paths only need to know which assistant version they're shutting
132
130
  * down against (e.g. for version-conditional cleanup of state files
133
131
  * written by a previous boot).
@@ -137,7 +135,7 @@ export interface ModelProfileInfo {
137
135
  * stash a version stamp at init can compare against the same name on
138
136
  * tear-down without keeping their own copy.
139
137
  */
140
- export interface PluginShutdownContext {
138
+ export interface ShutdownContext {
141
139
  /** Assistant semver for compatibility checks inside the plugin. */
142
140
  assistantVersion: string;
143
141
  }
@@ -153,7 +151,7 @@ export interface PluginShutdownContext {
153
151
  *
154
152
  * The hook may transform `latestMessages` either by mutating it in place
155
153
  * (`push` / `splice` / `length = 0`) or by returning a new context with
156
- * a fresh `latestMessages` array — see {@link PluginHookFn}'s polymorphic
154
+ * a fresh `latestMessages` array — see {@link HookFunction}'s polymorphic
157
155
  * return shape. The daemon threads the final `latestMessages` value into
158
156
  * `agentLoop.run()` as the run-messages argument.
159
157
  *
@@ -246,7 +244,7 @@ export interface UserPromptSubmitContext {
246
244
  * their own injected context the same way.
247
245
  *
248
246
  * The hook re-injects by mutating `history` in place (or returning a new
249
- * context with a replacement `history`) — see {@link PluginHookFn}'s
247
+ * context with a replacement `history`) — see {@link HookFunction}'s
250
248
  * polymorphic return shape. The agent loop reads the settled `history` back off
251
249
  * the context and resumes the turn from it. Multiple plugins' hooks chain in
252
250
  * registration order, each seeing the previous plugin's edits.
@@ -303,7 +301,7 @@ export interface PostCompactContext {
303
301
  *
304
302
  * The hook may transform the result either by mutating `toolResponse` in
305
303
  * place (e.g. reassigning `toolResponse.content`) or by returning a new
306
- * context with a fresh `toolResponse` — see {@link PluginHookFn}'s
304
+ * context with a fresh `toolResponse` — see {@link HookFunction}'s
307
305
  * polymorphic return shape. The daemon threads the final `toolResponse`
308
306
  * into the provider-bound history.
309
307
  *
@@ -84,16 +84,16 @@ describe("doesSupportVision", () => {
84
84
  expect(doesSupportVision(profile("text-profile"))).toBe(false);
85
85
  });
86
86
 
87
- test("fails open (true) for an unknown profile key not in config", () => {
87
+ test("returns false for an unknown profile key not in config", () => {
88
88
  setMockConfig({});
89
- expect(doesSupportVision(profile("nonexistent"))).toBe(true);
89
+ expect(doesSupportVision(profile("nonexistent"))).toBe(false);
90
90
  });
91
91
 
92
- test("fails open (true) for an unknown provider/model pair", () => {
92
+ test("returns false for an unknown provider/model pair", () => {
93
93
  setMockConfig({
94
94
  "unknown-model": { provider: "unknown-provider", model: "unknown-model" },
95
95
  });
96
- expect(doesSupportVision(profile("unknown-model"))).toBe(true);
96
+ expect(doesSupportVision(profile("unknown-model"))).toBe(false);
97
97
  });
98
98
 
99
99
  test("inherits provider from llm.default when profile only sets model", () => {
@@ -147,3 +147,27 @@ describe("doesSupportVision", () => {
147
147
  expect(doesSupportVision(profile("mix-profile"))).toBe(false);
148
148
  });
149
149
  });
150
+
151
+ describe("doesSupportVision with a bare string", () => {
152
+ test("returns true for a known vision-capable model id", () => {
153
+ expect(doesSupportVision("claude-opus-4-6")).toBe(true);
154
+ });
155
+
156
+ test("returns false for a known text-only model id", () => {
157
+ expect(doesSupportVision("accounts/fireworks/models/glm-5p2")).toBe(false);
158
+ });
159
+
160
+ test("falls back to resolving the string as a profile key", () => {
161
+ // "vision-profile" is not a catalog model id, so it resolves as a profile
162
+ // key → anthropic/claude-opus-4-6 (vision-capable).
163
+ setMockConfig({
164
+ "vision-profile": { provider: "anthropic", model: "claude-opus-4-6" },
165
+ });
166
+ expect(doesSupportVision("vision-profile")).toBe(true);
167
+ });
168
+
169
+ test("returns false for a string that is neither a model nor a profile", () => {
170
+ setMockConfig({});
171
+ expect(doesSupportVision("some-unknown-string")).toBe(false);
172
+ });
173
+ });
@@ -3,10 +3,17 @@
3
3
  *
4
4
  * A plugin that gates image processing on vision capability (e.g. an
5
5
  * image-to-text fallback for text-only models) calls {@link doesSupportVision}
6
- * instead of hardcoding model names. The function resolves the effective
7
- * (provider, model) for a profile — merging with `llm.default` to fill gaps,
8
- * inferring the provider for model-only profiles via the catalog and then
9
- * looks up `supportsVision` in the model catalog.
6
+ * instead of hardcoding model names. One entry point serves both shapes a
7
+ * caller might hold:
8
+ * - a concrete model id (e.g. the provider-reported model that just ran), and
9
+ * - a profile either a {@link ModelProfileInfo} or a bare profile key —
10
+ * resolved through `llm.profiles` to an effective `(provider, model)`.
11
+ *
12
+ * A bare string is tried as a model id first and then as a profile key, so the
13
+ * two callers share one function. Resolution returns `false` when nothing
14
+ * resolves (rather than failing open): a consumer gating an image→text
15
+ * fallback wants an unknown model treated as "can't show images" — caption it —
16
+ * over silently shipping a raw image to a provider that may reject it.
10
17
  */
11
18
 
12
19
  import { getConfig } from "../config/loader.js";
@@ -17,46 +24,74 @@ import {
17
24
  import type { ModelProfileInfo } from "./types.js";
18
25
 
19
26
  /**
20
- * Whether a profile's resolved model can process image input.
27
+ * Whether the given model or profile can process image input.
21
28
  *
22
- * Resolution mirrors the host's call-site resolver:
23
- * - The profile's `(provider, model)` fields are merged over `llm.default` so
24
- * a profile that only sets `model` (or only `provider`) inherits the other
25
- * from the workspace default.
26
- * - When `provider` is still missing but `model` is a known catalog model,
27
- * the provider is inferred via `getCatalogProviderForModel` (same logic as
28
- * the resolver's `withImpliedProviderForKnownModel`).
29
- * - For a mix profile, returns `true` if any constituent arm supports vision
30
- * (the mix can route to it) and `false` only if every arm is text-only.
31
- * - Unknown `(provider, model)` pairs default to `true` (fail-open), matching
32
- * the config GET route's `enrichProfilesWithVisionFlag`.
29
+ * `modelOrProfile` may be a concrete model id, a profile key, or a
30
+ * {@link ModelProfileInfo}. A bare string is resolved as a model id first and,
31
+ * failing that, as a profile key. Returns `false` when nothing resolves.
32
+ */
33
+ export function doesSupportVision(
34
+ modelOrProfile: ModelProfileInfo | string,
35
+ ): boolean {
36
+ if (typeof modelOrProfile === "string") {
37
+ // Concrete model id first, then fall back to treating it as a profile key.
38
+ return (
39
+ modelVision(modelOrProfile) ?? profileVision(modelOrProfile) ?? false
40
+ );
41
+ }
42
+ return profileVision(modelOrProfile.key) ?? false;
43
+ }
44
+
45
+ /**
46
+ * Catalog vision flag for a concrete model id, or `undefined` when the catalog
47
+ * doesn't know the model. The same model id carries the same capability under
48
+ * every provider that offers it, so the first catalog match wins.
33
49
  */
34
- export function doesSupportVision(profile: ModelProfileInfo): boolean {
50
+ function modelVision(model: string): boolean | undefined {
51
+ for (const provider of PROVIDER_CATALOG) {
52
+ const catalogModel = provider.models.find((m) => m.id === model);
53
+ if (catalogModel != null) return catalogModel.supportsVision ?? false;
54
+ }
55
+ return undefined;
56
+ }
57
+
58
+ /**
59
+ * Resolve a profile key through `llm.profiles` to its vision capability, or
60
+ * `undefined` when the key is unknown or resolves to a model the catalog
61
+ * doesn't know. A mix profile resolves to `true` if any arm supports vision
62
+ * (the mix can route to it) and `false` only once every arm is a known
63
+ * text-only model.
64
+ */
65
+ function profileVision(profileKey: string): boolean | undefined {
35
66
  const { llm } = getConfig();
36
- const entry = llm.profiles[profile.key];
37
- if (entry == null) return true;
67
+ const entry = llm.profiles[profileKey];
68
+ if (entry == null) return undefined;
38
69
 
39
- // Mix: fail-open if any arm supports vision.
40
70
  if (entry.mix != null) {
41
- return entry.mix.some((arm) => {
71
+ let sawUnknown = false;
72
+ for (const arm of entry.mix) {
42
73
  const armEntry = llm.profiles[arm.profile];
43
- if (armEntry == null) return true;
44
- return resolveEntrySupportsVision(armEntry, llm);
45
- });
74
+ const armVision =
75
+ armEntry == null ? undefined : resolveEntryVision(armEntry, llm);
76
+ if (armVision === true) return true;
77
+ if (armVision == null) sawUnknown = true;
78
+ }
79
+ return sawUnknown ? undefined : false;
46
80
  }
47
81
 
48
- return resolveEntrySupportsVision(entry, llm);
82
+ return resolveEntryVision(entry, llm);
49
83
  }
50
84
 
51
85
  /**
52
86
  * Resolve whether a concrete (non-mix) profile entry supports vision by
53
- * merging its fields over `llm.default` and inferring the provider when
54
- * only the model is set.
87
+ * merging its fields over `llm.default` and inferring the provider when only
88
+ * the model is set. Returns `undefined` when the effective `(provider, model)`
89
+ * can't be determined or isn't in the catalog.
55
90
  */
56
- function resolveEntrySupportsVision(
91
+ function resolveEntryVision(
57
92
  entry: { provider?: string; model?: string },
58
93
  llm: { default?: { provider?: string; model?: string } },
59
- ): boolean {
94
+ ): boolean | undefined {
60
95
  const provider = entry.provider ?? llm.default?.provider;
61
96
  const model = entry.model ?? llm.default?.model;
62
97
 
@@ -67,12 +102,12 @@ function resolveEntrySupportsVision(
67
102
  (typeof model === "string" ? getCatalogProviderForModel(model) : undefined);
68
103
 
69
104
  if (typeof effectiveProvider !== "string" || typeof model !== "string") {
70
- return true; // fail-open
105
+ return undefined;
71
106
  }
72
107
 
73
108
  const catalogProvider = PROVIDER_CATALOG.find(
74
109
  (p) => p.id === effectiveProvider,
75
110
  );
76
111
  const catalogModel = catalogProvider?.models.find((m) => m.id === model);
77
- return catalogModel?.supportsVision ?? true;
112
+ return catalogModel?.supportsVision;
78
113
  }
@@ -46,7 +46,7 @@
46
46
  * ignores the decision — so the hook returns early for both.
47
47
  */
48
48
 
49
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
49
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
50
50
 
51
51
  import type { ContentBlock, Message } from "../../../../providers/types.js";
52
52
  import {
@@ -112,7 +112,7 @@ function currentCycleMessages(
112
112
  return messages;
113
113
  }
114
114
 
115
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
115
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
116
116
  // A provider rejection carries no turn content to assess (a recovery hook
117
117
  // owns the rejection); the sibling `stop` hook clears the mark when the turn
118
118
  // terminates.
@@ -11,11 +11,11 @@
11
11
  * refused).
12
12
  */
13
13
 
14
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
14
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
15
15
 
16
16
  import { clearEmptyResponseNudged } from "../nudge-state-store.js";
17
17
 
18
- const stop: PluginHookFn<StopContext> = async (ctx) => {
18
+ const stop: HookFunction<StopContext> = async (ctx) => {
19
19
  clearEmptyResponseNudged(ctx.conversationId);
20
20
  };
21
21
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Default `post-tool-use` hook: when a turn's exploration tool calls (bash,
3
- * file_read, file_list) show drift — a long unbroken run with no text sent to
3
+ * code_search, file_read, file_list) show drift — a long unbroken run with no text sent to
4
4
  * the user, or the model re-issuing the exact same call — surface a notice
5
5
  * via `additionalContext` that coaches it to (a) give the user a brief
6
6
  * progress summary and (b) delegate the rest of the investigation to an
@@ -55,7 +55,7 @@
55
55
  * per-tool-result hot path.
56
56
  */
57
57
 
58
- import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
58
+ import type { HookFunction, PostToolUseContext } from "@vellumai/plugin-api";
59
59
 
60
60
  import type { Message } from "../../../../providers/types.js";
61
61
 
@@ -105,6 +105,7 @@ const LOOP_PRONE_MODEL_PATTERN = /kimi-k2[p.]6|minimax-m3/i;
105
105
  /** Read-only exploration tools whose unbroken runs indicate inline drift. */
106
106
  const EXPLORATION_TOOL_NAMES: ReadonlySet<string> = new Set([
107
107
  "bash",
108
+ "code_search",
108
109
  "file_read",
109
110
  "file_list",
110
111
  ]);
@@ -232,7 +233,7 @@ function currentCallRepeatCount(
232
233
  return count;
233
234
  }
234
235
 
235
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
236
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
236
237
  const usesById = toolUsesById(ctx.messages);
237
238
  const currentUse = usesById.get(ctx.toolResponse.tool_use_id);
238
239
  if (currentUse === undefined || !EXPLORATION_TOOL_NAMES.has(currentUse.name))
@@ -23,7 +23,7 @@
23
23
  * empty-response hook; only a provider rejection is this hook's to act on.
24
24
  */
25
25
 
26
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
26
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
27
27
 
28
28
  import {
29
29
  isOrderingRepairAttempted,
@@ -31,7 +31,7 @@ import {
31
31
  } from "../repair-state-store.js";
32
32
  import { deepRepairHistory, isRepairableOrderingError } from "../terminal.js";
33
33
 
34
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
34
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
35
35
  if (
36
36
  ctx.error &&
37
37
  isRepairableOrderingError(ctx.error.message) &&
@@ -11,11 +11,11 @@
11
11
  * retry the loop's per-run backstop refused).
12
12
  */
13
13
 
14
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
14
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
15
15
 
16
16
  import { clearOrderingRepairAttempted } from "../repair-state-store.js";
17
17
 
18
- const stop: PluginHookFn<StopContext> = async (ctx) => {
18
+ const stop: HookFunction<StopContext> = async (ctx) => {
19
19
  clearOrderingRepairAttempted(ctx.conversationId);
20
20
  };
21
21