@vellumai/assistant 0.8.12 → 0.9.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 (442) hide show
  1. package/AGENTS.md +0 -14
  2. package/ARCHITECTURE.md +45 -45
  3. package/README.md +1 -1
  4. package/bun.lock +200 -154
  5. package/docs/architecture/integrations.md +3 -3
  6. package/docs/architecture/memory.md +2 -2
  7. package/docs/architecture/security.md +10 -10
  8. package/docs/runbook-trusted-contacts.md +12 -12
  9. package/docs/skills.md +6 -6
  10. package/docs/workflows-testing.md +221 -0
  11. package/docs/workflows.md +510 -0
  12. package/examples/plugins/echo/README.md +5 -5
  13. package/knip.json +2 -0
  14. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
  15. package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
  16. package/openapi.yaml +7197 -5708
  17. package/package.json +8 -4
  18. package/scripts/generate-openapi.ts +66 -114
  19. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  20. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  21. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  22. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  24. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  25. package/src/__tests__/app-compiler.test.ts +7 -1
  26. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  27. package/src/__tests__/app-executors.test.ts +43 -0
  28. package/src/__tests__/approval-cascade.test.ts +0 -5
  29. package/src/__tests__/approval-routes-http.test.ts +91 -0
  30. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  31. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  32. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  33. package/src/__tests__/compaction-events.test.ts +63 -7
  34. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  35. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  36. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  37. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  39. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  40. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  41. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  43. package/src/__tests__/conversation-queue.test.ts +0 -5
  44. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  45. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  46. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  47. package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
  48. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  49. package/src/__tests__/conversation-usage.test.ts +2 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  52. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  53. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  54. package/src/__tests__/credential-broker.test.ts +1 -1
  55. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault.test.ts +37 -0
  58. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  59. package/src/__tests__/dynamic-page-surface.test.ts +219 -0
  60. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  61. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  62. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  63. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  64. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  65. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  66. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  67. package/src/__tests__/llm-resolver.test.ts +115 -0
  68. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  69. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  70. package/src/__tests__/media-generate-image.test.ts +20 -9
  71. package/src/__tests__/mock-gateway-ipc.ts +23 -0
  72. package/src/__tests__/model-intents.test.ts +1 -1
  73. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  74. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  75. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  76. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  77. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  78. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  79. package/src/__tests__/resolve-app-id.test.ts +56 -0
  80. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  81. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  82. package/src/__tests__/schedule-routes.test.ts +257 -4
  83. package/src/__tests__/schedule-store.test.ts +60 -0
  84. package/src/__tests__/schedule-tools.test.ts +247 -2
  85. package/src/__tests__/skill-execute-input.test.ts +85 -0
  86. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  87. package/src/__tests__/skills.test.ts +3 -3
  88. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  89. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  90. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  91. package/src/__tests__/system-prompt.test.ts +1 -1
  92. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  93. package/src/__tests__/task-scheduler.test.ts +299 -0
  94. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  95. package/src/__tests__/tool-result-spool.test.ts +3 -1
  96. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  97. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  98. package/src/agent/compaction-circuit.ts +11 -0
  99. package/src/agent/loop.ts +181 -12
  100. package/src/api/constants/call-sites.ts +12 -0
  101. package/src/api/events/assistant-thinking-delta.ts +10 -0
  102. package/src/api/events/usage-update.ts +7 -0
  103. package/src/api/index.ts +4 -1
  104. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  105. package/src/approvals/approval-primitive.ts +2 -2
  106. package/src/background-wake/background-wake-routes.test.ts +5 -2
  107. package/src/bundler/compiler-tools.ts +1 -1
  108. package/src/bundler/package-resolver.ts +0 -1
  109. package/src/calls/call-domain.ts +1 -1
  110. package/src/calls/guardian-action-sweep.ts +16 -93
  111. package/src/cli/AGENTS.md +4 -0
  112. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  113. package/src/cli/commands/credentials.ts +28 -24
  114. package/src/cli/commands/image-generation.ts +23 -9
  115. package/src/cli/commands/notifications.ts +1 -1
  116. package/src/cli/commands/plugins.ts +89 -46
  117. package/src/cli/commands/schedules.ts +384 -11
  118. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  119. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  120. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  121. package/src/cli/lib/inspect-plugin.ts +62 -1
  122. package/src/cli/lib/install-from-github.ts +52 -5
  123. package/src/cli/lib/upgrade-plugin.ts +18 -0
  124. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  125. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  126. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  127. package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
  128. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
  129. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
  130. package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
  131. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  132. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  133. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  134. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  135. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  136. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  137. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  138. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  139. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  140. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  141. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  142. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  143. package/src/config/bundled-tool-registry.ts +14 -2
  144. package/src/config/call-site-defaults.ts +5 -0
  145. package/src/config/feature-flag-registry.json +12 -4
  146. package/src/config/llm-context-resolution.ts +8 -0
  147. package/src/config/llm-resolver.ts +30 -0
  148. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  149. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  150. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  151. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  152. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  153. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  154. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  155. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  156. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  157. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  158. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  159. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  160. package/src/config/schema.ts +2 -0
  161. package/src/config/schemas/call-site-catalog.ts +7 -0
  162. package/src/config/schemas/heartbeat.ts +4 -1
  163. package/src/config/schemas/llm.ts +33 -26
  164. package/src/config/schemas/memory-retrospective.ts +19 -0
  165. package/src/config/schemas/platform.ts +8 -0
  166. package/src/config/schemas/services.ts +5 -2
  167. package/src/config/schemas/workflows.ts +42 -0
  168. package/src/config/skills.ts +3 -3
  169. package/src/context/compactor.ts +273 -39
  170. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  171. package/src/context/tool-result-spool.ts +12 -17
  172. package/src/credential-execution/executable-discovery.ts +1 -1
  173. package/src/credential-execution/process-manager.ts +37 -3
  174. package/src/credential-execution/prompted-credential.ts +205 -0
  175. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  176. package/src/daemon/conversation-process.ts +11 -2
  177. package/src/daemon/conversation-surfaces.ts +167 -3
  178. package/src/daemon/conversation-tool-setup.ts +103 -26
  179. package/src/daemon/conversation-usage.ts +2 -0
  180. package/src/daemon/conversation.ts +115 -11
  181. package/src/daemon/handlers/shared.ts +26 -14
  182. package/src/daemon/host-cu-proxy.ts +15 -12
  183. package/src/daemon/host-file-proxy.ts +15 -12
  184. package/src/daemon/host-transfer-proxy.ts +30 -24
  185. package/src/daemon/lifecycle.ts +40 -3
  186. package/src/daemon/message-protocol.ts +3 -0
  187. package/src/daemon/message-types/messages.ts +2 -10
  188. package/src/daemon/message-types/workflows.ts +49 -0
  189. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  190. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  191. package/src/daemon/process-message.ts +6 -0
  192. package/src/daemon/tool-setup-types.ts +57 -0
  193. package/src/daemon/wake-conversation-ops.ts +18 -0
  194. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  195. package/src/home/feed-types.ts +1 -1
  196. package/src/ipc/gateway-flag-listener.ts +28 -6
  197. package/src/mcp/mcp-auth-state.ts +8 -20
  198. package/src/media/__tests__/image-models.test.ts +57 -0
  199. package/src/media/image-models.ts +66 -0
  200. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  201. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  202. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  203. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  204. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  205. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  206. package/src/memory/auto-analysis-enqueue.ts +14 -1
  207. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  208. package/src/memory/conversation-crud.ts +9 -2
  209. package/src/memory/conversation-disk-view.ts +1 -1
  210. package/src/memory/conversation-queries.ts +22 -7
  211. package/src/memory/db-init.ts +20 -0
  212. package/src/memory/db-maintenance.ts +16 -0
  213. package/src/memory/embedding-runtime-manager.ts +1 -1
  214. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  215. package/src/memory/llm-request-log-source-local.ts +19 -1
  216. package/src/memory/llm-request-log-source.ts +35 -6
  217. package/src/memory/llm-request-log-store.ts +90 -2
  218. package/src/memory/memory-retrospective-constants.ts +9 -0
  219. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  220. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  221. package/src/memory/memory-retrospective-job.ts +500 -208
  222. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  223. package/src/memory/memory-retrospective-state.ts +85 -2
  224. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  225. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  226. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  227. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  228. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  229. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  230. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  231. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  232. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  233. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  234. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  235. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  236. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  237. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  238. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  239. package/src/memory/migrations/index.ts +10 -0
  240. package/src/memory/preloaded-apps.ts +116 -0
  241. package/src/memory/schema/infrastructure.ts +4 -0
  242. package/src/memory/schema/memory-core.ts +4 -0
  243. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  244. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  245. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  246. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  247. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  248. package/src/memory/v2/page-store.ts +4 -3
  249. package/src/memory/v2/qdrant.ts +42 -3
  250. package/src/memory/v2/types.ts +16 -10
  251. package/src/messaging/draft-store.ts +1 -1
  252. package/src/notifications/access-request-copy.ts +200 -113
  253. package/src/notifications/adapters/slack.ts +250 -111
  254. package/src/notifications/adapters/telegram.ts +7 -44
  255. package/src/notifications/approval-card-builder.ts +93 -0
  256. package/src/notifications/broadcaster.ts +74 -0
  257. package/src/notifications/conversation-pairing.ts +8 -6
  258. package/src/notifications/copy-composer.ts +32 -26
  259. package/src/notifications/decision-engine.ts +59 -7
  260. package/src/notifications/guardian-question-mode.ts +145 -155
  261. package/src/notifications/home-feed-side-effect.ts +28 -11
  262. package/src/notifications/notification-utils.ts +66 -0
  263. package/src/notifications/signal.ts +6 -0
  264. package/src/notifications/tool-approval-copy.ts +142 -0
  265. package/src/notifications/types.ts +19 -0
  266. package/src/permissions/threshold.ts +11 -0
  267. package/src/plugin-api/types.ts +16 -4
  268. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  269. package/src/plugins/defaults/index.ts +46 -0
  270. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  271. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  272. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  273. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  274. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  275. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  276. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  277. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  278. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  282. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  283. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  284. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  285. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  286. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  287. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  288. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  289. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  290. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  291. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  292. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  293. package/src/prompts/normalize-onboarding.ts +2 -0
  294. package/src/prompts/persona-resolver.ts +3 -0
  295. package/src/prompts/system-prompt.ts +51 -2
  296. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  297. package/src/prompts/templates/system-sections.ts +8 -3
  298. package/src/providers/call-site-routing.ts +6 -3
  299. package/src/providers/fireworks/client.ts +3 -0
  300. package/src/providers/inference/auth.ts +52 -46
  301. package/src/providers/model-intents.ts +2 -2
  302. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  303. package/src/providers/openai/chat-completions-provider.ts +47 -9
  304. package/src/providers/openai/coerce-object-args.ts +104 -0
  305. package/src/providers/retry.ts +8 -5
  306. package/src/providers/types.ts +10 -0
  307. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  308. package/src/runtime/access-request-helper.ts +16 -9
  309. package/src/runtime/agent-wake.ts +302 -51
  310. package/src/runtime/assistant-stream-state.ts +141 -8
  311. package/src/runtime/background-job-runner.ts +9 -0
  312. package/src/runtime/channel-approval-types.ts +1 -0
  313. package/src/runtime/channel-invite-transports/telegram.ts +6 -5
  314. package/src/runtime/channel-invite-transports/voice.ts +2 -2
  315. package/src/runtime/channel-invite-types.ts +4 -2
  316. package/src/runtime/channel-retry-sweep.ts +19 -41
  317. package/src/runtime/finalize-event-delivery.ts +72 -0
  318. package/src/runtime/guardian-action-message-composer.ts +0 -54
  319. package/src/runtime/http-server.ts +6 -14
  320. package/src/runtime/http-types.ts +0 -1
  321. package/src/runtime/message-composer-types.ts +0 -9
  322. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  323. package/src/runtime/middleware/auth.ts +27 -3
  324. package/src/runtime/middleware/rate-limiter.ts +28 -1
  325. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  326. package/src/runtime/pending-interactions.ts +20 -1
  327. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  328. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  329. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  330. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  331. package/src/runtime/routes/approval-routes.ts +35 -8
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  333. package/src/runtime/routes/btw-routes.ts +37 -1
  334. package/src/runtime/routes/channel-delivery-routes.ts +11 -7
  335. package/src/runtime/routes/channel-route-definitions.ts +3 -0
  336. package/src/runtime/routes/channel-route-shared.ts +3 -1
  337. package/src/runtime/routes/consolidation-routes.ts +17 -13
  338. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  339. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  340. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  341. package/src/runtime/routes/conversation-routes.ts +15 -1
  342. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  343. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  344. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  345. package/src/runtime/routes/image-generation-routes.ts +20 -2
  346. package/src/runtime/routes/inbound-message-handler.ts +13 -12
  347. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
  348. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
  349. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
  350. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  351. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
  352. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
  353. package/src/runtime/routes/index.ts +6 -0
  354. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  355. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  356. package/src/runtime/routes/migration-routes.ts +5 -9
  357. package/src/runtime/routes/plugins-routes.ts +26 -0
  358. package/src/runtime/routes/ps-routes.ts +10 -8
  359. package/src/runtime/routes/retrospective-routes.ts +235 -0
  360. package/src/runtime/routes/runs-pagination.ts +75 -0
  361. package/src/runtime/routes/schedule-routes.ts +367 -59
  362. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  363. package/src/runtime/routes/surface-action-routes.ts +84 -4
  364. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  365. package/src/runtime/routes/workflow-routes.ts +363 -0
  366. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  367. package/src/runtime/routes/workspace-routes.ts +26 -1
  368. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  369. package/src/runtime/services/analyze-conversation.ts +26 -13
  370. package/src/schedule/inference-profile.ts +28 -0
  371. package/src/schedule/schedule-store.ts +152 -4
  372. package/src/schedule/scheduler-types.ts +6 -0
  373. package/src/schedule/scheduler.ts +96 -0
  374. package/src/security/secret-allowlist.ts +1 -1
  375. package/src/skills/path-classifier.ts +1 -1
  376. package/src/tools/apps/executors.ts +23 -0
  377. package/src/tools/apps/resolve-app-id.ts +42 -0
  378. package/src/tools/browser/browser-execution.ts +9 -11
  379. package/src/tools/credentials/broker.ts +4 -4
  380. package/src/tools/credentials/vault.ts +26 -137
  381. package/src/tools/executor.ts +69 -0
  382. package/src/tools/flag-gated-tools.test.ts +76 -0
  383. package/src/tools/permission-checker.ts +8 -1
  384. package/src/tools/registry.ts +51 -0
  385. package/src/tools/schedule/create.ts +77 -1
  386. package/src/tools/schedule/list.ts +1 -0
  387. package/src/tools/schedule/update.ts +73 -1
  388. package/src/tools/skills/execute.ts +56 -0
  389. package/src/tools/terminal/shell.ts +1 -1
  390. package/src/tools/ui-surface/definitions.ts +91 -2
  391. package/src/tools/workflows/manage-workflows.ts +183 -0
  392. package/src/tools/workflows/run-workflow.test.ts +442 -0
  393. package/src/tools/workflows/run-workflow.ts +88 -0
  394. package/src/types/onboarding-context.ts +2 -0
  395. package/src/usage/attribution.ts +24 -0
  396. package/src/util/canonicalize-identity.ts +12 -3
  397. package/src/util/platform.ts +17 -17
  398. package/src/watcher/__tests__/engine.test.ts +24 -0
  399. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  400. package/src/watcher/engine.ts +7 -0
  401. package/src/watcher/telemetry.ts +74 -0
  402. package/src/workflows/capabilities.test.ts +365 -0
  403. package/src/workflows/capabilities.ts +359 -0
  404. package/src/workflows/deterministic-stringify.ts +27 -0
  405. package/src/workflows/engine-integration.test.ts +656 -0
  406. package/src/workflows/engine.test.ts +1144 -0
  407. package/src/workflows/engine.ts +1078 -0
  408. package/src/workflows/fanout-load.test.ts +168 -0
  409. package/src/workflows/journal-store.test.ts +369 -0
  410. package/src/workflows/journal-store.ts +470 -0
  411. package/src/workflows/leaf-runner.test.ts +704 -0
  412. package/src/workflows/leaf-runner.ts +589 -0
  413. package/src/workflows/library.test.ts +134 -0
  414. package/src/workflows/library.ts +124 -0
  415. package/src/workflows/run-manager.test.ts +711 -0
  416. package/src/workflows/run-manager.ts +593 -0
  417. package/src/workflows/sandbox-escape.test.ts +339 -0
  418. package/src/workflows/sandbox.test.ts +251 -0
  419. package/src/workflows/sandbox.ts +447 -0
  420. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  421. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  422. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  423. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  424. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  425. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  426. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  427. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  428. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  429. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  430. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  431. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  432. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  433. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  434. package/src/workspace/migrations/registry.ts +6 -0
  435. package/src/workspace/migrations/runner.ts +1 -1
  436. package/tsconfig.json +1 -1
  437. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  438. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  439. package/src/__tests__/guardian-action-store.test.ts +0 -106
  440. package/src/daemon/guardian-action-generators.ts +0 -71
  441. package/src/memory/guardian-action-store.ts +0 -484
  442. package/src/runtime/guardian-action-grant-minter.ts +0 -150
@@ -1,4 +1,4 @@
1
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  mock.module("../util/logger.js", () => ({
4
4
  getLogger: () =>
@@ -18,27 +18,11 @@ mock.module("../runtime/gateway-client.js", () => ({
18
18
  },
19
19
  }));
20
20
 
21
- import {
22
- createCallSession,
23
- createPendingQuestion,
24
- getPendingQuestion,
25
- } from "../calls/call-store.js";
26
- import {
27
- startGuardianActionSweep,
28
- stopGuardianActionSweep,
29
- sweepExpiredGuardianActions,
30
- } from "../calls/guardian-action-sweep.js";
21
+ import type { ExpiryDeliveryInfo } from "../calls/guardian-action-sweep.js";
22
+ import { sendGuardianExpiryNotices } from "../calls/guardian-action-sweep.js";
31
23
  import { getDb } from "../memory/db-connection.js";
32
24
  import { initializeDb } from "../memory/db-init.js";
33
- import {
34
- createGuardianActionDelivery,
35
- createGuardianActionRequest,
36
- getDeliveriesByRequestId,
37
- getGuardianActionRequest,
38
- updateDeliveryStatus,
39
- } from "../memory/guardian-action-store.js";
40
- import { conversations } from "../memory/schema.js";
41
- import { resetDbForTesting } from "./db-test-helpers.js";
25
+ import { conversations, messages } from "../memory/schema.js";
42
26
 
43
27
  initializeDb();
44
28
 
@@ -55,218 +39,71 @@ function ensureConversation(id: string): void {
55
39
  .run();
56
40
  }
57
41
 
58
- function resetTables(): void {
59
- const db = getDb();
60
- db.run("DELETE FROM guardian_action_deliveries");
61
- db.run("DELETE FROM guardian_action_requests");
62
- db.run("DELETE FROM call_pending_questions");
63
- db.run("DELETE FROM call_events");
64
- db.run("DELETE FROM call_sessions");
65
- db.run("DELETE FROM messages");
66
- db.run("DELETE FROM conversations");
67
- deliveredMessages.length = 0;
42
+ function makeDelivery(
43
+ overrides: Partial<ExpiryDeliveryInfo> = {},
44
+ ): ExpiryDeliveryInfo {
45
+ return {
46
+ id: "delivery-1",
47
+ status: "sent",
48
+ destinationChannel: "telegram",
49
+ destinationConversationId: null,
50
+ destinationChatId: "chat-123",
51
+ ...overrides,
52
+ };
68
53
  }
69
54
 
70
- describe("guardian-action-sweep", () => {
55
+ describe("sendGuardianExpiryNotices", () => {
71
56
  beforeEach(() => {
72
- resetTables();
73
- });
74
-
75
- afterAll(() => {
76
- stopGuardianActionSweep();
77
- resetDbForTesting();
57
+ const db = getDb();
58
+ db.run("DELETE FROM messages");
59
+ db.run("DELETE FROM conversations");
60
+ deliveredMessages.length = 0;
78
61
  });
79
62
 
80
- test("sweepExpiredGuardianActions expires requests past their expiresAt", async () => {
81
- const convId = "conv-sweep-1";
82
- ensureConversation(convId);
83
-
84
- const session = createCallSession({
85
- conversationId: convId,
86
- provider: "twilio",
87
- fromNumber: "+15550001111",
88
- toNumber: "+15550002222",
89
- });
90
- const pq = createPendingQuestion(session.id, "What is the code?");
91
-
92
- // Create request that has already expired
93
- const request = createGuardianActionRequest({
94
- kind: "ask_guardian",
95
- sourceChannel: "phone",
96
- sourceConversationId: convId,
97
- callSessionId: session.id,
98
- pendingQuestionId: pq.id,
99
- questionText: pq.questionText,
100
- expiresAt: Date.now() - 10_000, // expired 10s ago
101
- });
102
-
103
- ensureConversation("conv-mac-1");
104
- createGuardianActionDelivery({
105
- requestId: request.id,
106
- destinationChannel: "vellum",
107
- destinationConversationId: "conv-mac-1",
108
- });
109
- updateDeliveryStatus(
110
- getDeliveriesByRequestId(request.id).find(
111
- (d) => d.destinationChannel === "vellum",
112
- )!.id,
113
- "sent",
63
+ test("sends external channel expiry notices for sent deliveries", async () => {
64
+ await sendGuardianExpiryNotices(
65
+ [makeDelivery({ status: "sent" }), makeDelivery({ id: "d2", status: "pending", destinationChatId: "chat-456" })],
66
+ "assistant-1",
114
67
  );
115
68
 
116
- await sweepExpiredGuardianActions();
117
-
118
- const updatedRequest = getGuardianActionRequest(request.id);
119
- expect(updatedRequest).not.toBeNull();
120
- expect(updatedRequest!.status).toBe("expired");
121
-
122
- const deliveries = getDeliveriesByRequestId(request.id);
123
- expect(deliveries).toHaveLength(1);
124
- expect(deliveries[0].status).toBe("expired");
125
- });
126
-
127
- test("sweepExpiredGuardianActions expires pending questions", async () => {
128
- const convId = "conv-sweep-2";
129
- ensureConversation(convId);
130
-
131
- const session = createCallSession({
132
- conversationId: convId,
133
- provider: "twilio",
134
- fromNumber: "+15550001111",
135
- toNumber: "+15550002222",
136
- });
137
- const pq = createPendingQuestion(session.id, "What is the gate code?");
138
-
139
- createGuardianActionRequest({
140
- kind: "ask_guardian",
141
- sourceChannel: "phone",
142
- sourceConversationId: convId,
143
- callSessionId: session.id,
144
- pendingQuestionId: pq.id,
145
- questionText: pq.questionText,
146
- expiresAt: Date.now() - 5_000,
147
- });
148
-
149
- // Verify the question is still pending before sweep
150
- expect(getPendingQuestion(session.id)).not.toBeNull();
151
-
152
- await sweepExpiredGuardianActions();
153
-
154
- // Pending question should be expired
155
- expect(getPendingQuestion(session.id)).toBeNull();
69
+ expect(deliveredMessages).toHaveLength(2);
70
+ expect(deliveredMessages[0].url).toBe("/deliver/telegram");
71
+ expect(deliveredMessages[0].body.chatId).toBe("chat-123");
72
+ expect(deliveredMessages[0].body.assistantId).toBe("assistant-1");
73
+ expect(deliveredMessages[1].body.chatId).toBe("chat-456");
156
74
  });
157
75
 
158
- test("sweepExpiredGuardianActions does nothing when no expired requests exist", async () => {
159
- const convId = "conv-sweep-3";
160
- ensureConversation(convId);
161
-
162
- const session = createCallSession({
163
- conversationId: convId,
164
- provider: "twilio",
165
- fromNumber: "+15550001111",
166
- toNumber: "+15550002222",
167
- });
168
- const pq = createPendingQuestion(session.id, "Still valid?");
169
-
170
- // Request that expires in the future
171
- const request = createGuardianActionRequest({
172
- kind: "ask_guardian",
173
- sourceChannel: "phone",
174
- sourceConversationId: convId,
175
- callSessionId: session.id,
176
- pendingQuestionId: pq.id,
177
- questionText: pq.questionText,
178
- expiresAt: Date.now() + 60_000, // expires in 60s
179
- });
180
-
181
- await sweepExpiredGuardianActions();
182
-
183
- const updatedRequest = getGuardianActionRequest(request.id);
184
- expect(updatedRequest!.status).toBe("pending");
185
- expect(getPendingQuestion(session.id)).not.toBeNull();
186
- });
187
-
188
- test("sweepExpiredGuardianActions sends external channel expiry notices for sent deliveries", async () => {
189
- const convId = "conv-sweep-4";
190
- ensureConversation(convId);
191
-
192
- const session = createCallSession({
193
- conversationId: convId,
194
- provider: "twilio",
195
- fromNumber: "+15550001111",
196
- toNumber: "+15550002222",
197
- });
198
- const pq = createPendingQuestion(session.id, "External question?");
199
-
200
- const request = createGuardianActionRequest({
201
- kind: "ask_guardian",
202
- sourceChannel: "phone",
203
- sourceConversationId: convId,
204
- callSessionId: session.id,
205
- pendingQuestionId: pq.id,
206
- questionText: pq.questionText,
207
- expiresAt: Date.now() - 5_000,
208
- });
209
-
210
- const delivery = createGuardianActionDelivery({
211
- requestId: request.id,
212
- destinationChannel: "telegram",
213
- destinationChatId: "chat-123",
214
- });
215
- updateDeliveryStatus(delivery.id, "sent");
216
-
217
- await sweepExpiredGuardianActions();
218
-
219
- // Wait for the fire-and-forget async delivery to complete
220
- await new Promise((resolve) => setTimeout(resolve, 50));
221
-
222
- // The external delivery should trigger an HTTP POST to the gateway
223
- expect(deliveredMessages.length).toBeGreaterThanOrEqual(1);
224
- });
225
-
226
- test("sweepExpiredGuardianActions skips failed deliveries", async () => {
227
- const convId = "conv-sweep-5";
228
- ensureConversation(convId);
229
-
230
- const session = createCallSession({
231
- conversationId: convId,
232
- provider: "twilio",
233
- fromNumber: "+15550001111",
234
- toNumber: "+15550002222",
235
- });
236
- const pq = createPendingQuestion(session.id, "Skip this?");
237
-
238
- const request = createGuardianActionRequest({
239
- kind: "ask_guardian",
240
- sourceChannel: "phone",
241
- sourceConversationId: convId,
242
- callSessionId: session.id,
243
- pendingQuestionId: pq.id,
244
- questionText: pq.questionText,
245
- expiresAt: Date.now() - 5_000,
246
- });
247
-
248
- const delivery = createGuardianActionDelivery({
249
- requestId: request.id,
250
- destinationChannel: "telegram",
251
- destinationChatId: "chat-456",
252
- });
253
- updateDeliveryStatus(delivery.id, "failed", "Network error");
254
-
255
- deliveredMessages.length = 0;
256
-
257
- await sweepExpiredGuardianActions();
76
+ test("skips deliveries that are not sent or pending", async () => {
77
+ await sendGuardianExpiryNotices(
78
+ [
79
+ makeDelivery({ status: "failed" }),
80
+ makeDelivery({ id: "d2", status: "expired" }),
81
+ ],
82
+ "assistant-1",
83
+ );
258
84
 
259
- // Should NOT have sent an expiry notice for a failed delivery
260
85
  expect(deliveredMessages).toHaveLength(0);
261
86
  });
262
87
 
263
- test("startGuardianActionSweep and stopGuardianActionSweep manage timer", () => {
264
- startGuardianActionSweep();
265
- // Calling start again should be a no-op (idempotent)
266
- startGuardianActionSweep();
88
+ test("adds an expiry message to vellum guardian conversations", async () => {
89
+ ensureConversation("guardian-conv");
90
+
91
+ await sendGuardianExpiryNotices(
92
+ [
93
+ makeDelivery({
94
+ destinationChannel: "vellum",
95
+ destinationConversationId: "guardian-conv",
96
+ destinationChatId: null,
97
+ }),
98
+ ],
99
+ "assistant-1",
100
+ );
267
101
 
268
- stopGuardianActionSweep();
269
- // Calling stop again should be safe
270
- stopGuardianActionSweep();
102
+ expect(deliveredMessages).toHaveLength(0);
103
+ const db = getDb();
104
+ const rows = db.select().from(messages).all();
105
+ expect(rows).toHaveLength(1);
106
+ expect(rows[0].conversationId).toBe("guardian-conv");
107
+ expect(rows[0].content).toContain("expired");
271
108
  });
272
109
  });
@@ -45,12 +45,14 @@ import {
45
45
  } from "../memory/canonical-guardian-store.js";
46
46
  import { getDb } from "../memory/db-connection.js";
47
47
  import { initializeDb } from "../memory/db-init.js";
48
+ import { processGuardianDecision } from "../runtime/guardian-action-service.js";
48
49
  import { GUARDIAN_DECISION_ACTIONS } from "../runtime/guardian-decision-types.js";
49
50
  import {
50
51
  type GuardianReplyContext,
51
52
  routeGuardianReply,
52
53
  } from "../runtime/guardian-reply-router.js";
53
54
  import * as pendingInteractions from "../runtime/pending-interactions.js";
55
+ import { parseCallbackData } from "../runtime/routes/channel-route-shared.js";
54
56
  import { listGuardianDecisionPrompts } from "../runtime/routes/guardian-action-routes.js";
55
57
 
56
58
  initializeDb();
@@ -168,6 +170,12 @@ describe("routing invariant: all decision paths reference applyCanonicalGuardian
168
170
  path: "runtime/routes/guardian-action-routes.ts",
169
171
  symbols: ["processGuardianDecision"],
170
172
  },
173
+ // Surface action route handler (Vellum in-app card buttons) — intercepts
174
+ // apr:* action IDs and routes through processGuardianDecision
175
+ {
176
+ path: "runtime/routes/surface-action-routes.ts",
177
+ symbols: ["processGuardianDecision"],
178
+ },
171
179
  // Shared service where processGuardianDecision is defined — must route
172
180
  // through the canonical primitive to complete the chain:
173
181
  // entrypoint → processGuardianDecision → applyCanonicalGuardianDecision
@@ -1941,3 +1949,133 @@ describe("routing invariant: kind-specific action sets in prompt mapping", () =>
1941
1949
  expect(actionIds).not.toContain("approve_conversation");
1942
1950
  });
1943
1951
  });
1952
+
1953
+ // ===========================================================================
1954
+ // SECTION 11: Surface action → access request approval routing
1955
+ // ===========================================================================
1956
+
1957
+ describe("routing invariant: surface action apr:* buttons route through canonical primitive", () => {
1958
+ beforeEach(() => resetTables());
1959
+
1960
+ test("parseCallbackData extracts requestId and action from apr: action IDs", () => {
1961
+ const result = parseCallbackData(
1962
+ "apr:access-req-test-slack-U123-1700000000:approve_once",
1963
+ "vellum",
1964
+ );
1965
+ expect(result).not.toBeNull();
1966
+ expect(result!.requestId).toBe("access-req-test-slack-U123-1700000000");
1967
+ expect(result!.action).toBe("approve_once");
1968
+ expect(result!.source).toBe("vellum_surface");
1969
+ });
1970
+
1971
+ test("parseCallbackData returns vellum_surface source for vellum channel", () => {
1972
+ const result = parseCallbackData("apr:req-1:reject", "vellum");
1973
+ expect(result).not.toBeNull();
1974
+ expect(result!.source).toBe("vellum_surface");
1975
+ });
1976
+
1977
+ test("processGuardianDecision approves access_request from vellum surface", async () => {
1978
+ const requestId = `access-req-test-slack-U123-${Date.now()}`;
1979
+ createCanonicalGuardianRequest({
1980
+ id: requestId,
1981
+ kind: "access_request",
1982
+ sourceType: "channel",
1983
+ sourceChannel: "slack",
1984
+ conversationId: "conv-surface-test",
1985
+ requesterExternalUserId: "U123",
1986
+ requesterChatId: "D456",
1987
+ guardianExternalUserId: "guardian-1",
1988
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1989
+ toolName: "ingress_access_request",
1990
+ expiresAt: Date.now() + 60_000,
1991
+ });
1992
+
1993
+ const result = await processGuardianDecision({
1994
+ requestId,
1995
+ action: "approve_once",
1996
+ channel: "vellum",
1997
+ actorContext: {
1998
+ actorPrincipalId: TEST_PRINCIPAL_ID,
1999
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
2000
+ },
2001
+ });
2002
+
2003
+ expect(result.ok).toBe(true);
2004
+ if (result.ok && "applied" in result) {
2005
+ expect(result.applied).toBe(true);
2006
+ }
2007
+
2008
+ const resolved = getCanonicalGuardianRequest(requestId);
2009
+ expect(resolved!.status).toBe("approved");
2010
+ });
2011
+
2012
+ test("processGuardianDecision denies access_request from vellum surface", async () => {
2013
+ const requestId = `access-req-test-slack-U789-${Date.now()}`;
2014
+ createCanonicalGuardianRequest({
2015
+ id: requestId,
2016
+ kind: "access_request",
2017
+ sourceType: "channel",
2018
+ sourceChannel: "slack",
2019
+ conversationId: "conv-surface-deny",
2020
+ requesterExternalUserId: "U789",
2021
+ requesterChatId: "D012",
2022
+ guardianExternalUserId: "guardian-1",
2023
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
2024
+ toolName: "ingress_access_request",
2025
+ expiresAt: Date.now() + 60_000,
2026
+ });
2027
+
2028
+ const result = await processGuardianDecision({
2029
+ requestId,
2030
+ action: "reject",
2031
+ channel: "vellum",
2032
+ actorContext: {
2033
+ actorPrincipalId: TEST_PRINCIPAL_ID,
2034
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
2035
+ },
2036
+ });
2037
+
2038
+ expect(result.ok).toBe(true);
2039
+ if (result.ok && "applied" in result) {
2040
+ expect(result.applied).toBe(true);
2041
+ }
2042
+
2043
+ const resolved = getCanonicalGuardianRequest(requestId);
2044
+ expect(resolved!.status).toBe("denied");
2045
+ });
2046
+
2047
+ test("principal mismatch rejects surface action decision", async () => {
2048
+ const requestId = `access-req-test-slack-U999-${Date.now()}`;
2049
+ createCanonicalGuardianRequest({
2050
+ id: requestId,
2051
+ kind: "access_request",
2052
+ sourceType: "channel",
2053
+ sourceChannel: "slack",
2054
+ conversationId: "conv-surface-mismatch",
2055
+ requesterExternalUserId: "U999",
2056
+ requesterChatId: "D345",
2057
+ guardianExternalUserId: "guardian-1",
2058
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
2059
+ toolName: "ingress_access_request",
2060
+ expiresAt: Date.now() + 60_000,
2061
+ });
2062
+
2063
+ const result = await processGuardianDecision({
2064
+ requestId,
2065
+ action: "approve_once",
2066
+ channel: "vellum",
2067
+ actorContext: {
2068
+ actorPrincipalId: "wrong-principal",
2069
+ guardianPrincipalId: "wrong-principal",
2070
+ },
2071
+ });
2072
+
2073
+ expect(result.ok).toBe(true);
2074
+ if (result.ok && "applied" in result) {
2075
+ expect(result.applied).toBe(false);
2076
+ }
2077
+
2078
+ const req = getCanonicalGuardianRequest(requestId);
2079
+ expect(req!.status).toBe("pending");
2080
+ });
2081
+ });
@@ -64,7 +64,6 @@ mock.module("../../daemon/approval-generators.js", () => ({
64
64
  import type {
65
65
  ApprovalConversationGenerator,
66
66
  ApprovalCopyGenerator,
67
- GuardianActionCopyGenerator,
68
67
  MessageProcessor,
69
68
  } from "../../runtime/http-types.js";
70
69
  import {
@@ -111,7 +110,6 @@ export async function handleChannelInbound(
111
110
  _assistantId?: string,
112
111
  _approvalCopyGenerator?: ApprovalCopyGenerator,
113
112
  _approvalConversationGenerator?: ApprovalConversationGenerator,
114
- _guardianActionCopyGenerator?: GuardianActionCopyGenerator,
115
113
  ): Promise<Response> {
116
114
  const body = await req.json();
117
115
  return wrapHandler(() => _handleChannelInbound({ body }));
@@ -33,6 +33,10 @@ import {
33
33
  } from "../memory/conversation-crud.js";
34
34
  import { getDb } from "../memory/db-connection.js";
35
35
  import { initializeDb } from "../memory/db-init.js";
36
+ import {
37
+ MEMORY_RETROSPECTIVE_FORK_SOURCE,
38
+ MEMORY_RETROSPECTIVE_INSTRUCTION_KIND,
39
+ } from "../memory/memory-retrospective-constants.js";
36
40
  import { messages } from "../memory/schema.js";
37
41
  import { handleListMessages } from "../runtime/routes/conversation-routes.js";
38
42
 
@@ -334,4 +338,99 @@ describe("handleListMessages metadata.hidden filtering", () => {
334
338
  expect(latest.hasMore).toBe(false);
335
339
  expect(latest.oldestTimestamp).not.toBeNull();
336
340
  });
341
+
342
+ test("fork retrospective conversations expose the hidden instruction and keep the review as its own turn", async () => {
343
+ const conv = createConversation({
344
+ source: MEMORY_RETROSPECTIVE_FORK_SOURCE,
345
+ });
346
+ await addMessage(
347
+ conv.id,
348
+ "user",
349
+ JSON.stringify([{ type: "text", text: "source question" }]),
350
+ );
351
+ await addMessage(
352
+ conv.id,
353
+ "assistant",
354
+ JSON.stringify([{ type: "text", text: "source answer" }]),
355
+ );
356
+ await addMessage(
357
+ conv.id,
358
+ "user",
359
+ JSON.stringify([
360
+ { type: "text", text: "background memory pass instruction" },
361
+ ]),
362
+ {
363
+ metadata: { kind: MEMORY_RETROSPECTIVE_INSTRUCTION_KIND, hidden: true },
364
+ },
365
+ );
366
+ await addMessage(
367
+ conv.id,
368
+ "assistant",
369
+ JSON.stringify([{ type: "text", text: "retrospective review" }]),
370
+ );
371
+
372
+ const response = handleListMessages({
373
+ queryParams: { conversationId: conv.id },
374
+ });
375
+ const body = response as { messages: MessagePayload[] };
376
+
377
+ // The hidden instruction is shown, and because it sits between the two
378
+ // assistant rows they do NOT merge — the review stands as its own turn.
379
+ expect(body.messages.map((m) => m.role)).toEqual([
380
+ "user",
381
+ "assistant",
382
+ "user",
383
+ "assistant",
384
+ ]);
385
+ expect(plainText(body.messages[1])).toBe("source answer");
386
+ expect(plainText(body.messages[2])).toContain(
387
+ "background memory pass instruction",
388
+ );
389
+ expect(plainText(body.messages[3])).toBe("retrospective review");
390
+ });
391
+
392
+ test("non-fork conversations still hide the retrospective instruction (relaxation is fork-scoped)", async () => {
393
+ const conv = createConversation();
394
+ await addMessage(
395
+ conv.id,
396
+ "user",
397
+ JSON.stringify([{ type: "text", text: "source question" }]),
398
+ );
399
+ await addMessage(
400
+ conv.id,
401
+ "assistant",
402
+ JSON.stringify([{ type: "text", text: "source answer" }]),
403
+ );
404
+ await addMessage(
405
+ conv.id,
406
+ "user",
407
+ JSON.stringify([
408
+ { type: "text", text: "background memory pass instruction" },
409
+ ]),
410
+ {
411
+ metadata: { kind: MEMORY_RETROSPECTIVE_INSTRUCTION_KIND, hidden: true },
412
+ },
413
+ );
414
+ await addMessage(
415
+ conv.id,
416
+ "assistant",
417
+ JSON.stringify([{ type: "text", text: "retrospective review" }]),
418
+ );
419
+
420
+ const response = handleListMessages({
421
+ queryParams: { conversationId: conv.id },
422
+ });
423
+ const body = response as { messages: MessagePayload[] };
424
+
425
+ // Instruction stays hidden for non-retrospective conversations; the review
426
+ // (a normal assistant message) is still returned.
427
+ expect(
428
+ body.messages.some((m) =>
429
+ plainText(m).includes("background memory pass instruction"),
430
+ ),
431
+ ).toBe(false);
432
+ expect(
433
+ body.messages.some((m) => plainText(m).includes("retrospective review")),
434
+ ).toBe(true);
435
+ });
337
436
  });