@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2

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 (430) hide show
  1. package/bun.lock +0 -20
  2. package/docs/workspace-tools.md +33 -42
  3. package/eslint-rules/cli-no-daemon-internals.js +0 -6
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
  5. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
  8. package/node_modules/@vellumai/service-contracts/package.json +0 -1
  9. package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
  10. package/openapi.yaml +0 -155
  11. package/package.json +1 -4
  12. package/scripts/test.sh +15 -36
  13. package/src/__tests__/actor-token-service.test.ts +14 -36
  14. package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
  15. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
  16. package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
  17. package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
  18. package/src/__tests__/annotate-risk-options.test.ts +0 -2
  19. package/src/__tests__/approval-cascade.test.ts +0 -2
  20. package/src/__tests__/assistant-attachments.test.ts +0 -42
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  22. package/src/__tests__/btw-routes.test.ts +0 -2
  23. package/src/__tests__/build-persisted-content.test.ts +0 -2
  24. package/src/__tests__/call-controller.test.ts +0 -19
  25. package/src/__tests__/channel-guardian.test.ts +58 -94
  26. package/src/__tests__/channel-reply-delivery.test.ts +0 -2
  27. package/src/__tests__/compaction-events.test.ts +0 -2
  28. package/src/__tests__/compaction.benchmark.test.ts +0 -2
  29. package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
  30. package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
  31. package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
  32. package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
  33. package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
  34. package/src/__tests__/config-loader-backfill.test.ts +10 -123
  35. package/src/__tests__/config-schema.test.ts +0 -1
  36. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
  37. package/src/__tests__/contacts-relay-reads.test.ts +15 -13
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
  39. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  42. package/src/__tests__/conversation-agent-loop.test.ts +0 -134
  43. package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
  44. package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
  45. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
  46. package/src/__tests__/conversation-history-web-search.test.ts +0 -2
  47. package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
  48. package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
  49. package/src/__tests__/conversation-pairing.test.ts +0 -2
  50. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +0 -2
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
  53. package/src/__tests__/conversation-queue.test.ts +0 -91
  54. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
  56. package/src/__tests__/conversation-slash-queue.test.ts +0 -2
  57. package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
  58. package/src/__tests__/conversation-speed-override.test.ts +0 -2
  59. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
  60. package/src/__tests__/conversation-title-service.test.ts +0 -2
  61. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
  62. package/src/__tests__/conversation-usage.test.ts +0 -2
  63. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
  64. package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
  65. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
  66. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  67. package/src/__tests__/db-migration-rollback.test.ts +171 -205
  68. package/src/__tests__/db-test-helpers.ts +4 -5
  69. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
  70. package/src/__tests__/disk-pressure-guard.test.ts +0 -41
  71. package/src/__tests__/dm-persistence.test.ts +0 -2
  72. package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
  73. package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
  74. package/src/__tests__/exploration-drift-hook.test.ts +2 -3
  75. package/src/__tests__/filing-service.test.ts +0 -2
  76. package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
  77. package/src/__tests__/guardian-dispatch.test.ts +1 -95
  78. package/src/__tests__/guardian-outbound-http.test.ts +0 -13
  79. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
  80. package/src/__tests__/heartbeat-service.test.ts +0 -2
  81. package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
  82. package/src/__tests__/host-app-control-routes.test.ts +30 -24
  83. package/src/__tests__/host-bash-routes.test.ts +41 -31
  84. package/src/__tests__/host-browser-routes.test.ts +32 -26
  85. package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
  86. package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
  87. package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
  88. package/src/__tests__/http-user-message-parity.test.ts +8 -290
  89. package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
  90. package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
  91. package/src/__tests__/invite-redemption-service.test.ts +0 -198
  92. package/src/__tests__/llm-context-normalization.test.ts +0 -105
  93. package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
  94. package/src/__tests__/llm-usage-store.test.ts +0 -25
  95. package/src/__tests__/mcp-health-check.test.ts +1 -2
  96. package/src/__tests__/media-stream-server-integration.test.ts +0 -127
  97. package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
  98. package/src/__tests__/messaging-send-tool.test.ts +0 -2
  99. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  100. package/src/__tests__/mtime-cache.test.ts +5 -146
  101. package/src/__tests__/native-web-search.test.ts +0 -2
  102. package/src/__tests__/non-member-access-request.test.ts +17 -189
  103. package/src/__tests__/notification-broadcaster.test.ts +0 -4
  104. package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
  105. package/src/__tests__/notification-deep-link.test.ts +0 -6
  106. package/src/__tests__/notification-guardian-path.test.ts +0 -19
  107. package/src/__tests__/openai-provider.test.ts +12 -22
  108. package/src/__tests__/openai-responses-provider.test.ts +2 -12
  109. package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
  110. package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
  111. package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
  112. package/src/__tests__/plugin-bootstrap.test.ts +73 -3
  113. package/src/__tests__/plugin-route-contribution.test.ts +17 -4
  114. package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
  115. package/src/__tests__/plugin-types.test.ts +2 -0
  116. package/src/__tests__/process-message-background-slack.test.ts +0 -2
  117. package/src/__tests__/process-message-display-content.test.ts +0 -2
  118. package/src/__tests__/provider-error-scenarios.test.ts +4 -5
  119. package/src/__tests__/provider-usage-tracking.test.ts +0 -39
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
  121. package/src/__tests__/registry.test.ts +1 -4
  122. package/src/__tests__/relay-server.test.ts +25 -694
  123. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
  124. package/src/__tests__/secret-ingress-http.test.ts +0 -14
  125. package/src/__tests__/send-endpoint-busy.test.ts +8 -30
  126. package/src/__tests__/skills.test.ts +0 -44
  127. package/src/__tests__/slack-inbound-verification.test.ts +2 -47
  128. package/src/__tests__/stt-hints.test.ts +13 -44
  129. package/src/__tests__/subagent-detail.test.ts +0 -27
  130. package/src/__tests__/subagent-disposal.test.ts +0 -65
  131. package/src/__tests__/subagent-notify-parent.test.ts +0 -2
  132. package/src/__tests__/subagent-role-registry.test.ts +2 -7
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
  134. package/src/__tests__/subagent-tools.test.ts +0 -2
  135. package/src/__tests__/suggestion-routes.test.ts +0 -2
  136. package/src/__tests__/title-generate-hook.test.ts +0 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
  138. package/src/__tests__/tool-executor.test.ts +11 -16
  139. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
  140. package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
  141. package/src/__tests__/tool-start-timestamp.test.ts +0 -2
  142. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  143. package/src/__tests__/twilio-routes.test.ts +0 -96
  144. package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
  145. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  146. package/src/__tests__/voice-invite-redemption.test.ts +0 -33
  147. package/src/__tests__/web-search-backend-failure.test.ts +0 -2
  148. package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
  149. package/src/__tests__/workspace-tool-loader.test.ts +2 -195
  150. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  151. package/src/agent/loop.ts +0 -56
  152. package/src/api/index.ts +1 -19
  153. package/src/api/responses/llm-request-log-entry.ts +0 -29
  154. package/src/api/responses/subagent-detail.ts +0 -17
  155. package/src/api/surfaces.ts +3 -39
  156. package/src/approvals/guardian-request-resolvers.ts +11 -1
  157. package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
  158. package/src/calls/call-domain.ts +3 -3
  159. package/src/calls/guardian-dispatch.ts +8 -10
  160. package/src/calls/inbound-trust-reader.ts +1 -17
  161. package/src/calls/media-stream-server.ts +0 -21
  162. package/src/calls/relay-server.ts +50 -167
  163. package/src/calls/relay-setup-router.ts +7 -37
  164. package/src/calls/relay-verification.ts +4 -4
  165. package/src/calls/stt-hints.ts +12 -9
  166. package/src/calls/twilio-routes.ts +4 -14
  167. package/src/channels/types.ts +20 -10
  168. package/src/cli/commands/__tests__/cache.test.ts +1 -8
  169. package/src/cli/commands/cache.ts +181 -194
  170. package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
  171. package/src/cli/commands/db/status.ts +1 -37
  172. package/src/cli/commands/mcp.ts +218 -252
  173. package/src/cli/commands/memory/index.ts +0 -2
  174. package/src/cli/commands/plugins.ts +3 -75
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
  177. package/src/cli/lib/list-installed-plugins.ts +1 -179
  178. package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
  179. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
  180. package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
  181. package/src/config/bundled-skills/subagent/SKILL.md +1 -1
  182. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  183. package/src/config/feature-flag-registry.json +13 -5
  184. package/src/config/loader.ts +5 -38
  185. package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
  186. package/src/config/schemas/memory-lifecycle.ts +0 -12
  187. package/src/config/schemas/memory-v3.ts +0 -7
  188. package/src/config/schemas/memory.ts +0 -4
  189. package/src/config/schemas/timeouts.ts +0 -8
  190. package/src/config/seed-inference-profiles.ts +11 -21
  191. package/src/config/skills.ts +5 -27
  192. package/src/config/sync-gated-profiles.ts +13 -12
  193. package/src/contacts/contacts-write.ts +0 -3
  194. package/src/daemon/assistant-attachments.ts +4 -27
  195. package/src/daemon/conversation-agent-loop.ts +0 -28
  196. package/src/daemon/conversation-process.ts +16 -35
  197. package/src/daemon/conversation-surfaces.ts +38 -111
  198. package/src/daemon/conversation-tool-setup.ts +16 -50
  199. package/src/daemon/conversation.ts +1 -13
  200. package/src/daemon/disk-pressure-guard.ts +2 -12
  201. package/src/daemon/event-loop-watchdog.ts +1 -28
  202. package/src/daemon/external-plugins-bootstrap.ts +34 -4
  203. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
  204. package/src/daemon/handlers/config-a2a.ts +14 -6
  205. package/src/daemon/handlers/config-channels.ts +22 -78
  206. package/src/daemon/handlers/conversations.ts +0 -77
  207. package/src/daemon/lifecycle.ts +0 -4
  208. package/src/daemon/mcp-reload-service.ts +0 -10
  209. package/src/daemon/memory-v2-startup.test.ts +0 -72
  210. package/src/daemon/memory-v2-startup.ts +19 -87
  211. package/src/daemon/message-types/conversations.ts +0 -2
  212. package/src/daemon/message-types/surfaces.ts +12 -12
  213. package/src/daemon/server.ts +4 -0
  214. package/src/daemon/shutdown-handlers.ts +0 -20
  215. package/src/daemon/tool-setup-types.ts +0 -9
  216. package/src/daemon/workspace-tools-watcher.ts +328 -0
  217. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  218. package/src/ipc/assistant-server.ts +2 -2
  219. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  220. package/src/mcp/client.ts +1 -15
  221. package/src/mcp/mcp-auth-orchestrator.ts +1 -6
  222. package/src/mcp/mcp-oauth-provider.ts +8 -19
  223. package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
  224. package/src/memory/conversation-crud.ts +0 -38
  225. package/src/memory/db-connection.ts +3 -22
  226. package/src/memory/db-init.ts +502 -36
  227. package/src/memory/db-singleton.ts +4 -6
  228. package/src/memory/jobs-worker.ts +0 -58
  229. package/src/memory/llm-request-log-store.ts +1 -26
  230. package/src/memory/llm-usage-store.ts +20 -48
  231. package/src/memory/memory-retrospective-job.ts +8 -9
  232. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
  233. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  234. package/src/memory/migrations/registry.ts +573 -0
  235. package/src/memory/migrations/run-migrations.ts +6 -90
  236. package/src/memory/migrations/validate-migration-state.ts +66 -101
  237. package/src/memory/schema/conversations.ts +0 -9
  238. package/src/memory/schema/infrastructure.ts +0 -20
  239. package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
  240. package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
  241. package/src/memory/v2/cli-command-store.ts +38 -75
  242. package/src/memory/v2/prompts/consolidation.ts +82 -13
  243. package/src/memory/v2/prompts/router.ts +93 -21
  244. package/src/memory/v2/skill-store.ts +31 -68
  245. package/src/notifications/__tests__/broadcaster.test.ts +8 -16
  246. package/src/notifications/__tests__/decision-engine.test.ts +9 -78
  247. package/src/notifications/broadcaster.ts +1 -8
  248. package/src/notifications/decision-engine.ts +7 -15
  249. package/src/notifications/destination-resolver.ts +24 -68
  250. package/src/notifications/emit-signal.ts +14 -39
  251. package/src/permissions/question-prompter.test.ts +1 -1
  252. package/src/permissions/question-prompter.ts +4 -7
  253. package/src/plugin-api/index.ts +6 -6
  254. package/src/plugin-api/types.ts +5 -3
  255. package/src/plugin-api/vision-support.test.ts +4 -28
  256. package/src/plugin-api/vision-support.ts +31 -66
  257. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
  258. package/src/plugins/defaults/advisor/consult.ts +6 -110
  259. package/src/plugins/defaults/advisor/steering.ts +2 -14
  260. package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
  261. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
  262. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
  263. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
  264. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
  265. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
  269. package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
  270. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
  273. package/src/plugins/mtime-cache.ts +291 -76
  274. package/src/plugins/pipeline.ts +13 -111
  275. package/src/plugins/types.ts +2 -0
  276. package/src/providers/anthropic/client.ts +0 -5
  277. package/src/providers/call-site-routing.ts +0 -4
  278. package/src/providers/model-catalog.ts +0 -16
  279. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  280. package/src/providers/openai/chat-completions-provider.ts +83 -37
  281. package/src/providers/openai/responses-provider.ts +46 -50
  282. package/src/providers/openrouter/client.ts +0 -5
  283. package/src/providers/provider-send-message.ts +0 -4
  284. package/src/providers/ratelimit.ts +0 -4
  285. package/src/providers/retry.ts +0 -4
  286. package/src/providers/types.ts +0 -9
  287. package/src/providers/usage-tracking.ts +0 -4
  288. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
  289. package/src/runtime/access-request-helper.ts +39 -19
  290. package/src/runtime/actor-trust-resolver.ts +2 -2
  291. package/src/runtime/assistant-event-hub.ts +1 -1
  292. package/src/runtime/assistant-stream-state.ts +2 -9
  293. package/src/runtime/auth/require-bound-guardian.ts +11 -21
  294. package/src/runtime/channel-verification-service.ts +31 -56
  295. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  296. package/src/runtime/guardian-vellum-migration.ts +7 -66
  297. package/src/runtime/invite-redemption-service.ts +187 -198
  298. package/src/runtime/local-actor-identity.ts +11 -76
  299. package/src/runtime/pending-interactions.ts +1 -11
  300. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
  301. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  302. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
  303. package/src/runtime/routes/browser-routes.ts +1 -1
  304. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
  305. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  306. package/src/runtime/routes/contact-routes.ts +32 -8
  307. package/src/runtime/routes/conversation-cli-routes.ts +5 -4
  308. package/src/runtime/routes/conversation-list-routes.ts +7 -4
  309. package/src/runtime/routes/conversation-query-routes.ts +0 -72
  310. package/src/runtime/routes/conversation-routes.ts +85 -84
  311. package/src/runtime/routes/events-routes.ts +2 -2
  312. package/src/runtime/routes/global-search-routes.ts +1 -3
  313. package/src/runtime/routes/guardian-action-routes.ts +5 -4
  314. package/src/runtime/routes/host-app-control-routes.ts +4 -5
  315. package/src/runtime/routes/host-bash-routes.ts +4 -5
  316. package/src/runtime/routes/host-browser-routes.ts +11 -9
  317. package/src/runtime/routes/host-cu-routes.ts +4 -5
  318. package/src/runtime/routes/host-file-routes.ts +4 -5
  319. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  320. package/src/runtime/routes/http-adapter.ts +1 -1
  321. package/src/runtime/routes/identity-routes.ts +2 -3
  322. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  323. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
  324. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
  325. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
  326. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  327. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
  328. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
  329. package/src/runtime/routes/index.ts +0 -2
  330. package/src/runtime/routes/llm-context-normalization.ts +0 -83
  331. package/src/runtime/routes/mcp-auth-routes.ts +19 -171
  332. package/src/runtime/routes/migration-rollback-routes.ts +3 -4
  333. package/src/runtime/routes/migration-routes.ts +1 -4
  334. package/src/runtime/routes/subagents-routes.ts +0 -5
  335. package/src/runtime/routes/surface-action-routes.ts +56 -42
  336. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
  337. package/src/runtime/services/conversation-serializer.ts +9 -7
  338. package/src/runtime/tool-grant-request-helper.ts +3 -3
  339. package/src/runtime/trust-verdict-consumer.ts +9 -85
  340. package/src/runtime/verification-outbound-actions.ts +18 -18
  341. package/src/signals/user-message.ts +0 -16
  342. package/src/subagent/manager.ts +0 -9
  343. package/src/subagent/types.ts +3 -3
  344. package/src/telemetry/types.ts +1 -34
  345. package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
  346. package/src/telemetry/usage-telemetry-reporter.ts +3 -87
  347. package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
  348. package/src/tools/ask-question/ask-question-tool.ts +0 -13
  349. package/src/tools/executor.ts +4 -4
  350. package/src/tools/registry.ts +0 -18
  351. package/src/tools/shared/filesystem/path-policy.ts +5 -12
  352. package/src/tools/tool-approval-handler.ts +1 -1
  353. package/src/tools/tool-defaults.ts +2 -9
  354. package/src/tools/tool-manifest.ts +0 -3
  355. package/src/tools/types.ts +2 -17
  356. package/src/tools/workspace-tools/loader.ts +244 -348
  357. package/src/util/errors.ts +1 -26
  358. package/src/util/platform.ts +0 -5
  359. package/src/workflows/library.test.ts +0 -140
  360. package/src/workflows/library.ts +28 -82
  361. package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
  362. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
  363. package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
  364. package/src/workspace/migrations/registry.ts +0 -2
  365. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
  366. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
  367. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
  368. package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
  369. package/src/__tests__/code-search-tool.test.ts +0 -585
  370. package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
  371. package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
  372. package/src/__tests__/plugin-pipeline.test.ts +0 -96
  373. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
  374. package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
  375. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
  376. package/src/agent/loop-exclusive-tool.test.ts +0 -150
  377. package/src/api/constants/sse-replay.ts +0 -41
  378. package/src/api/events/conversation-notice.ts +0 -26
  379. package/src/approvals/guardian-channel-delivery.ts +0 -30
  380. package/src/approvals/guardian-expiry-notifier.ts +0 -148
  381. package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
  382. package/src/cli/commands/memory/worker.ts +0 -175
  383. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
  384. package/src/config/prune-seeded-callsite-defaults.ts +0 -110
  385. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
  386. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
  387. package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
  388. package/src/contacts/guardian-delivery-reader.ts +0 -223
  389. package/src/contacts/member-write-relay.ts +0 -189
  390. package/src/daemon/conversation-notices.ts +0 -60
  391. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
  392. package/src/hooks/hook-loader.ts +0 -341
  393. package/src/mcp/mcp-header-store.ts +0 -134
  394. package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
  395. package/src/memory/__tests__/prompt-override.test.ts +0 -192
  396. package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
  397. package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
  398. package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
  399. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
  400. package/src/memory/prompt-override.ts +0 -129
  401. package/src/memory/steps.ts +0 -573
  402. package/src/memory/watchdog-events-store.ts +0 -87
  403. package/src/memory/worker-control.ts +0 -118
  404. package/src/memory/worker-process.ts +0 -72
  405. package/src/notifications/__tests__/connected-channels.test.ts +0 -114
  406. package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
  407. package/src/onboarding/checkin-event.test.ts +0 -222
  408. package/src/onboarding/checkin-event.ts +0 -321
  409. package/src/onboarding/schedule-checkin.ts +0 -190
  410. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  411. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  412. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  413. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
  414. package/src/plugins/surface-import.ts +0 -121
  415. package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
  416. package/src/providers/openai/api-error-normalization.ts +0 -270
  417. package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
  418. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
  419. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
  420. package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
  421. package/src/runtime/anchored-guardian.test.ts +0 -156
  422. package/src/runtime/anchored-guardian.ts +0 -135
  423. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
  424. package/src/runtime/local-principal-trust.ts +0 -52
  425. package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
  426. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
  427. package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
  428. package/src/tools/filesystem/search.ts +0 -543
  429. package/src/util/telemetry-db-path.ts +0 -24
  430. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
@@ -1,222 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- buildCheckinDescription,
5
- buildCheckinTitle,
6
- type BusyInterval,
7
- checkinAvailabilityWindow,
8
- chooseCheckinSlot,
9
- extractBusyFromEvents,
10
- findFirstOpenSlot,
11
- type GcalEvent,
12
- tomorrowInTimeZone,
13
- zonedWallTimeToUtcMs,
14
- } from "./checkin-event.js";
15
-
16
- const TZ = "America/New_York";
17
- const MIN = 60 * 1000;
18
- const HOUR = 60 * MIN;
19
-
20
- // A fixed "now": 2024-01-15 18:00 UTC (= 1pm EST). Tomorrow is 2024-01-16.
21
- const NOW = Date.parse("2024-01-15T18:00:00Z");
22
-
23
- /** Helper: epoch ms for tomorrow (2024-01-16) at a given EST hour. */
24
- function estTomorrow(hour: number, minute = 0): number {
25
- return zonedWallTimeToUtcMs(2024, 1, 16, hour, minute, TZ);
26
- }
27
-
28
- describe("buildCheckinTitle", () => {
29
- test("both names", () => {
30
- expect(
31
- buildCheckinTitle({ userName: "Alex", assistantName: "Jarvis" }),
32
- ).toBe("Alex <> Jarvis: Day 2 Check-in");
33
- });
34
- test("user only", () => {
35
- expect(buildCheckinTitle({ userName: "Alex" })).toBe(
36
- "Alex: Day 2 Check-in",
37
- );
38
- });
39
- test("assistant only", () => {
40
- expect(buildCheckinTitle({ assistantName: "Jarvis" })).toBe(
41
- "Jarvis: Day 2 Check-in",
42
- );
43
- });
44
- test("neither (blank strings dropped)", () => {
45
- expect(buildCheckinTitle({ userName: " ", assistantName: "" })).toBe(
46
- "Day 2 Check-in",
47
- );
48
- });
49
- });
50
-
51
- describe("buildCheckinDescription", () => {
52
- test("embeds the uuid + fixed first-week prompt in the CTA link", () => {
53
- const html = buildCheckinDescription("uuid-123");
54
- expect(html).toContain(
55
- "https://www.vellum.ai/assistant/conversations/uuid-123?prompt=What%20would%20you%20recommend",
56
- );
57
- // Carries onboarding attribution for the calendar-event CTA.
58
- expect(html).toContain("&utm_source=onboarding&utm_medium=calendar_event");
59
- // Only sanitization-safe tags; the CTA is a bold link, not a styled button.
60
- expect(html).toContain("<a href=");
61
- expect(html).toContain("<strong>");
62
- expect(html).not.toContain("style=");
63
- });
64
- });
65
-
66
- describe("findFirstOpenSlot", () => {
67
- const windowStart = 0;
68
- const windowEnd = 60 * MIN;
69
- const dur = 15 * MIN;
70
-
71
- test("empty calendar → start of window", () => {
72
- expect(findFirstOpenSlot(windowStart, windowEnd, [], dur)).toBe(
73
- windowStart,
74
- );
75
- });
76
-
77
- test("busy at start → first gap after it", () => {
78
- const busy: BusyInterval[] = [{ start: 0, end: 20 * MIN }];
79
- expect(findFirstOpenSlot(windowStart, windowEnd, busy, dur)).toBe(20 * MIN);
80
- });
81
-
82
- test("finds a gap between two meetings", () => {
83
- const busy: BusyInterval[] = [
84
- { start: 0, end: 10 * MIN },
85
- { start: 30 * MIN, end: 60 * MIN },
86
- ];
87
- // gap [10,30) is 20min >= 15min → slot at 10min.
88
- expect(findFirstOpenSlot(windowStart, windowEnd, busy, dur)).toBe(10 * MIN);
89
- });
90
-
91
- test("skips a gap too small to fit", () => {
92
- const busy: BusyInterval[] = [
93
- { start: 0, end: 10 * MIN },
94
- { start: 20 * MIN, end: 60 * MIN }, // gap [10,20) only 10min
95
- ];
96
- expect(findFirstOpenSlot(windowStart, windowEnd, busy, dur)).toBeNull();
97
- });
98
-
99
- test("fully booked → null", () => {
100
- const busy: BusyInterval[] = [{ start: -MIN, end: windowEnd + MIN }];
101
- expect(findFirstOpenSlot(windowStart, windowEnd, busy, dur)).toBeNull();
102
- });
103
-
104
- test("ignores out-of-window busy intervals", () => {
105
- const busy: BusyInterval[] = [{ start: -2 * HOUR, end: -HOUR }];
106
- expect(findFirstOpenSlot(windowStart, windowEnd, busy, dur)).toBe(
107
- windowStart,
108
- );
109
- });
110
- });
111
-
112
- describe("tomorrowInTimeZone", () => {
113
- test("advances to the next local calendar day", () => {
114
- expect(tomorrowInTimeZone(NOW, TZ)).toEqual({
115
- year: 2024,
116
- month: 1,
117
- day: 16,
118
- });
119
- });
120
-
121
- test("late-evening local time still maps to the correct next day", () => {
122
- // 2024-01-16 02:00 UTC = 2024-01-15 21:00 EST → tomorrow is the 16th.
123
- const lateLocal = Date.parse("2024-01-16T02:00:00Z");
124
- expect(tomorrowInTimeZone(lateLocal, TZ)).toEqual({
125
- year: 2024,
126
- month: 1,
127
- day: 16,
128
- });
129
- });
130
- });
131
-
132
- describe("chooseCheckinSlot", () => {
133
- test("empty calendar → 12:00 (start of primary window)", () => {
134
- const slot = chooseCheckinSlot(NOW, TZ, []);
135
- expect(slot.window).toBe("primary");
136
- expect(slot.startMs).toBe(estTomorrow(12));
137
- expect(slot.endMs).toBe(estTomorrow(12, 15));
138
- });
139
-
140
- test("noon booked → next open slot inside the primary window", () => {
141
- const busy: BusyInterval[] = [
142
- { start: estTomorrow(12), end: estTomorrow(13) },
143
- ];
144
- const slot = chooseCheckinSlot(NOW, TZ, busy);
145
- expect(slot.window).toBe("primary");
146
- expect(slot.startMs).toBe(estTomorrow(13));
147
- });
148
-
149
- test("primary window full → widens to 8am–8pm and takes earliest slot", () => {
150
- // Block all of 12pm–5pm; leave the morning open.
151
- const busy: BusyInterval[] = [
152
- { start: estTomorrow(12), end: estTomorrow(17) },
153
- ];
154
- const slot = chooseCheckinSlot(NOW, TZ, busy);
155
- expect(slot.window).toBe("wide");
156
- // Earliest free slot in 8am–8pm is 8:00am.
157
- expect(slot.startMs).toBe(estTomorrow(8));
158
- });
159
-
160
- test("entire 8am–8pm full → fallback books at noon anyway", () => {
161
- const busy: BusyInterval[] = [
162
- { start: estTomorrow(8), end: estTomorrow(20) },
163
- ];
164
- const slot = chooseCheckinSlot(NOW, TZ, busy);
165
- expect(slot.window).toBe("fallback");
166
- expect(slot.startMs).toBe(estTomorrow(12));
167
- });
168
- });
169
-
170
- describe("extractBusyFromEvents", () => {
171
- const a = "2024-01-16T13:00:00Z";
172
- const b = "2024-01-16T13:30:00Z";
173
-
174
- test("timed event → busy interval", () => {
175
- const events: GcalEvent[] = [
176
- { start: { dateTime: a }, end: { dateTime: b } },
177
- ];
178
- expect(extractBusyFromEvents(events)).toEqual([
179
- { start: Date.parse(a), end: Date.parse(b) },
180
- ]);
181
- });
182
-
183
- test("skips cancelled, transparent, all-day, and declined events", () => {
184
- const events: GcalEvent[] = [
185
- { status: "cancelled", start: { dateTime: a }, end: { dateTime: b } },
186
- {
187
- transparency: "transparent",
188
- start: { dateTime: a },
189
- end: { dateTime: b },
190
- },
191
- { start: { date: "2024-01-16" }, end: { date: "2024-01-17" } },
192
- {
193
- start: { dateTime: a },
194
- end: { dateTime: b },
195
- attendees: [{ self: true, responseStatus: "declined" }],
196
- },
197
- ];
198
- expect(extractBusyFromEvents(events)).toEqual([]);
199
- });
200
-
201
- test("keeps an accepted event even with another attendee declined", () => {
202
- const events: GcalEvent[] = [
203
- {
204
- start: { dateTime: a },
205
- end: { dateTime: b },
206
- attendees: [
207
- { self: true, responseStatus: "accepted" },
208
- { responseStatus: "declined" },
209
- ],
210
- },
211
- ];
212
- expect(extractBusyFromEvents(events)).toHaveLength(1);
213
- });
214
- });
215
-
216
- describe("checkinAvailabilityWindow", () => {
217
- test("spans the full 8am–8pm fallback window tomorrow", () => {
218
- const { timeMinMs, timeMaxMs } = checkinAvailabilityWindow(NOW, TZ);
219
- expect(timeMinMs).toBe(estTomorrow(8));
220
- expect(timeMaxMs).toBe(estTomorrow(20));
221
- });
222
- });
@@ -1,321 +0,0 @@
1
- /**
2
- * Pure helpers for the onboarding "Day 2 Check-in" meeting: choosing the slot
3
- * and building the event's title and HTML description.
4
- *
5
- * The check-in is booked into the first open 15-minute slot in the user's local
6
- * afternoon tomorrow. The title uses fixed typography and the description uses a
7
- * sanitization-safe HTML body (Google Calendar strips most styling) with
8
- * default first-run substitutions and a deep-link CTA back into the app.
9
- *
10
- * Everything here is side-effect-free and timezone-pure so it can be unit
11
- * tested without a calendar connection; the orchestration that talks to Google
12
- * lives in `schedule-checkin.ts`.
13
- */
14
-
15
- /** Length of the check-in meeting. */
16
- export const CHECKIN_DURATION_MINUTES = 15;
17
-
18
- /**
19
- * Primary booking window in the user's local clock: the first open 15-minute
20
- * slot between 12pm and 5pm tomorrow.
21
- */
22
- export const PRIMARY_WINDOW = { startHour: 12, endHour: 17 } as const;
23
-
24
- /**
25
- * Fallback window when the primary 12pm–5pm window is fully booked: widen on
26
- * both ends to 8am–8pm and take the earliest open slot there.
27
- */
28
- export const WIDE_WINDOW = { startHour: 8, endHour: 20 } as const;
29
-
30
- /** The fixed, single-encoded first-week prompt carried by the CTA deep link. */
31
- const CTA_ENCODED_PROMPT =
32
- "What%20would%20you%20recommend%20I%20tackle%20first%20this%20week%3F%20Propose%20it%20but%20wait%20for%20my%20go-ahead%20before%20doing%20anything";
33
-
34
- export interface CheckinNames {
35
- /** The user's collected name. Blank/omitted → dropped from the title. */
36
- userName?: string;
37
- /** The assistant's display name. Blank/omitted → dropped from the title. */
38
- assistantName?: string;
39
- }
40
-
41
- /**
42
- * Build the check-in event title. Locked typography, four cases:
43
- * both names → `{me} <> {you}: Day 2 Check-in`
44
- * me only → `{me}: Day 2 Check-in`
45
- * you only → `{you}: Day 2 Check-in`
46
- * neither → `Day 2 Check-in`
47
- */
48
- export function buildCheckinTitle({
49
- userName,
50
- assistantName,
51
- }: CheckinNames): string {
52
- const me = userName?.trim();
53
- const you = assistantName?.trim();
54
- if (me && you) return `${me} <> ${you}: Day 2 Check-in`;
55
- if (me) return `${me}: Day 2 Check-in`;
56
- if (you) return `${you}: Day 2 Check-in`;
57
- return "Day 2 Check-in";
58
- }
59
-
60
- /**
61
- * Build the HTML event description with the default first-run substitutions.
62
- *
63
- * Google Calendar strips nearly all styling, so the body relies only on the
64
- * tags that survive (`<p>`, `<strong>`, `<a>`, emoji) and makes the CTA a bold
65
- * link rather than a styled button. The deep link opens a fresh conversation
66
- * (`uuid`) pre-seeded with the first-week prompt.
67
- */
68
- export function buildCheckinDescription(uuid: string): string {
69
- const href = `https://www.vellum.ai/assistant/conversations/${uuid}?prompt=${CTA_ENCODED_PROMPT}&utm_source=onboarding&utm_medium=calendar_event`;
70
- return [
71
- "<p>👋 <strong>Hi, it was great to meet you properly.</strong></p>",
72
- "<p>You just set me up, and I've already started learning <strong>what you're working on</strong>. This 15 minutes is the natural place to put that to work. I'll walk you through one thing I'd like to do for you this week.</p>",
73
- `<p><a href="${href}"><strong>Let's go →</strong></a></p>`,
74
- "<p>Click the link and we'll get started.</p>",
75
- ].join("\n");
76
- }
77
-
78
- // ---------------------------------------------------------------------------
79
- // Timezone math
80
- //
81
- // We resolve wall-clock windows ("tomorrow 12:00 in America/New_York") to
82
- // absolute instants using Intl, so DST transitions and arbitrary offsets are
83
- // handled without a date library.
84
- // ---------------------------------------------------------------------------
85
-
86
- /**
87
- * Offset (local − UTC, in ms) that `timeZone` has at the given instant.
88
- * Derived by formatting the instant in the zone and diffing the wall clock.
89
- */
90
- function tzOffsetMs(utcMs: number, timeZone: string): number {
91
- const dtf = new Intl.DateTimeFormat("en-US", {
92
- timeZone,
93
- hour12: false,
94
- year: "numeric",
95
- month: "2-digit",
96
- day: "2-digit",
97
- hour: "2-digit",
98
- minute: "2-digit",
99
- second: "2-digit",
100
- });
101
- const map: Record<string, string> = {};
102
- for (const part of dtf.formatToParts(new Date(utcMs))) {
103
- if (part.type !== "literal") map[part.type] = part.value;
104
- }
105
- // `hourCycle: h23` can emit "24" for midnight on some runtimes — normalize.
106
- let hour = Number(map.hour);
107
- if (hour === 24) hour = 0;
108
- const asUtc = Date.UTC(
109
- Number(map.year),
110
- Number(map.month) - 1,
111
- Number(map.day),
112
- hour,
113
- Number(map.minute),
114
- Number(map.second),
115
- );
116
- return asUtc - utcMs;
117
- }
118
-
119
- /**
120
- * Convert a wall-clock time in `timeZone` to an absolute epoch-ms instant.
121
- * Refines once to settle DST boundaries where the naive offset guess differs
122
- * from the offset that actually applies at the resolved instant.
123
- */
124
- export function zonedWallTimeToUtcMs(
125
- year: number,
126
- month: number,
127
- day: number,
128
- hour: number,
129
- minute: number,
130
- timeZone: string,
131
- ): number {
132
- const guess = Date.UTC(year, month - 1, day, hour, minute);
133
- const off1 = tzOffsetMs(guess, timeZone);
134
- let utc = guess - off1;
135
- const off2 = tzOffsetMs(utc, timeZone);
136
- if (off2 !== off1) utc = guess - off2;
137
- return utc;
138
- }
139
-
140
- /** Calendar date (1-based month) that is "tomorrow" in `timeZone` at `nowMs`. */
141
- export function tomorrowInTimeZone(
142
- nowMs: number,
143
- timeZone: string,
144
- ): { year: number; month: number; day: number } {
145
- const dtf = new Intl.DateTimeFormat("en-US", {
146
- timeZone,
147
- year: "numeric",
148
- month: "2-digit",
149
- day: "2-digit",
150
- });
151
- const map: Record<string, string> = {};
152
- for (const part of dtf.formatToParts(new Date(nowMs))) {
153
- if (part.type !== "literal") map[part.type] = part.value;
154
- }
155
- // Add a day via UTC arithmetic (handles month/year rollover), then read the
156
- // wall-clock date back out — we only care about Y/M/D, not the instant.
157
- const tomorrow = new Date(
158
- Date.UTC(Number(map.year), Number(map.month) - 1, Number(map.day)) +
159
- 24 * 60 * 60 * 1000,
160
- );
161
- return {
162
- year: tomorrow.getUTCFullYear(),
163
- month: tomorrow.getUTCMonth() + 1,
164
- day: tomorrow.getUTCDate(),
165
- };
166
- }
167
-
168
- // ---------------------------------------------------------------------------
169
- // Slot finding
170
- // ---------------------------------------------------------------------------
171
-
172
- /** A half-open busy interval `[start, end)` in epoch ms. */
173
- export interface BusyInterval {
174
- start: number;
175
- end: number;
176
- }
177
-
178
- /** Subset of a Google Calendar event needed to decide whether it blocks time. */
179
- export interface GcalEvent {
180
- status?: string;
181
- transparency?: string;
182
- start?: { dateTime?: string; date?: string };
183
- end?: { dateTime?: string; date?: string };
184
- attendees?: Array<{ self?: boolean; responseStatus?: string }>;
185
- }
186
-
187
- /**
188
- * Derive busy intervals (epoch ms) from listed calendar events, approximating
189
- * freeBusy semantics under the calendar.events scope (which authorizes
190
- * events.list/insert but not freeBusy.query). Skips events that don't occupy
191
- * the user's time: cancelled, transparent ("free"), all-day (date-only), and
192
- * ones the user has declined.
193
- */
194
- export function extractBusyFromEvents(events: GcalEvent[]): BusyInterval[] {
195
- const intervals: BusyInterval[] = [];
196
- for (const event of events) {
197
- if (event.status === "cancelled") continue;
198
- if (event.transparency === "transparent") continue;
199
- // All-day events (date-only) don't block a 15-minute afternoon slot.
200
- if (!event.start?.dateTime || !event.end?.dateTime) continue;
201
- const self = event.attendees?.find((a) => a.self);
202
- if (self?.responseStatus === "declined") continue;
203
-
204
- const start = Date.parse(event.start.dateTime);
205
- const end = Date.parse(event.end.dateTime);
206
- if (Number.isFinite(start) && Number.isFinite(end) && end > start) {
207
- intervals.push({ start, end });
208
- }
209
- }
210
- return intervals;
211
- }
212
-
213
- /**
214
- * Earliest start (epoch ms) of a free `durationMs` slot inside
215
- * `[windowStart, windowEnd)`, or `null` if the window is fully booked.
216
- *
217
- * Walks the busy intervals in order, advancing a cursor past each overlap; the
218
- * first gap wide enough to fit the duration wins. This returns the *first*
219
- * open slot, which is exactly the "first open time slot in the window"
220
- * behavior the onboarding flow wants.
221
- */
222
- export function findFirstOpenSlot(
223
- windowStart: number,
224
- windowEnd: number,
225
- busy: BusyInterval[],
226
- durationMs: number,
227
- ): number | null {
228
- const relevant = busy
229
- .filter((b) => b.end > windowStart && b.start < windowEnd)
230
- .sort((a, b) => a.start - b.start);
231
-
232
- let cursor = windowStart;
233
- for (const interval of relevant) {
234
- if (interval.start - cursor >= durationMs) {
235
- return cursor;
236
- }
237
- if (interval.end > cursor) {
238
- cursor = interval.end;
239
- }
240
- if (cursor + durationMs > windowEnd) {
241
- return null;
242
- }
243
- }
244
- return cursor + durationMs <= windowEnd ? cursor : null;
245
- }
246
-
247
- export interface ChosenSlot {
248
- /** Slot start, epoch ms. */
249
- startMs: number;
250
- /** Slot end, epoch ms. */
251
- endMs: number;
252
- /** Which window the slot came from — drives logging/telemetry only. */
253
- window: "primary" | "wide" | "fallback";
254
- }
255
-
256
- /**
257
- * Pick the check-in slot for tomorrow in `timeZone` given the busy intervals.
258
- *
259
- * 1. First open 15-min slot in 12pm–5pm.
260
- * 2. If that window is full, widen to 8am–8pm and take the earliest open slot.
261
- * 3. If even that is full (pathological), fall back to 12:00 so a reminder
262
- * still lands — better an overlapping check-in than none.
263
- */
264
- export function chooseCheckinSlot(
265
- nowMs: number,
266
- timeZone: string,
267
- busy: BusyInterval[],
268
- ): ChosenSlot {
269
- const { year, month, day } = tomorrowInTimeZone(nowMs, timeZone);
270
- const durationMs = CHECKIN_DURATION_MINUTES * 60 * 1000;
271
-
272
- const at = (hour: number) =>
273
- zonedWallTimeToUtcMs(year, month, day, hour, 0, timeZone);
274
-
275
- const primaryStart = at(PRIMARY_WINDOW.startHour);
276
- const primaryEnd = at(PRIMARY_WINDOW.endHour);
277
- const wideStart = at(WIDE_WINDOW.startHour);
278
- const wideEnd = at(WIDE_WINDOW.endHour);
279
-
280
- const primary = findFirstOpenSlot(primaryStart, primaryEnd, busy, durationMs);
281
- if (primary !== null) {
282
- return { startMs: primary, endMs: primary + durationMs, window: "primary" };
283
- }
284
-
285
- const wide = findFirstOpenSlot(wideStart, wideEnd, busy, durationMs);
286
- if (wide !== null) {
287
- return { startMs: wide, endMs: wide + durationMs, window: "wide" };
288
- }
289
-
290
- return {
291
- startMs: primaryStart,
292
- endMs: primaryStart + durationMs,
293
- window: "fallback",
294
- };
295
- }
296
-
297
- /** Window covering both the primary and fallback ranges — the availability query span. */
298
- export function checkinAvailabilityWindow(
299
- nowMs: number,
300
- timeZone: string,
301
- ): { timeMinMs: number; timeMaxMs: number } {
302
- const { year, month, day } = tomorrowInTimeZone(nowMs, timeZone);
303
- return {
304
- timeMinMs: zonedWallTimeToUtcMs(
305
- year,
306
- month,
307
- day,
308
- WIDE_WINDOW.startHour,
309
- 0,
310
- timeZone,
311
- ),
312
- timeMaxMs: zonedWallTimeToUtcMs(
313
- year,
314
- month,
315
- day,
316
- WIDE_WINDOW.endHour,
317
- 0,
318
- timeZone,
319
- ),
320
- };
321
- }