@vellumai/assistant 0.8.12 → 0.9.0-staging.1

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 (414) 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/openapi.yaml +6935 -5545
  15. package/package.json +8 -4
  16. package/scripts/generate-openapi.ts +66 -114
  17. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  18. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  19. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  20. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  21. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  22. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  23. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  24. package/src/__tests__/approval-cascade.test.ts +0 -5
  25. package/src/__tests__/approval-routes-http.test.ts +91 -0
  26. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  27. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  28. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  29. package/src/__tests__/compaction-events.test.ts +63 -7
  30. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  31. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  32. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  33. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  34. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  35. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  36. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  37. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  39. package/src/__tests__/conversation-queue.test.ts +0 -5
  40. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  41. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  42. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  43. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  44. package/src/__tests__/conversation-usage.test.ts +2 -0
  45. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  46. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  47. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  48. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  49. package/src/__tests__/credential-broker.test.ts +1 -1
  50. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  51. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  52. package/src/__tests__/credential-vault.test.ts +37 -0
  53. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  54. package/src/__tests__/dynamic-page-surface.test.ts +125 -0
  55. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  56. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  57. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  58. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  59. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  60. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  61. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  62. package/src/__tests__/llm-resolver.test.ts +115 -0
  63. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  64. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  65. package/src/__tests__/media-generate-image.test.ts +20 -9
  66. package/src/__tests__/model-intents.test.ts +1 -1
  67. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  68. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  69. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  70. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  71. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  72. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  73. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  74. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  75. package/src/__tests__/schedule-routes.test.ts +257 -4
  76. package/src/__tests__/schedule-store.test.ts +60 -0
  77. package/src/__tests__/schedule-tools.test.ts +247 -2
  78. package/src/__tests__/skill-execute-input.test.ts +85 -0
  79. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  80. package/src/__tests__/skills.test.ts +3 -3
  81. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  82. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  83. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  84. package/src/__tests__/system-prompt.test.ts +1 -1
  85. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  86. package/src/__tests__/task-scheduler.test.ts +299 -0
  87. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  88. package/src/__tests__/tool-result-spool.test.ts +3 -1
  89. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  90. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  91. package/src/agent/compaction-circuit.ts +11 -0
  92. package/src/agent/loop.ts +181 -12
  93. package/src/api/constants/call-sites.ts +12 -0
  94. package/src/api/events/assistant-thinking-delta.ts +10 -0
  95. package/src/api/events/usage-update.ts +7 -0
  96. package/src/api/index.ts +4 -1
  97. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  98. package/src/approvals/approval-primitive.ts +2 -2
  99. package/src/background-wake/background-wake-routes.test.ts +5 -2
  100. package/src/bundler/compiler-tools.ts +1 -1
  101. package/src/calls/call-domain.ts +1 -1
  102. package/src/calls/guardian-action-sweep.ts +16 -93
  103. package/src/cli/AGENTS.md +4 -0
  104. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  105. package/src/cli/commands/credentials.ts +28 -24
  106. package/src/cli/commands/image-generation.ts +23 -9
  107. package/src/cli/commands/notifications.ts +1 -1
  108. package/src/cli/commands/plugins.ts +89 -46
  109. package/src/cli/commands/schedules.ts +384 -11
  110. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  111. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  112. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  113. package/src/cli/lib/inspect-plugin.ts +62 -1
  114. package/src/cli/lib/install-from-github.ts +52 -5
  115. package/src/cli/lib/upgrade-plugin.ts +18 -0
  116. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  117. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  118. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  119. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  120. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  121. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  122. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  123. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  124. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  125. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  126. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  127. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  128. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  129. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  130. package/src/config/bundled-tool-registry.ts +14 -2
  131. package/src/config/call-site-defaults.ts +5 -0
  132. package/src/config/feature-flag-registry.json +12 -4
  133. package/src/config/llm-context-resolution.ts +8 -0
  134. package/src/config/llm-resolver.ts +30 -0
  135. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  136. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  137. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  138. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  139. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  140. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  141. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  142. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  143. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  144. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  145. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  146. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  147. package/src/config/schema.ts +2 -0
  148. package/src/config/schemas/call-site-catalog.ts +7 -0
  149. package/src/config/schemas/heartbeat.ts +4 -1
  150. package/src/config/schemas/llm.ts +33 -26
  151. package/src/config/schemas/memory-retrospective.ts +19 -0
  152. package/src/config/schemas/platform.ts +8 -0
  153. package/src/config/schemas/services.ts +5 -2
  154. package/src/config/schemas/workflows.ts +42 -0
  155. package/src/config/skills.ts +3 -3
  156. package/src/context/compactor.ts +273 -39
  157. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  158. package/src/context/tool-result-spool.ts +12 -17
  159. package/src/credential-execution/executable-discovery.ts +1 -1
  160. package/src/credential-execution/process-manager.ts +37 -3
  161. package/src/credential-execution/prompted-credential.ts +205 -0
  162. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  163. package/src/daemon/conversation-process.ts +11 -2
  164. package/src/daemon/conversation-surfaces.ts +75 -0
  165. package/src/daemon/conversation-tool-setup.ts +103 -26
  166. package/src/daemon/conversation-usage.ts +2 -0
  167. package/src/daemon/conversation.ts +115 -11
  168. package/src/daemon/handlers/shared.ts +26 -14
  169. package/src/daemon/host-cu-proxy.ts +15 -12
  170. package/src/daemon/host-file-proxy.ts +15 -12
  171. package/src/daemon/host-transfer-proxy.ts +30 -24
  172. package/src/daemon/lifecycle.ts +40 -3
  173. package/src/daemon/message-protocol.ts +3 -0
  174. package/src/daemon/message-types/messages.ts +2 -10
  175. package/src/daemon/message-types/workflows.ts +49 -0
  176. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  177. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  178. package/src/daemon/process-message.ts +6 -0
  179. package/src/daemon/tool-setup-types.ts +57 -0
  180. package/src/daemon/wake-conversation-ops.ts +18 -0
  181. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  182. package/src/home/feed-types.ts +1 -1
  183. package/src/ipc/gateway-flag-listener.ts +28 -6
  184. package/src/mcp/mcp-auth-state.ts +8 -20
  185. package/src/media/__tests__/image-models.test.ts +57 -0
  186. package/src/media/image-models.ts +66 -0
  187. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  188. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  189. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  190. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  191. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  192. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  193. package/src/memory/auto-analysis-enqueue.ts +14 -1
  194. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  195. package/src/memory/conversation-crud.ts +9 -2
  196. package/src/memory/conversation-disk-view.ts +1 -1
  197. package/src/memory/conversation-queries.ts +22 -7
  198. package/src/memory/db-init.ts +20 -0
  199. package/src/memory/db-maintenance.ts +16 -0
  200. package/src/memory/embedding-runtime-manager.ts +1 -1
  201. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  202. package/src/memory/llm-request-log-source-local.ts +19 -1
  203. package/src/memory/llm-request-log-source.ts +35 -6
  204. package/src/memory/llm-request-log-store.ts +90 -2
  205. package/src/memory/memory-retrospective-constants.ts +9 -0
  206. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  207. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  208. package/src/memory/memory-retrospective-job.ts +500 -208
  209. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  210. package/src/memory/memory-retrospective-state.ts +85 -2
  211. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  212. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  213. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  214. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  215. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  216. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  217. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  218. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  219. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  220. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  221. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  222. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  223. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  224. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  225. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  226. package/src/memory/migrations/index.ts +10 -0
  227. package/src/memory/preloaded-apps.ts +116 -0
  228. package/src/memory/schema/infrastructure.ts +4 -0
  229. package/src/memory/schema/memory-core.ts +4 -0
  230. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  231. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  232. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  233. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  234. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  235. package/src/memory/v2/page-store.ts +4 -3
  236. package/src/memory/v2/qdrant.ts +42 -3
  237. package/src/memory/v2/types.ts +16 -10
  238. package/src/messaging/draft-store.ts +1 -1
  239. package/src/notifications/access-request-copy.ts +200 -113
  240. package/src/notifications/adapters/slack.ts +250 -111
  241. package/src/notifications/adapters/telegram.ts +7 -44
  242. package/src/notifications/approval-card-builder.ts +93 -0
  243. package/src/notifications/broadcaster.ts +74 -0
  244. package/src/notifications/conversation-pairing.ts +8 -6
  245. package/src/notifications/copy-composer.ts +32 -26
  246. package/src/notifications/decision-engine.ts +59 -7
  247. package/src/notifications/guardian-question-mode.ts +145 -155
  248. package/src/notifications/home-feed-side-effect.ts +1 -1
  249. package/src/notifications/notification-utils.ts +66 -0
  250. package/src/notifications/signal.ts +6 -0
  251. package/src/notifications/tool-approval-copy.ts +142 -0
  252. package/src/notifications/types.ts +19 -0
  253. package/src/permissions/threshold.ts +11 -0
  254. package/src/plugin-api/types.ts +16 -4
  255. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  256. package/src/plugins/defaults/index.ts +46 -0
  257. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  258. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  259. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  260. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  261. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  262. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  263. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  264. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  265. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  269. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  270. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  271. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  272. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  273. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  274. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  275. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  276. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  277. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  278. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  279. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  280. package/src/prompts/normalize-onboarding.ts +2 -0
  281. package/src/prompts/persona-resolver.ts +3 -0
  282. package/src/prompts/system-prompt.ts +51 -2
  283. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  284. package/src/prompts/templates/system-sections.ts +8 -3
  285. package/src/providers/call-site-routing.ts +6 -3
  286. package/src/providers/fireworks/client.ts +3 -0
  287. package/src/providers/inference/auth.ts +52 -46
  288. package/src/providers/model-intents.ts +2 -2
  289. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  290. package/src/providers/openai/chat-completions-provider.ts +47 -9
  291. package/src/providers/openai/coerce-object-args.ts +104 -0
  292. package/src/providers/retry.ts +8 -5
  293. package/src/providers/types.ts +10 -0
  294. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  295. package/src/runtime/access-request-helper.ts +16 -9
  296. package/src/runtime/agent-wake.ts +302 -51
  297. package/src/runtime/assistant-stream-state.ts +141 -8
  298. package/src/runtime/background-job-runner.ts +9 -0
  299. package/src/runtime/channel-approval-types.ts +1 -0
  300. package/src/runtime/guardian-action-message-composer.ts +0 -54
  301. package/src/runtime/http-server.ts +6 -14
  302. package/src/runtime/http-types.ts +0 -1
  303. package/src/runtime/message-composer-types.ts +0 -9
  304. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  305. package/src/runtime/middleware/auth.ts +27 -3
  306. package/src/runtime/middleware/rate-limiter.ts +28 -1
  307. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  308. package/src/runtime/pending-interactions.ts +20 -1
  309. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  310. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  311. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  312. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  313. package/src/runtime/routes/approval-routes.ts +35 -8
  314. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  315. package/src/runtime/routes/btw-routes.ts +37 -1
  316. package/src/runtime/routes/channel-route-shared.ts +3 -1
  317. package/src/runtime/routes/consolidation-routes.ts +17 -13
  318. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  319. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  321. package/src/runtime/routes/conversation-routes.ts +15 -1
  322. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  323. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  324. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  325. package/src/runtime/routes/image-generation-routes.ts +20 -2
  326. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +26 -1
  327. package/src/runtime/routes/index.ts +6 -0
  328. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  329. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  330. package/src/runtime/routes/migration-routes.ts +5 -9
  331. package/src/runtime/routes/plugins-routes.ts +26 -0
  332. package/src/runtime/routes/ps-routes.ts +10 -8
  333. package/src/runtime/routes/retrospective-routes.ts +235 -0
  334. package/src/runtime/routes/runs-pagination.ts +75 -0
  335. package/src/runtime/routes/schedule-routes.ts +367 -59
  336. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  337. package/src/runtime/routes/surface-action-routes.ts +84 -4
  338. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  339. package/src/runtime/routes/workflow-routes.ts +363 -0
  340. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  341. package/src/runtime/routes/workspace-routes.ts +26 -1
  342. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  343. package/src/runtime/services/analyze-conversation.ts +26 -13
  344. package/src/schedule/inference-profile.ts +28 -0
  345. package/src/schedule/schedule-store.ts +122 -4
  346. package/src/schedule/scheduler-types.ts +6 -0
  347. package/src/schedule/scheduler.ts +96 -0
  348. package/src/security/secret-allowlist.ts +1 -1
  349. package/src/skills/path-classifier.ts +1 -1
  350. package/src/tools/browser/browser-execution.ts +9 -11
  351. package/src/tools/credentials/broker.ts +4 -4
  352. package/src/tools/credentials/vault.ts +26 -137
  353. package/src/tools/executor.ts +69 -0
  354. package/src/tools/flag-gated-tools.test.ts +76 -0
  355. package/src/tools/permission-checker.ts +8 -1
  356. package/src/tools/registry.ts +51 -0
  357. package/src/tools/schedule/create.ts +77 -1
  358. package/src/tools/schedule/list.ts +1 -0
  359. package/src/tools/schedule/update.ts +73 -1
  360. package/src/tools/skills/execute.ts +56 -0
  361. package/src/tools/terminal/shell.ts +1 -1
  362. package/src/tools/ui-surface/definitions.ts +48 -2
  363. package/src/tools/workflows/manage-workflows.ts +183 -0
  364. package/src/tools/workflows/run-workflow.test.ts +442 -0
  365. package/src/tools/workflows/run-workflow.ts +88 -0
  366. package/src/types/onboarding-context.ts +2 -0
  367. package/src/usage/attribution.ts +24 -0
  368. package/src/util/canonicalize-identity.ts +12 -3
  369. package/src/util/platform.ts +17 -17
  370. package/src/watcher/__tests__/engine.test.ts +24 -0
  371. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  372. package/src/watcher/engine.ts +7 -0
  373. package/src/watcher/telemetry.ts +74 -0
  374. package/src/workflows/capabilities.test.ts +365 -0
  375. package/src/workflows/capabilities.ts +359 -0
  376. package/src/workflows/deterministic-stringify.ts +27 -0
  377. package/src/workflows/engine-integration.test.ts +656 -0
  378. package/src/workflows/engine.test.ts +1144 -0
  379. package/src/workflows/engine.ts +1078 -0
  380. package/src/workflows/fanout-load.test.ts +168 -0
  381. package/src/workflows/journal-store.test.ts +369 -0
  382. package/src/workflows/journal-store.ts +470 -0
  383. package/src/workflows/leaf-runner.test.ts +704 -0
  384. package/src/workflows/leaf-runner.ts +589 -0
  385. package/src/workflows/library.test.ts +134 -0
  386. package/src/workflows/library.ts +124 -0
  387. package/src/workflows/run-manager.test.ts +711 -0
  388. package/src/workflows/run-manager.ts +593 -0
  389. package/src/workflows/sandbox-escape.test.ts +339 -0
  390. package/src/workflows/sandbox.test.ts +251 -0
  391. package/src/workflows/sandbox.ts +447 -0
  392. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  393. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  394. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  395. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  396. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  397. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  398. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  399. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  400. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  401. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  402. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  403. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  404. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  405. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  406. package/src/workspace/migrations/registry.ts +6 -0
  407. package/src/workspace/migrations/runner.ts +1 -1
  408. package/tsconfig.json +1 -1
  409. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  410. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  411. package/src/__tests__/guardian-action-store.test.ts +0 -106
  412. package/src/daemon/guardian-action-generators.ts +0 -71
  413. package/src/memory/guardian-action-store.ts +0 -484
  414. package/src/runtime/guardian-action-grant-minter.ts +0 -150
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Tests for the default `max-tokens-continue` plugin's hooks.
3
+ *
4
+ * Covers:
5
+ * - The `post-model-call` hook's decision for the canonical cases: a
6
+ * main-agent turn truncated at the output token limit → continue, with the
7
+ * truncated turn and the canonical continuation nudge appended to
8
+ * `messages`; any other stop reason → stop; non-main-agent call sites →
9
+ * stop; a provider rejection → stop; an entirely-stripped (empty) truncated
10
+ * turn → stop.
11
+ * - The per-run budget is split across the two hooks: `post-model-call`
12
+ * consumes one unit per continue (up to `MAX_TOKENS_AUTO_CONTINUES`) and
13
+ * the `stop` hook clears the counter on the definitive terminal so the next
14
+ * run starts with a full budget.
15
+ *
16
+ * The loop's actual side-effects (keeping the partial turn, per-run backstop,
17
+ * the continuation card on a terminal stop) live in `agent/loop.ts`. This
18
+ * file isolates the hook.
19
+ */
20
+
21
+ import { beforeEach, describe, expect, test } from "bun:test";
22
+
23
+ import type {
24
+ PluginLogger,
25
+ PostModelCallContext,
26
+ StopContext,
27
+ } from "../plugin-api/types.js";
28
+ import {
29
+ hasMaxTokensContinueBudget,
30
+ MAX_TOKENS_AUTO_CONTINUES,
31
+ resetMaxTokensContinueStoreForTests,
32
+ } from "../plugins/defaults/max-tokens-continue/continue-state-store.js";
33
+ import postModelCall, {
34
+ MAX_TOKENS_CONTINUE_NUDGE_TEXT,
35
+ } from "../plugins/defaults/max-tokens-continue/hooks/post-model-call.js";
36
+ import stop from "../plugins/defaults/max-tokens-continue/hooks/stop.js";
37
+ import type { ContentBlock } from "../providers/types.js";
38
+
39
+ // ─── Fixtures ────────────────────────────────────────────────────────────────
40
+
41
+ const noopLogger: PluginLogger = {
42
+ info: () => {},
43
+ warn: () => {},
44
+ error: () => {},
45
+ debug: () => {},
46
+ };
47
+
48
+ const truncatedText: ContentBlock = {
49
+ type: "text",
50
+ text: "Here is the start of a very long answer that got cut o",
51
+ };
52
+
53
+ function makeCtx(
54
+ overrides: Partial<PostModelCallContext> = {},
55
+ ): PostModelCallContext {
56
+ return {
57
+ conversationId: "conv-mtc",
58
+ callSite: "mainAgent",
59
+ content: [truncatedText],
60
+ messages: [{ role: "user", content: [{ type: "text", text: "build it" }] }],
61
+ stopReason: "max_tokens",
62
+ decision: "stop",
63
+ logger: noopLogger,
64
+ ...overrides,
65
+ };
66
+ }
67
+
68
+ beforeEach(() => {
69
+ resetMaxTokensContinueStoreForTests();
70
+ });
71
+
72
+ // ─── Decisions ───────────────────────────────────────────────────────────────
73
+
74
+ describe("max-tokens-continue post-model-call hook", () => {
75
+ test("truncated main-agent turn → continue with the turn and nudge appended", async () => {
76
+ /**
77
+ * Tests that a max_tokens stop on a main-agent turn auto-continues.
78
+ */
79
+ // GIVEN a main-agent turn that stopped at the output token limit.
80
+ const ctx = makeCtx();
81
+ const priorLength = ctx.messages.length;
82
+
83
+ // WHEN the hook runs.
84
+ await postModelCall(ctx);
85
+
86
+ // THEN it asks the loop to continue.
87
+ expect(ctx.decision).toBe("continue");
88
+ // AND it appends the truncated assistant turn followed by the nudge.
89
+ expect(ctx.messages).toHaveLength(priorLength + 2);
90
+ expect(ctx.messages[priorLength]).toEqual({
91
+ role: "assistant",
92
+ content: [truncatedText],
93
+ });
94
+ expect(ctx.messages[priorLength + 1]).toEqual({
95
+ role: "user",
96
+ content: [{ type: "text", text: MAX_TOKENS_CONTINUE_NUDGE_TEXT }],
97
+ });
98
+ });
99
+
100
+ test("non-max-tokens stop reason → stop", async () => {
101
+ /**
102
+ * Tests that ordinary stop reasons are left alone.
103
+ */
104
+ // GIVEN a turn that ended normally.
105
+ const ctx = makeCtx({ stopReason: "end_turn" });
106
+
107
+ // WHEN the hook runs.
108
+ await postModelCall(ctx);
109
+
110
+ // THEN the decision stays stop and messages are untouched.
111
+ expect(ctx.decision).toBe("stop");
112
+ expect(ctx.messages).toHaveLength(1);
113
+ });
114
+
115
+ test("non-mainAgent call site → stop", async () => {
116
+ /**
117
+ * Tests that background/subagent calls are never auto-continued.
118
+ */
119
+ // GIVEN a truncated turn on a non-main-agent call site.
120
+ const ctx = makeCtx({ callSite: "recall" });
121
+
122
+ // WHEN the hook runs.
123
+ await postModelCall(ctx);
124
+
125
+ // THEN the decision stays stop.
126
+ expect(ctx.decision).toBe("stop");
127
+ expect(ctx.messages).toHaveLength(1);
128
+ });
129
+
130
+ test("provider rejection → stop", async () => {
131
+ /**
132
+ * Tests that a rejection outcome (no reply content) is ignored.
133
+ */
134
+ // GIVEN a provider rejection carrying an error.
135
+ const ctx = makeCtx({ error: new Error("boom"), content: [] });
136
+
137
+ // WHEN the hook runs.
138
+ await postModelCall(ctx);
139
+
140
+ // THEN the decision stays stop.
141
+ expect(ctx.decision).toBe("stop");
142
+ });
143
+
144
+ test("empty truncated content → stop", async () => {
145
+ /**
146
+ * Tests that a turn with nothing left after truncation-block stripping
147
+ * ends terminally rather than pushing an empty assistant message.
148
+ */
149
+ // GIVEN a max_tokens stop whose safe content is empty.
150
+ const ctx = makeCtx({ content: [] });
151
+
152
+ // WHEN the hook runs.
153
+ await postModelCall(ctx);
154
+
155
+ // THEN the decision stays stop and nothing is appended.
156
+ expect(ctx.decision).toBe("stop");
157
+ expect(ctx.messages).toHaveLength(1);
158
+ });
159
+
160
+ test("budget exhausts after MAX_TOKENS_AUTO_CONTINUES and stop hook resets it", async () => {
161
+ /**
162
+ * Tests the per-run continue budget and its reset on the terminal stop.
163
+ */
164
+ // GIVEN the budget is consumed by repeated truncated turns.
165
+ for (let i = 0; i < MAX_TOKENS_AUTO_CONTINUES; i++) {
166
+ const ctx = makeCtx();
167
+ await postModelCall(ctx);
168
+ expect(ctx.decision).toBe("continue");
169
+ }
170
+ expect(hasMaxTokensContinueBudget("conv-mtc")).toBe(false);
171
+
172
+ // WHEN one more truncated turn arrives.
173
+ const exhausted = makeCtx();
174
+ await postModelCall(exhausted);
175
+
176
+ // THEN the hook lets the turn end.
177
+ expect(exhausted.decision).toBe("stop");
178
+ expect(exhausted.messages).toHaveLength(1);
179
+
180
+ // AND the stop hook clears the budget so the next run continues afresh.
181
+ await stop({ conversationId: "conv-mtc" } as StopContext);
182
+ expect(hasMaxTokensContinueBudget("conv-mtc")).toBe(true);
183
+ });
184
+ });
@@ -322,7 +322,13 @@ describe("image-studio skill script wrapper", () => {
322
322
  expect(result.isError).toBe(true);
323
323
  expect(result.content).toContain("Mock Gemini error: API failure");
324
324
  expect(result.content).toContain(
325
- "Do not change service configuration (mode, provider, or model) to try to fix it",
325
+ "Failed model: gemini-3.1-flash-image-preview",
326
+ );
327
+ expect(result.content).toContain(
328
+ "Do not change service configuration (managed/your-own mode or default provider/model settings)",
329
+ );
330
+ expect(result.content).toContain(
331
+ "Retrying this call once with a different model parameter is allowed",
326
332
  );
327
333
  });
328
334
 
@@ -335,7 +341,10 @@ describe("image-studio skill script wrapper", () => {
335
341
  expect(result.isError).toBe(true);
336
342
  expect(result.content).toContain("Mock OpenAI error: openai failure");
337
343
  expect(result.content).toContain(
338
- "Do not change service configuration (mode, provider, or model) to try to fix it",
344
+ "Do not change service configuration (managed/your-own mode or default provider/model settings)",
345
+ );
346
+ expect(result.content).toContain(
347
+ "Retrying this call once with a different model parameter is allowed",
339
348
  );
340
349
  });
341
350
 
@@ -346,8 +355,9 @@ describe("image-studio skill script wrapper", () => {
346
355
 
347
356
  expect(result.isError).toBe(true);
348
357
  expect(result.content).toContain("No Gemini API key");
358
+ expect(result.content).toContain("Report this error to the user as-is");
349
359
  expect(result.content).toContain(
350
- "Do not change service configuration (mode, provider, or model) to try to fix it",
360
+ "Do not change service configuration (managed/your-own mode or default provider/model settings)",
351
361
  );
352
362
  });
353
363
 
@@ -359,8 +369,9 @@ describe("image-studio skill script wrapper", () => {
359
369
 
360
370
  expect(result.isError).toBe(true);
361
371
  expect(result.content).toContain("Managed proxy is not available");
372
+ expect(result.content).toContain("Report this error to the user as-is");
362
373
  expect(result.content).toContain(
363
- "Do not change service configuration (mode, provider, or model) to try to fix it",
374
+ "Do not change service configuration (managed/your-own mode or default provider/model settings)",
364
375
  );
365
376
  });
366
377
 
@@ -463,11 +474,11 @@ describe("image-studio TOOLS.json manifest", () => {
463
474
  expect(props.mode.enum).toEqual(["generate", "edit"]);
464
475
  expect(props.source_paths.type).toBe("array");
465
476
  expect(props.attachment_ids).toBeUndefined();
466
- expect(props.model.enum).toEqual([
467
- "gemini-3.1-flash-image-preview",
468
- "gemini-3-pro-image-preview",
469
- "gpt-image-2",
470
- ]);
477
+ // No enum by design: model accepts tier aliases or concrete IDs, and is
478
+ // validated at runtime against the registry so the schema never goes
479
+ // stale when models change.
480
+ expect(props.model.type).toBe("string");
481
+ expect(props.model.enum).toBeUndefined();
471
482
  expect(props.variants.type).toBe("number");
472
483
  });
473
484
  });
@@ -20,7 +20,7 @@ describe("model intents", () => {
20
20
  "claude-haiku-4-5-20251001",
21
21
  );
22
22
  expect(resolveModelIntent("anthropic", "quality-optimized")).toBe(
23
- "claude-fable-5",
23
+ "claude-opus-4-8",
24
24
  );
25
25
  expect(resolveModelIntent("anthropic", "vision-optimized")).toBe(
26
26
  "claude-opus-4-6",
@@ -139,6 +139,32 @@ describe("normalizeOnboardingContext", () => {
139
139
  expect(result.preferredName).toBeUndefined();
140
140
  });
141
141
 
142
+ test("maps occupation through, trimmed", () => {
143
+ const ctx: OnboardingContext = {
144
+ tools: [],
145
+ tasks: [],
146
+ tone: "friendly",
147
+ occupation: " Software Engineer ",
148
+ };
149
+ const result = normalizeOnboardingContext(ctx);
150
+ expect(result.occupation).toBe("Software Engineer");
151
+ });
152
+
153
+ test("absent or blank occupation yields undefined", () => {
154
+ expect(
155
+ normalizeOnboardingContext({ tools: [], tasks: [], tone: "friendly" })
156
+ .occupation,
157
+ ).toBeUndefined();
158
+ expect(
159
+ normalizeOnboardingContext({
160
+ tools: [],
161
+ tasks: [],
162
+ tone: "friendly",
163
+ occupation: " ",
164
+ }).occupation,
165
+ ).toBeUndefined();
166
+ });
167
+
142
168
  test("tone passes through", () => {
143
169
  const ctx: OnboardingContext = {
144
170
  tools: [],
@@ -15,8 +15,6 @@ import {
15
15
  hasAccessRequestInstructions,
16
16
  hasInviteFlowDirective,
17
17
  normalizeForDirectiveMatching,
18
- sanitizeIdentityField,
19
- sanitizeMessagePreview,
20
18
  } from "../notifications/access-request-copy.js";
21
19
  import type { ConversationCandidateSet } from "../notifications/conversation-candidates.js";
22
20
  import { composeFallbackCopy } from "../notifications/copy-composer.js";
@@ -24,6 +22,10 @@ import {
24
22
  enforceGuardianCallConversationAffinity,
25
23
  validateConversationActions,
26
24
  } from "../notifications/decision-engine.js";
25
+ import {
26
+ sanitizeIdentityField,
27
+ sanitizeMessagePreview,
28
+ } from "../notifications/notification-utils.js";
27
29
  import type { NotificationSignal } from "../notifications/signal.js";
28
30
  import type {
29
31
  NotificationChannel,
@@ -186,9 +186,18 @@ describe("TelegramAdapter", () => {
186
186
  contextPayload: {
187
187
  requestId: "req-abc-123",
188
188
  requestCode: "XYZW",
189
- senderIdentifier: "Marina",
189
+ senderIdentifier: "TestUser",
190
190
  sourceChannel: "telegram",
191
191
  },
192
+ approvalContext: {
193
+ requestId: "req-abc-123",
194
+ actions: [
195
+ { id: "approve_once", label: "Approve once" },
196
+ { id: "reject", label: "Reject" },
197
+ ],
198
+ plainTextFallback:
199
+ 'TestUser is requesting access to the assistant.\nReply "XYZW approve" to grant access or "XYZW reject" to deny.\nReply "open invite flow" to start Trusted Contacts invite flow.',
200
+ },
192
201
  });
193
202
 
194
203
  const result = await adapter.send(payload, makeDestination());
@@ -237,7 +246,7 @@ describe("TelegramAdapter", () => {
237
246
  body: "Someone is requesting access.",
238
247
  },
239
248
  contextPayload: {
240
- senderIdentifier: "Marina",
249
+ senderIdentifier: "TestUser",
241
250
  sourceChannel: "telegram",
242
251
  // no requestId
243
252
  },
@@ -264,9 +273,18 @@ describe("TelegramAdapter", () => {
264
273
  contextPayload: {
265
274
  requestId: "req-abc-123",
266
275
  requestCode: "XYZW",
267
- senderIdentifier: "Marina",
276
+ senderIdentifier: "TestUser",
268
277
  sourceChannel: "telegram",
269
278
  },
279
+ approvalContext: {
280
+ requestId: "req-abc-123",
281
+ actions: [
282
+ { id: "approve_once", label: "Approve once" },
283
+ { id: "reject", label: "Reject" },
284
+ ],
285
+ plainTextFallback:
286
+ 'TestUser is requesting access to the assistant.\nReply "XYZW approve" to grant access or "XYZW reject" to deny.\nReply "open invite flow" to start Trusted Contacts invite flow.',
287
+ },
270
288
  });
271
289
 
272
290
  const result = await adapter.send(payload, makeDestination());
@@ -102,6 +102,28 @@ describe("pendingInteractions.resolve emits interaction_resolved", () => {
102
102
  expect(publishedMessages).toHaveLength(0);
103
103
  });
104
104
 
105
+ test("no event is emitted for a conversation-less interaction", () => {
106
+ /**
107
+ * Conversation-less interactions (e.g. the CLI `credentials prompt`
108
+ * command) resolve through their own resolver rather than a conversation.
109
+ * The `interaction_resolved` envelope requires a conversationId, so the
110
+ * tracker skips the broadcast instead of emitting an invalid event.
111
+ */
112
+ // GIVEN a registered interaction with no owning conversation
113
+ pendingInteractions.register("req-detached", { kind: "secret" });
114
+
115
+ // WHEN it is resolved
116
+ const returned = pendingInteractions.resolve("req-detached", "answered");
117
+
118
+ // THEN the entry is still returned to its caller
119
+ expect(returned).toBeDefined();
120
+
121
+ // AND no interaction_resolved envelope is published
122
+ expect(
123
+ publishedMessages.filter((m) => m.type === "interaction_resolved"),
124
+ ).toHaveLength(0);
125
+ });
126
+
105
127
  test("a single resolve emits exactly one event", () => {
106
128
  pendingInteractions.register("req-once", {
107
129
  conversationId: "conv-e",
@@ -186,4 +208,44 @@ describe("removeByConversation emits interaction_resolved per entry", () => {
186
208
  .state,
187
209
  ).toBe("cancelled");
188
210
  });
211
+
212
+ test("settles a swept secret prompt's resolver with a cancelled result", () => {
213
+ /**
214
+ * A secret prompt blocks its caller (the CLI `credentials prompt` command
215
+ * or the in-conversation SecretPrompter) on `rpcResolve`. Unlike questions
216
+ * (abort-signal teardown) and confirmations (denyAllPendingConfirmations),
217
+ * nothing else settles a secret when it is superseded, so removing the
218
+ * entry alone would hang the caller until its IPC client times out.
219
+ */
220
+ // GIVEN a pending secret whose caller is blocked on rpcResolve
221
+ const resolved: unknown[] = [];
222
+ pendingInteractions.register("secret-sweep", {
223
+ conversationId: "conv-sweep",
224
+ kind: "secret",
225
+ rpcResolve: (value) => resolved.push(value),
226
+ });
227
+
228
+ // WHEN a new user message supersedes the conversation's interactions
229
+ pendingInteractions.removeByConversation("conv-sweep");
230
+
231
+ // THEN the secret resolver is settled once with a cancelled result
232
+ expect(resolved).toEqual([{ value: null, delivery: "store" }]);
233
+ });
234
+
235
+ test("does not invoke a swept confirmation's resolver with a secret result", () => {
236
+ // GIVEN a pending confirmation carrying an rpcResolve callback
237
+ const resolved: unknown[] = [];
238
+ pendingInteractions.register("conf-sweep", {
239
+ conversationId: "conv-conf",
240
+ kind: "confirmation",
241
+ rpcResolve: (value) => resolved.push(value),
242
+ });
243
+
244
+ // WHEN the conversation is superseded
245
+ pendingInteractions.removeByConversation("conv-conf");
246
+
247
+ // THEN the confirmation resolver is left untouched — only secret prompts
248
+ // are settled with a SecretPromptResult-shaped value here.
249
+ expect(resolved).toHaveLength(0);
250
+ });
189
251
  });
@@ -49,8 +49,10 @@ describe("postTurnTruncateToolResults", () => {
49
49
  const shortContent = "a".repeat(THRESHOLD_CHARS);
50
50
  const messages = makeMessages([makeToolResult(shortContent)]);
51
51
 
52
- const { messages: result, truncatedCount } =
53
- postTurnTruncateToolResults(messages, { conversationDir: convDir });
52
+ const { messages: result, truncatedCount } = postTurnTruncateToolResults(
53
+ messages,
54
+ { conversationDir: convDir },
55
+ );
54
56
 
55
57
  expect(truncatedCount).toBe(0);
56
58
  expect(result).toBe(messages); // same reference — no copy
@@ -62,12 +64,17 @@ describe("postTurnTruncateToolResults", () => {
62
64
  const toolUseId = "tool_use_abc";
63
65
  const messages = makeMessages([makeToolResult(longContent, toolUseId)]);
64
66
 
65
- const { messages: result, truncatedCount } =
66
- postTurnTruncateToolResults(messages, { conversationDir: convDir });
67
+ const { messages: result, truncatedCount } = postTurnTruncateToolResults(
68
+ messages,
69
+ { conversationDir: convDir },
70
+ );
67
71
 
68
72
  expect(truncatedCount).toBe(1);
69
73
 
70
- const block = result[0].content[0] as { type: "tool_result"; content: string };
74
+ const block = result[0].content[0] as {
75
+ type: "tool_result";
76
+ content: string;
77
+ };
71
78
  expect(block.content).toContain(TRUNCATION_MARKER);
72
79
  expect(block.content.length).toBeLessThan(longContent.length);
73
80
 
@@ -83,8 +90,10 @@ describe("postTurnTruncateToolResults", () => {
83
90
  makeToolResult(longContent, "tool_err", true),
84
91
  ]);
85
92
 
86
- const { messages: result, truncatedCount } =
87
- postTurnTruncateToolResults(messages, { conversationDir: convDir });
93
+ const { messages: result, truncatedCount } = postTurnTruncateToolResults(
94
+ messages,
95
+ { conversationDir: convDir },
96
+ );
88
97
 
89
98
  expect(truncatedCount).toBe(0);
90
99
  expect(result).toBe(messages);
@@ -100,8 +109,10 @@ describe("postTurnTruncateToolResults", () => {
100
109
  makeToolResult(alreadyTruncated, "tool_idempotent"),
101
110
  ]);
102
111
 
103
- const { messages: result, truncatedCount } =
104
- postTurnTruncateToolResults(messages, { conversationDir: convDir });
112
+ const { messages: result, truncatedCount } = postTurnTruncateToolResults(
113
+ messages,
114
+ { conversationDir: convDir },
115
+ );
105
116
 
106
117
  expect(truncatedCount).toBe(0);
107
118
  expect(result).toBe(messages);
@@ -117,8 +128,10 @@ describe("postTurnTruncateToolResults", () => {
117
128
  makeToolResult(long2, "tool_long2"),
118
129
  ]);
119
130
 
120
- const { messages: result, truncatedCount } =
121
- postTurnTruncateToolResults(messages, { conversationDir: convDir });
131
+ const { messages: result, truncatedCount } = postTurnTruncateToolResults(
132
+ messages,
133
+ { conversationDir: convDir },
134
+ );
122
135
 
123
136
  expect(truncatedCount).toBe(2);
124
137
 
@@ -174,8 +187,10 @@ describe("postTurnTruncateToolResults", () => {
174
187
  { role: "user", content: [makeToolResult(skillBody, toolUseId)] },
175
188
  ];
176
189
 
177
- const { messages: result, truncatedCount } =
178
- postTurnTruncateToolResults(messages, { conversationDir: convDir });
190
+ const { messages: result, truncatedCount } = postTurnTruncateToolResults(
191
+ messages,
192
+ { conversationDir: convDir },
193
+ );
179
194
 
180
195
  expect(truncatedCount).toBe(0);
181
196
  expect(result).toBe(messages); // same reference — no copy
@@ -209,8 +224,10 @@ describe("postTurnTruncateToolResults", () => {
209
224
  { role: "user", content: [makeToolResult(longContent, toolUseId)] },
210
225
  ];
211
226
 
212
- const { messages: result, truncatedCount } =
213
- postTurnTruncateToolResults(messages, { conversationDir: convDir });
227
+ const { messages: result, truncatedCount } = postTurnTruncateToolResults(
228
+ messages,
229
+ { conversationDir: convDir },
230
+ );
214
231
 
215
232
  expect(truncatedCount).toBe(1);
216
233
  const block = result[1].content[0] as {
@@ -262,7 +279,38 @@ describe("derefToolResultReReads", () => {
262
279
  derefToolResultReReads(messages);
263
280
 
264
281
  expect(dereferencedCount).toBe(1);
265
- const block = result[1].content[0] as { type: "tool_result"; content: string };
282
+ const block = result[1].content[0] as {
283
+ type: "tool_result";
284
+ content: string;
285
+ };
286
+ expect(block.content).toBe(REREAD_STUB);
287
+ });
288
+
289
+ test("host_file_read of .tool-results/ path: tool_result content replaced with REREAD_STUB", () => {
290
+ const toolUseId = "tu_host_reread";
291
+ const messages: Message[] = [
292
+ {
293
+ role: "assistant",
294
+ content: [
295
+ makeToolUse(toolUseId, "host_file_read", {
296
+ path: `/home/user/.local/share/vellum/conversations/abc/${TOOL_RESULT_DIR}/abc123.txt`,
297
+ }),
298
+ ],
299
+ },
300
+ {
301
+ role: "user",
302
+ content: [makeToolResult("full host file contents here", toolUseId)],
303
+ },
304
+ ];
305
+
306
+ const { messages: result, dereferencedCount } =
307
+ derefToolResultReReads(messages);
308
+
309
+ expect(dereferencedCount).toBe(1);
310
+ const block = result[1].content[0] as {
311
+ type: "tool_result";
312
+ content: string;
313
+ };
266
314
  expect(block.content).toBe(REREAD_STUB);
267
315
  });
268
316
 
@@ -289,7 +337,10 @@ describe("derefToolResultReReads", () => {
289
337
 
290
338
  expect(dereferencedCount).toBe(0);
291
339
  expect(result).toBe(messages); // same reference — no copy
292
- const block = result[1].content[0] as { type: "tool_result"; content: string };
340
+ const block = result[1].content[0] as {
341
+ type: "tool_result";
342
+ content: string;
343
+ };
293
344
  expect(block.content).toBe(originalContent);
294
345
  });
295
346
 
@@ -316,7 +367,10 @@ describe("derefToolResultReReads", () => {
316
367
 
317
368
  expect(dereferencedCount).toBe(0);
318
369
  expect(result).toBe(messages);
319
- const block = result[1].content[0] as { type: "tool_result"; content: string };
370
+ const block = result[1].content[0] as {
371
+ type: "tool_result";
372
+ content: string;
373
+ };
320
374
  expect(block.content).toBe(outputMentioningDir);
321
375
  });
322
376