@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
@@ -68,6 +68,13 @@ let riskOverride: string = "high";
68
68
  /** Scope options override — controls whether persistent decisions are offered. */
69
69
  let scopeOptionsOverride: ScopeOption[] | undefined;
70
70
 
71
+ /**
72
+ * Auto-approve threshold returned to the permission checker. "high" is the
73
+ * full-access posture under which the requireFreshApproval promotion is
74
+ * skipped; every lower value still promotes allow → prompt.
75
+ */
76
+ let thresholdOverride: "none" | "low" | "medium" | "high" = "medium";
77
+
71
78
  mock.module("../config/loader.js", () => ({
72
79
  getConfig: () => mockConfig,
73
80
  loadConfig: () => mockConfig,
@@ -124,13 +131,26 @@ mock.module("../tools/registry.js", () => ({
124
131
  }));
125
132
 
126
133
  mock.module("../permissions/gateway-threshold-reader.js", () => ({
127
- getAutoApproveThreshold: async () => "medium",
134
+ getAutoApproveThreshold: async () => thresholdOverride,
128
135
  // Refresh failure ("null") keeps the original decision — these tests
129
136
  // exercise the cached-threshold paths only.
130
137
  refreshAutoApproveThreshold: async () => null,
131
138
  _clearGlobalCacheForTesting: () => {},
132
139
  }));
133
140
 
141
+ // Stub the workflow run manager so the `manage_workflows` resume gate can read a
142
+ // target run's STORED capabilities without a real journal/DB. Tests set
143
+ // `fakeWorkflowRun` to control the stored manifest (or null for "not found").
144
+ let fakeWorkflowRun: {
145
+ capabilities: unknown;
146
+ conversationId: string | null;
147
+ } | null = null;
148
+ mock.module("../workflows/run-manager.js", () => ({
149
+ getWorkflowRunManager: () => ({
150
+ status: (_runId: string) => fakeWorkflowRun,
151
+ }),
152
+ }));
153
+
134
154
  mock.module("../tools/shared/filesystem/path-policy.js", () => ({
135
155
  sandboxPolicy: () => ({ ok: false }),
136
156
  hostPolicy: () => ({ ok: false }),
@@ -173,6 +193,7 @@ describe("requireFreshApproval: non-interactive guardian denial", () => {
173
193
  checkResultOverride = undefined;
174
194
  scopeOptionsOverride = undefined;
175
195
  riskOverride = "high";
196
+ thresholdOverride = "medium";
176
197
  });
177
198
 
178
199
  afterEach(() => {});
@@ -322,6 +343,7 @@ describe("requireFreshApproval: persistent decisions disabled", () => {
322
343
  checkResultOverride = undefined;
323
344
  scopeOptionsOverride = undefined;
324
345
  riskOverride = "high";
346
+ thresholdOverride = "medium";
325
347
  });
326
348
 
327
349
  afterEach(() => {});
@@ -385,6 +407,7 @@ describe("requireFreshApproval: grant-consumed does not skip permission check",
385
407
  checkResultOverride = undefined;
386
408
  scopeOptionsOverride = undefined;
387
409
  riskOverride = "high";
410
+ thresholdOverride = "medium";
388
411
  });
389
412
 
390
413
  afterEach(() => {});
@@ -437,6 +460,7 @@ describe("requireFreshApproval: context flag propagation", () => {
437
460
  checkResultOverride = undefined;
438
461
  scopeOptionsOverride = undefined;
439
462
  riskOverride = "high";
463
+ thresholdOverride = "medium";
440
464
  });
441
465
 
442
466
  afterEach(() => {});
@@ -476,3 +500,403 @@ describe("requireFreshApproval: context flag propagation", () => {
476
500
  expect(ctx.requireFreshApproval).toBeUndefined();
477
501
  });
478
502
  });
503
+
504
+ // ---------------------------------------------------------------------------
505
+ // Workflow launch consent: a run_workflow whose capability manifest grants
506
+ // side-effecting tools/host functions must prompt at LAUNCH (the manifest is
507
+ // model-declared and leaves execute granted tools directly, with no per-call
508
+ // check). A read-only run launches silently.
509
+ // ---------------------------------------------------------------------------
510
+
511
+ describe("requireFreshApproval: workflow capability grants", () => {
512
+ beforeEach(() => {
513
+ fakeToolResult = { content: "ok", isError: false };
514
+ checkResultOverride = undefined;
515
+ scopeOptionsOverride = undefined;
516
+ riskOverride = "low";
517
+ thresholdOverride = "medium";
518
+ fakeWorkflowRun = null;
519
+ });
520
+
521
+ afterEach(() => {});
522
+
523
+ function trackingPrompter(state: { prompted: boolean }): PermissionPrompter {
524
+ return {
525
+ prompt: async () => {
526
+ state.prompted = true;
527
+ return { decision: "allow" as const };
528
+ },
529
+ resolveConfirmation: () => {},
530
+ updateSender: () => {},
531
+ dispose: () => {},
532
+ } as unknown as PermissionPrompter;
533
+ }
534
+
535
+ test("run_workflow granting side-effecting tools prompts at launch", async () => {
536
+ // A grant of `bash` would normally auto-allow (mocked check() returns
537
+ // allow); requireFreshApproval promotes it to an interactive prompt.
538
+ checkResultOverride = { decision: "allow", reason: "allowed" };
539
+ const state = { prompted: false };
540
+ const executor = new ToolExecutor(trackingPrompter(state));
541
+ const ctx = makeContext();
542
+
543
+ const result = await executor.execute(
544
+ "run_workflow",
545
+ { script: "export const meta={};", capabilities: { tools: ["bash"] } },
546
+ ctx,
547
+ );
548
+
549
+ expect(ctx.requireFreshApproval).toBe(true);
550
+ expect(state.prompted).toBe(true);
551
+ expect(result.isError).toBe(false);
552
+ });
553
+
554
+ test.each(["none", "low", "medium"] as const)(
555
+ "run_workflow side-effecting launch prompts at the %s threshold (non-full-access posture)",
556
+ async (threshold) => {
557
+ checkResultOverride = { decision: "allow", reason: "allowed" };
558
+ thresholdOverride = threshold;
559
+ const state = { prompted: false };
560
+ const executor = new ToolExecutor(trackingPrompter(state));
561
+ const ctx = makeContext();
562
+
563
+ const result = await executor.execute(
564
+ "run_workflow",
565
+ { script: "export const meta={};", capabilities: { tools: ["bash"] } },
566
+ ctx,
567
+ );
568
+
569
+ expect(ctx.requireFreshApproval).toBe(true);
570
+ expect(state.prompted).toBe(true);
571
+ expect(result.isError).toBe(false);
572
+ },
573
+ );
574
+
575
+ test("run_workflow side-effecting launch does NOT prompt at full access (high)", async () => {
576
+ // Full-access posture auto-approves even high-risk tools, so the
577
+ // requireFreshApproval promotion is skipped — the grant auto-allows.
578
+ checkResultOverride = { decision: "allow", reason: "allowed" };
579
+ thresholdOverride = "high";
580
+ const state = { prompted: false };
581
+ const executor = new ToolExecutor(trackingPrompter(state));
582
+ const ctx = makeContext();
583
+
584
+ const result = await executor.execute(
585
+ "run_workflow",
586
+ { script: "export const meta={};", capabilities: { tools: ["bash"] } },
587
+ ctx,
588
+ );
589
+
590
+ // requireFreshApproval is still SET on the context (launch is
591
+ // side-effecting); only the allow → prompt promotion is suppressed.
592
+ expect(ctx.requireFreshApproval).toBe(true);
593
+ expect(state.prompted).toBe(false);
594
+ expect(result.isError).toBe(false);
595
+ });
596
+
597
+ test("a deny rule still blocks a side-effecting launch at full access (high)", async () => {
598
+ // Full access skips the fresh-approval promotion but never overrides an
599
+ // explicit deny — deny rules win regardless of posture.
600
+ checkResultOverride = { decision: "deny", reason: "blocked by rule" };
601
+ thresholdOverride = "high";
602
+ const state = { prompted: false };
603
+ const executor = new ToolExecutor(trackingPrompter(state));
604
+ const ctx = makeContext();
605
+
606
+ const result = await executor.execute(
607
+ "run_workflow",
608
+ { script: "export const meta={};", capabilities: { tools: ["bash"] } },
609
+ ctx,
610
+ );
611
+
612
+ expect(state.prompted).toBe(false);
613
+ expect(result.isError).toBe(true);
614
+ });
615
+
616
+ test("a deny rule still blocks a side-effecting launch at a normal threshold", async () => {
617
+ checkResultOverride = { decision: "deny", reason: "blocked by rule" };
618
+ thresholdOverride = "medium";
619
+ const state = { prompted: false };
620
+ const executor = new ToolExecutor(trackingPrompter(state));
621
+ const ctx = makeContext();
622
+
623
+ const result = await executor.execute(
624
+ "run_workflow",
625
+ { script: "export const meta={};", capabilities: { tools: ["bash"] } },
626
+ ctx,
627
+ );
628
+
629
+ expect(state.prompted).toBe(false);
630
+ expect(result.isError).toBe(true);
631
+ });
632
+
633
+ test("a read-only launch (no requireFreshApproval) is unaffected by a normal threshold", async () => {
634
+ // No requireFreshApproval means the allow stands untouched at any posture.
635
+ checkResultOverride = { decision: "allow", reason: "allowed" };
636
+ thresholdOverride = "medium";
637
+ const state = { prompted: false };
638
+ const executor = new ToolExecutor(trackingPrompter(state));
639
+ const ctx = makeContext();
640
+
641
+ const result = await executor.execute(
642
+ "run_workflow",
643
+ { script: "export const meta={};", capabilities: {} },
644
+ ctx,
645
+ );
646
+
647
+ expect(ctx.requireFreshApproval).toBeUndefined();
648
+ expect(state.prompted).toBe(false);
649
+ expect(result.isError).toBe(false);
650
+ });
651
+
652
+ test("run_workflow granting host functions prompts at launch", async () => {
653
+ checkResultOverride = { decision: "allow", reason: "allowed" };
654
+ const state = { prompted: false };
655
+ const executor = new ToolExecutor(trackingPrompter(state));
656
+ const ctx = makeContext();
657
+
658
+ await executor.execute(
659
+ "run_workflow",
660
+ {
661
+ script: "export const meta={};",
662
+ capabilities: { hostFunctions: ["notify"] },
663
+ },
664
+ ctx,
665
+ );
666
+
667
+ expect(ctx.requireFreshApproval).toBe(true);
668
+ expect(state.prompted).toBe(true);
669
+ });
670
+
671
+ test("read-only run_workflow launches silently (no fresh approval, no prompt)", async () => {
672
+ checkResultOverride = { decision: "allow", reason: "allowed" };
673
+ const state = { prompted: false };
674
+ const executor = new ToolExecutor(trackingPrompter(state));
675
+ const ctx = makeContext();
676
+
677
+ const result = await executor.execute(
678
+ "run_workflow",
679
+ { script: "export const meta={};", capabilities: {} },
680
+ ctx,
681
+ );
682
+
683
+ expect(ctx.requireFreshApproval).toBeUndefined();
684
+ expect(state.prompted).toBe(false);
685
+ expect(result.isError).toBe(false);
686
+ });
687
+
688
+ test("workflow-mode schedule_create granting side-effecting tools prompts at creation", async () => {
689
+ checkResultOverride = { decision: "allow", reason: "allowed" };
690
+ const state = { prompted: false };
691
+ const executor = new ToolExecutor(trackingPrompter(state));
692
+ const ctx = makeContext();
693
+
694
+ const result = await executor.execute(
695
+ "schedule_create",
696
+ {
697
+ name: "Nightly writeback",
698
+ mode: "workflow",
699
+ workflow_name: "nightly-report",
700
+ capabilities: { tools: ["bash"] },
701
+ },
702
+ ctx,
703
+ );
704
+
705
+ expect(ctx.requireFreshApproval).toBe(true);
706
+ expect(state.prompted).toBe(true);
707
+ expect(result.isError).toBe(false);
708
+ });
709
+
710
+ test("workflow-mode schedule_create granting host functions prompts at creation", async () => {
711
+ checkResultOverride = { decision: "allow", reason: "allowed" };
712
+ const state = { prompted: false };
713
+ const executor = new ToolExecutor(trackingPrompter(state));
714
+ const ctx = makeContext();
715
+
716
+ await executor.execute(
717
+ "schedule_create",
718
+ {
719
+ name: "Nightly notify",
720
+ mode: "workflow",
721
+ workflow_name: "nightly-report",
722
+ capabilities: { hostFunctions: ["notify"] },
723
+ },
724
+ ctx,
725
+ );
726
+
727
+ expect(ctx.requireFreshApproval).toBe(true);
728
+ expect(state.prompted).toBe(true);
729
+ });
730
+
731
+ test("workflow-mode schedule_create with a read-only manifest stays silent", async () => {
732
+ checkResultOverride = { decision: "allow", reason: "allowed" };
733
+ const state = { prompted: false };
734
+ const executor = new ToolExecutor(trackingPrompter(state));
735
+ const ctx = makeContext();
736
+
737
+ const result = await executor.execute(
738
+ "schedule_create",
739
+ {
740
+ name: "Read-only schedule",
741
+ mode: "workflow",
742
+ workflow_name: "nightly-report",
743
+ capabilities: {},
744
+ },
745
+ ctx,
746
+ );
747
+
748
+ expect(ctx.requireFreshApproval).toBeUndefined();
749
+ expect(state.prompted).toBe(false);
750
+ expect(result.isError).toBe(false);
751
+ });
752
+
753
+ test("non-workflow schedule_create never gates even with a side-effecting capabilities blob", async () => {
754
+ // The capabilities field is workflow-mode only; an execute-mode schedule
755
+ // must not be promoted to a fresh approval by a stray capabilities object.
756
+ checkResultOverride = { decision: "allow", reason: "allowed" };
757
+ const state = { prompted: false };
758
+ const executor = new ToolExecutor(trackingPrompter(state));
759
+ const ctx = makeContext();
760
+
761
+ await executor.execute(
762
+ "schedule_create",
763
+ {
764
+ name: "Execute schedule",
765
+ mode: "execute",
766
+ message: "do the thing",
767
+ capabilities: { tools: ["bash"] },
768
+ },
769
+ ctx,
770
+ );
771
+
772
+ expect(ctx.requireFreshApproval).toBeUndefined();
773
+ expect(state.prompted).toBe(false);
774
+ });
775
+
776
+ test("manage_workflows resume of a side-effecting run requires fresh approval", async () => {
777
+ // The target run's STORED manifest granted tools, so resuming it (which
778
+ // restarts unfinished side-effecting leaves) must re-prompt.
779
+ fakeWorkflowRun = {
780
+ capabilities: { tools: ["bash"] },
781
+ conversationId: "conversation-1",
782
+ };
783
+ checkResultOverride = { decision: "allow", reason: "allowed" };
784
+ const state = { prompted: false };
785
+ const executor = new ToolExecutor(trackingPrompter(state));
786
+ const ctx = makeContext();
787
+
788
+ await executor.execute(
789
+ "manage_workflows",
790
+ { action: "resume", run_id: "wf-run-1" },
791
+ ctx,
792
+ );
793
+
794
+ expect(ctx.requireFreshApproval).toBe(true);
795
+ expect(state.prompted).toBe(true);
796
+ });
797
+
798
+ test("manage_workflows resume of a read-only run stays silent", async () => {
799
+ fakeWorkflowRun = { capabilities: {}, conversationId: "conversation-1" };
800
+ checkResultOverride = { decision: "allow", reason: "allowed" };
801
+ const state = { prompted: false };
802
+ const executor = new ToolExecutor(trackingPrompter(state));
803
+ const ctx = makeContext();
804
+
805
+ await executor.execute(
806
+ "manage_workflows",
807
+ { action: "resume", run_id: "wf-run-1" },
808
+ ctx,
809
+ );
810
+
811
+ expect(ctx.requireFreshApproval).toBeUndefined();
812
+ expect(state.prompted).toBe(false);
813
+ });
814
+
815
+ test("manage_workflows non-resume actions never gate (status of a side-effecting run)", async () => {
816
+ // Even though the run granted tools, status/abort/list_runs are pure
817
+ // control/reads and must stay low-risk and silent.
818
+ fakeWorkflowRun = {
819
+ capabilities: { tools: ["bash"] },
820
+ conversationId: "conversation-1",
821
+ };
822
+ checkResultOverride = { decision: "allow", reason: "allowed" };
823
+ const state = { prompted: false };
824
+ const executor = new ToolExecutor(trackingPrompter(state));
825
+ const ctx = makeContext();
826
+
827
+ await executor.execute(
828
+ "manage_workflows",
829
+ { action: "status", run_id: "wf-run-1" },
830
+ ctx,
831
+ );
832
+
833
+ expect(ctx.requireFreshApproval).toBeUndefined();
834
+ expect(state.prompted).toBe(false);
835
+ });
836
+
837
+ test("manage_workflows resume of an unknown run does not gate (run not found)", async () => {
838
+ fakeWorkflowRun = null;
839
+ checkResultOverride = { decision: "allow", reason: "allowed" };
840
+ const state = { prompted: false };
841
+ const executor = new ToolExecutor(trackingPrompter(state));
842
+ const ctx = makeContext();
843
+
844
+ await executor.execute(
845
+ "manage_workflows",
846
+ { action: "resume", run_id: "missing" },
847
+ ctx,
848
+ );
849
+
850
+ expect(ctx.requireFreshApproval).toBeUndefined();
851
+ expect(state.prompted).toBe(false);
852
+ });
853
+
854
+ test("a non-owner resuming another conversation's side-effecting run does NOT gate (no prompt, no existence leak)", async () => {
855
+ // The run is side-effecting but belongs to a different conversation. The
856
+ // tool will hide it as not-found, so the gate must not prompt — otherwise it
857
+ // leaks that the run exists and nags the guardian for a no-op resume.
858
+ fakeWorkflowRun = {
859
+ capabilities: { tools: ["bash"] },
860
+ conversationId: "other-conversation",
861
+ };
862
+ checkResultOverride = { decision: "allow", reason: "allowed" };
863
+ const state = { prompted: false };
864
+ const executor = new ToolExecutor(trackingPrompter(state));
865
+ const ctx = makeContext({
866
+ trustClass: "trusted_contact",
867
+ conversationId: "conversation-1",
868
+ });
869
+
870
+ await executor.execute(
871
+ "manage_workflows",
872
+ { action: "resume", run_id: "wf-run-1" },
873
+ ctx,
874
+ );
875
+
876
+ expect(ctx.requireFreshApproval).toBeUndefined();
877
+ expect(state.prompted).toBe(false);
878
+ });
879
+
880
+ test("a non-owner resuming its OWN side-effecting run still gates", async () => {
881
+ fakeWorkflowRun = {
882
+ capabilities: { tools: ["bash"] },
883
+ conversationId: "conversation-1",
884
+ };
885
+ checkResultOverride = { decision: "allow", reason: "allowed" };
886
+ const state = { prompted: false };
887
+ const executor = new ToolExecutor(trackingPrompter(state));
888
+ const ctx = makeContext({
889
+ trustClass: "trusted_contact",
890
+ conversationId: "conversation-1",
891
+ });
892
+
893
+ await executor.execute(
894
+ "manage_workflows",
895
+ { action: "resume", run_id: "wf-run-1" },
896
+ ctx,
897
+ );
898
+
899
+ expect(ctx.requireFreshApproval).toBe(true);
900
+ expect(state.prompted).toBe(true);
901
+ });
902
+ });
@@ -0,0 +1,56 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ import type { AppDefinition } from "../memory/app-store.js";
4
+
5
+ let appsByConversation: AppDefinition[] = [];
6
+
7
+ const realStore = await import("../memory/app-store.js");
8
+ mock.module("../memory/app-store.js", () => ({
9
+ ...realStore,
10
+ listAppsByConversation: (_conversationId: string) => appsByConversation,
11
+ }));
12
+
13
+ const { resolveAppId, missingAppIdError } =
14
+ await import("../tools/apps/resolve-app-id.js");
15
+
16
+ function makeApp(id: string, updatedAt: number): AppDefinition {
17
+ return {
18
+ id,
19
+ name: id,
20
+ schemaJson: "{}",
21
+ htmlDefinition: "",
22
+ createdAt: updatedAt,
23
+ updatedAt,
24
+ };
25
+ }
26
+
27
+ describe("resolveAppId", () => {
28
+ test("returns an explicit non-empty app_id unchanged", () => {
29
+ appsByConversation = [makeApp("other", 1)];
30
+ expect(resolveAppId({ app_id: "explicit" }, "conv-1")).toBe("explicit");
31
+ });
32
+
33
+ test("falls back to the most-recently-updated conversation app when missing", () => {
34
+ // listAppsByConversation inherits listApps' updatedAt-descending order.
35
+ appsByConversation = [makeApp("newest", 30), makeApp("older", 10)];
36
+ expect(resolveAppId({}, "conv-1")).toBe("newest");
37
+ });
38
+
39
+ test("treats a blank app_id as missing", () => {
40
+ appsByConversation = [makeApp("active", 5)];
41
+ expect(resolveAppId({ app_id: " " }, "conv-1")).toBe("active");
42
+ });
43
+
44
+ test("returns null when no app_id is given and the conversation has no app", () => {
45
+ appsByConversation = [];
46
+ expect(resolveAppId({}, "conv-1")).toBeNull();
47
+ });
48
+ });
49
+
50
+ describe("missingAppIdError", () => {
51
+ test("is an actionable error result", () => {
52
+ const result = missingAppIdError();
53
+ expect(result.isError).toBe(true);
54
+ expect(JSON.parse(result.content).error).toContain("app_create");
55
+ });
56
+ });
@@ -138,6 +138,7 @@ describe("SSE HTTP parity — streaming/delta message types", () => {
138
138
  type: "assistant_thinking_delta" as const,
139
139
  thinking: "Let me reason through this...",
140
140
  conversationId: "conv-thinking-test",
141
+ timestampMs: 1_700_000_000_000,
141
142
  };
142
143
  const event = await publishAndReadFrame("parity-thinking-delta", msg);
143
144
 
@@ -145,6 +146,7 @@ describe("SSE HTTP parity — streaming/delta message types", () => {
145
146
  const m = event.message as typeof msg;
146
147
  expect(m.thinking).toBe("Let me reason through this...");
147
148
  expect(m.conversationId).toBe("conv-thinking-test");
149
+ expect(m.timestampMs).toBe(1_700_000_000_000);
148
150
  });
149
151
 
150
152
  // ── tool_input_delta ─────────────────────────────────────────────────────