@vellumai/assistant 0.10.1 → 0.10.2-dev.202606241651.2d2b40d

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 (367) hide show
  1. package/docs/workspace-tools.md +42 -33
  2. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  3. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  5. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  8. package/openapi.yaml +74 -1
  9. package/package.json +1 -1
  10. package/scripts/test.sh +36 -15
  11. package/src/__tests__/actor-token-service.test.ts +36 -14
  12. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  13. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  14. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  15. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  16. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  17. package/src/__tests__/approval-cascade.test.ts +2 -0
  18. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  19. package/src/__tests__/btw-routes.test.ts +2 -0
  20. package/src/__tests__/build-persisted-content.test.ts +2 -0
  21. package/src/__tests__/call-controller.test.ts +19 -0
  22. package/src/__tests__/channel-guardian.test.ts +94 -58
  23. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  24. package/src/__tests__/compaction-events.test.ts +2 -0
  25. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  26. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  27. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  28. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  29. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  30. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  31. package/src/__tests__/computer-use-tools.test.ts +13 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +5 -1
  33. package/src/__tests__/config-schema.test.ts +1 -0
  34. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  35. package/src/__tests__/contacts-relay-reads.test.ts +13 -15
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  37. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  38. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  40. package/src/__tests__/conversation-agent-loop.test.ts +7 -0
  41. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  42. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  44. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  45. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  46. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  47. package/src/__tests__/conversation-pairing.test.ts +2 -0
  48. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  51. package/src/__tests__/conversation-queue.test.ts +91 -0
  52. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  53. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  54. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  55. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  56. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  57. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +65 -0
  58. package/src/__tests__/conversation-title-service.test.ts +2 -0
  59. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  60. package/src/__tests__/conversation-usage.test.ts +2 -0
  61. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  64. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  65. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  66. package/src/__tests__/db-test-helpers.ts +5 -4
  67. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  68. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  69. package/src/__tests__/dm-persistence.test.ts +2 -0
  70. package/src/__tests__/emit-signal-routing-intent.test.ts +10 -5
  71. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  72. package/src/__tests__/filing-service.test.ts +2 -0
  73. package/src/__tests__/guardian-binding-drift-heal.test.ts +75 -10
  74. package/src/__tests__/guardian-dispatch.test.ts +95 -1
  75. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  76. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  77. package/src/__tests__/heartbeat-service.test.ts +2 -0
  78. package/src/__tests__/helpers/channel-test-adapter.ts +1 -7
  79. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  80. package/src/__tests__/host-bash-routes.test.ts +31 -41
  81. package/src/__tests__/host-browser-routes.test.ts +26 -32
  82. package/src/__tests__/host-cu-proxy.test.ts +299 -0
  83. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  84. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  85. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  86. package/src/__tests__/http-user-message-parity.test.ts +167 -8
  87. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  88. package/src/__tests__/invite-redemption-service.test.ts +43 -0
  89. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  90. package/src/__tests__/llm-usage-store.test.ts +25 -0
  91. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  92. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  93. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  94. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  95. package/src/__tests__/native-web-search.test.ts +2 -0
  96. package/src/__tests__/non-member-access-request.test.ts +189 -17
  97. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  98. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  99. package/src/__tests__/notification-deep-link.test.ts +6 -0
  100. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  101. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  102. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  103. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  104. package/src/__tests__/plugin-bootstrap.test.ts +3 -73
  105. package/src/__tests__/plugin-route-contribution.test.ts +4 -17
  106. package/src/__tests__/plugin-tool-contribution.test.ts +3 -18
  107. package/src/__tests__/plugin-types.test.ts +0 -2
  108. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  109. package/src/__tests__/process-message-display-content.test.ts +2 -0
  110. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  111. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  112. package/src/__tests__/registry.test.ts +3 -0
  113. package/src/__tests__/relay-server.test.ts +694 -25
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  115. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  116. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  117. package/src/__tests__/skills.test.ts +44 -0
  118. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  119. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +102 -0
  120. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  121. package/src/__tests__/stt-hints.test.ts +44 -13
  122. package/src/__tests__/subagent-detail.test.ts +27 -0
  123. package/src/__tests__/subagent-disposal.test.ts +65 -0
  124. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  125. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  126. package/src/__tests__/subagent-tools.test.ts +2 -0
  127. package/src/__tests__/suggestion-routes.test.ts +2 -0
  128. package/src/__tests__/title-generate-hook.test.ts +2 -0
  129. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  130. package/src/__tests__/tool-executor.test.ts +16 -11
  131. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  132. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  133. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  134. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  135. package/src/__tests__/twilio-routes.test.ts +96 -0
  136. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  137. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  138. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  139. package/src/agent/loop-exclusive-tool.test.ts +150 -0
  140. package/src/agent/loop.ts +56 -0
  141. package/src/api/constants/sse-replay.ts +41 -0
  142. package/src/api/index.ts +6 -0
  143. package/src/api/responses/llm-request-log-entry.ts +25 -0
  144. package/src/api/responses/subagent-detail.ts +17 -0
  145. package/src/calls/__tests__/relay-setup-router.test.ts +262 -4
  146. package/src/calls/call-domain.ts +3 -3
  147. package/src/calls/guardian-dispatch.ts +10 -8
  148. package/src/calls/inbound-trust-reader.ts +17 -1
  149. package/src/calls/media-stream-server.ts +21 -0
  150. package/src/calls/relay-server.ts +167 -50
  151. package/src/calls/relay-setup-router.ts +37 -7
  152. package/src/calls/relay-verification.ts +4 -4
  153. package/src/calls/stt-hints.ts +9 -12
  154. package/src/calls/twilio-routes.ts +14 -4
  155. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  156. package/src/cli/commands/cache.ts +194 -181
  157. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  158. package/src/cli/commands/db/status.ts +37 -1
  159. package/src/cli/commands/mcp.ts +252 -218
  160. package/src/cli/commands/memory/__tests__/worker.test.ts +302 -0
  161. package/src/cli/commands/memory/index.ts +2 -0
  162. package/src/cli/commands/memory/worker.ts +175 -0
  163. package/src/cli/commands/plugins.ts +75 -3
  164. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  165. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  166. package/src/cli/lib/list-installed-plugins.ts +179 -1
  167. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  168. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -1
  169. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  170. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  171. package/src/config/feature-flag-registry.json +0 -8
  172. package/src/config/loader.ts +36 -5
  173. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  174. package/src/config/schemas/memory-lifecycle.ts +12 -0
  175. package/src/config/schemas/memory-v3.ts +7 -0
  176. package/src/config/schemas/memory.ts +4 -0
  177. package/src/config/schemas/timeouts.ts +8 -0
  178. package/src/config/seed-inference-profiles.ts +14 -5
  179. package/src/config/skills.ts +27 -5
  180. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  181. package/src/contacts/contacts-write.ts +3 -0
  182. package/src/contacts/guardian-delivery-reader.ts +223 -0
  183. package/src/daemon/conversation-agent-loop.ts +9 -0
  184. package/src/daemon/conversation-process.ts +39 -17
  185. package/src/daemon/conversation-surfaces.ts +8 -0
  186. package/src/daemon/conversation-tool-setup.ts +49 -16
  187. package/src/daemon/conversation.ts +21 -2
  188. package/src/daemon/disk-pressure-guard.ts +12 -2
  189. package/src/daemon/event-loop-watchdog.ts +28 -1
  190. package/src/daemon/external-plugins-bootstrap.ts +4 -34
  191. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -0
  192. package/src/daemon/handlers/__tests__/config-channels.test.ts +225 -0
  193. package/src/daemon/handlers/config-a2a.ts +6 -14
  194. package/src/daemon/handlers/config-channels.ts +78 -22
  195. package/src/daemon/handlers/conversations.ts +77 -0
  196. package/src/daemon/host-cu-proxy.ts +102 -11
  197. package/src/daemon/lifecycle.ts +4 -0
  198. package/src/daemon/memory-v2-startup.test.ts +72 -0
  199. package/src/daemon/memory-v2-startup.ts +87 -19
  200. package/src/daemon/server.ts +0 -4
  201. package/src/daemon/shutdown-handlers.ts +20 -0
  202. package/src/daemon/tool-setup-types.ts +9 -0
  203. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  204. package/src/ipc/assistant-server.ts +2 -2
  205. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  206. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  207. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  208. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  209. package/src/memory/conversation-crud.ts +38 -0
  210. package/src/memory/db-connection.ts +22 -3
  211. package/src/memory/db-init.ts +36 -502
  212. package/src/memory/db-singleton.ts +6 -4
  213. package/src/memory/jobs-worker.ts +58 -0
  214. package/src/memory/llm-usage-store.ts +48 -20
  215. package/src/memory/memory-retrospective-job.ts +9 -8
  216. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +13 -3
  217. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -27
  218. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +130 -56
  219. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  220. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  221. package/src/memory/migrations/__tests__/014-backfill-inbox-thread-state.test.ts +108 -0
  222. package/src/memory/migrations/__tests__/136-drop-assistant-id-columns.test.ts +82 -0
  223. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +224 -0
  224. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  225. package/src/memory/migrations/run-migrations.ts +90 -6
  226. package/src/memory/migrations/schema-introspection.ts +14 -0
  227. package/src/memory/migrations/validate-migration-state.ts +101 -66
  228. package/src/memory/prompt-override.ts +129 -0
  229. package/src/memory/schema/conversations.ts +9 -0
  230. package/src/memory/schema/infrastructure.ts +20 -0
  231. package/src/memory/steps.ts +573 -0
  232. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  233. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  234. package/src/memory/v2/cli-command-store.ts +75 -38
  235. package/src/memory/v2/prompts/consolidation.ts +13 -82
  236. package/src/memory/v2/prompts/router.ts +21 -93
  237. package/src/memory/v2/skill-store.ts +68 -31
  238. package/src/memory/watchdog-events-store.ts +87 -0
  239. package/src/memory/worker-control.ts +118 -0
  240. package/src/memory/worker-process.ts +72 -0
  241. package/src/notifications/__tests__/broadcaster.test.ts +16 -8
  242. package/src/notifications/__tests__/connected-channels.test.ts +114 -0
  243. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  244. package/src/notifications/__tests__/destination-resolver.test.ts +256 -0
  245. package/src/notifications/broadcaster.ts +8 -1
  246. package/src/notifications/decision-engine.ts +15 -7
  247. package/src/notifications/destination-resolver.ts +68 -24
  248. package/src/notifications/emit-signal.ts +39 -14
  249. package/src/onboarding/checkin-event.test.ts +220 -0
  250. package/src/onboarding/checkin-event.ts +321 -0
  251. package/src/onboarding/schedule-checkin.ts +190 -0
  252. package/src/permissions/question-prompter.test.ts +1 -1
  253. package/src/permissions/question-prompter.ts +7 -4
  254. package/src/plugin-api/index.ts +6 -6
  255. package/src/plugin-api/types.ts +3 -5
  256. package/src/plugin-api/vision-support.test.ts +28 -4
  257. package/src/plugin-api/vision-support.ts +66 -31
  258. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +161 -0
  259. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +106 -0
  260. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +60 -0
  261. package/src/plugins/defaults/advisor/consult.ts +110 -6
  262. package/src/plugins/defaults/advisor/context-pack.ts +288 -0
  263. package/src/plugins/defaults/advisor/steering.ts +14 -2
  264. package/src/plugins/defaults/advisor/tools/advisor.ts +32 -5
  265. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  266. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +10 -11
  267. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +12 -20
  268. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  269. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  270. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +29 -1
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +8 -1
  273. package/src/plugins/mtime-cache.ts +7 -2
  274. package/src/plugins/types.ts +0 -2
  275. package/src/providers/anthropic/client.ts +5 -0
  276. package/src/providers/call-site-routing.ts +4 -0
  277. package/src/providers/model-catalog.ts +16 -0
  278. package/src/providers/openai/responses-provider.ts +5 -0
  279. package/src/providers/openrouter/client.ts +5 -0
  280. package/src/providers/provider-send-message.ts +4 -0
  281. package/src/providers/ratelimit.ts +4 -0
  282. package/src/providers/retry.ts +4 -0
  283. package/src/providers/types.ts +9 -0
  284. package/src/providers/usage-tracking.ts +4 -0
  285. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  286. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +181 -0
  287. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  288. package/src/runtime/__tests__/local-principal-trust.test.ts +164 -0
  289. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +335 -3
  290. package/src/runtime/access-request-helper.ts +19 -39
  291. package/src/runtime/actor-trust-resolver.ts +2 -2
  292. package/src/runtime/anchored-guardian.test.ts +156 -0
  293. package/src/runtime/anchored-guardian.ts +135 -0
  294. package/src/runtime/assistant-event-hub.ts +1 -1
  295. package/src/runtime/assistant-stream-state.ts +9 -2
  296. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  297. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  298. package/src/runtime/channel-verification-service.ts +56 -31
  299. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  300. package/src/runtime/guardian-vellum-migration.ts +66 -7
  301. package/src/runtime/invite-redemption-service.ts +50 -18
  302. package/src/runtime/local-actor-identity.ts +76 -11
  303. package/src/runtime/local-principal-trust.ts +52 -0
  304. package/src/runtime/pending-interactions.ts +11 -1
  305. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  306. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  307. package/src/runtime/routes/__tests__/contact-routes.test.ts +212 -0
  308. package/src/runtime/routes/__tests__/global-search-routes.test.ts +93 -0
  309. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +215 -1
  310. package/src/runtime/routes/browser-routes.ts +1 -1
  311. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  312. package/src/runtime/routes/contact-routes.ts +8 -32
  313. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  314. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  315. package/src/runtime/routes/conversation-routes.ts +74 -81
  316. package/src/runtime/routes/events-routes.ts +2 -2
  317. package/src/runtime/routes/global-search-routes.ts +3 -1
  318. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  319. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  320. package/src/runtime/routes/host-bash-routes.ts +5 -4
  321. package/src/runtime/routes/host-browser-routes.ts +9 -11
  322. package/src/runtime/routes/host-cu-routes.ts +5 -4
  323. package/src/runtime/routes/host-file-routes.ts +5 -4
  324. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  325. package/src/runtime/routes/http-adapter.ts +1 -1
  326. package/src/runtime/routes/identity-routes.ts +3 -2
  327. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  328. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -49
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +16 -4
  331. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  332. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  333. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  334. package/src/runtime/routes/index.ts +2 -0
  335. package/src/runtime/routes/llm-context-normalization.ts +71 -0
  336. package/src/runtime/routes/mcp-auth-routes.ts +38 -15
  337. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  338. package/src/runtime/routes/migration-routes.ts +4 -1
  339. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  340. package/src/runtime/routes/subagents-routes.ts +5 -0
  341. package/src/runtime/routes/surface-action-routes.ts +51 -55
  342. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  343. package/src/runtime/services/conversation-serializer.ts +7 -9
  344. package/src/runtime/tool-grant-request-helper.ts +3 -3
  345. package/src/runtime/trust-verdict-consumer.ts +85 -9
  346. package/src/runtime/verification-outbound-actions.ts +18 -18
  347. package/src/signals/user-message.ts +16 -0
  348. package/src/subagent/manager.ts +9 -0
  349. package/src/telemetry/types.ts +34 -1
  350. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  351. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  352. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  353. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  354. package/src/tools/computer-use/definitions.ts +8 -2
  355. package/src/tools/executor.ts +4 -4
  356. package/src/tools/registry.ts +18 -0
  357. package/src/tools/tool-approval-handler.ts +1 -1
  358. package/src/tools/tool-defaults.ts +9 -2
  359. package/src/tools/types.ts +17 -2
  360. package/src/tools/workspace-tools/loader.ts +348 -244
  361. package/src/util/platform.ts +5 -0
  362. package/src/util/telemetry-db-path.ts +24 -0
  363. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  364. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  365. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  366. package/src/daemon/workspace-tools-watcher.ts +0 -328
  367. package/src/memory/migrations/registry.ts +0 -573
@@ -5,9 +5,9 @@ add a brand-new one) by dropping a single file under their
5
5
  `<workspaceDir>/tools/` directory. The override survives assistant restarts,
6
6
  takes effect during the same startup phase as core tool initialization,
7
7
  and is recoverable: removing the file restores the original core behavior.
8
- When the `workspace-tools-watcher` feature flag is enabled, overrides are
9
- also hot-reloaded by a filesystem watcher (no restart required after the
10
- initial boot).
8
+ Overrides are reconciled from disk on every conversation read, so edits
9
+ under `tools/` take effect on the next conversation with no restart and no
10
+ filesystem watcher.
11
11
 
12
12
  This page explains the file convention, lifecycle position, and the
13
13
  "single canonical source per name" invariant the design is built around.
@@ -100,8 +100,8 @@ Workspace tools are the highest-priority origin in the tool registry:
100
100
 
101
101
  - **Same name as a core tool** → the original core tool is moved into an
102
102
  internal stash (`getCoreToolOverride(name)`) and the workspace tool takes
103
- its place. Removing the workspace file causes the watcher to restore
104
- the original — workspace tools are not destructive to the core baseline.
103
+ its place. Removing the workspace file restores the original on the next
104
+ reconcile — workspace tools are not destructive to the core baseline.
105
105
  - **Brand-new name** → registers as a net-new entry. No stash.
106
106
  - **`<name>.removed` sentinel for a core tool** → the core tool is
107
107
  stripped (stashed in the same map as override-style stashing) and no
@@ -128,35 +128,44 @@ on the incoming tool.
128
128
 
129
129
  ```
130
130
  initializeTools() # core tools register
131
- → loadWorkspaceTools() # initial workspace tool scan
131
+ → loadWorkspaceTools() # initial workspace tool reconcile
132
132
  → MCP tool registration
133
133
  → loadUserPlugins()
134
134
  → bootstrapPlugins()
135
135
 
136
- # after providers-setup completes:
137
- DaemonServer.start()
138
- WorkspaceToolsWatcher.getInstance().start() # hot register/unregister via fs.watch
136
+ # on every conversation turn (createResolveToolsCallback):
137
+ resolveTools(history)
138
+ loadWorkspaceTools() # reconcile registry against disk (fire-and-forget)
139
+ → getWorkspaceToolDefinitions() # re-read workspace tools from the registry
140
+ → getMcpToolDefinitions() # re-read MCP tools (same pattern)
139
141
  ```
140
142
 
141
143
  Workspace tools register _after_ core tools and _before_ every other
142
- extension surface during the initial scan so that every subsequent
143
- registration sees the workspace tool as already-owned. The initial scan
144
- (`loadWorkspaceTools()`) always runs, so workspace tools load from disk
145
- at every boot regardless of the flag.
146
-
147
- The filesystem watcher is gated on the `workspace-tools-watcher` feature
148
- flag (default off). When enabled, it runs for the lifetime of the
149
- assistant, picking up add/change/delete events on `<workspaceDir>/tools/`
150
- and reconciling the registry without requiring a restart. When disabled,
151
- no watch loop is mounted and live edits to `<workspaceDir>/tools/` take
152
- effect only on the next daemon restart. The flag is read at startup, so
153
- toggling it takes effect on restart rather than mid-process.
154
-
155
- The watcher debounces per filename stem and reconciles by re-deriving
156
- the world from disk ("given what's on disk right now for `<stem>.*`,
157
- what registry state should the assistant be in?") rather than routing
158
- on `fs.watch`'s unreliable add/change/rename event types. This is the
159
- same eventual-consistency pattern the plugin source watcher uses.
144
+ extension surface during the initial reconcile so that every subsequent
145
+ registration sees the workspace tool as already-owned. The initial
146
+ reconcile always runs at boot, so workspace tools load from disk at every
147
+ startup.
148
+
149
+ There is no filesystem watcher. Instead, `loadWorkspaceTools()` is
150
+ idempotent and is re-invoked by the per-turn tool resolver
151
+ (`createResolveToolsCallback`), which then re-reads workspace tools from
152
+ the registry the same way it re-reads MCP tools. Each reconcile re-derives
153
+ the world from disk ("given what's on disk right now under `tools/`, what
154
+ registry state should the assistant be in?") and applies the delta
155
+ registering added files, re-importing changed files, unregistering deleted
156
+ files, and restoring core tools whose `.removed` sentinel was deleted. A
157
+ conversation therefore picks up on-disk edits on its next turn, with no
158
+ restart and without being recreated.
159
+
160
+ The reconcile is fire-and-forget and eventually consistent: an edit lands
161
+ in the registry during one turn's reconcile and is read on a subsequent
162
+ turn. Unchanged files are skipped via an mtime cache, so a no-op reconcile
163
+ costs one `readdir` plus a `stat` per file and never re-imports. Concurrent
164
+ callers coalesce onto a single in-flight reconcile so their
165
+ unregister/register sequences never interleave. This is the same
166
+ eventual-consistency, re-derive-from-disk approach the plugin loader's
167
+ mtime cache uses, with the per-turn tool read — rather than a watcher —
168
+ kicking the reconcile.
160
169
 
161
170
  ## Per-tool isolation
162
171
 
@@ -182,15 +191,15 @@ the whole call without partially populating the registry.
182
191
 
183
192
  ## Unregistering
184
193
 
185
- Deleting `<workspaceDir>/tools/<name>.{ts,js,json}` triggers the file
186
- watcher's reconcile, which calls `unregisterWorkspaceTool(name)` and
187
- restores the stashed core tool when one exists, or simply deletes the
188
- entry when the workspace tool was net-new. No assistant restart is
189
- required.
194
+ Deleting `<workspaceDir>/tools/<name>.{ts,js,json}` is picked up by the
195
+ next reconcile, which calls `unregisterWorkspaceTool(name)` and restores
196
+ the stashed core tool when one exists, or simply deletes the entry when
197
+ the workspace tool was net-new. The change takes effect on the next
198
+ conversation — no assistant restart is required.
190
199
 
191
200
  To strip a core tool without substituting a replacement, drop an empty
192
201
  `<workspaceDir>/tools/<name>.removed` file. Removing that sentinel
193
202
  restores the core tool on the next reconcile. The two states (override
194
203
  vs. strip) are mutually exclusive — placing both `<name>.ts` and
195
- `<name>.removed` for the same stem causes the watcher to tear down
204
+ `<name>.removed` for the same stem causes the reconcile to tear down
196
205
  both states until the conflict is resolved on disk.
@@ -51,11 +51,17 @@ const ALLOWED_PREFIXES = {
51
51
  "../../config/schema",
52
52
  "../../config/env",
53
53
  "../../util/platform",
54
+ "../../../util/platform",
54
55
  // Memory retrospective — the retrospective CLI runs the fork-based
55
56
  // retrospective in-process (no daemon, no IPC), so it imports the
56
57
  // job handler directly. Depth-2 for commands/memory/ nesting.
57
58
  "../../memory/memory-retrospective-job",
58
59
  "../../../memory/memory-retrospective-job",
60
+ // Memory worker control — the `memory worker` CLI spawns/probes/stops
61
+ // the worker OS process directly (no daemon, no IPC), so it imports the
62
+ // shared PID-file control helpers. Depth-2 for commands/memory/ nesting.
63
+ "../../memory/worker-control",
64
+ "../../../memory/worker-control",
59
65
  "../logger",
60
66
  "../output",
61
67
  "../../logger",
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Tests for the guardian binding + delivery pull contract.
3
+ */
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+
7
+ import {
8
+ GuardianDeliverySchema,
9
+ ResolveGuardianDeliveryRequestSchema,
10
+ type GuardianDelivery,
11
+ } from "../guardian-delivery-contract.js";
12
+
13
+ const fullGuardian: GuardianDelivery = {
14
+ channelType: "imessage",
15
+ contactId: "c1",
16
+ principalId: "p1",
17
+ displayName: "Guardian Name",
18
+ address: "+15555550100",
19
+ externalChatId: "ext-chat-1",
20
+ status: "active",
21
+ verifiedAt: 1699999999,
22
+ };
23
+
24
+ describe("ResolveGuardianDeliveryRequestSchema", () => {
25
+ test("parses with channelTypes", () => {
26
+ const req = { channelTypes: ["imessage", "sms"] };
27
+ expect(ResolveGuardianDeliveryRequestSchema.parse(req)).toEqual(req);
28
+ });
29
+
30
+ test("parses with channelTypes omitted", () => {
31
+ expect(ResolveGuardianDeliveryRequestSchema.parse({})).toEqual({});
32
+ });
33
+
34
+ test("defaults to {} when params are undefined (no-param IPC call)", () => {
35
+ expect(ResolveGuardianDeliveryRequestSchema.parse(undefined)).toEqual({});
36
+ });
37
+ });
38
+
39
+ describe("GuardianDeliverySchema", () => {
40
+ test("round-trips a fully-populated row", () => {
41
+ expect(GuardianDeliverySchema.parse(fullGuardian)).toEqual(fullGuardian);
42
+ });
43
+
44
+ test("requires channelType", () => {
45
+ const { channelType: _omit, ...rest } = fullGuardian;
46
+ expect(() => GuardianDeliverySchema.parse(rest)).toThrow();
47
+ });
48
+
49
+ test("requires contactId", () => {
50
+ const { contactId: _omit, ...rest } = fullGuardian;
51
+ expect(() => GuardianDeliverySchema.parse(rest)).toThrow();
52
+ });
53
+
54
+ test("requires address", () => {
55
+ const { address: _omit, ...rest } = fullGuardian;
56
+ expect(() => GuardianDeliverySchema.parse(rest)).toThrow();
57
+ });
58
+
59
+ test("requires status", () => {
60
+ const { status: _omit, ...rest } = fullGuardian;
61
+ expect(() => GuardianDeliverySchema.parse(rest)).toThrow();
62
+ });
63
+
64
+ test("optional fields accept null", () => {
65
+ const row = {
66
+ channelType: "imessage",
67
+ contactId: "c1",
68
+ address: "+15555550100",
69
+ status: "active",
70
+ principalId: null,
71
+ displayName: null,
72
+ externalChatId: null,
73
+ verifiedAt: null,
74
+ } satisfies GuardianDelivery;
75
+ expect(GuardianDeliverySchema.parse(row)).toEqual(row);
76
+ });
77
+
78
+ test("optional fields accept undefined (omitted)", () => {
79
+ const row = {
80
+ channelType: "imessage",
81
+ contactId: "c1",
82
+ address: "+15555550100",
83
+ status: "active",
84
+ } satisfies GuardianDelivery;
85
+ const parsed = GuardianDeliverySchema.parse(row);
86
+ expect(parsed.principalId).toBeUndefined();
87
+ expect(parsed.displayName).toBeUndefined();
88
+ expect(parsed.externalChatId).toBeUndefined();
89
+ expect(parsed.verifiedAt).toBeUndefined();
90
+ });
91
+ });
@@ -7,6 +7,7 @@ import { describe, expect, test } from "bun:test";
7
7
 
8
8
  import { SourceMetadataSchema } from "../inbound-contract.js";
9
9
  import {
10
+ makeResolutionFailedVerdict,
10
11
  TrustVerdictSchema,
11
12
  type TrustVerdict,
12
13
  } from "../trust-verdict-contract.js";
@@ -43,6 +44,36 @@ describe("TrustVerdictSchema", () => {
43
44
  expect(TrustVerdictSchema.parse(minimal)).toEqual(minimal);
44
45
  });
45
46
 
47
+ test("parses a verdict carrying resolutionFailed", () => {
48
+ const verdict = {
49
+ trustClass: "unknown",
50
+ canonicalSenderId: null,
51
+ resolutionFailed: true,
52
+ } satisfies TrustVerdict;
53
+ expect(TrustVerdictSchema.parse(verdict)).toEqual(verdict);
54
+ });
55
+
56
+ test("leaves resolutionFailed undefined when absent", () => {
57
+ const parsed = TrustVerdictSchema.parse({
58
+ trustClass: "unknown",
59
+ canonicalSenderId: null,
60
+ });
61
+ expect(parsed.resolutionFailed).toBeUndefined();
62
+ });
63
+
64
+ test("makeResolutionFailedVerdict builds an unknown sentinel", () => {
65
+ expect(makeResolutionFailedVerdict("+15555550100")).toEqual({
66
+ trustClass: "unknown",
67
+ canonicalSenderId: "+15555550100",
68
+ resolutionFailed: true,
69
+ });
70
+ expect(makeResolutionFailedVerdict(null)).toEqual({
71
+ trustClass: "unknown",
72
+ canonicalSenderId: null,
73
+ resolutionFailed: true,
74
+ });
75
+ });
76
+
46
77
  test("rejects an invalid trustClass", () => {
47
78
  expect(() =>
48
79
  TrustVerdictSchema.parse({
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Shared contract for the gateway-owned guardian binding + delivery pull.
3
+ *
4
+ * The gateway resolves the active guardian binding(s) and their per-channel
5
+ * delivery endpoints from its DB; the daemon pulls them via the
6
+ * `resolve_guardian_delivery` IPC route. INFO (notes / userFile) is NOT
7
+ * carried here — the daemon joins it locally by `contactId`.
8
+ */
9
+
10
+ import { z } from "zod";
11
+
12
+ /**
13
+ * IPC request for `resolve_guardian_delivery`. `channelTypes` is an optional
14
+ * filter; omitted ⇒ all active guardian channels.
15
+ */
16
+ export const ResolveGuardianDeliveryRequestSchema = z
17
+ .object({
18
+ channelTypes: z.array(z.string()).optional(),
19
+ })
20
+ .default({});
21
+
22
+ export type ResolveGuardianDeliveryRequest = z.infer<
23
+ typeof ResolveGuardianDeliveryRequestSchema
24
+ >;
25
+
26
+ /**
27
+ * One active guardian binding + delivery endpoint for a single channel.
28
+ */
29
+ export const GuardianDeliverySchema = z.object({
30
+ channelType: z.string(),
31
+ contactId: z.string(),
32
+ principalId: z.string().nullable().optional(),
33
+ displayName: z.string().nullable().optional(),
34
+ address: z.string(),
35
+ externalChatId: z.string().nullable().optional(),
36
+ status: z.string(),
37
+ verifiedAt: z.number().nullable().optional(),
38
+ });
39
+
40
+ export type GuardianDelivery = z.infer<typeof GuardianDeliverySchema>;
41
+
42
+ export const ResolveGuardianDeliveryResponseSchema = z.object({
43
+ guardians: z.array(GuardianDeliverySchema),
44
+ });
45
+
46
+ export type ResolveGuardianDeliveryResponse = z.infer<
47
+ typeof ResolveGuardianDeliveryResponseSchema
48
+ >;
@@ -73,6 +73,7 @@ export type { AdmissionPolicy } from "./admission-policy-contract.js";
73
73
 
74
74
  // Trust verdict contract (gateway → daemon) — Zod schemas + derived types
75
75
  export {
76
+ makeResolutionFailedVerdict,
76
77
  ResolveInboundTrustRequestSchema,
77
78
  TRUST_CLASS_VALUES,
78
79
  TrustClassSchema,
@@ -84,3 +85,16 @@ export type {
84
85
  TrustClass,
85
86
  TrustVerdict,
86
87
  } from "./trust-verdict-contract.js";
88
+
89
+ // Guardian delivery contract (daemon → gateway pull) — Zod schemas + derived types
90
+ export {
91
+ GuardianDeliverySchema,
92
+ ResolveGuardianDeliveryRequestSchema,
93
+ ResolveGuardianDeliveryResponseSchema,
94
+ } from "./guardian-delivery-contract.js";
95
+
96
+ export type {
97
+ GuardianDelivery,
98
+ ResolveGuardianDeliveryRequest,
99
+ ResolveGuardianDeliveryResponse,
100
+ } from "./guardian-delivery-contract.js";
@@ -42,6 +42,11 @@ export const TrustVerdictSchema = z.object({
42
42
  trustClass: TrustClassSchema,
43
43
  canonicalSenderId: z.string().nullable(),
44
44
 
45
+ // Present+true ⇒ gateway attempted resolution but failed (DB error);
46
+ // consumer treats it as "could not vouch", distinct from a real `unknown`
47
+ // stranger.
48
+ resolutionFailed: z.boolean().optional(),
49
+
45
50
  // Guardian binding — present only when a guardian binding matches.
46
51
  guardianExternalUserId: z.string().optional(),
47
52
  guardianDeliveryChatId: z.string().nullable().optional(),
@@ -63,6 +68,18 @@ export const TrustVerdictSchema = z.object({
63
68
 
64
69
  export type TrustVerdict = z.infer<typeof TrustVerdictSchema>;
65
70
 
71
+ /**
72
+ * Sentinel for a gateway resolver failure; consumers treat it as
73
+ * could-not-vouch (distinct from a real `unknown` stranger). Takes the
74
+ * already-canonicalized sender id so this module stays free of the gateway's
75
+ * canonicalization util.
76
+ */
77
+ export function makeResolutionFailedVerdict(
78
+ canonicalSenderId: string | null,
79
+ ): TrustVerdict {
80
+ return { trustClass: "unknown", canonicalSenderId, resolutionFailed: true };
81
+ }
82
+
66
83
  /**
67
84
  * IPC request for `resolve_inbound_trust`. Per-actor identity keys the
68
85
  * gateway resolver needs to classify the inbound sender. The response reuses
package/openapi.yaml CHANGED
@@ -3,7 +3,7 @@
3
3
  openapi: 3.1.0
4
4
  info:
5
5
  title: Vellum Assistant API
6
- version: 0.10.1
6
+ version: 0.10.2
7
7
  description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
8
8
  servers:
9
9
  - url: http://127.0.0.1:7821
@@ -3973,6 +3973,8 @@ paths:
3973
3973
  anyOf:
3974
3974
  - type: string
3975
3975
  - type: "null"
3976
+ resolutionFailed:
3977
+ type: boolean
3976
3978
  guardianExternalUserId:
3977
3979
  type: string
3978
3980
  guardianDeliveryChatId:
@@ -14709,6 +14711,12 @@ paths:
14709
14711
  type: string
14710
14712
  disabled:
14711
14713
  type: boolean
14714
+ headers:
14715
+ type: object
14716
+ propertyNames:
14717
+ type: string
14718
+ additionalProperties:
14719
+ type: string
14712
14720
  required:
14713
14721
  - name
14714
14722
  - transportType
@@ -14936,6 +14944,14 @@ paths:
14936
14944
  items:
14937
14945
  type: string
14938
14946
  - type: "null"
14947
+ headers:
14948
+ anyOf:
14949
+ - type: object
14950
+ propertyNames:
14951
+ type: string
14952
+ additionalProperties:
14953
+ type: string
14954
+ - type: "null"
14939
14955
  required:
14940
14956
  - name
14941
14957
  responses:
@@ -19605,6 +19621,56 @@ paths:
19605
19621
  responses:
19606
19622
  "200":
19607
19623
  description: Successful response
19624
+ /v1/onboarding/checkin:
19625
+ post:
19626
+ operationId: onboarding_checkin_post
19627
+ summary: Schedule the onboarding Day 2 check-in
19628
+ description:
19629
+ "Find the first open 15-minute slot between 12pm and 5pm tomorrow (widening to 8am–8pm if booked) on the
19630
+ user's Google Calendar and create the Day 2 Check-in event. Best-effort: returns scheduled=false when no
19631
+ calendar is connected or the calendar scope wasn't granted."
19632
+ tags:
19633
+ - onboarding
19634
+ requestBody:
19635
+ required: true
19636
+ content:
19637
+ application/json:
19638
+ schema:
19639
+ type: object
19640
+ properties:
19641
+ userName:
19642
+ type: string
19643
+ assistantName:
19644
+ type: string
19645
+ timezone:
19646
+ type: string
19647
+ responses:
19648
+ "200":
19649
+ description: Successful response
19650
+ content:
19651
+ application/json:
19652
+ schema:
19653
+ type: object
19654
+ properties:
19655
+ scheduled:
19656
+ type: boolean
19657
+ reason:
19658
+ type: string
19659
+ eventId:
19660
+ type: string
19661
+ htmlLink:
19662
+ anyOf:
19663
+ - type: string
19664
+ - type: "null"
19665
+ start:
19666
+ type: string
19667
+ end:
19668
+ type: string
19669
+ timeZone:
19670
+ type: string
19671
+ required:
19672
+ - scheduled
19673
+ additionalProperties: false
19608
19674
  /v1/pages/{appId}:
19609
19675
  get:
19610
19676
  operationId: pages_by_appId_get
@@ -25978,6 +26044,13 @@ paths:
25978
26044
  type: boolean
25979
26045
  messageId:
25980
26046
  type: string
26047
+ toolUseId:
26048
+ type: string
26049
+ input:
26050
+ type: object
26051
+ propertyNames:
26052
+ type: string
26053
+ additionalProperties: {}
25981
26054
  required:
25982
26055
  - type
25983
26056
  - content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.10.1",
3
+ "version": "0.10.2-dev.202606241651.2d2b40d",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
package/scripts/test.sh CHANGED
@@ -169,6 +169,7 @@ printf '%s\n' "${test_files[@]}" | xargs -P "${WORKERS}" -I {} bash -c '
169
169
 
170
170
  safe_name="$(echo "${test_file}" | tr "/" "_")"
171
171
  out_file="${results_dir}/${safe_name}.out"
172
+ base="$(basename "${test_file}")"
172
173
 
173
174
  coverage_args=""
174
175
  if [[ "${coverage_enabled}" == "true" ]]; then
@@ -185,28 +186,48 @@ printf '%s\n' "${test_files[@]}" | xargs -P "${WORKERS}" -I {} bash -c '
185
186
  timeout_cmd="gtimeout"
186
187
  fi
187
188
 
188
- start_ms=$(perl -MTime::HiRes=time -e "printf \"%d\", time*1000")
189
-
190
- if [[ -n "${timeout_cmd}" ]]; then
191
- if [[ "${exclude_exp}" == "true" ]]; then
192
- "${timeout_cmd}" -k 10 "${per_test_timeout}" bun test ${coverage_args} --test-name-pattern "^(?!.*\\[experimental\\])" "${test_file}" > "${out_file}" 2>&1
193
- else
194
- "${timeout_cmd}" -k 10 "${per_test_timeout}" bun test ${coverage_args} "${test_file}" > "${out_file}" 2>&1
195
- fi
196
- else
197
- if [[ "${exclude_exp}" == "true" ]]; then
198
- bun test ${coverage_args} --test-name-pattern "^(?!.*\\[experimental\\])" "${test_file}" > "${out_file}" 2>&1
189
+ # Run the test file in its own bun process. Wrapped in a function so a
190
+ # transient infrastructure crash can be retried without duplicating the
191
+ # four-way (timeout × experimental-filter) invocation.
192
+ run_bun_test() {
193
+ if [[ -n "${timeout_cmd}" ]]; then
194
+ if [[ "${exclude_exp}" == "true" ]]; then
195
+ "${timeout_cmd}" -k 10 "${per_test_timeout}" bun test ${coverage_args} --test-name-pattern "^(?!.*\\[experimental\\])" "${test_file}" > "${out_file}" 2>&1
196
+ else
197
+ "${timeout_cmd}" -k 10 "${per_test_timeout}" bun test ${coverage_args} "${test_file}" > "${out_file}" 2>&1
198
+ fi
199
199
  else
200
- bun test ${coverage_args} "${test_file}" > "${out_file}" 2>&1
200
+ if [[ "${exclude_exp}" == "true" ]]; then
201
+ bun test ${coverage_args} --test-name-pattern "^(?!.*\\[experimental\\])" "${test_file}" > "${out_file}" 2>&1
202
+ else
203
+ bun test ${coverage_args} "${test_file}" > "${out_file}" 2>&1
204
+ fi
201
205
  fi
202
- fi
206
+ }
207
+
208
+ start_ms=$(perl -MTime::HiRes=time -e "printf \"%d\", time*1000")
209
+
210
+ run_bun_test
203
211
  exit_code=$?
204
212
 
213
+ # Retry once on a transient bun loader/runtime crash. Bun occasionally aborts
214
+ # a worker before its tests run — e.g. "# Unhandled error between tests" with
215
+ # "error: ENOENT reading <corrupted path>" — failing the whole job even though
216
+ # the file is green on a clean process. Such a crash truncates the run before
217
+ # any assertion executes, so it prints no "(fail)" line; genuine assertion
218
+ # failures do, and are reported immediately without a retry (fast feedback, no
219
+ # masking of intermittent real failures). Timeouts (124/137) are handled by the
220
+ # hang branch below, not here.
221
+ if [[ ${exit_code} -ne 0 && ${exit_code} -ne 124 && ${exit_code} -ne 137 ]] \
222
+ && ! grep -q "^(fail)" "${out_file}" 2>/dev/null; then
223
+ echo " ↻ ${base} (transient crash, exit ${exit_code} — retrying once)"
224
+ run_bun_test
225
+ exit_code=$?
226
+ fi
227
+
205
228
  end_ms=$(perl -MTime::HiRes=time -e "printf \"%d\", time*1000")
206
229
  elapsed=$(( end_ms - start_ms ))
207
230
 
208
- base="$(basename "${test_file}")"
209
-
210
231
  # Record duration for longest-first scheduling in future runs.
211
232
  # Write the repo-relative path (not the basename) so the lookup in future
212
233
  # runs disambiguates files that share a basename across directories.