@vellumai/assistant 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -0,0 +1,441 @@
1
+ /**
2
+ * Tests for the profiler run store: manifest management, retention sweep,
3
+ * active-run protection, oldest-first pruning, max-run-count pruning,
4
+ * active-run-over-budget signaling, and idempotent rescans.
5
+ */
6
+ import {
7
+ existsSync,
8
+ mkdirSync,
9
+ readFileSync,
10
+ rmSync,
11
+ writeFileSync,
12
+ } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
16
+
17
+ import type { ProfilerRunManifest } from "../daemon/profiler-run-store.js";
18
+ import { rescanRuns, runProfilerSweep } from "../daemon/profiler-run-store.js";
19
+
20
+ // ── Test scaffolding ────────────────────────────────────────────────────
21
+
22
+ let testDir: string;
23
+ let runsDir: string;
24
+ let origEnv: Record<string, string | undefined>;
25
+
26
+ /**
27
+ * Create a fake profiler run directory with some payload files.
28
+ */
29
+ function createRun(
30
+ runId: string,
31
+ opts?: {
32
+ sizeBytes?: number;
33
+ manifest?: Partial<ProfilerRunManifest>;
34
+ },
35
+ ): string {
36
+ const dir = join(runsDir, runId);
37
+ mkdirSync(dir, { recursive: true });
38
+
39
+ // Write a payload file of the requested size
40
+ const size = opts?.sizeBytes ?? 1024;
41
+ writeFileSync(join(dir, "profile.cpuprofile"), Buffer.alloc(size));
42
+
43
+ // Optionally write a pre-existing manifest
44
+ if (opts?.manifest) {
45
+ const m: ProfilerRunManifest = {
46
+ runId,
47
+ status: opts.manifest.status ?? "completed",
48
+ createdAt: opts.manifest.createdAt ?? new Date().toISOString(),
49
+ updatedAt: opts.manifest.updatedAt ?? new Date().toISOString(),
50
+ totalBytes: opts.manifest.totalBytes ?? size,
51
+ };
52
+ writeFileSync(join(dir, "manifest.json"), JSON.stringify(m, null, 2));
53
+ }
54
+
55
+ return dir;
56
+ }
57
+
58
+ function readManifestFromDisk(runId: string): ProfilerRunManifest | null {
59
+ const manifestPath = join(runsDir, runId, "manifest.json");
60
+ try {
61
+ return JSON.parse(readFileSync(manifestPath, "utf-8"));
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ beforeEach(() => {
68
+ testDir = join(
69
+ tmpdir(),
70
+ `vellum-profiler-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
71
+ );
72
+ runsDir = join(testDir, "data", "profiler", "runs");
73
+ mkdirSync(runsDir, { recursive: true });
74
+
75
+ // Save and override env
76
+ origEnv = {
77
+ VELLUM_WORKSPACE_DIR: process.env.VELLUM_WORKSPACE_DIR,
78
+ VELLUM_PROFILER_RUN_ID: process.env.VELLUM_PROFILER_RUN_ID,
79
+ VELLUM_PROFILER_MAX_BYTES: process.env.VELLUM_PROFILER_MAX_BYTES,
80
+ VELLUM_PROFILER_MAX_RUNS: process.env.VELLUM_PROFILER_MAX_RUNS,
81
+ VELLUM_PROFILER_MIN_FREE_MB: process.env.VELLUM_PROFILER_MIN_FREE_MB,
82
+ };
83
+
84
+ // Point workspace dir to our temp directory
85
+ process.env.VELLUM_WORKSPACE_DIR = testDir;
86
+
87
+ // Clear profiler env vars
88
+ delete process.env.VELLUM_PROFILER_RUN_ID;
89
+ delete process.env.VELLUM_PROFILER_MAX_BYTES;
90
+ delete process.env.VELLUM_PROFILER_MAX_RUNS;
91
+ delete process.env.VELLUM_PROFILER_MIN_FREE_MB;
92
+ });
93
+
94
+ afterEach(() => {
95
+ // Restore env
96
+ for (const [key, value] of Object.entries(origEnv)) {
97
+ if (value === undefined) {
98
+ delete process.env[key];
99
+ } else {
100
+ process.env[key] = value;
101
+ }
102
+ }
103
+
104
+ // Clean up temp directory
105
+ if (existsSync(testDir)) {
106
+ rmSync(testDir, { recursive: true, force: true });
107
+ }
108
+ });
109
+
110
+ // ── Tests ───────────────────────────────────────────────────────────────
111
+
112
+ describe("Profiler run store", () => {
113
+ describe("rescanRuns", () => {
114
+ test("returns empty array when no runs directory exists", () => {
115
+ // Remove the runs directory
116
+ rmSync(runsDir, { recursive: true, force: true });
117
+ const manifests = rescanRuns();
118
+ expect(manifests).toEqual([]);
119
+ });
120
+
121
+ test("returns empty array when runs directory is empty", () => {
122
+ const manifests = rescanRuns();
123
+ expect(manifests).toEqual([]);
124
+ });
125
+
126
+ test("creates manifests for run directories without existing manifests", () => {
127
+ createRun("run-001", { sizeBytes: 2048 });
128
+ createRun("run-002", { sizeBytes: 4096 });
129
+
130
+ const manifests = rescanRuns();
131
+ expect(manifests).toHaveLength(2);
132
+
133
+ const run1 = manifests.find((m) => m.runId === "run-001");
134
+ expect(run1).toBeDefined();
135
+ expect(run1!.status).toBe("completed");
136
+ // totalBytes includes manifest.json that rescan just wrote
137
+ expect(run1!.totalBytes).toBeGreaterThanOrEqual(2048);
138
+
139
+ const run2 = manifests.find((m) => m.runId === "run-002");
140
+ expect(run2).toBeDefined();
141
+ expect(run2!.status).toBe("completed");
142
+ expect(run2!.totalBytes).toBeGreaterThanOrEqual(4096);
143
+ });
144
+
145
+ test("marks the active run correctly", () => {
146
+ process.env.VELLUM_PROFILER_RUN_ID = "active-run";
147
+ createRun("active-run", { sizeBytes: 1024 });
148
+ createRun("old-run", { sizeBytes: 1024 });
149
+
150
+ const manifests = rescanRuns();
151
+ const active = manifests.find((m) => m.runId === "active-run");
152
+ const old = manifests.find((m) => m.runId === "old-run");
153
+
154
+ expect(active!.status).toBe("active");
155
+ expect(old!.status).toBe("completed");
156
+ });
157
+
158
+ test("transitions previously-active run to completed when no longer active", () => {
159
+ // Create a run with an "active" manifest
160
+ createRun("old-active", {
161
+ sizeBytes: 1024,
162
+ manifest: { status: "active", createdAt: "2025-01-01T00:00:00Z" },
163
+ });
164
+
165
+ // No VELLUM_PROFILER_RUN_ID set, so nothing is active
166
+ const manifests = rescanRuns();
167
+ const run = manifests.find((m) => m.runId === "old-active");
168
+
169
+ expect(run!.status).toBe("completed");
170
+
171
+ // Verify it was persisted to disk
172
+ const onDisk = readManifestFromDisk("old-active");
173
+ expect(onDisk!.status).toBe("completed");
174
+ });
175
+
176
+ test("is idempotent — repeated calls after initial scan produce the same result", () => {
177
+ createRun("run-a", { sizeBytes: 1024 });
178
+ process.env.VELLUM_PROFILER_RUN_ID = "run-a";
179
+
180
+ // First call writes the manifest, which changes totalBytes
181
+ rescanRuns();
182
+ // Second and third calls should be stable
183
+ const second = rescanRuns();
184
+ const third = rescanRuns();
185
+
186
+ expect(second).toHaveLength(1);
187
+ expect(third).toHaveLength(1);
188
+ expect(second[0]!.runId).toBe(third[0]!.runId);
189
+ expect(second[0]!.status).toBe(third[0]!.status);
190
+ expect(second[0]!.totalBytes).toBe(third[0]!.totalBytes);
191
+ });
192
+
193
+ test("preserves createdAt from existing manifest", () => {
194
+ const originalCreatedAt = "2024-06-15T12:00:00Z";
195
+ createRun("preserved-run", {
196
+ sizeBytes: 1024,
197
+ manifest: {
198
+ status: "completed",
199
+ createdAt: originalCreatedAt,
200
+ },
201
+ });
202
+
203
+ const manifests = rescanRuns();
204
+ const run = manifests.find((m) => m.runId === "preserved-run");
205
+ expect(run!.createdAt).toBe(originalCreatedAt);
206
+ });
207
+ });
208
+
209
+ describe("runProfilerSweep", () => {
210
+ test("no-ops when no runs exist", () => {
211
+ const result = runProfilerSweep();
212
+ expect(result.prunedCount).toBe(0);
213
+ expect(result.freedBytes).toBe(0);
214
+ expect(result.activeRunOverBudget).toBe(false);
215
+ expect(result.remainingRuns).toBe(0);
216
+ });
217
+
218
+ test("does not prune when under all budgets", () => {
219
+ process.env.VELLUM_PROFILER_MAX_BYTES = "1000000"; // 1 MB
220
+ process.env.VELLUM_PROFILER_MAX_RUNS = "10";
221
+
222
+ createRun("run-1", { sizeBytes: 1024 });
223
+ createRun("run-2", { sizeBytes: 1024 });
224
+
225
+ const result = runProfilerSweep();
226
+ expect(result.prunedCount).toBe(0);
227
+ expect(result.remainingRuns).toBe(2);
228
+
229
+ // Both directories still exist
230
+ expect(existsSync(join(runsDir, "run-1"))).toBe(true);
231
+ expect(existsSync(join(runsDir, "run-2"))).toBe(true);
232
+ });
233
+
234
+ test("prunes oldest completed runs when byte budget exceeded", () => {
235
+ // Set a very small byte budget
236
+ process.env.VELLUM_PROFILER_MAX_BYTES = "3000";
237
+ process.env.VELLUM_PROFILER_MAX_RUNS = "100";
238
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
239
+
240
+ // Create runs with explicit timestamps for ordering
241
+ createRun("oldest", {
242
+ sizeBytes: 2000,
243
+ manifest: {
244
+ status: "completed",
245
+ createdAt: "2025-01-01T00:00:00Z",
246
+ },
247
+ });
248
+ createRun("middle", {
249
+ sizeBytes: 2000,
250
+ manifest: {
251
+ status: "completed",
252
+ createdAt: "2025-02-01T00:00:00Z",
253
+ },
254
+ });
255
+ createRun("newest", {
256
+ sizeBytes: 2000,
257
+ manifest: {
258
+ status: "completed",
259
+ createdAt: "2025-03-01T00:00:00Z",
260
+ },
261
+ });
262
+
263
+ const result = runProfilerSweep();
264
+
265
+ // Should prune until total bytes fit within 3000.
266
+ // Each run is ~2000 payload + manifest overhead. The sweep recomputes
267
+ // sizes so actual totals include the manifest file. At least 1 run
268
+ // should be pruned (the oldest).
269
+ expect(result.prunedCount).toBeGreaterThanOrEqual(1);
270
+ expect(result.freedBytes).toBeGreaterThan(0);
271
+
272
+ // The oldest should be gone
273
+ expect(existsSync(join(runsDir, "oldest"))).toBe(false);
274
+ });
275
+
276
+ test("prunes oldest completed runs when max-run-count exceeded", () => {
277
+ process.env.VELLUM_PROFILER_MAX_BYTES = "999999999";
278
+ process.env.VELLUM_PROFILER_MAX_RUNS = "2";
279
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
280
+
281
+ createRun("run-a", {
282
+ sizeBytes: 100,
283
+ manifest: {
284
+ status: "completed",
285
+ createdAt: "2025-01-01T00:00:00Z",
286
+ },
287
+ });
288
+ createRun("run-b", {
289
+ sizeBytes: 100,
290
+ manifest: {
291
+ status: "completed",
292
+ createdAt: "2025-02-01T00:00:00Z",
293
+ },
294
+ });
295
+ createRun("run-c", {
296
+ sizeBytes: 100,
297
+ manifest: {
298
+ status: "completed",
299
+ createdAt: "2025-03-01T00:00:00Z",
300
+ },
301
+ });
302
+ createRun("run-d", {
303
+ sizeBytes: 100,
304
+ manifest: {
305
+ status: "completed",
306
+ createdAt: "2025-04-01T00:00:00Z",
307
+ },
308
+ });
309
+
310
+ const result = runProfilerSweep();
311
+
312
+ // 4 completed runs, max 2: should prune 2 oldest
313
+ expect(result.prunedCount).toBe(2);
314
+ expect(existsSync(join(runsDir, "run-a"))).toBe(false);
315
+ expect(existsSync(join(runsDir, "run-b"))).toBe(false);
316
+ expect(existsSync(join(runsDir, "run-c"))).toBe(true);
317
+ expect(existsSync(join(runsDir, "run-d"))).toBe(true);
318
+ expect(result.remainingRuns).toBe(2);
319
+ });
320
+
321
+ test("never deletes the active run", () => {
322
+ process.env.VELLUM_PROFILER_RUN_ID = "current";
323
+ process.env.VELLUM_PROFILER_MAX_BYTES = "500";
324
+ process.env.VELLUM_PROFILER_MAX_RUNS = "1";
325
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
326
+
327
+ createRun("current", { sizeBytes: 2000 });
328
+ createRun("old-completed", {
329
+ sizeBytes: 2000,
330
+ manifest: {
331
+ status: "completed",
332
+ createdAt: "2025-01-01T00:00:00Z",
333
+ },
334
+ });
335
+
336
+ const result = runProfilerSweep();
337
+
338
+ // old-completed should be pruned, current should survive
339
+ expect(existsSync(join(runsDir, "current"))).toBe(true);
340
+ expect(existsSync(join(runsDir, "old-completed"))).toBe(false);
341
+ expect(result.prunedCount).toBe(1);
342
+ });
343
+
344
+ test("signals active-run-over-budget when active run exceeds byte budget", () => {
345
+ process.env.VELLUM_PROFILER_RUN_ID = "big-active";
346
+ process.env.VELLUM_PROFILER_MAX_BYTES = "500";
347
+ process.env.VELLUM_PROFILER_MAX_RUNS = "100";
348
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
349
+
350
+ createRun("big-active", { sizeBytes: 10000 });
351
+
352
+ const result = runProfilerSweep();
353
+
354
+ expect(result.activeRunOverBudget).toBe(true);
355
+ // Active run must still exist
356
+ expect(existsSync(join(runsDir, "big-active"))).toBe(true);
357
+ expect(result.remainingRuns).toBe(1);
358
+ });
359
+
360
+ test("deletes single oversized completed run to recover space", () => {
361
+ process.env.VELLUM_PROFILER_MAX_BYTES = "100";
362
+ process.env.VELLUM_PROFILER_MAX_RUNS = "100";
363
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
364
+
365
+ createRun("huge-completed", {
366
+ sizeBytes: 50000,
367
+ manifest: {
368
+ status: "completed",
369
+ createdAt: "2025-01-01T00:00:00Z",
370
+ },
371
+ });
372
+
373
+ const result = runProfilerSweep();
374
+
375
+ expect(result.prunedCount).toBe(1);
376
+ expect(result.freedBytes).toBeGreaterThanOrEqual(50000);
377
+ expect(existsSync(join(runsDir, "huge-completed"))).toBe(false);
378
+ });
379
+
380
+ test("creates profiler directories on first sweep if missing", () => {
381
+ // Remove everything
382
+ rmSync(join(testDir, "data", "profiler"), {
383
+ recursive: true,
384
+ force: true,
385
+ });
386
+
387
+ const result = runProfilerSweep();
388
+ expect(result.prunedCount).toBe(0);
389
+ expect(existsSync(runsDir)).toBe(true);
390
+ });
391
+
392
+ test("sweep is idempotent — repeated calls produce consistent state", () => {
393
+ process.env.VELLUM_PROFILER_MAX_BYTES = "999999";
394
+ process.env.VELLUM_PROFILER_MAX_RUNS = "10";
395
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
396
+
397
+ createRun("stable-1", { sizeBytes: 1024 });
398
+ createRun("stable-2", { sizeBytes: 1024 });
399
+
400
+ const first = runProfilerSweep();
401
+ const second = runProfilerSweep();
402
+
403
+ expect(first.prunedCount).toBe(0);
404
+ expect(second.prunedCount).toBe(0);
405
+ expect(first.remainingRuns).toBe(second.remainingRuns);
406
+ });
407
+
408
+ test("active run is not counted against max completed runs", () => {
409
+ process.env.VELLUM_PROFILER_RUN_ID = "live";
410
+ process.env.VELLUM_PROFILER_MAX_BYTES = "999999";
411
+ process.env.VELLUM_PROFILER_MAX_RUNS = "2";
412
+ process.env.VELLUM_PROFILER_MIN_FREE_MB = "0";
413
+
414
+ createRun("live", { sizeBytes: 100 });
415
+ createRun("done-1", {
416
+ sizeBytes: 100,
417
+ manifest: {
418
+ status: "completed",
419
+ createdAt: "2025-01-01T00:00:00Z",
420
+ },
421
+ });
422
+ createRun("done-2", {
423
+ sizeBytes: 100,
424
+ manifest: {
425
+ status: "completed",
426
+ createdAt: "2025-02-01T00:00:00Z",
427
+ },
428
+ });
429
+
430
+ const result = runProfilerSweep();
431
+
432
+ // 2 completed runs = max, so nothing should be pruned
433
+ expect(result.prunedCount).toBe(0);
434
+ // Active + 2 completed = 3 remaining
435
+ expect(result.remainingRuns).toBe(3);
436
+ expect(existsSync(join(runsDir, "live"))).toBe(true);
437
+ expect(existsSync(join(runsDir, "done-1"))).toBe(true);
438
+ expect(existsSync(join(runsDir, "done-2"))).toBe(true);
439
+ });
440
+ });
441
+ });
@@ -189,7 +189,7 @@ describe("createProxyApprovalCallback", () => {
189
189
  expect(prompterSendToClient).not.toHaveBeenCalled();
190
190
  });
191
191
 
192
- test("high-risk with plain allow rule (no allowHighRisk) falls through to prompt", async () => {
192
+ test("ask_missing_credential with allow rule auto-allows (medium risk)", async () => {
193
193
  findHighestPriorityRuleMock.mockReturnValue({
194
194
  id: "rule-hr-1",
195
195
  tool: "network_request",
@@ -198,53 +198,17 @@ describe("createProxyApprovalCallback", () => {
198
198
  decision: "allow" as const,
199
199
  priority: 100,
200
200
  createdAt: Date.now(),
201
- // No allowHighRisk — should NOT auto-allow for high-risk decisions
202
201
  });
203
202
 
204
203
  const ctx = makeContext();
205
204
  const prompterSendToClient = mock(() => {});
206
205
  const prompter = new PermissionPrompter(prompterSendToClient);
207
206
 
208
- const originalPrompt = prompter.prompt.bind(prompter);
209
- prompter.prompt = async (...args) => {
210
- const p = originalPrompt(...args);
211
- await new Promise((r) => setTimeout(r, 10));
212
- const call = (prompterSendToClient.mock.calls as unknown[][])[0];
213
- const msg = call[0] as { requestId: string };
214
- prompter.resolveConfirmation(msg.requestId, "allow");
215
- return p;
216
- };
217
-
218
207
  const callback = createProxyApprovalCallback(prompter, ctx);
219
- // ask_missing_credential is high risk
220
208
  const result = await callback(makeAskMissingCredentialRequest());
221
209
 
222
210
  expect(result).toBe(true);
223
- // Prompter SHOULD have been called — plain allow rule doesn't auto-allow high-risk
224
- expect(prompterSendToClient).toHaveBeenCalled();
225
- });
226
-
227
- test("high-risk with allowHighRisk allow rule auto-allows without prompting", async () => {
228
- findHighestPriorityRuleMock.mockReturnValue({
229
- id: "rule-hr-2",
230
- tool: "network_request",
231
- pattern: "network_request:https://api.fal.ai:443/*",
232
- scope: "/tmp/test-project",
233
- decision: "allow" as const,
234
- priority: 100,
235
- createdAt: Date.now(),
236
- allowHighRisk: true,
237
- });
238
-
239
- const ctx = makeContext();
240
- const prompterSendToClient = mock(() => {});
241
- const prompter = new PermissionPrompter(prompterSendToClient);
242
-
243
- const callback = createProxyApprovalCallback(prompter, ctx);
244
- const result = await callback(makeAskMissingCredentialRequest());
245
-
246
- expect(result).toBe(true);
247
- // Prompter should NOT have been called — allowHighRisk rule auto-allows
211
+ // Plain allow rule auto-allows medium-risk requests
248
212
  expect(prompterSendToClient).not.toHaveBeenCalled();
249
213
  });
250
214
 
@@ -436,7 +400,7 @@ describe("createProxyApprovalCallback", () => {
436
400
  await callback(makeAskUnauthenticatedRequest());
437
401
  });
438
402
 
439
- test("uses high risk level for ask_missing_credential decisions", async () => {
403
+ test("uses medium risk level for ask_missing_credential decisions", async () => {
440
404
  const ctx = makeContext();
441
405
  const prompterSendToClient = mock(() => {});
442
406
  const prompter = new PermissionPrompter(prompterSendToClient);
@@ -447,8 +411,7 @@ describe("createProxyApprovalCallback", () => {
447
411
  await new Promise((r) => setTimeout(r, 10));
448
412
  const call = (prompterSendToClient.mock.calls as unknown[][])[0];
449
413
  const msg = call[0] as { requestId: string; riskLevel: string };
450
- // Missing credential prompts are high risk — the target wants auth
451
- expect(msg.riskLevel).toBe("high");
414
+ expect(msg.riskLevel).toBe("medium");
452
415
  prompter.resolveConfirmation(msg.requestId, "allow");
453
416
  return p;
454
417
  };
@@ -523,40 +486,6 @@ describe("createProxyApprovalCallback", () => {
523
486
  // in contrast to the proxied bash activation path which CANNOT (tested
524
487
  // in tool-executor.test.ts).
525
488
 
526
- test("always_allow_high_risk persists rule with allowHighRisk flag", async () => {
527
- const ctx = makeContext();
528
- const prompterSendToClient = mock(() => {});
529
- const prompter = new PermissionPrompter(prompterSendToClient);
530
-
531
- const originalPrompt = prompter.prompt.bind(prompter);
532
- prompter.prompt = async (...args) => {
533
- const p = originalPrompt(...args);
534
- await new Promise((r) => setTimeout(r, 10));
535
- const call = (prompterSendToClient.mock.calls as unknown[][])[0];
536
- const msg = call[0] as { requestId: string };
537
- prompter.resolveConfirmation(
538
- msg.requestId,
539
- "always_allow_high_risk",
540
- "network_request:https://api.fal.ai:443/*",
541
- "/tmp/test-project",
542
- );
543
- return p;
544
- };
545
-
546
- const callback = createProxyApprovalCallback(prompter, ctx);
547
- const result = await callback(makeAskMissingCredentialRequest());
548
-
549
- expect(result).toBe(true);
550
- expect(addRuleMock).toHaveBeenCalledWith(
551
- "network_request",
552
- "network_request:https://api.fal.ai:443/*",
553
- "/tmp/test-project",
554
- "allow",
555
- 100,
556
- { allowHighRisk: true },
557
- );
558
- });
559
-
560
489
  test("one-time allow does NOT persist any rule", async () => {
561
490
  const ctx = makeContext();
562
491
  const prompterSendToClient = mock(() => {});
@@ -109,7 +109,7 @@ describe("tool registry dynamic-tools tools", () => {
109
109
 
110
110
  describe("tool manifest", () => {
111
111
  test("eager module tool names list contains expected count", () => {
112
- expect(eagerModuleToolNames.length).toBe(9);
112
+ expect(eagerModuleToolNames.length).toBe(11);
113
113
  });
114
114
 
115
115
  test("explicit tools list includes memory and credential tools", () => {
@@ -416,7 +416,6 @@ describe("requireFreshApproval: persistent decisions disabled", () => {
416
416
  _allowlistOptions: unknown[],
417
417
  _scopeOptions: unknown[],
418
418
  _previewDiff: unknown,
419
- _sandboxed: boolean | undefined,
420
419
  _conversationId: string,
421
420
  _executionTarget: string,
422
421
  persistentDecisionsAllowed: boolean,
@@ -467,7 +466,6 @@ describe("requireFreshApproval: persistent decisions disabled", () => {
467
466
  _allowlistOptions: unknown[],
468
467
  _scopeOptions: unknown[],
469
468
  _previewDiff: unknown,
470
- _sandboxed: boolean | undefined,
471
469
  _conversationId: string,
472
470
  _executionTarget: string,
473
471
  persistentDecisionsAllowed: boolean,
@@ -10,22 +10,6 @@ mock.module("node:child_process", () => ({
10
10
  execSync: execSyncMock,
11
11
  }));
12
12
 
13
- // Mock config loader — return a config with sandbox settings
14
- let mockSandboxConfig: {
15
- enabled: boolean;
16
- } = {
17
- enabled: true,
18
- };
19
-
20
- mock.module("../config/loader.js", () => ({
21
- getConfig: () => ({
22
- ui: {},
23
-
24
- sandbox: mockSandboxConfig,
25
- }),
26
- loadRawConfig: () => ({}),
27
- }));
28
-
29
13
  mock.module("../util/logger.js", () => ({
30
14
  getLogger: () => ({
31
15
  error: () => {},
@@ -49,9 +33,6 @@ function setPlatform(platform: string): void {
49
33
 
50
34
  beforeEach(() => {
51
35
  execSyncMock.mockReset();
52
- mockSandboxConfig = {
53
- enabled: true,
54
- };
55
36
  // Default: all commands succeed.
56
37
  execSyncMock.mockImplementation(() => undefined);
57
38
  });
@@ -62,26 +43,14 @@ afterEach(() => {
62
43
  });
63
44
 
64
45
  describe("runSandboxDiagnostics — config reporting", () => {
65
- test("reports sandbox enabled state", () => {
66
- const result = runSandboxDiagnostics();
67
- expect(result.config.enabled).toBe(true);
68
- });
69
-
70
- test("reports sandbox disabled state", () => {
71
- mockSandboxConfig.enabled = false;
46
+ test("reports sandbox disabled (sandbox removed)", () => {
72
47
  const result = runSandboxDiagnostics();
73
48
  expect(result.config.enabled).toBe(false);
74
49
  });
75
50
  });
76
51
 
77
52
  describe("runSandboxDiagnostics — active backend reason", () => {
78
- test("explains native backend selection", () => {
79
- const result = runSandboxDiagnostics();
80
- expect(result.activeBackendReason).toContain("Native backend");
81
- });
82
-
83
53
  test("explains when sandbox is disabled", () => {
84
- mockSandboxConfig.enabled = false;
85
54
  const result = runSandboxDiagnostics();
86
55
  expect(result.activeBackendReason).toContain("disabled");
87
56
  });
@@ -705,10 +705,11 @@ describe("Terminal output format: formatShellOutput shared by sandbox and host",
705
705
  const longOutput = "x".repeat(MAX_OUTPUT_LENGTH + 100);
706
706
  const result = formatShellOutput(longOutput, "", 0, false, 120);
707
707
 
708
- expect(result.content.length).toBe(
709
- MAX_OUTPUT_LENGTH + 1 + '<output_truncated limit="50K" />'.length,
710
- );
711
- expect(result.content).toContain("<output_truncated");
708
+ expect(result.content).toContain('limit="20K"');
709
+ expect(result.content).toContain('file="');
710
+ // The <output_truncated tag starts right after MAX_OUTPUT_LENGTH chars + 1 newline
711
+ const tagStart = result.content.indexOf("<output_truncated");
712
+ expect(tagStart).toBe(MAX_OUTPUT_LENGTH + 1);
712
713
  });
713
714
 
714
715
  test("timed-out command appends timeout tag and sets isError", () => {