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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -102,6 +102,17 @@ export class AssistantError extends VellumError {
102
102
  export class ProviderError extends AssistantError {
103
103
  /** Delay (in ms) suggested by the server's Retry-After header, if present. */
104
104
  public readonly retryAfterMs?: number;
105
+ /** Upstream provider error metadata, parsed from the raw non-2xx body. */
106
+ public readonly apiErrorCode?: string;
107
+ public readonly apiErrorType?: string;
108
+ public readonly apiErrorParam?: string;
109
+ public readonly requestId?: string;
110
+ /**
111
+ * Verbatim upstream non-2xx body (possibly truncated). Persisted so the
112
+ * inspector's Raw tab can show the actual provider error payload, not just
113
+ * the extracted fields.
114
+ */
115
+ public readonly rawBody?: string;
105
116
  /**
106
117
  * Tagged daemon-owned abort reason carried over from the AbortSignal that
107
118
  * triggered this error. Untyped here to avoid a daemon→util import cycle;
@@ -114,11 +125,25 @@ export class ProviderError extends AssistantError {
114
125
  message: string,
115
126
  public readonly provider: string,
116
127
  public readonly statusCode?: number,
117
- options?: { cause?: unknown; retryAfterMs?: number; abortReason?: unknown },
128
+ options?: {
129
+ cause?: unknown;
130
+ retryAfterMs?: number;
131
+ abortReason?: unknown;
132
+ apiErrorCode?: string;
133
+ apiErrorType?: string;
134
+ apiErrorParam?: string;
135
+ requestId?: string;
136
+ rawBody?: string;
137
+ },
118
138
  ) {
119
139
  super(message, ErrorCode.PROVIDER_ERROR, options);
120
140
  this.name = "ProviderError";
121
141
  this.retryAfterMs = options?.retryAfterMs;
142
+ this.apiErrorCode = options?.apiErrorCode;
143
+ this.apiErrorType = options?.apiErrorType;
144
+ this.apiErrorParam = options?.apiErrorParam;
145
+ this.requestId = options?.requestId;
146
+ this.rawBody = options?.rawBody;
122
147
  this.abortReason = options?.abortReason;
123
148
  }
124
149
  }
@@ -49,6 +49,15 @@ function logFilePathForDate(dir: string, date: Date): string {
49
49
  return join(dir, `${LOG_FILE_PREFIX}${formatDate(date)}${LOG_FILE_SUFFIX}`);
50
50
  }
51
51
 
52
+ /**
53
+ * Returns the path to today's log file (`<logsDir>/assistant-YYYY-MM-DD.log`).
54
+ * Used by callers that need to open the same file the logger writes to, e.g.
55
+ * the memory worker spawner piping the child's stderr into the log file.
56
+ */
57
+ export function getCurrentLogFilePath(): string {
58
+ return logFilePathForDate(getLogsDir(), new Date());
59
+ }
60
+
52
61
  export function pruneOldLogFiles(dir: string, retentionDays: number): number {
53
62
  if (!existsSync(dir)) return 0;
54
63
 
@@ -242,6 +242,25 @@ export function getEmbedWorkerPidPath(): string {
242
242
  return join(getWorkspaceDir(), "embed-worker.pid");
243
243
  }
244
244
 
245
+ /** Returns the path to the memory-worker PID file ($VELLUM_WORKSPACE_DIR/memory-worker.pid). */
246
+ export function getMemoryWorkerPidPath(): string {
247
+ return join(getWorkspaceDir(), "memory-worker.pid");
248
+ }
249
+
250
+ /**
251
+ * Returns the path to the memory sync-runner marker file
252
+ * ($VELLUM_WORKSPACE_DIR/memory-sync-runner.pid).
253
+ *
254
+ * The daemon's in-process memory-jobs supervisor writes this marker (with its
255
+ * own PID) while the synchronous in-process runner is actively draining the
256
+ * queue, and removes it when it stands down for an out-of-process worker. It
257
+ * lets `assistant memory worker status` report whether the synchronous runner
258
+ * is still going without an IPC round-trip to the daemon.
259
+ */
260
+ export function getMemorySyncRunnerMarkerPath(): string {
261
+ return join(getWorkspaceDir(), "memory-sync-runner.pid");
262
+ }
263
+
245
264
  /**
246
265
  * Returns the workspace root for user-facing state.
247
266
  *
@@ -0,0 +1,24 @@
1
+ import { join } from "node:path";
2
+
3
+ import { getDataDir } from "./platform.js";
4
+
5
+ /**
6
+ * Path to the dedicated SQLite file that houses telemetry event tables
7
+ * (starting with `watchdog_events`). It lives in the same `data/db` directory
8
+ * as the main DB and is opened on its own connection (see `getTelemetryDb()`
9
+ * in `memory/db-connection.ts`). Splitting telemetry tables into their own
10
+ * file keeps the main DB — and its WAL — small and lets the two files
11
+ * VACUUM/checkpoint independently, so a burst of watchdog events can no longer
12
+ * bloat the main database.
13
+ *
14
+ * Kept in its own leaf module rather than alongside `getDbPath()` in
15
+ * `platform.ts`: `platform.ts` is imported very early and widely, and adding an
16
+ * export to it that low-level consumers (e.g. `db-connection.ts`) pull in
17
+ * across the daemon's large, cyclic import graph trips a Bun link-order bug
18
+ * ("Export named 'getTelemetryDbPath' not found"). Isolating it here keeps
19
+ * `platform.ts`'s module shape stable — same reasoning as `logs-db-path.ts`
20
+ * and `memory-db-path.ts`.
21
+ */
22
+ export function getTelemetryDbPath(): string {
23
+ return join(getDataDir(), "db", "assistant-telemetry.db");
24
+ }
@@ -37,6 +37,13 @@ function writeWorkflow(file: string, source: string): void {
37
37
  writeFileSync(join(dir, file), source, "utf8");
38
38
  }
39
39
 
40
+ /** Write `<workspace>/workflows/<name>/workflow.ts` (directory-style). */
41
+ function writeDirWorkflow(name: string, source: string): void {
42
+ const dir = join(workspaceDir, "workflows", name);
43
+ mkdirSync(dir, { recursive: true });
44
+ writeFileSync(join(dir, "workflow.ts"), source, "utf8");
45
+ }
46
+
40
47
  describe("listWorkflows", () => {
41
48
  test("returns [] and does not create the dir when none exists", () => {
42
49
  expect(listWorkflows()).toEqual([]);
@@ -84,6 +91,51 @@ describe("listWorkflows", () => {
84
91
  expect(entries.map((e) => e.name)).toEqual(["real"]);
85
92
  });
86
93
 
94
+ test("includes both flat and directory-style workflows", () => {
95
+ writeWorkflow(
96
+ "flat.workflow.ts",
97
+ `export const meta = { name: "flat-wf", description: "flat" };\nreturn 1;`,
98
+ );
99
+ writeDirWorkflow(
100
+ "dir-wf",
101
+ `export const meta = { name: "dir-wf", description: "dir" };\nreturn 1;`,
102
+ );
103
+
104
+ const entries = listWorkflows().sort((a, b) =>
105
+ a.name.localeCompare(b.name),
106
+ );
107
+ expect(entries.map((e) => e.name)).toEqual(["dir-wf", "flat-wf"]);
108
+ expect(entries[0]!.path).toContain(join("dir-wf", "workflow.ts"));
109
+ expect(entries[1]!.path).toContain("flat.workflow.ts");
110
+ });
111
+
112
+ test("ignores a subdir without a workflow.ts and does not recurse deeper", () => {
113
+ writeWorkflow(
114
+ "real.workflow.ts",
115
+ `export const meta = { name: "real", description: "d" };\nreturn 1;`,
116
+ );
117
+ // Subdir with no entry-point workflow.ts.
118
+ mkdirSync(join(workspaceDir, "workflows", "empty-dir"), {
119
+ recursive: true,
120
+ });
121
+ writeFileSync(
122
+ join(workspaceDir, "workflows", "empty-dir", "notes.ts"),
123
+ `export const meta = { name: "ignored", description: "d" };`,
124
+ "utf8",
125
+ );
126
+ // A nested workflow.ts one level deeper than supported must not be picked up.
127
+ mkdirSync(join(workspaceDir, "workflows", "outer", "inner"), {
128
+ recursive: true,
129
+ });
130
+ writeFileSync(
131
+ join(workspaceDir, "workflows", "outer", "inner", "workflow.ts"),
132
+ `export const meta = { name: "too-deep", description: "d" };`,
133
+ "utf8",
134
+ );
135
+
136
+ expect(listWorkflows().map((e) => e.name)).toEqual(["real"]);
137
+ });
138
+
87
139
  test("skips a file whose meta is computed/non-literal (cannot be statically extracted)", () => {
88
140
  writeWorkflow(
89
141
  "ok.workflow.ts",
@@ -131,4 +183,92 @@ describe("getWorkflow", () => {
131
183
  );
132
184
  expect(getWorkflow("ghost")).toBeNull();
133
185
  });
186
+
187
+ test("resolves a directory-style workflow by meta.name", () => {
188
+ const source = `export const meta = { name: "dir-digest", description: "d" };\nreturn agent("go");`;
189
+ writeDirWorkflow("dir-digest", source);
190
+
191
+ const resolved = getWorkflow("dir-digest");
192
+ expect(resolved).not.toBeNull();
193
+ expect(resolved!.source).toBe(source);
194
+ expect(resolved!.path).toContain(join("dir-digest", "workflow.ts"));
195
+ });
196
+
197
+ test("resolves a directory-style workflow by directory name when meta.name differs", () => {
198
+ // meta.name is "canonical" but the dir (and caller's name) is "by-dir".
199
+ const source = `export const meta = { name: "canonical", description: "d" };\nreturn 1;`;
200
+ writeDirWorkflow("by-dir", source);
201
+
202
+ expect(getWorkflow("by-dir")!.source).toBe(source);
203
+ // The canonical meta.name also resolves.
204
+ expect(getWorkflow("canonical")!.source).toBe(source);
205
+ });
206
+
207
+ test("prefers a meta.name match over a base-name match", () => {
208
+ // Asking for "wanted" should hit the file whose meta.name is "wanted",
209
+ // not the file whose base name is "wanted" (whose meta.name differs).
210
+ writeWorkflow(
211
+ "wanted.workflow.ts",
212
+ `export const meta = { name: "other", description: "by base name" };\nreturn 1;`,
213
+ );
214
+ const metaSource = `export const meta = { name: "wanted", description: "by meta name" };\nreturn 1;`;
215
+ writeWorkflow("real.workflow.ts", metaSource);
216
+
217
+ expect(getWorkflow("wanted")!.source).toBe(metaSource);
218
+ });
219
+ });
220
+
221
+ describe("name collisions", () => {
222
+ test("a directory wins over a same-base-name flat file", () => {
223
+ writeWorkflow(
224
+ "foo.workflow.ts",
225
+ `export const meta = { name: "foo", description: "flat" };\nreturn "FLAT";`,
226
+ );
227
+ writeDirWorkflow(
228
+ "foo",
229
+ `export const meta = { name: "foo", description: "dir" };\nreturn "DIR";`,
230
+ );
231
+
232
+ const resolved = getWorkflow("foo")!;
233
+ expect(resolved.source).toContain("DIR");
234
+ expect(resolved.path.endsWith(join("foo", "workflow.ts"))).toBe(true);
235
+
236
+ const foos = listWorkflows().filter((e) => e.name === "foo");
237
+ expect(foos).toHaveLength(1);
238
+ expect(foos[0]!.description).toBe("dir");
239
+ });
240
+
241
+ test("the shadowed flat file's own meta.name no longer resolves", () => {
242
+ writeWorkflow(
243
+ "bar.workflow.ts",
244
+ `export const meta = { name: "flat-name", description: "flat" };\nreturn "FLAT";`,
245
+ );
246
+ writeDirWorkflow(
247
+ "bar",
248
+ `export const meta = { name: "dir-name", description: "dir" };\nreturn "DIR";`,
249
+ );
250
+
251
+ expect(getWorkflow("dir-name")!.source).toContain("DIR");
252
+ expect(getWorkflow("flat-name")).toBeNull();
253
+ // Base-name fallback also lands on the directory.
254
+ expect(getWorkflow("bar")!.source).toContain("DIR");
255
+ expect(listWorkflows().map((e) => e.name)).toEqual(["dir-name"]);
256
+ });
257
+
258
+ test("listWorkflows dedups a duplicate meta.name across different files", () => {
259
+ writeWorkflow(
260
+ "a.workflow.ts",
261
+ `export const meta = { name: "dup", description: "from-a" };\nreturn "A";`,
262
+ );
263
+ writeDirWorkflow(
264
+ "b",
265
+ `export const meta = { name: "dup", description: "from-b" };\nreturn "B";`,
266
+ );
267
+
268
+ // One entry; the directory (yielded first) wins, consistent with getWorkflow.
269
+ const dups = listWorkflows().filter((e) => e.name === "dup");
270
+ expect(dups).toHaveLength(1);
271
+ expect(dups[0]!.description).toBe("from-b");
272
+ expect(getWorkflow("dup")!.source).toContain("B");
273
+ });
134
274
  });
@@ -2,13 +2,17 @@
2
2
  * Saved-workflow LIBRARY — named workflows the assistant can invoke by name and
3
3
  * the scheduler can trigger.
4
4
  *
5
- * Saved workflows live at `<workspace>/workflows/*.workflow.ts`. Each file is a
6
- * normal workflow script: a leading literal `export const meta = { name,
7
- * description }` followed by the script body. The library only reads the STATIC
8
- * `meta` (via {@link extractWorkflowMeta}, a pure-literal extractor that never
9
- * executes the untrusted source — only the QuickJS sandbox may run it) and the
10
- * raw source. Resolution to an actual run goes through {@link executeWorkflow} /
11
- * {@link WorkflowRunManager}, which run the source in the sandbox.
5
+ * Saved workflows live at `<workspace>/workflows/`. The preferred layout is
6
+ * directory-style `<name>/workflow.ts` (so a workflow and its state can sit in
7
+ * one folder); flat `<name>.workflow.ts` is still supported but discouraged. On
8
+ * a base-name collision the directory wins. Each is a normal workflow script: a
9
+ * leading literal
10
+ * `export const meta = { name, description }` followed by the script body. The
11
+ * library only reads the STATIC `meta` (via {@link extractWorkflowMeta}, a
12
+ * pure-literal extractor that never executes the untrusted source — only the
13
+ * QuickJS sandbox may run it) and the raw source. Resolution to an actual run
14
+ * goes through {@link executeWorkflow} / {@link WorkflowRunManager}, which run
15
+ * the source in the sandbox.
12
16
  *
13
17
  * The directory is read lazily and is NOT created eagerly: if it does not exist,
14
18
  * {@link listWorkflows} returns `[]` and {@link getWorkflow} returns `null`.
@@ -19,7 +23,7 @@ import { join } from "node:path";
19
23
 
20
24
  import { getLogger } from "../util/logger.js";
21
25
  import { getWorkspaceDir } from "../util/platform.js";
22
- import { extractWorkflowMeta } from "./engine.js";
26
+ import { extractWorkflowMeta, type WorkflowMeta } from "./engine.js";
23
27
 
24
28
  const log = getLogger("workflow-library");
25
29
 
@@ -49,31 +53,66 @@ function workflowsDir(): string {
49
53
  return join(getWorkspaceDir(), "workflows");
50
54
  }
51
55
 
56
+ /** Filename of the entry-point script inside a directory-style workflow. */
57
+ const DIRECTORY_ENTRY = "workflow.ts";
58
+
52
59
  /** The filename (sans `.workflow.ts`) for `<workspace>/workflows/<base>.workflow.ts`. */
53
60
  function fileBaseName(file: string): string {
54
61
  return file.slice(0, -WORKFLOW_SUFFIX.length);
55
62
  }
56
63
 
57
- /** A `*.workflow.ts` file read off disk, before meta extraction. */
64
+ /** A saved workflow read off disk, before meta extraction. */
58
65
  interface RawWorkflowFile {
59
- file: string;
66
+ /** Base identity for the filename/dir fallback match (see {@link getWorkflow}). */
67
+ baseName: string;
60
68
  path: string;
61
69
  source: string;
62
70
  }
63
71
 
64
72
  /**
65
- * Yield every readable `*.workflow.ts` file in the workflows directory (each read
66
- * once). Returns nothing if the directory does not exist it is never created.
67
- * An unreadable file is skipped with a logged warning.
73
+ * Yield every readable saved workflow in a STABLE order (the raw filesystem
74
+ * listing order is not guaranteed, so callers that take "the first match" stay
75
+ * deterministic). Directory-style `<name>/workflow.ts` is yielded first and
76
+ * WINS: a flat `<name>.workflow.ts` with the same base name is shadowed (skipped
77
+ * with a warning). Yields nothing if the directory does not exist — it is never
78
+ * created. An unreadable entry is skipped with a logged warning.
68
79
  */
69
80
  function* readWorkflowFiles(): Generator<RawWorkflowFile> {
70
81
  const dir = workflowsDir();
71
82
  if (!existsSync(dir)) return;
72
- for (const file of readdirSync(dir)) {
73
- if (!file.endsWith(WORKFLOW_SUFFIX)) continue;
74
- const path = join(dir, file);
83
+ const entries = readdirSync(dir).sort();
84
+ const seen = new Set<string>();
85
+
86
+ // Directory-style first, so it wins over a same-base-name flat file.
87
+ for (const entry of entries) {
88
+ if (entry.endsWith(WORKFLOW_SUFFIX)) continue;
89
+ const path = join(dir, entry, DIRECTORY_ENTRY);
90
+ if (!existsSync(path)) continue;
91
+ try {
92
+ const source = readFileSync(path, "utf8");
93
+ seen.add(entry);
94
+ yield { baseName: entry, path, source };
95
+ } catch (err) {
96
+ log.warn({ err, path }, "Failed to read saved workflow; skipping");
97
+ }
98
+ }
99
+
100
+ // Flat files second; skip any shadowed by a directory of the same base name.
101
+ for (const entry of entries) {
102
+ if (!entry.endsWith(WORKFLOW_SUFFIX)) continue;
103
+ const baseName = fileBaseName(entry);
104
+ const path = join(dir, entry);
105
+ if (seen.has(baseName)) {
106
+ log.warn(
107
+ { path },
108
+ `Flat workflow "${entry}" is shadowed by directory "${baseName}/"; skipping`,
109
+ );
110
+ continue;
111
+ }
75
112
  try {
76
- yield { file, path, source: readFileSync(path, "utf8") };
113
+ const source = readFileSync(path, "utf8");
114
+ seen.add(baseName);
115
+ yield { baseName, path, source };
77
116
  } catch (err) {
78
117
  log.warn({ err, path }, "Failed to read saved workflow; skipping");
79
118
  }
@@ -81,42 +120,57 @@ function* readWorkflowFiles(): Generator<RawWorkflowFile> {
81
120
  }
82
121
 
83
122
  /**
84
- * List every saved workflow with a statically-extractable `meta`. A file whose
85
- * `meta` cannot be statically extracted (computed/missing/malformed) is SKIPPED
86
- * with a logged warning rather than failing the whole listing.
123
+ * List every saved workflow with a statically-extractable `meta`, deduplicated
124
+ * by `meta.name` the first in {@link readWorkflowFiles}' stable order wins, a
125
+ * later duplicate is skipped with a warning (matching {@link getWorkflow}'s
126
+ * winner). A file whose `meta` cannot be statically extracted is skipped with a
127
+ * warning rather than failing the whole listing.
87
128
  */
88
129
  export function listWorkflows(): SavedWorkflowEntry[] {
89
130
  const entries: SavedWorkflowEntry[] = [];
131
+ const seenNames = new Set<string>();
90
132
  for (const { path, source } of readWorkflowFiles()) {
133
+ let meta: WorkflowMeta;
91
134
  try {
92
- const meta = extractWorkflowMeta(source);
93
- entries.push({ name: meta.name, description: meta.description, path });
135
+ meta = extractWorkflowMeta(source);
94
136
  } catch (err) {
95
137
  log.warn(
96
138
  { err, path },
97
139
  "Saved workflow has no statically-extractable meta; skipping",
98
140
  );
141
+ continue;
142
+ }
143
+ if (seenNames.has(meta.name)) {
144
+ log.warn(
145
+ { path, name: meta.name },
146
+ `Duplicate workflow name "${meta.name}"; skipping shadowed entry`,
147
+ );
148
+ continue;
99
149
  }
150
+ seenNames.add(meta.name);
151
+ entries.push({ name: meta.name, description: meta.description, path });
100
152
  }
101
153
  return entries;
102
154
  }
103
155
 
104
156
  /**
105
- * Resolve a saved workflow by name. Matches against the workflow's `meta.name`
106
- * first, then falls back to the filename base (`<base>.workflow.ts`). Returns the
107
- * source + path, or `null` if no saved workflow matches.
157
+ * Resolve a saved workflow by name, deterministically {@link readWorkflowFiles}
158
+ * yields in a stable order with directory-over-flat precedence, so "the first
159
+ * match" is well-defined. Matches `meta.name` first (the canonical identity),
160
+ * then falls back to the base name (directory or flat-file). Returns the source
161
+ * + path, or `null` if nothing matches.
108
162
  */
109
163
  export function getWorkflow(name: string): SavedWorkflowSource | null {
110
164
  let fileMatch: SavedWorkflowSource | null = null;
111
- for (const { file, path, source } of readWorkflowFiles()) {
165
+ for (const { baseName, path, source } of readWorkflowFiles()) {
112
166
  // Prefer a `meta.name` match — it is the canonical identity.
113
167
  try {
114
168
  if (extractWorkflowMeta(source).name === name) return { source, path };
115
169
  } catch {
116
170
  // Fall through: a file with non-extractable meta can still match by name.
117
171
  }
118
- // Remember the first filename-base match as a fallback.
119
- if (fileMatch === null && fileBaseName(file) === name) {
172
+ // Remember the first base-name match as a fallback.
173
+ if (fileMatch === null && baseName === name) {
120
174
  fileMatch = { source, path };
121
175
  }
122
176
  }
@@ -8,11 +8,6 @@ import {
8
8
  } from "node:fs";
9
9
  import { join } from "node:path";
10
10
 
11
- import { desc, eq } from "drizzle-orm";
12
-
13
- import { generateUserFileSlug } from "../../contacts/contact-store.js";
14
- import { getDb } from "../../memory/db-connection.js";
15
- import { contacts } from "../../memory/schema/contacts.js";
16
11
  import type { WorkspaceMigration } from "./types.js";
17
12
 
18
13
  // ── Inlined helpers ───────────────────────────────────────────────
@@ -122,35 +117,9 @@ export const seedPersonaDirsMigration: WorkspaceMigration = {
122
117
  // template from disk, since migration 031 deletes that template file.
123
118
  if (content === LEGACY_USER_MD_TEMPLATE_STRIPPED) return;
124
119
 
125
- // Determine destination filename based on guardian contact
126
- let destFilename = "guardian.md";
127
- try {
128
- const db = getDb();
129
- const guardian = db
130
- .select()
131
- .from(contacts)
132
- .where(eq(contacts.role, "guardian"))
133
- .orderBy(desc(contacts.createdAt))
134
- .limit(1)
135
- .get();
136
-
137
- if (guardian) {
138
- if (guardian.userFile) {
139
- destFilename = guardian.userFile;
140
- } else {
141
- const slug = generateUserFileSlug(guardian.displayName);
142
- db.update(contacts)
143
- .set({ userFile: slug })
144
- .where(eq(contacts.id, guardian.id))
145
- .run();
146
- destFilename = slug;
147
- }
148
- }
149
- } catch {
150
- // DB might not be initialized yet — fall back to guardian.md
151
- }
152
-
153
- const destPath = join(workspaceDir, "users", destFilename);
120
+ // Seed the canonical `users/guardian.md` the runtime persona resolver
121
+ // falls back to this name, so no assistant-DB lookup is needed.
122
+ const destPath = join(workspaceDir, "users", "guardian.md");
154
123
  if (!existsSync(destPath)) {
155
124
  copyFileSync(userMdPath, destPath);
156
125
  }
@@ -8,10 +8,6 @@ import {
8
8
  } from "node:fs";
9
9
  import { join } from "node:path";
10
10
 
11
- import { desc, eq } from "drizzle-orm";
12
-
13
- import { getDb } from "../../memory/db-connection.js";
14
- import { contacts } from "../../memory/schema/contacts.js";
15
11
  import type { WorkspaceMigration } from "./types.js";
16
12
 
17
13
  export const scopeJournalToGuardianMigration: WorkspaceMigration = {
@@ -40,26 +36,9 @@ export const scopeJournalToGuardianMigration: WorkspaceMigration = {
40
36
  });
41
37
  if (mdFiles.length === 0) return;
42
38
 
43
- // Resolve guardian user slug (same pattern as 017-seed-persona-dirs)
44
- let slug = "guardian";
45
- try {
46
- const db = getDb();
47
- const guardian = db
48
- .select()
49
- .from(contacts)
50
- .where(eq(contacts.role, "guardian"))
51
- .orderBy(desc(contacts.createdAt))
52
- .limit(1)
53
- .get();
54
- if (guardian?.userFile) {
55
- slug = guardian.userFile.replace(/\.md$/, "");
56
- }
57
- } catch {
58
- // DB not ready — use fallback "guardian"
59
- }
60
-
61
- // Create per-user directory and move files (renameSync preserves birthtimes)
62
- const destDir = join(journalDir, slug);
39
+ // Scope under the canonical `guardian` slug the runtime resolver falls
40
+ // back to it, so no assistant-DB lookup is needed.
41
+ const destDir = join(journalDir, "guardian");
63
42
  mkdirSync(destDir, { recursive: true });
64
43
  for (const f of mdFiles) {
65
44
  const src = join(journalDir, f);