@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.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 (430) hide show
  1. package/bun.lock +0 -20
  2. package/docs/workspace-tools.md +33 -42
  3. package/eslint-rules/cli-no-daemon-internals.js +0 -6
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
  5. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
  6. package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
  7. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
  8. package/node_modules/@vellumai/service-contracts/package.json +0 -1
  9. package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
  10. package/openapi.yaml +0 -155
  11. package/package.json +1 -4
  12. package/scripts/test.sh +15 -36
  13. package/src/__tests__/actor-token-service.test.ts +14 -36
  14. package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
  15. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
  16. package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
  17. package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
  18. package/src/__tests__/annotate-risk-options.test.ts +0 -2
  19. package/src/__tests__/approval-cascade.test.ts +0 -2
  20. package/src/__tests__/assistant-attachments.test.ts +0 -42
  21. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  22. package/src/__tests__/btw-routes.test.ts +0 -2
  23. package/src/__tests__/build-persisted-content.test.ts +0 -2
  24. package/src/__tests__/call-controller.test.ts +0 -19
  25. package/src/__tests__/channel-guardian.test.ts +58 -94
  26. package/src/__tests__/channel-reply-delivery.test.ts +0 -2
  27. package/src/__tests__/compaction-events.test.ts +0 -2
  28. package/src/__tests__/compaction.benchmark.test.ts +0 -2
  29. package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
  30. package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
  31. package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
  32. package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
  33. package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
  34. package/src/__tests__/config-loader-backfill.test.ts +10 -123
  35. package/src/__tests__/config-schema.test.ts +0 -1
  36. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
  37. package/src/__tests__/contacts-relay-reads.test.ts +15 -13
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
  39. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  42. package/src/__tests__/conversation-agent-loop.test.ts +0 -134
  43. package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
  44. package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
  45. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
  46. package/src/__tests__/conversation-history-web-search.test.ts +0 -2
  47. package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
  48. package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
  49. package/src/__tests__/conversation-pairing.test.ts +0 -2
  50. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
  51. package/src/__tests__/conversation-process-callsite.test.ts +0 -2
  52. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
  53. package/src/__tests__/conversation-queue.test.ts +0 -91
  54. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
  55. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
  56. package/src/__tests__/conversation-slash-queue.test.ts +0 -2
  57. package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
  58. package/src/__tests__/conversation-speed-override.test.ts +0 -2
  59. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
  60. package/src/__tests__/conversation-title-service.test.ts +0 -2
  61. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
  62. package/src/__tests__/conversation-usage.test.ts +0 -2
  63. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
  64. package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
  65. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
  66. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  67. package/src/__tests__/db-migration-rollback.test.ts +171 -205
  68. package/src/__tests__/db-test-helpers.ts +4 -5
  69. package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
  70. package/src/__tests__/disk-pressure-guard.test.ts +0 -41
  71. package/src/__tests__/dm-persistence.test.ts +0 -2
  72. package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
  73. package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
  74. package/src/__tests__/exploration-drift-hook.test.ts +2 -3
  75. package/src/__tests__/filing-service.test.ts +0 -2
  76. package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
  77. package/src/__tests__/guardian-dispatch.test.ts +1 -95
  78. package/src/__tests__/guardian-outbound-http.test.ts +0 -13
  79. package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
  80. package/src/__tests__/heartbeat-service.test.ts +0 -2
  81. package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
  82. package/src/__tests__/host-app-control-routes.test.ts +30 -24
  83. package/src/__tests__/host-bash-routes.test.ts +41 -31
  84. package/src/__tests__/host-browser-routes.test.ts +32 -26
  85. package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
  86. package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
  87. package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
  88. package/src/__tests__/http-user-message-parity.test.ts +8 -290
  89. package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
  90. package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
  91. package/src/__tests__/invite-redemption-service.test.ts +0 -198
  92. package/src/__tests__/llm-context-normalization.test.ts +0 -105
  93. package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
  94. package/src/__tests__/llm-usage-store.test.ts +0 -25
  95. package/src/__tests__/mcp-health-check.test.ts +1 -2
  96. package/src/__tests__/media-stream-server-integration.test.ts +0 -127
  97. package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
  98. package/src/__tests__/messaging-send-tool.test.ts +0 -2
  99. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  100. package/src/__tests__/mtime-cache.test.ts +5 -146
  101. package/src/__tests__/native-web-search.test.ts +0 -2
  102. package/src/__tests__/non-member-access-request.test.ts +17 -189
  103. package/src/__tests__/notification-broadcaster.test.ts +0 -4
  104. package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
  105. package/src/__tests__/notification-deep-link.test.ts +0 -6
  106. package/src/__tests__/notification-guardian-path.test.ts +0 -19
  107. package/src/__tests__/openai-provider.test.ts +12 -22
  108. package/src/__tests__/openai-responses-provider.test.ts +2 -12
  109. package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
  110. package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
  111. package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
  112. package/src/__tests__/plugin-bootstrap.test.ts +73 -3
  113. package/src/__tests__/plugin-route-contribution.test.ts +17 -4
  114. package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
  115. package/src/__tests__/plugin-types.test.ts +2 -0
  116. package/src/__tests__/process-message-background-slack.test.ts +0 -2
  117. package/src/__tests__/process-message-display-content.test.ts +0 -2
  118. package/src/__tests__/provider-error-scenarios.test.ts +4 -5
  119. package/src/__tests__/provider-usage-tracking.test.ts +0 -39
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
  121. package/src/__tests__/registry.test.ts +1 -4
  122. package/src/__tests__/relay-server.test.ts +25 -694
  123. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
  124. package/src/__tests__/secret-ingress-http.test.ts +0 -14
  125. package/src/__tests__/send-endpoint-busy.test.ts +8 -30
  126. package/src/__tests__/skills.test.ts +0 -44
  127. package/src/__tests__/slack-inbound-verification.test.ts +2 -47
  128. package/src/__tests__/stt-hints.test.ts +13 -44
  129. package/src/__tests__/subagent-detail.test.ts +0 -27
  130. package/src/__tests__/subagent-disposal.test.ts +0 -65
  131. package/src/__tests__/subagent-notify-parent.test.ts +0 -2
  132. package/src/__tests__/subagent-role-registry.test.ts +2 -7
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
  134. package/src/__tests__/subagent-tools.test.ts +0 -2
  135. package/src/__tests__/suggestion-routes.test.ts +0 -2
  136. package/src/__tests__/title-generate-hook.test.ts +0 -2
  137. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
  138. package/src/__tests__/tool-executor.test.ts +11 -16
  139. package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
  140. package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
  141. package/src/__tests__/tool-start-timestamp.test.ts +0 -2
  142. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
  143. package/src/__tests__/twilio-routes.test.ts +0 -96
  144. package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
  145. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  146. package/src/__tests__/voice-invite-redemption.test.ts +0 -33
  147. package/src/__tests__/web-search-backend-failure.test.ts +0 -2
  148. package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
  149. package/src/__tests__/workspace-tool-loader.test.ts +2 -195
  150. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  151. package/src/agent/loop.ts +0 -56
  152. package/src/api/index.ts +1 -19
  153. package/src/api/responses/llm-request-log-entry.ts +0 -29
  154. package/src/api/responses/subagent-detail.ts +0 -17
  155. package/src/api/surfaces.ts +3 -39
  156. package/src/approvals/guardian-request-resolvers.ts +11 -1
  157. package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
  158. package/src/calls/call-domain.ts +3 -3
  159. package/src/calls/guardian-dispatch.ts +8 -10
  160. package/src/calls/inbound-trust-reader.ts +1 -17
  161. package/src/calls/media-stream-server.ts +0 -21
  162. package/src/calls/relay-server.ts +50 -167
  163. package/src/calls/relay-setup-router.ts +7 -37
  164. package/src/calls/relay-verification.ts +4 -4
  165. package/src/calls/stt-hints.ts +12 -9
  166. package/src/calls/twilio-routes.ts +4 -14
  167. package/src/channels/types.ts +20 -10
  168. package/src/cli/commands/__tests__/cache.test.ts +1 -8
  169. package/src/cli/commands/cache.ts +181 -194
  170. package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
  171. package/src/cli/commands/db/status.ts +1 -37
  172. package/src/cli/commands/mcp.ts +218 -252
  173. package/src/cli/commands/memory/index.ts +0 -2
  174. package/src/cli/commands/plugins.ts +3 -75
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
  177. package/src/cli/lib/list-installed-plugins.ts +1 -179
  178. package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
  179. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
  180. package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
  181. package/src/config/bundled-skills/subagent/SKILL.md +1 -1
  182. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  183. package/src/config/feature-flag-registry.json +13 -5
  184. package/src/config/loader.ts +5 -38
  185. package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
  186. package/src/config/schemas/memory-lifecycle.ts +0 -12
  187. package/src/config/schemas/memory-v3.ts +0 -7
  188. package/src/config/schemas/memory.ts +0 -4
  189. package/src/config/schemas/timeouts.ts +0 -8
  190. package/src/config/seed-inference-profiles.ts +11 -21
  191. package/src/config/skills.ts +5 -27
  192. package/src/config/sync-gated-profiles.ts +13 -12
  193. package/src/contacts/contacts-write.ts +0 -3
  194. package/src/daemon/assistant-attachments.ts +4 -27
  195. package/src/daemon/conversation-agent-loop.ts +0 -28
  196. package/src/daemon/conversation-process.ts +16 -35
  197. package/src/daemon/conversation-surfaces.ts +38 -111
  198. package/src/daemon/conversation-tool-setup.ts +16 -50
  199. package/src/daemon/conversation.ts +1 -13
  200. package/src/daemon/disk-pressure-guard.ts +2 -12
  201. package/src/daemon/event-loop-watchdog.ts +1 -28
  202. package/src/daemon/external-plugins-bootstrap.ts +34 -4
  203. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
  204. package/src/daemon/handlers/config-a2a.ts +14 -6
  205. package/src/daemon/handlers/config-channels.ts +22 -78
  206. package/src/daemon/handlers/conversations.ts +0 -77
  207. package/src/daemon/lifecycle.ts +0 -4
  208. package/src/daemon/mcp-reload-service.ts +0 -10
  209. package/src/daemon/memory-v2-startup.test.ts +0 -72
  210. package/src/daemon/memory-v2-startup.ts +19 -87
  211. package/src/daemon/message-types/conversations.ts +0 -2
  212. package/src/daemon/message-types/surfaces.ts +12 -12
  213. package/src/daemon/server.ts +4 -0
  214. package/src/daemon/shutdown-handlers.ts +0 -20
  215. package/src/daemon/tool-setup-types.ts +0 -9
  216. package/src/daemon/workspace-tools-watcher.ts +328 -0
  217. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  218. package/src/ipc/assistant-server.ts +2 -2
  219. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  220. package/src/mcp/client.ts +1 -15
  221. package/src/mcp/mcp-auth-orchestrator.ts +1 -6
  222. package/src/mcp/mcp-oauth-provider.ts +8 -19
  223. package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
  224. package/src/memory/conversation-crud.ts +0 -38
  225. package/src/memory/db-connection.ts +3 -22
  226. package/src/memory/db-init.ts +502 -36
  227. package/src/memory/db-singleton.ts +4 -6
  228. package/src/memory/jobs-worker.ts +0 -58
  229. package/src/memory/llm-request-log-store.ts +1 -26
  230. package/src/memory/llm-usage-store.ts +20 -48
  231. package/src/memory/memory-retrospective-job.ts +8 -9
  232. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
  233. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  234. package/src/memory/migrations/registry.ts +573 -0
  235. package/src/memory/migrations/run-migrations.ts +6 -90
  236. package/src/memory/migrations/validate-migration-state.ts +66 -101
  237. package/src/memory/schema/conversations.ts +0 -9
  238. package/src/memory/schema/infrastructure.ts +0 -20
  239. package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
  240. package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
  241. package/src/memory/v2/cli-command-store.ts +38 -75
  242. package/src/memory/v2/prompts/consolidation.ts +82 -13
  243. package/src/memory/v2/prompts/router.ts +93 -21
  244. package/src/memory/v2/skill-store.ts +31 -68
  245. package/src/notifications/__tests__/broadcaster.test.ts +8 -16
  246. package/src/notifications/__tests__/decision-engine.test.ts +9 -78
  247. package/src/notifications/broadcaster.ts +1 -8
  248. package/src/notifications/decision-engine.ts +7 -15
  249. package/src/notifications/destination-resolver.ts +24 -68
  250. package/src/notifications/emit-signal.ts +14 -39
  251. package/src/permissions/question-prompter.test.ts +1 -1
  252. package/src/permissions/question-prompter.ts +4 -7
  253. package/src/plugin-api/index.ts +6 -6
  254. package/src/plugin-api/types.ts +5 -3
  255. package/src/plugin-api/vision-support.test.ts +4 -28
  256. package/src/plugin-api/vision-support.ts +31 -66
  257. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
  258. package/src/plugins/defaults/advisor/consult.ts +6 -110
  259. package/src/plugins/defaults/advisor/steering.ts +2 -14
  260. package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
  261. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
  262. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
  263. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
  264. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
  265. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
  266. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
  267. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
  268. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
  269. package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
  270. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
  271. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
  272. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
  273. package/src/plugins/mtime-cache.ts +291 -76
  274. package/src/plugins/pipeline.ts +13 -111
  275. package/src/plugins/types.ts +2 -0
  276. package/src/providers/anthropic/client.ts +0 -5
  277. package/src/providers/call-site-routing.ts +0 -4
  278. package/src/providers/model-catalog.ts +0 -16
  279. package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
  280. package/src/providers/openai/chat-completions-provider.ts +83 -37
  281. package/src/providers/openai/responses-provider.ts +46 -50
  282. package/src/providers/openrouter/client.ts +0 -5
  283. package/src/providers/provider-send-message.ts +0 -4
  284. package/src/providers/ratelimit.ts +0 -4
  285. package/src/providers/retry.ts +0 -4
  286. package/src/providers/types.ts +0 -9
  287. package/src/providers/usage-tracking.ts +0 -4
  288. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
  289. package/src/runtime/access-request-helper.ts +39 -19
  290. package/src/runtime/actor-trust-resolver.ts +2 -2
  291. package/src/runtime/assistant-event-hub.ts +1 -1
  292. package/src/runtime/assistant-stream-state.ts +2 -9
  293. package/src/runtime/auth/require-bound-guardian.ts +11 -21
  294. package/src/runtime/channel-verification-service.ts +31 -56
  295. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  296. package/src/runtime/guardian-vellum-migration.ts +7 -66
  297. package/src/runtime/invite-redemption-service.ts +187 -198
  298. package/src/runtime/local-actor-identity.ts +11 -76
  299. package/src/runtime/pending-interactions.ts +1 -11
  300. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
  301. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  302. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
  303. package/src/runtime/routes/browser-routes.ts +1 -1
  304. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
  305. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  306. package/src/runtime/routes/contact-routes.ts +32 -8
  307. package/src/runtime/routes/conversation-cli-routes.ts +5 -4
  308. package/src/runtime/routes/conversation-list-routes.ts +7 -4
  309. package/src/runtime/routes/conversation-query-routes.ts +0 -72
  310. package/src/runtime/routes/conversation-routes.ts +85 -84
  311. package/src/runtime/routes/events-routes.ts +2 -2
  312. package/src/runtime/routes/global-search-routes.ts +1 -3
  313. package/src/runtime/routes/guardian-action-routes.ts +5 -4
  314. package/src/runtime/routes/host-app-control-routes.ts +4 -5
  315. package/src/runtime/routes/host-bash-routes.ts +4 -5
  316. package/src/runtime/routes/host-browser-routes.ts +11 -9
  317. package/src/runtime/routes/host-cu-routes.ts +4 -5
  318. package/src/runtime/routes/host-file-routes.ts +4 -5
  319. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  320. package/src/runtime/routes/http-adapter.ts +1 -1
  321. package/src/runtime/routes/identity-routes.ts +2 -3
  322. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  323. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
  324. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
  325. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
  326. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  327. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
  328. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
  329. package/src/runtime/routes/index.ts +0 -2
  330. package/src/runtime/routes/llm-context-normalization.ts +0 -83
  331. package/src/runtime/routes/mcp-auth-routes.ts +19 -171
  332. package/src/runtime/routes/migration-rollback-routes.ts +3 -4
  333. package/src/runtime/routes/migration-routes.ts +1 -4
  334. package/src/runtime/routes/subagents-routes.ts +0 -5
  335. package/src/runtime/routes/surface-action-routes.ts +56 -42
  336. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
  337. package/src/runtime/services/conversation-serializer.ts +9 -7
  338. package/src/runtime/tool-grant-request-helper.ts +3 -3
  339. package/src/runtime/trust-verdict-consumer.ts +9 -85
  340. package/src/runtime/verification-outbound-actions.ts +18 -18
  341. package/src/signals/user-message.ts +0 -16
  342. package/src/subagent/manager.ts +0 -9
  343. package/src/subagent/types.ts +3 -3
  344. package/src/telemetry/types.ts +1 -34
  345. package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
  346. package/src/telemetry/usage-telemetry-reporter.ts +3 -87
  347. package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
  348. package/src/tools/ask-question/ask-question-tool.ts +0 -13
  349. package/src/tools/executor.ts +4 -4
  350. package/src/tools/registry.ts +0 -18
  351. package/src/tools/shared/filesystem/path-policy.ts +5 -12
  352. package/src/tools/tool-approval-handler.ts +1 -1
  353. package/src/tools/tool-defaults.ts +2 -9
  354. package/src/tools/tool-manifest.ts +0 -3
  355. package/src/tools/types.ts +2 -17
  356. package/src/tools/workspace-tools/loader.ts +244 -348
  357. package/src/util/errors.ts +1 -26
  358. package/src/util/platform.ts +0 -5
  359. package/src/workflows/library.test.ts +0 -140
  360. package/src/workflows/library.ts +28 -82
  361. package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
  362. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
  363. package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
  364. package/src/workspace/migrations/registry.ts +0 -2
  365. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
  366. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
  367. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
  368. package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
  369. package/src/__tests__/code-search-tool.test.ts +0 -585
  370. package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
  371. package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
  372. package/src/__tests__/plugin-pipeline.test.ts +0 -96
  373. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
  374. package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
  375. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
  376. package/src/agent/loop-exclusive-tool.test.ts +0 -150
  377. package/src/api/constants/sse-replay.ts +0 -41
  378. package/src/api/events/conversation-notice.ts +0 -26
  379. package/src/approvals/guardian-channel-delivery.ts +0 -30
  380. package/src/approvals/guardian-expiry-notifier.ts +0 -148
  381. package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
  382. package/src/cli/commands/memory/worker.ts +0 -175
  383. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
  384. package/src/config/prune-seeded-callsite-defaults.ts +0 -110
  385. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
  386. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
  387. package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
  388. package/src/contacts/guardian-delivery-reader.ts +0 -223
  389. package/src/contacts/member-write-relay.ts +0 -189
  390. package/src/daemon/conversation-notices.ts +0 -60
  391. package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
  392. package/src/hooks/hook-loader.ts +0 -341
  393. package/src/mcp/mcp-header-store.ts +0 -134
  394. package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
  395. package/src/memory/__tests__/prompt-override.test.ts +0 -192
  396. package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
  397. package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
  398. package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
  399. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
  400. package/src/memory/prompt-override.ts +0 -129
  401. package/src/memory/steps.ts +0 -573
  402. package/src/memory/watchdog-events-store.ts +0 -87
  403. package/src/memory/worker-control.ts +0 -118
  404. package/src/memory/worker-process.ts +0 -72
  405. package/src/notifications/__tests__/connected-channels.test.ts +0 -114
  406. package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
  407. package/src/onboarding/checkin-event.test.ts +0 -222
  408. package/src/onboarding/checkin-event.ts +0 -321
  409. package/src/onboarding/schedule-checkin.ts +0 -190
  410. package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
  411. package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
  412. package/src/plugins/defaults/advisor/context-pack.ts +0 -288
  413. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
  414. package/src/plugins/surface-import.ts +0 -121
  415. package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
  416. package/src/providers/openai/api-error-normalization.ts +0 -270
  417. package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
  418. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
  419. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
  420. package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
  421. package/src/runtime/anchored-guardian.test.ts +0 -156
  422. package/src/runtime/anchored-guardian.ts +0 -135
  423. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
  424. package/src/runtime/local-principal-trust.ts +0 -52
  425. package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
  426. package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
  427. package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
  428. package/src/tools/filesystem/search.ts +0 -543
  429. package/src/util/telemetry-db-path.ts +0 -24
  430. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
@@ -1,585 +0,0 @@
1
- import {
2
- chmodSync,
3
- mkdirSync,
4
- mkdtempSync,
5
- readdirSync,
6
- readFileSync,
7
- realpathSync,
8
- rmSync,
9
- statSync,
10
- writeFileSync,
11
- } from "node:fs";
12
- import { tmpdir } from "node:os";
13
- import { join } from "node:path";
14
- import { afterEach, describe, expect, test } from "bun:test";
15
-
16
- import { SUBAGENT_ONLY_TOOL_NAMES } from "../daemon/conversation-tool-setup.js";
17
- import { codeSearchTool } from "../tools/filesystem/search.js";
18
- import type { ToolContext } from "../tools/types.js";
19
-
20
- // ---------------------------------------------------------------------------
21
- // Helpers
22
- // ---------------------------------------------------------------------------
23
-
24
- const testDirs: string[] = [];
25
-
26
- function makeTempDir(): string {
27
- const dir = realpathSync(mkdtempSync(join(tmpdir(), "code-search-test-")));
28
- testDirs.push(dir);
29
- return dir;
30
- }
31
-
32
- afterEach(() => {
33
- for (const dir of testDirs.splice(0)) {
34
- rmSync(dir, { recursive: true, force: true });
35
- }
36
- });
37
-
38
- function makeToolContext(workingDir: string): ToolContext {
39
- return {
40
- workingDir,
41
- conversationId: "test-conv",
42
- trustClass: "guardian",
43
- };
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // code_search
48
- // ---------------------------------------------------------------------------
49
-
50
- describe("codeSearchTool", () => {
51
- test("finds a known pattern with correct file:line", async () => {
52
- const dir = makeTempDir();
53
- writeFileSync(join(dir, "a.ts"), "const foo = 1;\nconst bar = 2;\n");
54
-
55
- const result = await codeSearchTool.execute(
56
- { pattern: "bar", activity: "search" },
57
- makeToolContext(dir),
58
- );
59
-
60
- expect(result.isError).toBe(false);
61
- expect(result.content).toBe("a.ts:2: const bar = 2;");
62
- });
63
-
64
- test("respects glob filter", async () => {
65
- const dir = makeTempDir();
66
- writeFileSync(join(dir, "match.ts"), "needle here\n");
67
- writeFileSync(join(dir, "skip.md"), "needle here too\n");
68
-
69
- const result = await codeSearchTool.execute(
70
- { pattern: "needle", glob: "*.ts", activity: "search" },
71
- makeToolContext(dir),
72
- );
73
-
74
- expect(result.isError).toBe(false);
75
- expect(result.content).toContain("match.ts:1:");
76
- expect(result.content).not.toContain("skip.md");
77
- });
78
-
79
- test("rejects a path escaping the workspace root", async () => {
80
- const dir = makeTempDir();
81
-
82
- const result = await codeSearchTool.execute(
83
- { pattern: "anything", path: "../../../etc", activity: "search" },
84
- makeToolContext(dir),
85
- );
86
-
87
- expect(result.isError).toBe(true);
88
- expect(result.content).toContain("outside the working directory");
89
- });
90
-
91
- test("case_insensitive matching", async () => {
92
- const dir = makeTempDir();
93
- writeFileSync(join(dir, "a.txt"), "Hello WORLD\n");
94
-
95
- const sensitive = await codeSearchTool.execute(
96
- { pattern: "world", activity: "search" },
97
- makeToolContext(dir),
98
- );
99
- expect(sensitive.content).toContain("No matches found");
100
-
101
- const insensitive = await codeSearchTool.execute(
102
- { pattern: "world", case_insensitive: true, activity: "search" },
103
- makeToolContext(dir),
104
- );
105
- expect(insensitive.isError).toBe(false);
106
- expect(insensitive.content).toBe("a.txt:1: Hello WORLD");
107
- });
108
-
109
- test("no match returns a clear empty result", async () => {
110
- const dir = makeTempDir();
111
- writeFileSync(join(dir, "a.txt"), "nothing relevant\n");
112
-
113
- const result = await codeSearchTool.execute(
114
- { pattern: "absent-token", activity: "search" },
115
- makeToolContext(dir),
116
- );
117
-
118
- expect(result.isError).toBe(false);
119
- expect(result.content).toContain("No matches found");
120
- });
121
-
122
- test("non-existent root path returns a path error, not a false 'No matches'", async () => {
123
- const dir = makeTempDir();
124
- writeFileSync(join(dir, "a.ts"), "needle\n");
125
-
126
- // A typo'd subdirectory (e.g. "srcc") must surface an actionable path error
127
- // rather than being swallowed and reported as a successful empty search.
128
- const result = await codeSearchTool.execute(
129
- { pattern: "needle", path: "srcc", activity: "search" },
130
- makeToolContext(dir),
131
- );
132
-
133
- expect(result.isError).toBe(true);
134
- expect(result.content).toContain("path not found");
135
- expect(result.content).not.toContain("No matches found");
136
- });
137
-
138
- test("searches a single file when the root resolves to a regular file", async () => {
139
- const dir = makeTempDir();
140
- writeFileSync(join(dir, "a.ts"), "const foo = 1;\nconst needle = 2;\n");
141
-
142
- const result = await codeSearchTool.execute(
143
- { pattern: "needle", path: "a.ts", activity: "search" },
144
- makeToolContext(dir),
145
- );
146
-
147
- expect(result.isError).toBe(false);
148
- expect(result.content).toBe("a.ts:2: const needle = 2;");
149
- });
150
-
151
- test("an oversized explicit-file root returns an error, not a false 'No matches'", async () => {
152
- const dir = makeTempDir();
153
- // A single-file root larger than MAX_FILE_BYTES (8 MiB) was never searched,
154
- // so reporting "No matches found" would be a silent false negative. Surface
155
- // a hard error instead.
156
- const oversize = 8 * 1024 * 1024 + 1;
157
- writeFileSync(join(dir, "huge.txt"), "x".repeat(oversize));
158
-
159
- const result = await codeSearchTool.execute(
160
- { pattern: "x", path: "huge.txt", activity: "search" },
161
- makeToolContext(dir),
162
- );
163
-
164
- expect(result.isError).toBe(true);
165
- expect(result.content).toContain("file too large to search");
166
- expect(result.content).not.toContain("No matches found");
167
- });
168
-
169
- test("an unreadable explicit file root surfaces a read error, not a false 'No matches'", async () => {
170
- const dir = makeTempDir();
171
- const f = join(dir, "secret.txt");
172
- writeFileSync(f, "needle here\n");
173
- chmodSync(f, 0o000);
174
- // When the suite runs as root (e.g. CI in Docker), chmod 000 does not block
175
- // reads, so EACCES can't be simulated — skip the assertion in that case.
176
- let readable = true;
177
- try {
178
- readFileSync(f);
179
- } catch {
180
- readable = false;
181
- }
182
- if (!readable) {
183
- const result = await codeSearchTool.execute(
184
- { pattern: "needle", path: "secret.txt", activity: "search" },
185
- makeToolContext(dir),
186
- );
187
- expect(result.isError).toBe(true);
188
- expect(result.content).toContain("failed to read file");
189
- expect(result.content).not.toContain("No matches found");
190
- }
191
- chmodSync(f, 0o644);
192
- });
193
-
194
- test("an unreadable explicit directory root surfaces a read error, not a false 'No matches'", async () => {
195
- const dir = makeTempDir();
196
- const subdir = join(dir, "locked");
197
- mkdirSync(subdir);
198
- writeFileSync(join(subdir, "a.ts"), "needle here\n");
199
- chmodSync(subdir, 0o000);
200
- // When the suite runs as root (e.g. CI in Docker), chmod 000 does not block
201
- // reads, so EACCES can't be simulated — probe readdir and skip the assertion
202
- // in that case.
203
- let readable = true;
204
- try {
205
- readdirSync(subdir);
206
- } catch {
207
- readable = false;
208
- }
209
- if (!readable) {
210
- const result = await codeSearchTool.execute(
211
- { pattern: "needle", path: "locked", activity: "search" },
212
- makeToolContext(dir),
213
- );
214
- expect(result.isError).toBe(true);
215
- expect(result.content).toContain("failed to read directory");
216
- expect(result.content).not.toContain("No matches found");
217
- }
218
- chmodSync(subdir, 0o755);
219
- });
220
-
221
- test("an oversized file skipped mid-walk keeps scanning others but flags the result incomplete", async () => {
222
- const dir = makeTempDir();
223
- // A directory search must keep scanning sibling files when it hits an
224
- // oversized one (so a smaller file still matches), yet flag the overall
225
- // result incomplete since the skipped file could have contained the pattern.
226
- const oversize = 8 * 1024 * 1024 + 1;
227
- writeFileSync(join(dir, "huge.txt"), "needle " + "x".repeat(oversize));
228
- writeFileSync(join(dir, "small.txt"), "needle here\n");
229
-
230
- const result = await codeSearchTool.execute(
231
- { pattern: "needle", activity: "search" },
232
- makeToolContext(dir),
233
- );
234
-
235
- expect(result.isError).toBe(false);
236
- // The walk wasn't aborted by the skip — the small file still matched...
237
- expect(result.content).toContain("small.txt");
238
- // ...and the result is flagged incomplete with a size-limit note.
239
- expect(result.status).toBe("truncated");
240
- expect(result.content).toContain("size limit");
241
- });
242
-
243
- test("a directory whose only candidate is oversized reports incompleteness, not a clean miss", async () => {
244
- const dir = makeTempDir();
245
- const oversize = 8 * 1024 * 1024 + 1;
246
- writeFileSync(join(dir, "huge.txt"), "needle " + "x".repeat(oversize));
247
-
248
- const result = await codeSearchTool.execute(
249
- { pattern: "needle", activity: "search" },
250
- makeToolContext(dir),
251
- );
252
-
253
- expect(result.isError).toBe(false);
254
- // Zero matches because the only file was skipped — but the result must say
255
- // it's incomplete (status truncated + size-limit note), not a definitive miss.
256
- expect(result.status).toBe("truncated");
257
- expect(result.content).toContain("size limit");
258
- });
259
-
260
- test("a single multi-MiB matching line is found but its printed output is display-bounded", async () => {
261
- const dir = makeTempDir();
262
- // One matching line many megabytes wide whose file stays under MAX_FILE_BYTES
263
- // (8 MiB) so it isn't skipped. The match is on the full line (so it's found),
264
- // but the EMITTED line is truncated to the display cap — so a single wide
265
- // line cannot, by itself, blow the output-byte budget anymore. The result is
266
- // a clean single match, not truncated, with the printed line bounded.
267
- const lineLen = 5 * 1024 * 1024;
268
- writeFileSync(join(dir, "wide.txt"), "needle" + "z".repeat(lineLen) + "\n");
269
-
270
- const result = await codeSearchTool.execute(
271
- { pattern: "needle", path: "wide.txt", activity: "search" },
272
- makeToolContext(dir),
273
- );
274
-
275
- expect(result.isError).toBe(false);
276
- // Found on the full line — never a false "No matches" or scan-cap message.
277
- expect(result.content).not.toContain("No matches found");
278
- expect(result.content).not.toContain("scan cap");
279
- expect(result.content).toContain("wide.txt:1:");
280
- // The printed line is display-truncated, so the output stays tiny and the
281
- // output budget is never hit — single match => not truncated.
282
- expect(result.content).toContain("…[line truncated]");
283
- expect(result.status).toBeUndefined();
284
- expect(result.content).not.toContain("Output capped");
285
- // The multi-MiB body never reaches the emitted output.
286
- expect(Buffer.byteLength(result.content, "utf8")).toBeLessThan(64 * 1024);
287
- });
288
-
289
- test("single-file search still honors the denied-basename guard", async () => {
290
- const dir = makeTempDir();
291
- writeFileSync(join(dir, "backup.key"), "supersecret token\n");
292
-
293
- const result = await codeSearchTool.execute(
294
- { pattern: "supersecret", path: "backup.key", activity: "search" },
295
- makeToolContext(dir),
296
- );
297
-
298
- // The denied basename is rejected by the sandbox policy before any read,
299
- // so the secret never surfaces in the result.
300
- expect(result.isError).toBe(true);
301
- expect(result.content).not.toContain("supersecret");
302
- });
303
-
304
- test("max_results truncates and reports it", async () => {
305
- const dir = makeTempDir();
306
- const body = Array.from({ length: 10 }, () => "match").join("\n");
307
- writeFileSync(join(dir, "a.txt"), body + "\n");
308
-
309
- const result = await codeSearchTool.execute(
310
- { pattern: "match", max_results: 3, activity: "search" },
311
- makeToolContext(dir),
312
- );
313
-
314
- expect(result.isError).toBe(false);
315
- expect(result.status).toBe("truncated");
316
- expect(result.content).toContain("truncated at 3 matches");
317
- const matchLines = result.content
318
- .split("\n")
319
- .filter((l) => l.startsWith("a.txt:"));
320
- expect(matchLines.length).toBe(3);
321
- });
322
-
323
- test("includes context lines when requested", async () => {
324
- const dir = makeTempDir();
325
- writeFileSync(join(dir, "a.txt"), "before\nTARGET\nafter\n");
326
-
327
- const result = await codeSearchTool.execute(
328
- { pattern: "TARGET", context_lines: 1, activity: "search" },
329
- makeToolContext(dir),
330
- );
331
-
332
- expect(result.isError).toBe(false);
333
- expect(result.content).toContain("a.txt:1- before");
334
- expect(result.content).toContain("a.txt:2: TARGET");
335
- expect(result.content).toContain("a.txt:3- after");
336
- });
337
-
338
- test("matches a token past the display cap on a long line, truncating only the printed output", async () => {
339
- const dir = makeTempDir();
340
- // A line far longer than MAX_DISPLAY_LINE_LENGTH (2000) whose matchable token
341
- // sits well beyond column 2000. With RE2's linear-time matching the pattern
342
- // is run against the FULL line, so the token MUST be found — but the emitted
343
- // line is truncated for display with a clear marker. This also exercises that
344
- // a huge line is handled without throwing/hanging.
345
- const prefix = "x".repeat(5000);
346
- writeFileSync(
347
- join(dir, "huge.txt"),
348
- `${prefix}BEYOND_CAP_TOKEN\ninside INSIDE_CAP_TOKEN here\n`,
349
- );
350
-
351
- const beyond = await codeSearchTool.execute(
352
- { pattern: "BEYOND_CAP_TOKEN", activity: "search" },
353
- makeToolContext(dir),
354
- );
355
- expect(beyond.isError).toBe(false);
356
- // The token appears past column 2000, but matching runs against the full
357
- // line, so it IS found (correct grep behavior — no false "No matches").
358
- expect(beyond.content).not.toContain("No matches found");
359
- expect(beyond.content).toContain("huge.txt:1:");
360
- // The displayed line is truncated with a marker, so the multi-kilobyte line
361
- // never balloons the output...
362
- expect(beyond.content).toContain("…[line truncated]");
363
- // ...and the matched token (which sits beyond the display cap) is NOT echoed
364
- // back in the bounded output even though the match was found.
365
- expect(beyond.content).not.toContain("BEYOND_CAP_TOKEN\n");
366
- expect(beyond.content.length).toBeLessThan(prefix.length);
367
-
368
- const inside = await codeSearchTool.execute(
369
- { pattern: "INSIDE_CAP_TOKEN", activity: "search" },
370
- makeToolContext(dir),
371
- );
372
- expect(inside.isError).toBe(false);
373
- expect(inside.content).toContain("huge.txt:2:");
374
- // A short line is emitted verbatim — no truncation marker.
375
- expect(inside.content).toContain("inside INSIDE_CAP_TOKEN here");
376
- expect(inside.content).not.toContain("…[line truncated]");
377
- });
378
-
379
- test("caps output via the byte budget when many matches have context", async () => {
380
- const dir = makeTempDir();
381
- // Many matches with context lines. Even though context_lines is requested
382
- // far above the clamp (MAX_CONTEXT_LINES = 20), the clamp plus the
383
- // output-byte budget must produce a truncated result rather than an
384
- // unbounded one. The source file stays under MAX_FILE_BYTES (8 MiB) so it
385
- // isn't skipped, but because every line both matches and is re-emitted as
386
- // context for ~41 nearby matches, the accumulated output crosses the 4 MiB
387
- // budget.
388
- const filler = "y".repeat(200);
389
- const lineCount = 20_000;
390
- const body = Array.from(
391
- { length: lineCount },
392
- () => `match ${filler}`,
393
- ).join("\n");
394
- writeFileSync(join(dir, "big.txt"), body + "\n");
395
-
396
- const result = await codeSearchTool.execute(
397
- {
398
- pattern: "match",
399
- context_lines: 1000,
400
- max_results: 1_000_000,
401
- activity: "search",
402
- },
403
- makeToolContext(dir),
404
- );
405
-
406
- expect(result.isError).toBe(false);
407
- expect(result.status).toBe("truncated");
408
- expect(result.content).toContain("Output capped");
409
- // The accumulated output must stay near the budget, not balloon to the full
410
- // file size (~20 MiB of body * surrounding context).
411
- expect(Buffer.byteLength(result.content, "utf8")).toBeLessThan(
412
- 8 * 1024 * 1024,
413
- );
414
- });
415
-
416
- test("an already-aborted signal stops the scan and reports a timed-out result", async () => {
417
- const dir = makeTempDir();
418
- writeFileSync(join(dir, "a.txt"), "needle here\nneedle there\n");
419
-
420
- // The wall-clock deadline (MAX_SEARCH_MS) and the abort signal are checked
421
- // at the same per-line checkpoint. MAX_SEARCH_MS is a 10s const that isn't
422
- // injectable, so we exercise the shared stop path deterministically via an
423
- // already-aborted signal: the scan must stop at the first line check and
424
- // return a truncated/timed-out result instead of running the regex.
425
- const controller = new AbortController();
426
- controller.abort();
427
- const context = { ...makeToolContext(dir), signal: controller.signal };
428
-
429
- const result = await codeSearchTool.execute(
430
- { pattern: "needle", activity: "search" },
431
- context,
432
- );
433
-
434
- // Aborted before any match was emitted, so this is the zero-match
435
- // timed-out branch (incomplete), not a definitive "No matches found".
436
- expect(result.isError).toBe(false);
437
- expect(result.status).toBe("truncated");
438
- expect(result.content).toContain("timed out");
439
- expect(result.content).not.toContain("No matches found");
440
- });
441
-
442
- test("an already-aborted signal stops the directory traversal, not just per-line scanning", async () => {
443
- const dir = makeTempDir();
444
- // Build a small tree of subdirectories and files. The traversal deadline /
445
- // abort check now lives at the top of walk() and inside its per-entry loop
446
- // (shared with the per-line checkpoint), so an already-aborted signal must
447
- // stop the walk before it descends/scans the whole tree, returning a
448
- // truncated/timed-out result rather than scanning everything.
449
- for (let d = 0; d < 5; d++) {
450
- const sub = join(dir, `sub${d}`);
451
- mkdirSync(sub);
452
- for (let f = 0; f < 5; f++) {
453
- writeFileSync(join(sub, `f${f}.txt`), "needle here\n");
454
- }
455
- }
456
-
457
- const controller = new AbortController();
458
- controller.abort();
459
- const context = { ...makeToolContext(dir), signal: controller.signal };
460
-
461
- const result = await codeSearchTool.execute(
462
- { pattern: "needle", activity: "search" },
463
- context,
464
- );
465
-
466
- // The traversal honored the abort: stopped promptly with a timed-out /
467
- // incomplete result instead of a definitive miss or full scan.
468
- expect(result.isError).toBe(false);
469
- expect(result.status).toBe("truncated");
470
- expect(result.content).toContain("timed out");
471
- expect(result.content).not.toContain("No matches found");
472
- });
473
-
474
- test("a catastrophic-backtracking pattern returns near-instantly (linear-time RE2)", async () => {
475
- const dir = makeTempDir();
476
- // The classic ReDoS proof: `(a+)+$` against many non-matching lines of the
477
- // form `'a'.repeat(50) + '!'`. Under V8's backtracking RegExp a single
478
- // `regex.test()` on one such line blocks the event loop for seconds, and
479
- // the synchronous scan never yields so neither the wall-clock deadline nor
480
- // the promise timeout can interrupt it — the call would hang. With the
481
- // linear-time RE2 engine the whole file scans in milliseconds and returns
482
- // a clean, non-truncated "No matches found". The small per-call timeout
483
- // below would trip if matching ever fell back to backtracking.
484
- const evilLine = "a".repeat(50) + "!"; // forces backtracking, no match
485
- const body = Array.from({ length: 200 }, () => evilLine).join("\n");
486
- writeFileSync(join(dir, "evil.txt"), body + "\n");
487
-
488
- const result = await codeSearchTool.execute(
489
- { pattern: "(a+)+$", activity: "search" },
490
- makeToolContext(dir),
491
- );
492
-
493
- expect(result.isError).toBe(false);
494
- // Linear-time matching completes well within the deadline, so this is a
495
- // definitive miss, not a timed-out/truncated result.
496
- expect(result.status).toBeUndefined();
497
- expect(result.content).toContain("No matches found");
498
- }, 5_000);
499
-
500
- test("an unsupported pattern (backreference) returns a clean error, not a throw", async () => {
501
- const dir = makeTempDir();
502
- writeFileSync(join(dir, "a.txt"), "aa\n");
503
-
504
- // RE2 does not support backreferences (or lookarounds). The pattern must be
505
- // rejected at compile time and surfaced as an isError result rather than
506
- // throwing out of execute().
507
- const result = await codeSearchTool.execute(
508
- { pattern: "(a)\\1", activity: "search" },
509
- makeToolContext(dir),
510
- );
511
-
512
- expect(result.isError).toBe(true);
513
- expect(result.content).toContain("invalid or unsupported pattern");
514
- });
515
-
516
- test("does not return contents of denied-basename files", async () => {
517
- const dir = makeTempDir();
518
- // A denied file (forbidden to file_read/file_write) that contains the
519
- // search pattern must never surface in code_search results.
520
- writeFileSync(join(dir, ".backup.key"), "supersecret token\n");
521
- writeFileSync(join(dir, "backup.key"), "another supersecret\n");
522
- writeFileSync(join(dir, "ok.txt"), "supersecret in a normal file\n");
523
-
524
- const result = await codeSearchTool.execute(
525
- { pattern: "supersecret", activity: "search" },
526
- makeToolContext(dir),
527
- );
528
-
529
- expect(result.isError).toBe(false);
530
- expect(result.content).toContain("ok.txt:1:");
531
- expect(result.content).not.toContain(".backup.key");
532
- expect(result.content).not.toContain("backup.key");
533
- });
534
-
535
- test("ignores node_modules and .git", async () => {
536
- const dir = makeTempDir();
537
- mkdirSync(join(dir, "node_modules"));
538
- writeFileSync(join(dir, "node_modules", "dep.js"), "secret\n");
539
- writeFileSync(join(dir, "src.js"), "secret\n");
540
-
541
- const result = await codeSearchTool.execute(
542
- { pattern: "secret", activity: "search" },
543
- makeToolContext(dir),
544
- );
545
-
546
- expect(result.content).toContain("src.js:1:");
547
- expect(result.content).not.toContain("node_modules");
548
- });
549
-
550
- test("requires a non-empty pattern", async () => {
551
- const dir = makeTempDir();
552
- const result = await codeSearchTool.execute(
553
- { pattern: "", activity: "search" },
554
- makeToolContext(dir),
555
- );
556
- expect(result.isError).toBe(true);
557
- expect(result.content).toContain("pattern is required");
558
- });
559
-
560
- test("is read-only: directory is unchanged after a search", async () => {
561
- const dir = makeTempDir();
562
- const filePath = join(dir, "a.txt");
563
- writeFileSync(filePath, "hello search\n");
564
- const before = readFileSync(filePath, "utf8");
565
- const mtimeBefore = statSync(filePath).mtimeMs;
566
-
567
- await codeSearchTool.execute(
568
- { pattern: "search", activity: "search" },
569
- makeToolContext(dir),
570
- );
571
-
572
- expect(readFileSync(filePath, "utf8")).toBe(before);
573
- expect(statSync(filePath).mtimeMs).toBe(mtimeBefore);
574
- });
575
- });
576
-
577
- // ---------------------------------------------------------------------------
578
- // Subagent-only visibility
579
- // ---------------------------------------------------------------------------
580
-
581
- describe("code_search subagent visibility", () => {
582
- test("code_search is in SUBAGENT_ONLY_TOOL_NAMES", () => {
583
- expect(SUBAGENT_ONLY_TOOL_NAMES.has("code_search")).toBe(true);
584
- });
585
- });