@vellumai/assistant 0.4.56 → 0.5.0

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 (457) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +3 -0
  3. package/README.md +11 -11
  4. package/docs/architecture/integrations.md +2 -2
  5. package/docs/architecture/memory.md +3 -4
  6. package/docs/credential-execution-service.md +13 -20
  7. package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +7 -7
  10. package/src/__tests__/anthropic-provider.test.ts +172 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
  12. package/src/__tests__/approval-cascade.test.ts +2 -2
  13. package/src/__tests__/approval-routes-http.test.ts +3 -4
  14. package/src/__tests__/asset-materialize-tool.test.ts +5 -5
  15. package/src/__tests__/asset-search-tool.test.ts +1 -1
  16. package/src/__tests__/assistant-attachments.test.ts +5 -5
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
  19. package/src/__tests__/attachments-store.test.ts +2 -2
  20. package/src/__tests__/avatar-e2e.test.ts +5 -3
  21. package/src/__tests__/browser-skill-endstate.test.ts +0 -1
  22. package/src/__tests__/call-routes-http.test.ts +2 -2
  23. package/src/__tests__/callback-handoff-copy.test.ts +1 -1
  24. package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
  25. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  26. package/src/__tests__/channel-readiness-service.test.ts +0 -1
  27. package/src/__tests__/checker.test.ts +31 -32
  28. package/src/__tests__/chrome-cdp.test.ts +47 -18
  29. package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
  30. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  31. package/src/__tests__/config-schema.test.ts +9 -18
  32. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
  33. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
  34. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
  35. package/src/__tests__/conversation-agent-loop.test.ts +11 -4
  36. package/src/__tests__/conversation-attachments.test.ts +1 -1
  37. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
  38. package/src/__tests__/conversation-error.test.ts +33 -0
  39. package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
  40. package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
  41. package/src/__tests__/conversation-pairing.test.ts +1 -1
  42. package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
  43. package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
  44. package/src/__tests__/conversation-queue.test.ts +23 -14
  45. package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
  46. package/src/__tests__/conversation-runtime-assembly.test.ts +204 -185
  47. package/src/__tests__/conversation-seed-composer.test.ts +1 -1
  48. package/src/__tests__/conversation-slash-queue.test.ts +4 -4
  49. package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
  50. package/src/__tests__/conversation-starter-routes.test.ts +291 -0
  51. package/src/__tests__/conversation-wipe.test.ts +438 -0
  52. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
  53. package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
  54. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
  55. package/src/__tests__/credential-security-e2e.test.ts +20 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault-unit.test.ts +227 -0
  58. package/src/__tests__/credentials-cli.test.ts +3 -0
  59. package/src/__tests__/date-context.test.ts +59 -377
  60. package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
  61. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
  62. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
  63. package/src/__tests__/encrypted-store.test.ts +249 -15
  64. package/src/__tests__/ephemeral-permissions.test.ts +4 -5
  65. package/src/__tests__/event-bus.test.ts +3 -3
  66. package/src/__tests__/file-read-tool.test.ts +40 -0
  67. package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
  68. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  69. package/src/__tests__/gemini-image-service.test.ts +4 -4
  70. package/src/__tests__/gemini-provider.test.ts +6 -9
  71. package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
  72. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  73. package/src/__tests__/host-file-read-tool.test.ts +87 -0
  74. package/src/__tests__/host-shell-tool.test.ts +6 -6
  75. package/src/__tests__/http-user-message-parity.test.ts +2 -2
  76. package/src/__tests__/identity-intro-cache.test.ts +209 -0
  77. package/src/__tests__/intent-routing.test.ts +51 -99
  78. package/src/__tests__/invite-routes-http.test.ts +5 -0
  79. package/src/__tests__/list-messages-attachments.test.ts +1 -1
  80. package/src/__tests__/managed-proxy-context.test.ts +2 -5
  81. package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
  82. package/src/__tests__/media-generate-image.test.ts +32 -15
  83. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
  84. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
  86. package/src/__tests__/memory-recall-quality.test.ts +4 -3
  87. package/src/__tests__/memory-regressions.test.ts +86 -90
  88. package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
  89. package/src/__tests__/migration-export-http.test.ts +26 -27
  90. package/src/__tests__/migration-import-commit-http.test.ts +165 -37
  91. package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
  92. package/src/__tests__/migration-validate-http.test.ts +16 -16
  93. package/src/__tests__/model-intents.test.ts +2 -2
  94. package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
  95. package/src/__tests__/non-member-access-request.test.ts +3 -3
  96. package/src/__tests__/notification-broadcaster.test.ts +1 -1
  97. package/src/__tests__/notification-decision-fallback.test.ts +2 -2
  98. package/src/__tests__/notification-decision-identity.test.ts +8 -9
  99. package/src/__tests__/notification-decision-strategy.test.ts +1 -1
  100. package/src/__tests__/notification-deep-link.test.ts +1 -1
  101. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  102. package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
  103. package/src/__tests__/oauth-store.test.ts +1 -3
  104. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
  105. package/src/__tests__/onboarding-template-contract.test.ts +23 -59
  106. package/src/__tests__/provider-error-scenarios.test.ts +154 -0
  107. package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
  108. package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
  109. package/src/__tests__/provider-registry-ollama.test.ts +5 -2
  110. package/src/__tests__/qdrant-manager.test.ts +7 -7
  111. package/src/__tests__/ratelimit.test.ts +0 -74
  112. package/src/__tests__/recording-handler.test.ts +0 -1
  113. package/src/__tests__/require-fresh-approval.test.ts +1 -1
  114. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  115. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
  116. package/src/__tests__/runtime-events-sse.test.ts +1 -1
  117. package/src/__tests__/scheduler-recurrence.test.ts +46 -2
  118. package/src/__tests__/schema-transforms.test.ts +114 -54
  119. package/src/__tests__/secret-onetime-send.test.ts +20 -0
  120. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
  121. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  122. package/src/__tests__/send-endpoint-busy.test.ts +63 -4
  123. package/src/__tests__/send-notification-tool.test.ts +2 -2
  124. package/src/__tests__/shell-credential-ref.test.ts +0 -1
  125. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
  126. package/src/__tests__/skill-memory.test.ts +549 -0
  127. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
  128. package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
  129. package/src/__tests__/slack-channel-config.test.ts +109 -94
  130. package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
  131. package/src/__tests__/swarm-recursion.test.ts +2 -2
  132. package/src/__tests__/swarm-tool.test.ts +2 -2
  133. package/src/__tests__/system-prompt.test.ts +19 -66
  134. package/src/__tests__/telegram-config.test.ts +121 -0
  135. package/src/__tests__/terminal-tools.test.ts +1 -1
  136. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  138. package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
  139. package/src/__tests__/tool-executor.test.ts +1 -1
  140. package/src/__tests__/trace-emitter.test.ts +8 -1
  141. package/src/__tests__/trust-store.test.ts +7 -8
  142. package/src/__tests__/twilio-routes.test.ts +1 -18
  143. package/src/__tests__/user-reference.test.ts +82 -2
  144. package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
  145. package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
  146. package/src/approvals/guardian-request-resolvers.ts +3 -3
  147. package/src/avatar/ascii-renderer.ts +2 -2
  148. package/src/avatar/png-renderer.ts +2 -2
  149. package/src/avatar/resvg-lazy.ts +21 -0
  150. package/src/calls/guardian-dispatch.ts +1 -1
  151. package/src/calls/relay-access-wait.ts +2 -2
  152. package/src/calls/twilio-rest.ts +0 -248
  153. package/src/cli/AGENTS.md +5 -8
  154. package/src/cli/__tests__/notifications.test.ts +5 -5
  155. package/src/cli/commands/avatar.ts +64 -2
  156. package/src/cli/commands/conversations.ts +131 -1
  157. package/src/cli/commands/credentials.ts +2 -0
  158. package/src/cli/commands/notifications.ts +3 -3
  159. package/src/cli.ts +10 -0
  160. package/src/config/bundled-skills/acp/SKILL.md +5 -5
  161. package/src/config/bundled-skills/acp/TOOLS.json +6 -6
  162. package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
  163. package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
  164. package/src/config/bundled-skills/browser/SKILL.md +15 -15
  165. package/src/config/bundled-skills/browser/TOOLS.json +14 -14
  166. package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
  167. package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
  168. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  169. package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
  170. package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
  171. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
  172. package/src/config/bundled-skills/contacts/SKILL.md +3 -3
  173. package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
  174. package/src/config/bundled-skills/document/SKILL.md +4 -4
  175. package/src/config/bundled-skills/document/TOOLS.json +2 -2
  176. package/src/config/bundled-skills/followups/TOOLS.json +3 -3
  177. package/src/config/bundled-skills/gmail/SKILL.md +32 -32
  178. package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
  179. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
  180. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  181. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
  182. package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
  183. package/src/config/bundled-skills/google-calendar/types.ts +1 -1
  184. package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
  185. package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
  186. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
  187. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
  188. package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
  189. package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
  190. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
  191. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
  192. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
  193. package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
  194. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
  195. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
  196. package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
  197. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  198. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
  199. package/src/config/bundled-skills/messaging/SKILL.md +29 -25
  200. package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
  201. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  202. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  203. package/src/config/bundled-skills/notifications/SKILL.md +3 -3
  204. package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
  205. package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
  206. package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
  207. package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
  208. package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
  209. package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
  210. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
  211. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
  212. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
  213. package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
  214. package/src/config/bundled-skills/schedule/SKILL.md +26 -26
  215. package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
  216. package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
  217. package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
  218. package/src/config/bundled-skills/sequences/SKILL.md +2 -2
  219. package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
  220. package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
  221. package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
  222. package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
  223. package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
  224. package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
  225. package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
  226. package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
  227. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  228. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
  229. package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
  230. package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
  231. package/src/config/bundled-skills/slack/SKILL.md +2 -2
  232. package/src/config/bundled-skills/slack/TOOLS.json +8 -8
  233. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
  234. package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
  235. package/src/config/bundled-skills/tasks/SKILL.md +1 -1
  236. package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
  237. package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
  238. package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
  240. package/src/config/bundled-skills/watcher/SKILL.md +4 -4
  241. package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
  242. package/src/config/feature-flag-registry.json +33 -17
  243. package/src/config/schemas/sandbox.ts +1 -1
  244. package/src/config/schemas/services.ts +13 -3
  245. package/src/config/schemas/timeouts.ts +0 -10
  246. package/src/contacts/contact-store.ts +63 -0
  247. package/src/contacts/contacts-write.ts +1 -1
  248. package/src/daemon/assistant-attachments.ts +2 -2
  249. package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
  250. package/src/daemon/conversation-agent-loop.ts +7 -30
  251. package/src/daemon/conversation-error.ts +24 -0
  252. package/src/daemon/conversation-memory.ts +8 -7
  253. package/src/daemon/conversation-runtime-assembly.ts +141 -275
  254. package/src/daemon/conversation-slash.ts +7 -26
  255. package/src/daemon/conversation-surfaces.ts +14 -0
  256. package/src/daemon/conversation-tool-setup.ts +9 -8
  257. package/src/daemon/conversation.ts +2 -0
  258. package/src/daemon/daemon-control.ts +1 -1
  259. package/src/daemon/date-context.ts +10 -83
  260. package/src/daemon/handlers/config-channels.ts +12 -2
  261. package/src/daemon/handlers/config-slack-channel.ts +7 -1
  262. package/src/daemon/handlers/config-telegram.ts +6 -1
  263. package/src/daemon/handlers/conversations.ts +2 -2
  264. package/src/daemon/handlers/skills.ts +4 -0
  265. package/src/daemon/lifecycle.ts +28 -4
  266. package/src/daemon/providers-setup.ts +1 -1
  267. package/src/daemon/server.ts +1 -5
  268. package/src/daemon/shutdown-handlers.ts +9 -3
  269. package/src/daemon/tool-side-effects.ts +40 -0
  270. package/src/daemon/trace-emitter.ts +26 -2
  271. package/src/events/domain-events.ts +1 -1
  272. package/src/events/tool-permission-telemetry-listener.ts +46 -0
  273. package/src/inbound/platform-callback-registration.ts +0 -18
  274. package/src/media/app-icon-generator.ts +15 -8
  275. package/src/media/avatar-router.ts +15 -8
  276. package/src/media/gemini-image-service.ts +125 -21
  277. package/src/memory/attachments-store.ts +3 -3
  278. package/src/memory/channel-verification-sessions.ts +6 -6
  279. package/src/memory/conversation-crud.ts +196 -1
  280. package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
  281. package/src/memory/conversation-title-service.ts +2 -3
  282. package/src/memory/db-init.ts +25 -1
  283. package/src/memory/invite-store.ts +4 -4
  284. package/src/memory/items-extractor.ts +4 -4
  285. package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
  286. package/src/memory/jobs-store.ts +3 -2
  287. package/src/memory/jobs-worker.ts +7 -5
  288. package/src/memory/lifecycle-events-store.ts +63 -0
  289. package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
  290. package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
  291. package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
  292. package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
  293. package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
  294. package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
  295. package/src/memory/migrations/index.ts +6 -0
  296. package/src/memory/migrations/registry.ts +13 -0
  297. package/src/memory/retriever.test.ts +223 -96
  298. package/src/memory/retriever.ts +115 -138
  299. package/src/memory/schema/calls.ts +1 -1
  300. package/src/memory/schema/contacts.ts +1 -1
  301. package/src/memory/schema/infrastructure.ts +29 -0
  302. package/src/memory/schema/memory-core.ts +7 -17
  303. package/src/memory/schema/notifications.ts +1 -1
  304. package/src/memory/search/formatting.ts +23 -6
  305. package/src/memory/search/lexical.ts +2 -0
  306. package/src/memory/search/semantic.ts +2 -0
  307. package/src/memory/search/staleness.ts +5 -1
  308. package/src/memory/search/types.ts +4 -0
  309. package/src/memory/task-memory-cleanup.ts +96 -6
  310. package/src/memory/trace-event-store.ts +148 -0
  311. package/src/notifications/README.md +1 -1
  312. package/src/notifications/decision-engine.ts +45 -4
  313. package/src/notifications/emit-signal.ts +5 -4
  314. package/src/notifications/events-store.ts +4 -4
  315. package/src/notifications/signal.ts +1 -1
  316. package/src/oauth/manual-token-connection.ts +49 -25
  317. package/src/permissions/checker.ts +6 -5
  318. package/src/permissions/defaults.ts +4 -4
  319. package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
  320. package/src/prompts/cache-boundary.ts +8 -0
  321. package/src/prompts/system-prompt.ts +105 -634
  322. package/src/prompts/templates/BOOTSTRAP.md +172 -33
  323. package/src/prompts/templates/IDENTITY.md +8 -24
  324. package/src/prompts/templates/SOUL.md +20 -41
  325. package/src/prompts/templates/USER.md +3 -19
  326. package/src/prompts/user-reference.ts +14 -16
  327. package/src/providers/anthropic/client.ts +51 -19
  328. package/src/providers/gemini/client.ts +6 -9
  329. package/src/providers/managed-proxy/constants.ts +1 -7
  330. package/src/providers/managed-proxy/context.ts +0 -1
  331. package/src/providers/model-intents.ts +5 -5
  332. package/src/providers/openai/client.ts +10 -1
  333. package/src/providers/openrouter/client.ts +1 -0
  334. package/src/providers/ratelimit.ts +0 -35
  335. package/src/providers/registry.ts +3 -5
  336. package/src/providers/retry.ts +18 -1
  337. package/src/runtime/access-request-helper.ts +16 -2
  338. package/src/runtime/auth/route-policy.ts +7 -0
  339. package/src/runtime/channel-verification-service.ts +1 -1
  340. package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
  341. package/src/runtime/guardian-vellum-migration.ts +61 -1
  342. package/src/runtime/http-server.ts +8 -4
  343. package/src/runtime/migrations/vbundle-builder.ts +212 -32
  344. package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
  345. package/src/runtime/migrations/vbundle-importer.ts +66 -1
  346. package/src/runtime/migrations/vbundle-validator.ts +17 -3
  347. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
  348. package/src/runtime/routes/attachment-routes.ts +2 -2
  349. package/src/runtime/routes/btw-routes.ts +93 -0
  350. package/src/runtime/routes/channel-verification-routes.ts +19 -2
  351. package/src/runtime/routes/conversation-management-routes.ts +55 -1
  352. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-routes.ts +49 -5
  354. package/src/runtime/routes/conversation-starter-routes.ts +207 -0
  355. package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
  356. package/src/runtime/routes/identity-intro-cache.ts +105 -0
  357. package/src/runtime/routes/identity-routes.ts +51 -0
  358. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
  359. package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
  360. package/src/runtime/routes/migration-routes.ts +25 -13
  361. package/src/runtime/routes/secret-routes.ts +18 -0
  362. package/src/runtime/routes/settings-routes.ts +9 -9
  363. package/src/runtime/routes/telemetry-routes.ts +53 -0
  364. package/src/runtime/routes/trace-event-routes.ts +62 -0
  365. package/src/runtime/tool-grant-request-helper.ts +1 -1
  366. package/src/runtime/verification-outbound-actions.ts +47 -31
  367. package/src/security/encrypted-store.ts +262 -78
  368. package/src/skills/catalog-install.ts +10 -0
  369. package/src/skills/managed-store.ts +2 -0
  370. package/src/skills/skill-memory.ts +222 -0
  371. package/src/subagent/manager.ts +1 -4
  372. package/src/telemetry/types.ts +10 -1
  373. package/src/telemetry/usage-telemetry-reporter.test.ts +7 -2
  374. package/src/telemetry/usage-telemetry-reporter.ts +53 -4
  375. package/src/tools/AGENTS.md +11 -11
  376. package/src/tools/acp/spawn.ts +1 -1
  377. package/src/tools/apps/executors.ts +8 -8
  378. package/src/tools/apps/registry.ts +1 -1
  379. package/src/tools/assets/materialize.ts +6 -6
  380. package/src/tools/assets/search.ts +10 -10
  381. package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
  382. package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
  383. package/src/tools/browser/auth-detector.ts +6 -6
  384. package/src/tools/browser/browser-execution.ts +13 -13
  385. package/src/tools/browser/browser-manager.ts +3 -3
  386. package/src/tools/browser/chrome-cdp.ts +5 -5
  387. package/src/tools/browser/jit-auth.ts +2 -2
  388. package/src/tools/browser/network-recorder.test.ts +2 -2
  389. package/src/tools/browser/network-recorder.ts +3 -3
  390. package/src/tools/browser/runtime-check.ts +3 -3
  391. package/src/tools/claude-code/claude-code.ts +2 -2
  392. package/src/tools/computer-use/definitions.ts +18 -18
  393. package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
  394. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
  395. package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
  396. package/src/tools/credentials/broker-types.ts +5 -5
  397. package/src/tools/credentials/broker.ts +15 -15
  398. package/src/tools/credentials/metadata-store.ts +2 -2
  399. package/src/tools/credentials/resolve.ts +1 -1
  400. package/src/tools/credentials/selection.ts +1 -1
  401. package/src/tools/credentials/tool-policy.ts +1 -1
  402. package/src/tools/credentials/vault.ts +115 -25
  403. package/src/tools/execution-target.ts +2 -2
  404. package/src/tools/executor.ts +7 -7
  405. package/src/tools/filesystem/edit.ts +2 -2
  406. package/src/tools/filesystem/read.ts +15 -4
  407. package/src/tools/filesystem/write.ts +1 -1
  408. package/src/tools/host-filesystem/edit.ts +2 -1
  409. package/src/tools/host-filesystem/read.ts +18 -1
  410. package/src/tools/host-filesystem/write.ts +1 -1
  411. package/src/tools/host-terminal/host-shell.ts +9 -8
  412. package/src/tools/mcp/mcp-tool-factory.ts +7 -6
  413. package/src/tools/memory/definitions.ts +6 -5
  414. package/src/tools/memory/handlers.test.ts +1 -1
  415. package/src/tools/network/__tests__/web-search.test.ts +3 -3
  416. package/src/tools/network/domain-normalize.ts +2 -2
  417. package/src/tools/network/script-proxy/session-manager.ts +10 -10
  418. package/src/tools/network/web-fetch.ts +1 -1
  419. package/src/tools/network/web-search.ts +3 -3
  420. package/src/tools/permission-checker.ts +8 -8
  421. package/src/tools/registry.ts +7 -7
  422. package/src/tools/schedule/list.ts +2 -2
  423. package/src/tools/schema-transforms.ts +31 -21
  424. package/src/tools/secret-detection-handler.ts +1 -1
  425. package/src/tools/sensitive-output-placeholders.ts +1 -1
  426. package/src/tools/shared/filesystem/edit-engine.ts +1 -1
  427. package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
  428. package/src/tools/shared/filesystem/image-read.ts +25 -5
  429. package/src/tools/shared/filesystem/path-policy.ts +2 -2
  430. package/src/tools/shared/shell-output.ts +1 -1
  431. package/src/tools/side-effects.ts +1 -1
  432. package/src/tools/skills/execute.ts +1 -1
  433. package/src/tools/skills/load.ts +3 -3
  434. package/src/tools/skills/sandbox-runner.ts +3 -3
  435. package/src/tools/subagent/read.ts +1 -1
  436. package/src/tools/subagent/spawn.ts +2 -2
  437. package/src/tools/swarm/delegate.ts +3 -3
  438. package/src/tools/system/request-permission.ts +5 -4
  439. package/src/tools/terminal/backends/native.ts +4 -4
  440. package/src/tools/terminal/parser.ts +6 -6
  441. package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
  442. package/src/tools/terminal/shell.ts +16 -16
  443. package/src/tools/tool-approval-handler.ts +21 -12
  444. package/src/tools/tool-manifest.ts +4 -4
  445. package/src/tools/types.ts +3 -3
  446. package/src/tools/ui-surface/definitions.ts +9 -37
  447. package/src/tools/watcher/list.ts +1 -1
  448. package/src/util/logger.ts +7 -2
  449. package/src/util/pricing.ts +4 -0
  450. package/src/util/retry.ts +29 -1
  451. package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
  452. package/src/workspace/migrations/registry.ts +2 -0
  453. package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
  454. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
  455. package/src/cli/reference.ts +0 -38
  456. package/src/memory/job-handlers/capability-cards.ts +0 -420
  457. package/src/runtime/routes/thread-starter-routes.ts +0 -294
@@ -33,18 +33,18 @@ describe("EventBus", () => {
33
33
 
34
34
  bus.onAny((event) => {
35
35
  seenType = event.type;
36
- if (event.type === "daemon.session.created") {
36
+ if (event.type === "daemon.conversation.created") {
37
37
  seenConversationId = event.payload.conversationId;
38
38
  }
39
39
  expect(typeof event.emittedAtMs).toBe("number");
40
40
  });
41
41
 
42
- await bus.emit("daemon.session.created", {
42
+ await bus.emit("daemon.conversation.created", {
43
43
  conversationId: "conv-2",
44
44
  createdAtMs: Date.now(),
45
45
  });
46
46
 
47
- expect(seenType).toBe("daemon.session.created");
47
+ expect(seenType).toBe("daemon.conversation.created");
48
48
  expect(seenConversationId).toBe("conv-2");
49
49
  });
50
50
 
@@ -177,3 +177,43 @@ describe("file_read image support", () => {
177
177
  expect(result.content).toContain("outside the working directory");
178
178
  });
179
179
  });
180
+
181
+ // ── Out-of-bounds hint for host_file_read ─────────────────────────────
182
+
183
+ describe("file_read out-of-bounds hint", () => {
184
+ test("suggests host_file_read for out-of-bounds text file path", async () => {
185
+ const dir = makeTempDir();
186
+
187
+ const result = await fileReadTool.execute(
188
+ { path: "/etc/passwd" },
189
+ makeContext(dir),
190
+ );
191
+
192
+ expect(result.isError).toBe(true);
193
+ expect(result.content).toContain("host_file_read");
194
+ });
195
+
196
+ test("suggests host_file_read for out-of-bounds image file path", async () => {
197
+ const dir = makeTempDir();
198
+
199
+ const result = await fileReadTool.execute(
200
+ { path: "/Users/someone/Desktop/screenshot.png" },
201
+ makeContext(dir),
202
+ );
203
+
204
+ expect(result.isError).toBe(true);
205
+ expect(result.content).toContain("host_file_read");
206
+ });
207
+
208
+ test("does not suggest host_file_read for missing file within sandbox", async () => {
209
+ const dir = makeTempDir();
210
+
211
+ const result = await fileReadTool.execute(
212
+ { path: "nonexistent.txt" },
213
+ makeContext(dir),
214
+ );
215
+
216
+ expect(result.isError).toBe(true);
217
+ expect(result.content).not.toContain("host_file_read");
218
+ });
219
+ });
@@ -58,7 +58,7 @@ mock.module("../config/loader.js", () => ({
58
58
  model: "test",
59
59
  provider: "test",
60
60
  memory: { enabled: false },
61
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
61
+ rateLimit: { maxRequestsPerMinute: 0 },
62
62
  secretDetection: { enabled: false },
63
63
  calls: {
64
64
  enabled: true,
@@ -80,7 +80,7 @@ mock.module("../config/loader.js", () => ({
80
80
  model: "test",
81
81
  provider: "test",
82
82
  memory: { enabled: false },
83
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
83
+ rateLimit: { maxRequestsPerMinute: 0 },
84
84
  secretDetection: { enabled: false },
85
85
  ingress: {
86
86
  publicBaseUrl: "https://test.example.com",
@@ -22,6 +22,7 @@ const ALLOWLIST = new Set([
22
22
  // Matched by prefix check below: gateway/
23
23
 
24
24
  // --- Intentional local daemon-control paths ---
25
+ "assistant/src/cli/commands/conversations.ts", // CLI wipe talks to runtime directly
25
26
  "clients/shared/Network/DaemonClient.swift",
26
27
  "clients/macos/vellum-assistant/App/AppDelegate.swift",
27
28
  "clients/macos/vellum-assistant/Features/Settings/SettingsConnectTab.swift",
@@ -111,7 +111,7 @@ describe("generateImage", () => {
111
111
  expect(result.images).toHaveLength(1);
112
112
  expect(result.images[0].mimeType).toBe("image/png");
113
113
  expect(result.images[0].dataBase64).toBe("abc123");
114
- expect(result.resolvedModel).toBe("gemini-2.5-flash-image");
114
+ expect(result.resolvedModel).toBe("gemini-3.1-flash-image-preview");
115
115
  });
116
116
 
117
117
  test("generate mode collects text commentary from response", async () => {
@@ -168,7 +168,7 @@ describe("generateImage", () => {
168
168
 
169
169
  expect(lastGenerateParams).not.toBeNull();
170
170
  expect((lastGenerateParams as Record<string, unknown>).model).toBe(
171
- "gemini-2.5-flash-image",
171
+ "gemini-3.1-flash-image-preview",
172
172
  );
173
173
  });
174
174
 
@@ -180,12 +180,12 @@ describe("generateImage", () => {
180
180
  {
181
181
  prompt: "test",
182
182
  mode: "generate",
183
- model: "gemini-3-pro-image",
183
+ model: "gemini-3-pro-image-preview",
184
184
  },
185
185
  );
186
186
 
187
187
  expect((lastGenerateParams as Record<string, unknown>).model).toBe(
188
- "gemini-3-pro-image",
188
+ "gemini-3-pro-image-preview",
189
189
  );
190
190
  });
191
191
 
@@ -739,17 +739,14 @@ describe("GeminiProvider", () => {
739
739
  expect(lastConstructorOpts).toEqual({ apiKey: "test-key" });
740
740
  });
741
741
 
742
- test("sets vertexai mode with httpOptions.baseUrl when managedBaseUrl is provided", () => {
742
+ test("sets httpOptions.baseUrl when managedBaseUrl is provided", () => {
743
743
  new GeminiProvider("managed-key", "gemini-3-flash", {
744
- managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
744
+ managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
745
745
  });
746
746
  expect(lastConstructorOpts).toEqual({
747
- vertexai: true,
748
- project: "proxy",
749
- location: "us-central1",
747
+ apiKey: "managed-key",
750
748
  httpOptions: {
751
- baseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
752
- headers: { Authorization: "Bearer managed-key" },
749
+ baseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
753
750
  },
754
751
  });
755
752
  });
@@ -759,7 +756,7 @@ describe("GeminiProvider", () => {
759
756
  "managed-key",
760
757
  "gemini-3-flash",
761
758
  {
762
- managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
759
+ managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
763
760
  },
764
761
  );
765
762
 
@@ -784,7 +781,7 @@ describe("GeminiProvider", () => {
784
781
  "managed-key",
785
782
  "gemini-3-flash",
786
783
  {
787
- managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/vertex",
784
+ managedBaseUrl: "https://platform.example.com/v1/runtime-proxy/gemini",
788
785
  },
789
786
  );
790
787
 
@@ -0,0 +1,128 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ const testDir = mkdtempSync(
7
+ join(tmpdir(), "guardian-binding-drift-heal-test-"),
8
+ );
9
+
10
+ mock.module("../util/platform.js", () => ({
11
+ getDataDir: () => testDir,
12
+ isMacOS: () => process.platform === "darwin",
13
+ isLinux: () => process.platform === "linux",
14
+ isWindows: () => process.platform === "win32",
15
+ getPidPath: () => join(testDir, "test.pid"),
16
+ getDbPath: () => join(testDir, "test.db"),
17
+ getLogPath: () => join(testDir, "test.log"),
18
+ ensureDataDir: () => {},
19
+ }));
20
+
21
+ mock.module("../util/logger.js", () => ({
22
+ getLogger: () =>
23
+ new Proxy({} as Record<string, unknown>, {
24
+ get: () => () => {},
25
+ }),
26
+ }));
27
+
28
+ import { findGuardianForChannel } from "../contacts/contact-store.js";
29
+ import { createGuardianBinding } from "../contacts/contacts-write.js";
30
+ import { getDb, initializeDb, resetDb } from "../memory/db.js";
31
+ import { healGuardianBindingDrift } from "../runtime/guardian-vellum-migration.js";
32
+
33
+ initializeDb();
34
+
35
+ function resetTables(): void {
36
+ const db = getDb();
37
+ db.run("DELETE FROM contact_channels");
38
+ db.run("DELETE FROM contacts");
39
+ }
40
+
41
+ describe("healGuardianBindingDrift", () => {
42
+ beforeEach(() => {
43
+ resetTables();
44
+ });
45
+
46
+ afterAll(() => {
47
+ resetDb();
48
+ try {
49
+ rmSync(testDir, { recursive: true });
50
+ } catch {
51
+ // best-effort cleanup
52
+ }
53
+ });
54
+
55
+ test("heals drift when both principals have vellum-principal- prefix", () => {
56
+ // Simulate DB reset: new guardian binding with a different UUID
57
+ createGuardianBinding({
58
+ channel: "vellum",
59
+ guardianExternalUserId: "vellum-principal-new-uuid",
60
+ guardianDeliveryChatId: "local",
61
+ guardianPrincipalId: "vellum-principal-new-uuid",
62
+ verifiedVia: "startup-migration",
63
+ });
64
+
65
+ // Client arrives with the old JWT principal
66
+ const healed = healGuardianBindingDrift("vellum-principal-old-uuid");
67
+ expect(healed).toBe(true);
68
+
69
+ // Guardian binding now matches the old JWT
70
+ const guardian = findGuardianForChannel("vellum");
71
+ expect(guardian).not.toBeNull();
72
+ expect(guardian!.contact.principalId).toBe("vellum-principal-old-uuid");
73
+ expect(guardian!.channel.externalUserId).toBe("vellum-principal-old-uuid");
74
+ });
75
+
76
+ test("no-op when principals already match", () => {
77
+ createGuardianBinding({
78
+ channel: "vellum",
79
+ guardianExternalUserId: "vellum-principal-same",
80
+ guardianDeliveryChatId: "local",
81
+ guardianPrincipalId: "vellum-principal-same",
82
+ verifiedVia: "startup-migration",
83
+ });
84
+
85
+ const healed = healGuardianBindingDrift("vellum-principal-same");
86
+ expect(healed).toBe(false);
87
+ });
88
+
89
+ test("refuses to heal when incoming principal lacks vellum-principal- prefix", () => {
90
+ createGuardianBinding({
91
+ channel: "vellum",
92
+ guardianExternalUserId: "vellum-principal-aaa",
93
+ guardianDeliveryChatId: "local",
94
+ guardianPrincipalId: "vellum-principal-aaa",
95
+ verifiedVia: "startup-migration",
96
+ });
97
+
98
+ // External/platform principal — should NOT be adopted
99
+ const healed = healGuardianBindingDrift("platform-user-12345");
100
+ expect(healed).toBe(false);
101
+
102
+ // Guardian unchanged
103
+ const guardian = findGuardianForChannel("vellum");
104
+ expect(guardian!.contact.principalId).toBe("vellum-principal-aaa");
105
+ });
106
+
107
+ test("refuses to heal when stored principal lacks vellum-principal- prefix", () => {
108
+ createGuardianBinding({
109
+ channel: "vellum",
110
+ guardianExternalUserId: "verified-phone-guardian",
111
+ guardianDeliveryChatId: "local",
112
+ guardianPrincipalId: "verified-phone-guardian",
113
+ verifiedVia: "challenge",
114
+ });
115
+
116
+ // Even with a vellum-principal- incoming, don't overwrite a real binding
117
+ const healed = healGuardianBindingDrift("vellum-principal-attacker");
118
+ expect(healed).toBe(false);
119
+
120
+ const guardian = findGuardianForChannel("vellum");
121
+ expect(guardian!.contact.principalId).toBe("verified-phone-guardian");
122
+ });
123
+
124
+ test("returns false when no guardian binding exists", () => {
125
+ const healed = healGuardianBindingDrift("vellum-principal-orphan");
126
+ expect(healed).toBe(false);
127
+ });
128
+ });
@@ -195,7 +195,6 @@ describe("guardian-dispatch", () => {
195
195
  expect(vellumDelivery!.destination_conversation_id).toBe("conv-vellum-1");
196
196
 
197
197
  const signalParams = emitCalls[0] as Record<string, unknown>;
198
- expect(signalParams.skipVellumThread).toBeUndefined();
199
198
  expect(typeof signalParams.onConversationCreated).toBe("function");
200
199
  });
201
200
 
@@ -8,6 +8,12 @@ import type { ToolContext } from "../tools/types.js";
8
8
 
9
9
  const testDirs: string[] = [];
10
10
 
11
+ function makeTempDir(): string {
12
+ const dir = mkdtempSync(join(tmpdir(), "host-file-read-test-"));
13
+ testDirs.push(dir);
14
+ return dir;
15
+ }
16
+
11
17
  function makeContext(): ToolContext {
12
18
  return {
13
19
  workingDir: "/tmp",
@@ -22,6 +28,18 @@ afterEach(() => {
22
28
  }
23
29
  });
24
30
 
31
+ // Minimal valid JPEG: FF D8 FF E0 header
32
+ const JPEG_HEADER = Buffer.from([
33
+ 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01,
34
+ 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
35
+ ]);
36
+
37
+ // Minimal PNG header
38
+ const PNG_HEADER = Buffer.from([
39
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49,
40
+ 0x48, 0x44, 0x52,
41
+ ]);
42
+
25
43
  describe("host_file_read tool", () => {
26
44
  test("rejects relative paths", async () => {
27
45
  const result = await hostFileReadTool.execute(
@@ -145,3 +163,72 @@ describe("host_file_read tool", () => {
145
163
  expect(result.content).toContain("symlink-content");
146
164
  });
147
165
  });
166
+
167
+ describe("host_file_read image support", () => {
168
+ test("returns image content block for .png file", async () => {
169
+ const dir = makeTempDir();
170
+ const filePath = join(dir, "screenshot.png");
171
+ writeFileSync(filePath, PNG_HEADER);
172
+
173
+ const result = await hostFileReadTool.execute(
174
+ { path: filePath },
175
+ makeContext(),
176
+ );
177
+
178
+ expect(result.isError).toBe(false);
179
+ expect(result.content).toContain("Image loaded");
180
+ expect(result.content).toContain("image/png");
181
+ expect((result as any).contentBlocks).toBeDefined();
182
+ expect((result as any).contentBlocks[0].type).toBe("image");
183
+ expect((result as any).contentBlocks[0].source.media_type).toBe(
184
+ "image/png",
185
+ );
186
+ });
187
+
188
+ test("returns correct media type for .jpg file", async () => {
189
+ const dir = makeTempDir();
190
+ const filePath = join(dir, "photo.jpg");
191
+ writeFileSync(filePath, JPEG_HEADER);
192
+
193
+ const result = await hostFileReadTool.execute(
194
+ { path: filePath },
195
+ makeContext(),
196
+ );
197
+
198
+ expect(result.isError).toBe(false);
199
+ expect(result.content).toContain("Image loaded");
200
+ expect(result.content).toContain("image/jpeg");
201
+ expect((result as any).contentBlocks).toBeDefined();
202
+ expect((result as any).contentBlocks[0].type).toBe("image");
203
+ expect((result as any).contentBlocks[0].source.media_type).toBe(
204
+ "image/jpeg",
205
+ );
206
+ });
207
+
208
+ test("returns error for non-existent image path", async () => {
209
+ const filePath = join(tmpdir(), `host-file-read-missing-${Date.now()}.png`);
210
+ const result = await hostFileReadTool.execute(
211
+ { path: filePath },
212
+ makeContext(),
213
+ );
214
+
215
+ expect(result.isError).toBe(true);
216
+ expect(result.content).toContain("file not found");
217
+ });
218
+
219
+ test("text file still works as before (regression)", async () => {
220
+ const dir = makeTempDir();
221
+ const filePath = join(dir, "notes.txt");
222
+ writeFileSync(filePath, "hello world\nsecond line\n");
223
+
224
+ const result = await hostFileReadTool.execute(
225
+ { path: filePath },
226
+ makeContext(),
227
+ );
228
+
229
+ expect(result.isError).toBe(false);
230
+ expect(result.content).toContain("1 hello world");
231
+ expect(result.content).toContain("2 second line");
232
+ expect((result as any).contentBlocks).toBeUndefined();
233
+ });
234
+ });
@@ -29,14 +29,14 @@ const mockConfig = {
29
29
  shellMaxTimeoutSec: 600,
30
30
  permissionTimeoutSec: 300,
31
31
  },
32
- sandbox: { enabled: true },
33
- rateLimit: { maxRequestsPerMinute: 0, maxTokensPerSession: 0 },
32
+ rateLimit: { maxRequestsPerMinute: 0 },
34
33
  secretDetection: {
35
34
  enabled: true,
36
35
  action: "warn" as const,
37
36
  entropyThreshold: 4.0,
38
37
  },
39
38
  auditLog: { retentionDays: 0 },
39
+ sandbox: { enabled: true },
40
40
  };
41
41
 
42
42
  // Track whether wrapCommand was ever called — host_bash must never invoke it
@@ -283,11 +283,11 @@ describe("host_bash — regression: no proxied-mode additions", () => {
283
283
  expect(schemaProps).not.toHaveProperty("credential_ids");
284
284
  });
285
285
 
286
- test("schema only contains the expected properties (command, working_dir, timeout_seconds, reason)", () => {
286
+ test("schema only contains the expected properties (command, working_dir, timeout_seconds, activity)", () => {
287
287
  const propertyNames = Object.keys(schemaProps).sort();
288
288
  expect(propertyNames).toEqual([
289
+ "activity",
289
290
  "command",
290
- "reason",
291
291
  "timeout_seconds",
292
292
  "working_dir",
293
293
  ]);
@@ -348,10 +348,10 @@ describe("host_bash — regression: no proxied-mode additions", () => {
348
348
  expect(definition.name).toBe("host_bash");
349
349
  });
350
350
 
351
- test("required fields contains command and reason", () => {
351
+ test("required fields contains command and activity", () => {
352
352
  expect(
353
353
  (definition.input_schema as Record<string, unknown>).required,
354
- ).toEqual(["command", "reason"]);
354
+ ).toEqual(["command", "activity"]);
355
355
  });
356
356
  });
357
357
 
@@ -142,9 +142,9 @@ mock.module("../config/loader.js", () => ({
142
142
  "image-generation": {
143
143
  mode: "your-own",
144
144
  provider: "gemini",
145
- model: "gemini-2.5-flash-image",
145
+ model: "gemini-3.1-flash-image-preview",
146
146
  },
147
- "web-search": { mode: "your-own", provider: "anthropic-native" },
147
+ "web-search": { mode: "your-own", provider: "inference-provider-native" },
148
148
  },
149
149
  }),
150
150
  }));
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Unit tests for the identity intro cache (identity-intro-cache.ts).
3
+ *
4
+ * Validates TTL-based expiration, content-hash-based invalidation when
5
+ * workspace identity files change, and round-trip get/set behavior.
6
+ */
7
+
8
+ import { afterEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Mocks — must be defined before importing the module under test
12
+ // ---------------------------------------------------------------------------
13
+
14
+ // Suppress logger output
15
+ mock.module("../util/logger.js", () => ({
16
+ getLogger: () =>
17
+ new Proxy({} as Record<string, unknown>, {
18
+ get: () => () => {},
19
+ }),
20
+ }));
21
+
22
+ // In-memory checkpoint store
23
+ const checkpointStore = new Map<string, string>();
24
+
25
+ mock.module("../memory/checkpoints.js", () => ({
26
+ getMemoryCheckpoint: (key: string) => checkpointStore.get(key) ?? null,
27
+ setMemoryCheckpoint: (key: string, value: string) => {
28
+ checkpointStore.set(key, value);
29
+ },
30
+ }));
31
+
32
+ // Simulated workspace file contents
33
+ const workspaceFiles: Record<string, string> = {};
34
+
35
+ mock.module("../util/platform.js", () => ({
36
+ getWorkspacePromptPath: (name: string) => `/mock/workspace/${name}`,
37
+ }));
38
+
39
+ mock.module("node:fs", () => ({
40
+ existsSync: (path: string) => {
41
+ const name = path.split("/").pop() ?? "";
42
+ return name in workspaceFiles;
43
+ },
44
+ readFileSync: (path: string, _encoding: string) => {
45
+ const name = path.split("/").pop() ?? "";
46
+ if (name in workspaceFiles) return workspaceFiles[name];
47
+ throw new Error(`ENOENT: ${path}`);
48
+ },
49
+ }));
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // Imports (after mocks)
53
+ // ---------------------------------------------------------------------------
54
+
55
+ import {
56
+ computeIdentityContentHash,
57
+ getCachedIntro,
58
+ setCachedIntro,
59
+ } from "../runtime/routes/identity-intro-cache.js";
60
+
61
+ // ---------------------------------------------------------------------------
62
+ // Helpers
63
+ // ---------------------------------------------------------------------------
64
+
65
+ afterEach(() => {
66
+ checkpointStore.clear();
67
+ for (const key of Object.keys(workspaceFiles)) {
68
+ delete workspaceFiles[key];
69
+ }
70
+ });
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Tests
74
+ // ---------------------------------------------------------------------------
75
+
76
+ describe("identity intro cache", () => {
77
+ test("returns null when cache is empty", () => {
78
+ expect(getCachedIntro()).toBeNull();
79
+ });
80
+
81
+ test("round-trip: set then get returns cached text", () => {
82
+ workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
83
+ workspaceFiles["SOUL.md"] = "Be playful.";
84
+ workspaceFiles["USER.md"] = "The user likes coffee.";
85
+
86
+ setCachedIntro("Hey, I'm Atlas.");
87
+ const cached = getCachedIntro();
88
+ expect(cached).not.toBeNull();
89
+ expect(cached!.text).toBe("Hey, I'm Atlas.");
90
+ });
91
+
92
+ test("returns null when cache is expired (TTL exceeded)", () => {
93
+ workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
94
+
95
+ setCachedIntro("Hello!");
96
+
97
+ // Manually set the timestamp to 5 hours ago
98
+ const fiveHoursAgo = String(Date.now() - 5 * 60 * 60 * 1000);
99
+ checkpointStore.set("identity:intro:cached_at", fiveHoursAgo);
100
+
101
+ expect(getCachedIntro()).toBeNull();
102
+ });
103
+
104
+ test("returns cached text when within TTL (3 hours ago)", () => {
105
+ workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
106
+
107
+ setCachedIntro("Hello!");
108
+
109
+ // Set timestamp to 3 hours ago (within 4-hour TTL)
110
+ const threeHoursAgo = String(Date.now() - 3 * 60 * 60 * 1000);
111
+ checkpointStore.set("identity:intro:cached_at", threeHoursAgo);
112
+
113
+ const cached = getCachedIntro();
114
+ expect(cached).not.toBeNull();
115
+ expect(cached!.text).toBe("Hello!");
116
+ });
117
+
118
+ test("busts cache when IDENTITY.md changes", () => {
119
+ workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
120
+ setCachedIntro("I'm Atlas!");
121
+
122
+ // Change IDENTITY.md
123
+ workspaceFiles["IDENTITY.md"] = "- **Name:** Nova";
124
+
125
+ expect(getCachedIntro()).toBeNull();
126
+ });
127
+
128
+ test("busts cache when SOUL.md changes", () => {
129
+ workspaceFiles["SOUL.md"] = "Be playful.";
130
+ setCachedIntro("Hey there!");
131
+
132
+ // Change SOUL.md
133
+ workspaceFiles["SOUL.md"] = "Be serious and formal.";
134
+
135
+ expect(getCachedIntro()).toBeNull();
136
+ });
137
+
138
+ test("busts cache when USER.md changes", () => {
139
+ workspaceFiles["USER.md"] = "Likes coffee.";
140
+ setCachedIntro("Good morning!");
141
+
142
+ // Change USER.md
143
+ workspaceFiles["USER.md"] = "Likes tea.";
144
+
145
+ expect(getCachedIntro()).toBeNull();
146
+ });
147
+
148
+ test("cache survives when files are unchanged", () => {
149
+ workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
150
+ workspaceFiles["SOUL.md"] = "Be chill.";
151
+ workspaceFiles["USER.md"] = "Likes sunsets.";
152
+
153
+ setCachedIntro("Atlas here.");
154
+
155
+ // Read twice — both should return the cached value
156
+ expect(getCachedIntro()?.text).toBe("Atlas here.");
157
+ expect(getCachedIntro()?.text).toBe("Atlas here.");
158
+ });
159
+
160
+ test("computeIdentityContentHash is deterministic", () => {
161
+ workspaceFiles["IDENTITY.md"] = "test";
162
+ workspaceFiles["SOUL.md"] = "test2";
163
+ workspaceFiles["USER.md"] = "test3";
164
+
165
+ const hash1 = computeIdentityContentHash();
166
+ const hash2 = computeIdentityContentHash();
167
+ expect(hash1).toBe(hash2);
168
+ expect(hash1).toMatch(/^[a-f0-9]{64}$/); // SHA-256 hex
169
+ });
170
+
171
+ test("computeIdentityContentHash changes when file content changes", () => {
172
+ workspaceFiles["IDENTITY.md"] = "v1";
173
+ const hash1 = computeIdentityContentHash();
174
+
175
+ workspaceFiles["IDENTITY.md"] = "v2";
176
+ const hash2 = computeIdentityContentHash();
177
+
178
+ expect(hash1).not.toBe(hash2);
179
+ });
180
+
181
+ test("handles missing workspace files gracefully", () => {
182
+ // No files exist — should still work (empty content hashed)
183
+ setCachedIntro("Hello!");
184
+ const cached = getCachedIntro();
185
+ expect(cached).not.toBeNull();
186
+ expect(cached!.text).toBe("Hello!");
187
+ });
188
+
189
+ test("returns null when text checkpoint is missing", () => {
190
+ checkpointStore.set("identity:intro:content_hash", "abc");
191
+ checkpointStore.set("identity:intro:cached_at", String(Date.now()));
192
+ // Missing text — should return null
193
+ expect(getCachedIntro()).toBeNull();
194
+ });
195
+
196
+ test("returns null when hash checkpoint is missing", () => {
197
+ checkpointStore.set("identity:intro:text", "Hello");
198
+ checkpointStore.set("identity:intro:cached_at", String(Date.now()));
199
+ // Missing hash — should return null
200
+ expect(getCachedIntro()).toBeNull();
201
+ });
202
+
203
+ test("returns null when timestamp checkpoint is missing", () => {
204
+ checkpointStore.set("identity:intro:text", "Hello");
205
+ checkpointStore.set("identity:intro:content_hash", "abc");
206
+ // Missing timestamp — should return null
207
+ expect(getCachedIntro()).toBeNull();
208
+ });
209
+ });