@vellumai/assistant 0.9.0 → 0.10.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -0,0 +1,364 @@
1
+ import { and, asc, eq, gt, lt, lte, or, sql } from "drizzle-orm";
2
+
3
+ import { findConversation } from "../daemon/conversation-registry.js";
4
+ import type {
5
+ TurnTrace,
6
+ TurnTraceMessage,
7
+ TurnTraceToolCall,
8
+ } from "../telemetry/types.js";
9
+ import { getLogger } from "../util/logger.js";
10
+ import { getDb } from "./db-connection.js";
11
+ import { messages, toolInvocations } from "./schema.js";
12
+
13
+ const log = getLogger("turn-trace-store");
14
+
15
+ /**
16
+ * SQL fragment that excludes tool-result rows persisted with role="user".
17
+ * Duplicated from `turn-events-store.ts` on purpose: a turn boundary in the
18
+ * trace must use exactly the same notion of "real user turn" the eligibility
19
+ * predicate / `turn_index` count use, so the trace window can never disagree
20
+ * with the `turn` event it rides on. `<alias>` is interpolated as the SQL
21
+ * identifier for the table whose `content` column is filtered.
22
+ */
23
+ function realUserTurnContentFilter(alias: string): ReturnType<typeof sql> {
24
+ return sql.raw(
25
+ `${alias}.content NOT LIKE '%"type":"tool\\_result"%' ESCAPE '\\' ` +
26
+ `AND ${alias}.content NOT LIKE '%"type":"web\\_search\\_tool\\_result"%' ESCAPE '\\'`,
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Identifies the user message a trace is being assembled for. Mirrors the
32
+ * `(createdAt, id)` compound cursor the turn-event stream uses so the window
33
+ * boundaries line up exactly.
34
+ */
35
+ export interface TurnTraceBoundary {
36
+ conversationId: string;
37
+ /** `messages.id` of the real user turn that opens this turn. */
38
+ userMessageId: string;
39
+ /** `messages.created_at` of that user message. */
40
+ userMessageCreatedAt: number;
41
+ }
42
+
43
+ /** Parse a stored `messages.content` string into its JSON value, verbatim. */
44
+ function parseStoredContent(raw: string): unknown {
45
+ try {
46
+ return JSON.parse(raw) as unknown;
47
+ } catch {
48
+ // Legacy rows stored a plain (non-JSON) string. Forward as-is.
49
+ return raw;
50
+ }
51
+ }
52
+
53
+ /** Compound `(createdAt, id)` boundary of the next real user turn. */
54
+ interface NextTurnBoundary {
55
+ createdAt: number;
56
+ id: string;
57
+ }
58
+
59
+ /**
60
+ * Find the next real user turn strictly after the given boundary in the same
61
+ * conversation, by the same `(createdAt, id)` cursor the turn-event stream
62
+ * uses. Returns `null` when the boundary is the latest real user turn (the
63
+ * window then runs to the end of the conversation).
64
+ *
65
+ * Returns the next turn's `id` alongside its `createdAt` so the window upper
66
+ * bound is a compound `(createdAt, id)` comparison. Two real user messages can
67
+ * share a `created_at` (forked conversations preserve the source `created_at`
68
+ * with fresh ids; `monotonicNow()` only guarantees monotonicity within a single
69
+ * process), so a timestamp-only upper bound would equal the current turn's own
70
+ * `created_at` and truncate the trace.
71
+ *
72
+ * "Next real user turn" excludes tool-result rows persisted with role="user"
73
+ * (same filter as the turn-event stream), so a turn that issued tool calls —
74
+ * whose results land as role="user" rows — is not truncated at its own tool
75
+ * results.
76
+ */
77
+ function nextRealUserTurn(
78
+ boundary: TurnTraceBoundary,
79
+ ): NextTurnBoundary | null {
80
+ const db = getDb();
81
+ const row = db
82
+ .select({ createdAt: messages.createdAt, id: messages.id })
83
+ .from(messages)
84
+ .where(
85
+ and(
86
+ eq(messages.conversationId, boundary.conversationId),
87
+ eq(messages.role, "user"),
88
+ realUserTurnContentFilter("messages"),
89
+ or(
90
+ gt(messages.createdAt, boundary.userMessageCreatedAt),
91
+ and(
92
+ eq(messages.createdAt, boundary.userMessageCreatedAt),
93
+ gt(messages.id, boundary.userMessageId),
94
+ ),
95
+ ),
96
+ ),
97
+ )
98
+ .orderBy(asc(messages.createdAt), asc(messages.id))
99
+ .limit(1)
100
+ .get();
101
+ return row ?? null;
102
+ }
103
+
104
+ /**
105
+ * Whether a turn is COMPLETE — its own assistant response has finished and the
106
+ * full transcript is durably persisted — so its trace can be assembled without
107
+ * risk of capturing a partial mid-turn snapshot.
108
+ *
109
+ * A turn is settled iff its live conversation is not actively processing.
110
+ * `Conversation.isProcessing()` is flipped to `false` in the agent-loop
111
+ * `finally` *after* the awaited turn-boundary commit, so a non-processing
112
+ * conversation has no in-flight response and all of its persisted turn rows are
113
+ * durable. A conversation absent from the live registry (evicted, or never
114
+ * loaded this process) has no in-flight turn either, so it reads as settled —
115
+ * including after a restart, where a turn that was mid-flight when the process
116
+ * died reads as settled because no more rows are coming.
117
+ *
118
+ * Why this gates on processing for EVERY turn, not just the latest: a
119
+ * "successor real user turn exists ⟹ settled" shortcut is unsound in the
120
+ * batched-message path. When queued messages drain as a batch, `drainBatch`
121
+ * (daemon/conversation-process.ts) persists the head user row AND the tail user
122
+ * rows up front, then runs ONE shared `runAgentLoop` whose response is
123
+ * broadcast to all of them. So the batched HEAD turn has a later real user row
124
+ * (a tail) while the shared response and its tool calls are still in flight —
125
+ * the shortcut would mark the head settled and ship a trace missing the shared
126
+ * response, never retried. Gating on `isProcessing()` defers the head (and any
127
+ * backlog) until the shared response completes and the conversation goes idle.
128
+ *
129
+ * Turns are serialized per conversation and `isProcessing()` is per-response,
130
+ * so the conversation is idle between turns and completed past turns settle
131
+ * promptly; the only cost is that a just-finished turn waits for the current
132
+ * response when one started before it could be reported — minimal latency,
133
+ * preferred over shipping a partial trace.
134
+ *
135
+ * The `isProcessing()` read relies on the daemon's in-memory state, so the
136
+ * reporter and the agent loop share one process (they do — both live in the
137
+ * daemon).
138
+ */
139
+ export function isTurnSettled(boundary: TurnTraceBoundary): boolean {
140
+ return findConversation(boundary.conversationId)?.isProcessing() !== true;
141
+ }
142
+
143
+ /** Message rows belonging to the turn window, oldest-first. */
144
+ function queryTurnMessages(
145
+ boundary: TurnTraceBoundary,
146
+ nextTurn: NextTurnBoundary | null,
147
+ ): TurnTraceMessage[] {
148
+ const db = getDb();
149
+ // Lower bound: the user message itself (inclusive), using the same
150
+ // `(createdAt, id)` lex-comparison as the cursor so a row sharing the
151
+ // user message's millisecond isn't dropped.
152
+ const lowerBound = or(
153
+ gt(messages.createdAt, boundary.userMessageCreatedAt),
154
+ and(
155
+ eq(messages.createdAt, boundary.userMessageCreatedAt),
156
+ sql`${messages.id} >= ${boundary.userMessageId}`,
157
+ ),
158
+ );
159
+ // Upper bound: strictly before the next real user turn, using the same
160
+ // compound `(createdAt, id)` comparison. A timestamp-only bound would empty
161
+ // the window when the next user turn shares this turn's `created_at`.
162
+ const upperBound =
163
+ nextTurn == null
164
+ ? undefined
165
+ : or(
166
+ lt(messages.createdAt, nextTurn.createdAt),
167
+ and(
168
+ eq(messages.createdAt, nextTurn.createdAt),
169
+ sql`${messages.id} < ${nextTurn.id}`,
170
+ ),
171
+ );
172
+
173
+ const rows = db
174
+ .select({
175
+ id: messages.id,
176
+ role: messages.role,
177
+ content: messages.content,
178
+ createdAt: messages.createdAt,
179
+ })
180
+ .from(messages)
181
+ .where(
182
+ and(
183
+ eq(messages.conversationId, boundary.conversationId),
184
+ lowerBound,
185
+ ...(upperBound ? [upperBound] : []),
186
+ ),
187
+ )
188
+ .orderBy(asc(messages.createdAt), asc(messages.id))
189
+ .all();
190
+
191
+ return rows.map((r) => ({
192
+ id: r.id,
193
+ role: r.role,
194
+ created_at: r.createdAt,
195
+ content: parseStoredContent(r.content),
196
+ }));
197
+ }
198
+
199
+ /**
200
+ * Parse a stored tool-invocation input verbatim.
201
+ *
202
+ * The consented trace is full-fidelity: the input is forwarded exactly as
203
+ * stored (structured JSON when it parses, raw string otherwise — symmetric
204
+ * with how message content is handled). No field-level redaction is applied;
205
+ * the protections for this PII are the consent gate, the PII-segregated
206
+ * `pii_turn_raw` table, and its 30-day TTL.
207
+ */
208
+ function parseToolInput(raw: string): unknown {
209
+ try {
210
+ return JSON.parse(raw) as unknown;
211
+ } catch {
212
+ // Non-JSON input — forward the raw string verbatim.
213
+ return raw;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Tool invocations recorded inside the turn window, oldest-first.
219
+ *
220
+ * `tool_invocations` has no message-id link and its ids are unrelated to
221
+ * `messages.id`, so the window is correlated purely by `created_at`:
222
+ * `[userMessageCreatedAt, nextTurn.createdAt)`. The lower bound is inclusive
223
+ * (a tool can fire in the same millisecond as the user message).
224
+ *
225
+ * Degenerate same-`created_at` case: when the next real user turn shares this
226
+ * turn's `created_at`, a strict `<` upper bound yields an empty `[X, X)` window
227
+ * and drops this turn's same-millisecond tools. In that case the upper bound is
228
+ * widened to `<=` so those tools are retained. Same-millisecond tools cannot be
229
+ * attributed to one of two colliding user turns by timestamp alone; including
230
+ * them in the earlier turn matches the message window (which keeps this turn's
231
+ * rows at that millisecond) and is the conservative choice for a diagnostic
232
+ * trace.
233
+ */
234
+ function queryTurnToolCalls(
235
+ boundary: TurnTraceBoundary,
236
+ nextTurn: NextTurnBoundary | null,
237
+ ): TurnTraceToolCall[] {
238
+ const db = getDb();
239
+ let upperBound;
240
+ if (nextTurn == null) {
241
+ upperBound = undefined;
242
+ } else if (nextTurn.createdAt <= boundary.userMessageCreatedAt) {
243
+ // Next turn collides on (or, defensively, predates) this turn's
244
+ // millisecond — keep tools at that millisecond instead of emptying the
245
+ // window.
246
+ upperBound = lte(toolInvocations.createdAt, nextTurn.createdAt);
247
+ } else {
248
+ upperBound = lt(toolInvocations.createdAt, nextTurn.createdAt);
249
+ }
250
+
251
+ const rows = db
252
+ .select({
253
+ id: toolInvocations.id,
254
+ toolName: toolInvocations.toolName,
255
+ input: toolInvocations.input,
256
+ result: toolInvocations.result,
257
+ decision: toolInvocations.decision,
258
+ durationMs: toolInvocations.durationMs,
259
+ createdAt: toolInvocations.createdAt,
260
+ })
261
+ .from(toolInvocations)
262
+ .where(
263
+ and(
264
+ eq(toolInvocations.conversationId, boundary.conversationId),
265
+ sql`${toolInvocations.createdAt} >= ${boundary.userMessageCreatedAt}`,
266
+ ...(upperBound ? [upperBound] : []),
267
+ ),
268
+ )
269
+ .orderBy(asc(toolInvocations.createdAt), asc(toolInvocations.id))
270
+ .all();
271
+
272
+ return rows.map((r) => ({
273
+ id: r.id,
274
+ tool_name: r.toolName,
275
+ input: parseToolInput(r.input),
276
+ result: r.result,
277
+ decision: r.decision,
278
+ duration_ms: r.durationMs,
279
+ created_at: r.createdAt,
280
+ }));
281
+ }
282
+
283
+ /**
284
+ * Assemble the full transcript for one turn: the user message, the assistant
285
+ * response message(s), any intervening tool-result rows, and the tool
286
+ * invocations recorded for the turn — bounded to the window between this real
287
+ * user turn and the next one.
288
+ *
289
+ * The trace is each turn's natural window. A turn whose own window holds no
290
+ * assistant response (a coalesced-batch head, or a turn that failed/cancelled
291
+ * before producing a response) traces user-only, which is faithful: its window
292
+ * genuinely has no response. A coalesced batch's shared response lives on the
293
+ * batch's FINAL turn's window — exactly where the daemon already attributes it
294
+ * (via `lastUserMessageId` / `llm_usage` / `turn_index`). There is no durable
295
+ * batch signal in the stored messages to distinguish a real batch from a
296
+ * failed/cancelled turn, so no batch inference is attempted.
297
+ *
298
+ * Read-only; touches only existing tables (`messages`, `tool_invocations`), so
299
+ * no migration is involved. Caller is responsible for the consent gate and the
300
+ * serialized-size cap — this function always returns a faithful trace for the
301
+ * window.
302
+ */
303
+ export function assembleTurnTrace(boundary: TurnTraceBoundary): TurnTrace {
304
+ const nextTurn = nextRealUserTurn(boundary);
305
+ return {
306
+ schema_version: 1,
307
+ messages: queryTurnMessages(boundary, nextTurn),
308
+ tool_calls: queryTurnToolCalls(boundary, nextTurn),
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Maximum serialized size of a trace the platform will accept (it drops traces
314
+ * whose JSON exceeds ~256 KiB). Keep the daemon-side cap a touch under that so
315
+ * we never ship a trace the platform will silently drop. A trace over the cap
316
+ * is omitted entirely (the trace-free turn row still flushes).
317
+ */
318
+ export const MAX_TRACE_SERIALIZED_BYTES = 256 * 1024;
319
+
320
+ /**
321
+ * Assemble a turn trace and serialize it, returning the JSON-ready object only
322
+ * when it fits under {@link MAX_TRACE_SERIALIZED_BYTES}. Returns `null` (no
323
+ * trace) when assembly fails or the trace is too large — both fail-closed so a
324
+ * single oversized/broken turn never blocks the turn event from flushing.
325
+ */
326
+ export function assembleBoundedTurnTrace(
327
+ boundary: TurnTraceBoundary,
328
+ ): TurnTrace | null {
329
+ let trace: TurnTrace;
330
+ try {
331
+ trace = assembleTurnTrace(boundary);
332
+ } catch (err) {
333
+ log.warn(
334
+ { err, conversationId: boundary.conversationId },
335
+ "Failed to assemble turn trace — omitting trace for this turn",
336
+ );
337
+ return null;
338
+ }
339
+
340
+ let serializedBytes: number;
341
+ try {
342
+ serializedBytes = Buffer.byteLength(JSON.stringify(trace), "utf8");
343
+ } catch (err) {
344
+ log.warn(
345
+ { err, conversationId: boundary.conversationId },
346
+ "Failed to serialize turn trace — omitting trace for this turn",
347
+ );
348
+ return null;
349
+ }
350
+
351
+ if (serializedBytes > MAX_TRACE_SERIALIZED_BYTES) {
352
+ log.debug(
353
+ {
354
+ conversationId: boundary.conversationId,
355
+ serializedBytes,
356
+ cap: MAX_TRACE_SERIALIZED_BYTES,
357
+ },
358
+ "Turn trace exceeds size cap — omitting trace for this turn",
359
+ );
360
+ return null;
361
+ }
362
+
363
+ return trace;
364
+ }
@@ -117,6 +117,14 @@ mock.module("../../../config/assistant-feature-flags.js", () => ({
117
117
  isAssistantFeatureFlagEnabled: (key: string) => flagStates[key] ?? v3FlagOn,
118
118
  }));
119
119
 
120
+ // The v3-live gate moved to config (`config.memory.v3.live`), read via
121
+ // `isMemoryV3Live`. Mirror the flag mock so the shadow-vs-live tests keep
122
+ // driving live through `flagStates["memory-v3-live"]` (and `v3FlagOn` toggles
123
+ // it alongside shadow for the existing on/off tests).
124
+ mock.module("../../../config/memory-v3-gate.js", () => ({
125
+ isMemoryV3Live: () => flagStates["memory-v3-live"] ?? v3FlagOn,
126
+ }));
127
+
120
128
  // ── Workspace pin ───────────────────────────────────────────────────
121
129
  let tmpWorkspace: string;
122
130
  let previousWorkspaceEnv: string | undefined;
@@ -45,6 +45,36 @@ describe("buildSkillContent", () => {
45
45
  const out = buildSkillContent(input);
46
46
  expect(out.length).toBeLessThanOrEqual(500);
47
47
  });
48
+
49
+ test("respects a larger maxChars budget", async () => {
50
+ const { buildSkillContent } = await import("../skill-content.js");
51
+ const input: SkillCapabilityInput = {
52
+ id: "example-skill",
53
+ displayName: "Example Skill",
54
+ description: "x".repeat(1000),
55
+ };
56
+ const out = buildSkillContent(input, 900);
57
+ expect(out.length).toBeLessThanOrEqual(900);
58
+ expect(out.length).toBeGreaterThan(500);
59
+ });
60
+
61
+ test("renders hints as a bulleted list when the budget is enlarged", async () => {
62
+ const { buildSkillContent } = await import("../skill-content.js");
63
+ const input: SkillCapabilityInput = {
64
+ id: "wf",
65
+ displayName: "Workflows",
66
+ description: "Delegate a big job",
67
+ activationHints: ["Batch many items", "Exhaustive sweep"],
68
+ avoidWhen: ["A single lookup"],
69
+ };
70
+ const rich = buildSkillContent(input, 900);
71
+ expect(rich).toContain("Use when:\n- Batch many items\n- Exhaustive sweep");
72
+ expect(rich).toContain("Avoid when:\n- A single lookup");
73
+ // The default (500) budget keeps the compact inline form.
74
+ expect(buildSkillContent(input)).toContain(
75
+ "Use when: Batch many items; Exhaustive sweep.",
76
+ );
77
+ });
48
78
  });
49
79
 
50
80
  describe("augmentMcpSetupDescription", () => {
@@ -65,6 +65,7 @@ import {
65
65
  import { dirname, join } from "node:path";
66
66
 
67
67
  import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
68
+ import { isMemoryV3Live } from "../../config/memory-v3-gate.js";
68
69
  import type { AssistantConfig } from "../../config/types.js";
69
70
  import { runBackgroundJob } from "../../runtime/background-job-runner.js";
70
71
  import { getLogger } from "../../util/logger.js";
@@ -89,7 +90,6 @@ const JOB_NAME = "memory.consolidate";
89
90
  * post-consolidation follow-up. These gate the v3 plugin itself.
90
91
  */
91
92
  const MEMORY_V3_SHADOW = "memory-v3-shadow" as const;
92
- const MEMORY_V3_LIVE = "memory-v3-live" as const;
93
93
 
94
94
  /**
95
95
  * Hard timeout for the consolidation run. Consolidation reads the buffer,
@@ -269,7 +269,7 @@ export async function memoryV2ConsolidateJob(
269
269
  // The article SHAPE is keyed on the live flag alone: under shadow, live
270
270
  // prompts are still assembled by v2's injection model, so consolidation
271
271
  // must keep producing `summary:`-bearing fragment pages until the flip.
272
- const memoryV3Live = isAssistantFeatureFlagEnabled(MEMORY_V3_LIVE, config);
272
+ const memoryV3Live = isMemoryV3Live(config);
273
273
  const memoryV3Active =
274
274
  isAssistantFeatureFlagEnabled(MEMORY_V3_SHADOW, config) || memoryV3Live;
275
275
  const prompt = resolveConsolidationPrompt(
@@ -1,22 +1,40 @@
1
1
  import { getConfig } from "../../config/loader.js";
2
2
  import type { SkillCapabilityInput } from "../../skills/skill-memory.js";
3
3
 
4
+ /**
5
+ * Character budget for an always-candidate skill's capability statement. Larger
6
+ * than the default so a cross-cutting skill (pinned into the selector pool every
7
+ * turn) can describe its full range of uses rather than a single mode — see
8
+ * `buildSkillContent`'s `maxChars` and `skill-store.ts`'s seeding loop.
9
+ */
10
+ export const ALWAYS_CANDIDATE_CARD_CHARS = 900;
11
+
4
12
  /**
5
13
  * Render the prose-style capability statement embedded into the unified
6
14
  * `memory_v2_concept_pages` Qdrant collection (under the `skills/<id>` slug
7
- * prefix) and rendered in `### Skills You Can Use`. Capped at 500 chars to
8
- * match v1's behavior.
15
+ * prefix) and rendered in `### Skills You Can Use` / the memory-v3 selector
16
+ * card. Capped at `maxChars` (default 500). A larger budget switches the hints
17
+ * to a bulleted list — easier for the selector to parse one mode per line — so
18
+ * always-candidate skills can carry a fuller, multi-mode description.
9
19
  */
10
- export function buildSkillContent(input: SkillCapabilityInput): string {
20
+ export function buildSkillContent(
21
+ input: SkillCapabilityInput,
22
+ maxChars = 500,
23
+ ): string {
24
+ const list = maxChars > 500;
11
25
  let content = `The "${input.displayName}" skill (${input.id}) is available. ${input.description}.`;
12
26
  if (input.activationHints && input.activationHints.length > 0) {
13
- content += ` Use when: ${input.activationHints.join("; ")}.`;
27
+ content += list
28
+ ? `\nUse when:\n${input.activationHints.map((h) => `- ${h}`).join("\n")}`
29
+ : ` Use when: ${input.activationHints.join("; ")}.`;
14
30
  }
15
31
  if (input.avoidWhen && input.avoidWhen.length > 0) {
16
- content += ` Avoid when: ${input.avoidWhen.join("; ")}.`;
32
+ content += list
33
+ ? `\nAvoid when:\n${input.avoidWhen.map((a) => `- ${a}`).join("\n")}`
34
+ : ` Avoid when: ${input.avoidWhen.join("; ")}.`;
17
35
  }
18
- if (content.length > 500) {
19
- content = content.slice(0, 500);
36
+ if (content.length > maxChars) {
37
+ content = content.slice(0, maxChars);
20
38
  }
21
39
  return content;
22
40
  }
@@ -41,6 +41,7 @@ import {
41
41
  upsertConceptPageEmbedding,
42
42
  } from "./qdrant.js";
43
43
  import {
44
+ ALWAYS_CANDIDATE_CARD_CHARS,
44
45
  augmentMcpSetupDescription,
45
46
  buildSkillContent,
46
47
  } from "./skill-content.js";
@@ -188,7 +189,12 @@ async function runSeedV2SkillEntries(generation: number): Promise<void> {
188
189
  if (flagKey && !isAssistantFeatureFlagEnabled(flagKey, config)) continue;
189
190
 
190
191
  const augmented = augmentMcpSetupDescription(fromSkillSummary(summary));
191
- const content = buildSkillContent(augmented);
192
+ // Always-candidate skills are pinned into the selector pool every turn, so
193
+ // they get a larger budget for a fuller, multi-mode capability statement.
194
+ const content = buildSkillContent(
195
+ augmented,
196
+ summary.alwaysCandidate ? ALWAYS_CANDIDATE_CARD_CHARS : undefined,
197
+ );
192
198
  seeds.push({ id: summary.id, content });
193
199
  }
194
200