@vellumai/assistant 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1346) hide show
  1. package/AGENTS.md +20 -0
  2. package/ARCHITECTURE.md +45 -36
  3. package/Dockerfile +26 -6
  4. package/README.md +8 -10
  5. package/__tests__/permissions/gateway-threshold-reader.test.ts +14 -20
  6. package/bun.lock +306 -119
  7. package/docs/architecture/memory.md +1 -90
  8. package/docs/architecture/security.md +15 -30
  9. package/docs/credential-execution-service.md +7 -5
  10. package/docs/skills.md +10 -10
  11. package/docs/stt-provider-onboarding.md +17 -45
  12. package/examples/plugins/echo/bun.lock +25 -0
  13. package/knip.json +8 -22
  14. package/node_modules/@vellumai/ces-client/bun.lock +33 -0
  15. package/node_modules/@vellumai/ces-client/package.json +25 -0
  16. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +631 -0
  17. package/node_modules/@vellumai/ces-client/src/__tests__/package-boundary.test.ts +138 -0
  18. package/node_modules/@vellumai/ces-client/src/credential-rpc.ts +13 -0
  19. package/node_modules/@vellumai/ces-client/src/http-credentials.ts +296 -0
  20. package/node_modules/@vellumai/ces-client/src/http-log-export.ts +111 -0
  21. package/node_modules/@vellumai/ces-client/src/index.ts +43 -0
  22. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +445 -0
  23. package/node_modules/@vellumai/credential-storage/src/__tests__/package-boundary.test.ts +32 -6
  24. package/node_modules/@vellumai/egress-proxy/src/__tests__/package-boundary.test.ts +32 -1
  25. package/node_modules/@vellumai/gateway-client/bun.lock +39 -0
  26. package/node_modules/@vellumai/gateway-client/package.json +23 -0
  27. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +343 -0
  28. package/node_modules/@vellumai/gateway-client/src/__tests__/package-boundary.test.ts +140 -0
  29. package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +422 -0
  30. package/node_modules/@vellumai/gateway-client/src/index.ts +35 -0
  31. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +331 -0
  32. package/node_modules/@vellumai/gateway-client/src/types.ts +131 -0
  33. package/node_modules/@vellumai/gateway-client/tsconfig.json +20 -0
  34. package/node_modules/@vellumai/{ces-contracts → service-contracts}/bun.lock +1 -1
  35. package/node_modules/@vellumai/{ces-contracts → service-contracts}/package.json +4 -2
  36. package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/__tests__/contracts.test.ts +5 -1
  37. package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +155 -0
  38. package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +23 -0
  39. package/node_modules/@vellumai/service-contracts/src/index.ts +25 -0
  40. package/node_modules/@vellumai/{ces-contracts/src/index.ts → service-contracts/src/transport.ts} +6 -28
  41. package/node_modules/@vellumai/service-contracts/src/trust-rules.ts +116 -0
  42. package/node_modules/@vellumai/service-contracts/tsconfig.json +20 -0
  43. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +891 -0
  44. package/node_modules/@vellumai/skill-host-contracts/bun.lock +24 -0
  45. package/node_modules/@vellumai/skill-host-contracts/package.json +18 -0
  46. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +91 -0
  47. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +1348 -0
  48. package/node_modules/@vellumai/skill-host-contracts/src/index.ts +6 -0
  49. package/node_modules/@vellumai/skill-host-contracts/src/runtime-mode.ts +11 -0
  50. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +32 -0
  51. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +333 -0
  52. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +444 -0
  53. package/node_modules/@vellumai/skill-host-contracts/tsconfig.json +20 -0
  54. package/node_modules/@vellumai/skill-host-contracts/tsconfig.test.json +12 -0
  55. package/openapi.yaml +2855 -556
  56. package/package.json +13 -7
  57. package/scripts/check-circular-deps.ts +80 -0
  58. package/scripts/generate-openapi.ts +24 -7
  59. package/{src/memory/graph/inspect.ts → scripts/memory-inspect.ts} +27 -27
  60. package/src/__tests__/access-request-decision.test.ts +2 -11
  61. package/src/__tests__/acp-session.test.ts +4 -150
  62. package/src/__tests__/actor-token-service.test.ts +17 -678
  63. package/src/__tests__/agent-loop-callsite-precedence.test.ts +2 -6
  64. package/src/__tests__/agent-loop-override-profile.test.ts +404 -0
  65. package/src/__tests__/agent-loop-thinking.test.ts +4 -4
  66. package/src/__tests__/agent-wake-override-profile.test.ts +261 -0
  67. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -1
  68. package/src/__tests__/anthropic-provider.test.ts +127 -15
  69. package/src/__tests__/app-routes-csp.test.ts +106 -55
  70. package/src/__tests__/approval-cascade.test.ts +3 -355
  71. package/src/__tests__/approval-conversation-turn.test.ts +3 -8
  72. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +1 -1
  73. package/src/__tests__/approval-primitive.test.ts +2 -1
  74. package/src/__tests__/approval-routes-http.test.ts +34 -451
  75. package/src/__tests__/assistant-events-sse-hardening.test.ts +73 -80
  76. package/src/__tests__/assistant-id-boundary-guard.test.ts +0 -3
  77. package/src/__tests__/attachment-upload-trusted-source.test.ts +139 -0
  78. package/src/__tests__/attachments-store.test.ts +46 -1
  79. package/src/__tests__/audit-log-rotation.test.ts +2 -1
  80. package/src/__tests__/auto-analysis-end-to-end.test.ts +8 -20
  81. package/src/__tests__/background-shell-bash.test.ts +227 -0
  82. package/src/__tests__/background-shell-host-bash.test.ts +474 -0
  83. package/src/__tests__/background-tool-registry.test.ts +145 -0
  84. package/src/__tests__/background-tool-routes.test.ts +175 -0
  85. package/src/__tests__/btw-routes.test.ts +147 -183
  86. package/src/__tests__/call-controller.test.ts +15 -2
  87. package/src/__tests__/call-conversation-messages.test.ts +2 -1
  88. package/src/__tests__/call-domain.test.ts +2 -2
  89. package/src/__tests__/call-pointer-messages.test.ts +11 -13
  90. package/src/__tests__/call-recovery.test.ts +2 -1
  91. package/src/__tests__/call-routes-http.test.ts +3 -14
  92. package/src/__tests__/call-store.test.ts +2 -1
  93. package/src/__tests__/cancel-resolves-conversation-key.test.ts +31 -62
  94. package/src/__tests__/canonical-guardian-store.test.ts +2 -2
  95. package/src/__tests__/catalog-files.test.ts +0 -26
  96. package/src/__tests__/ces-rpc-credential-backend.test.ts +1 -1
  97. package/src/__tests__/channel-approval-routes.test.ts +79 -49
  98. package/src/__tests__/channel-approval.test.ts +9 -7
  99. package/src/__tests__/channel-approvals.test.ts +9 -180
  100. package/src/__tests__/channel-delivery-store.test.ts +11 -10
  101. package/src/__tests__/channel-guardian.test.ts +14 -25
  102. package/src/__tests__/channel-readiness-service.test.ts +8 -6
  103. package/src/__tests__/channel-reply-delivery.test.ts +3 -19
  104. package/src/__tests__/channel-retry-sweep.test.ts +2 -5
  105. package/src/__tests__/checker.test.ts +274 -3921
  106. package/src/__tests__/circuit-breaker-pipeline.test.ts +1 -1
  107. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +208 -0
  108. package/src/__tests__/cli.test.ts +1 -38
  109. package/src/__tests__/compaction-events.test.ts +0 -1
  110. package/src/__tests__/compaction-pipeline.test.ts +1 -1
  111. package/src/__tests__/compaction-strip-metadata-clear.test.ts +2 -2
  112. package/src/__tests__/compaction-timeout-recovery.test.ts +1 -1
  113. package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -7
  114. package/src/__tests__/config-model-image-provider.test.ts +0 -1
  115. package/src/__tests__/config-schema-cmd.test.ts +1 -1
  116. package/src/__tests__/config-schema.test.ts +30 -221
  117. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +4 -25
  118. package/src/__tests__/contact-store-user-file.test.ts +2 -1
  119. package/src/__tests__/contacts-tools.test.ts +56 -29
  120. package/src/__tests__/contacts-write.test.ts +6 -61
  121. package/src/__tests__/context-search-agent-protocol.test.ts +230 -0
  122. package/src/__tests__/context-search-agent-runner.test.ts +998 -0
  123. package/src/__tests__/context-search-conversations-source.test.ts +320 -0
  124. package/src/__tests__/context-search-fanout.test.ts +380 -0
  125. package/src/__tests__/context-search-memory-source.test.ts +311 -0
  126. package/src/__tests__/context-search-pkb-source.test.ts +444 -0
  127. package/src/__tests__/context-search-types.test.ts +95 -0
  128. package/src/__tests__/context-search-workspace-source.test.ts +545 -0
  129. package/src/__tests__/context-window-manager.test.ts +25 -0
  130. package/src/__tests__/conversation-abort-tool-results.test.ts +10 -1
  131. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +631 -0
  132. package/src/__tests__/conversation-agent-loop-overflow.test.ts +15 -2
  133. package/src/__tests__/conversation-agent-loop.test.ts +24 -2
  134. package/src/__tests__/conversation-analysis-routes.test.ts +60 -82
  135. package/src/__tests__/conversation-attachments.test.ts +9 -20
  136. package/src/__tests__/conversation-attention-store.test.ts +2 -1
  137. package/src/__tests__/conversation-attention-telegram.test.ts +4 -2
  138. package/src/__tests__/conversation-clear-safety.test.ts +53 -95
  139. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -39
  140. package/src/__tests__/conversation-crud-inference-profile.test.ts +54 -0
  141. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +63 -157
  142. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  143. package/src/__tests__/conversation-disk-view.test.ts +5 -4
  144. package/src/__tests__/conversation-fork-crud.test.ts +26 -55
  145. package/src/__tests__/conversation-fork-route.test.ts +5 -74
  146. package/src/__tests__/conversation-inference-profile-list.test.ts +128 -0
  147. package/src/__tests__/conversation-inference-profile-route.test.ts +216 -0
  148. package/src/__tests__/conversation-init.benchmark.test.ts +4 -81
  149. package/src/__tests__/conversation-key-store-disk-view.test.ts +2 -1
  150. package/src/__tests__/conversation-lifecycle.test.ts +0 -1
  151. package/src/__tests__/conversation-list-source.test.ts +2 -2
  152. package/src/__tests__/conversation-load-history-repair.test.ts +0 -1
  153. package/src/__tests__/conversation-pairing.test.ts +0 -1
  154. package/src/__tests__/conversation-pre-run-repair.test.ts +137 -297
  155. package/src/__tests__/conversation-process-callsite.test.ts +0 -1
  156. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -1
  157. package/src/__tests__/conversation-queue.test.ts +1 -33
  158. package/src/__tests__/conversation-routes-disk-view.test.ts +124 -97
  159. package/src/__tests__/conversation-routes-guardian-reply.test.ts +80 -55
  160. package/src/__tests__/conversation-routes-slash-commands.test.ts +83 -12
  161. package/src/__tests__/conversation-runtime-assembly.test.ts +41 -84
  162. package/src/__tests__/conversation-slash-commands.test.ts +6 -43
  163. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  164. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  165. package/src/__tests__/conversation-speed-override.test.ts +0 -1
  166. package/src/__tests__/conversation-starter-routes.test.ts +177 -55
  167. package/src/__tests__/conversation-starters-cadence.test.ts +2 -2
  168. package/src/__tests__/conversation-store.test.ts +2 -375
  169. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  170. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +6 -6
  171. package/src/__tests__/conversation-unread-route.test.ts +1 -1
  172. package/src/__tests__/conversation-usage.test.ts +2 -1
  173. package/src/__tests__/conversation-wipe.test.ts +2 -103
  174. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -1
  175. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  176. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  177. package/src/__tests__/conversations-defer-cli.test.ts +150 -0
  178. package/src/__tests__/credential-execution-admin-cli.test.ts +1 -1
  179. package/src/__tests__/credential-execution-api-key-propagation.test.ts +2 -2
  180. package/src/__tests__/credential-execution-approval-bridge.test.ts +22 -289
  181. package/src/__tests__/credential-execution-client.test.ts +1 -1
  182. package/src/__tests__/credential-execution-managed-contract.test.ts +1 -1
  183. package/src/__tests__/credential-security-invariants.test.ts +14 -0
  184. package/src/__tests__/credentials-cli.test.ts +45 -21
  185. package/src/__tests__/daemon-credential-client.test.ts +23 -108
  186. package/src/__tests__/db-acp-history.test.ts +284 -0
  187. package/src/__tests__/db-activation-state.test.ts +240 -0
  188. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +2 -1
  189. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +248 -0
  190. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +2 -1
  191. package/src/__tests__/db-memory-graph-event-date-repair.test.ts +116 -0
  192. package/src/__tests__/db-rename-inference-profile-snake-case-migration.test.ts +132 -0
  193. package/src/__tests__/db-schedule-syntax-migration.test.ts +1 -0
  194. package/src/__tests__/delete-propagation.test.ts +3 -2
  195. package/src/__tests__/deterministic-verification-control-plane.test.ts +39 -32
  196. package/src/__tests__/dm-backfill.test.ts +3 -2
  197. package/src/__tests__/edit-propagation.test.ts +5 -7
  198. package/src/__tests__/embedding-managed-proxy-selection.test.ts +1 -1
  199. package/src/__tests__/empty-response-pipeline.test.ts +1 -1
  200. package/src/__tests__/events-client-registration.test.ts +297 -0
  201. package/src/__tests__/file-write-tool.test.ts +2 -4
  202. package/src/__tests__/filing-service.test.ts +144 -17
  203. package/src/__tests__/followup-tools.test.ts +2 -1
  204. package/src/__tests__/gateway-client-managed-outbound.test.ts +8 -12
  205. package/src/__tests__/gateway-only-enforcement.test.ts +2 -6
  206. package/src/__tests__/gateway-only-guard.test.ts +4 -3
  207. package/src/__tests__/gemini-provider.test.ts +276 -10
  208. package/src/__tests__/graph-extraction-event-date.test.ts +30 -0
  209. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -1
  210. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -2
  211. package/src/__tests__/guardian-action-followup-store.test.ts +2 -1
  212. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +9 -9
  213. package/src/__tests__/guardian-action-late-reply.test.ts +2 -1
  214. package/src/__tests__/guardian-action-store.test.ts +2 -1
  215. package/src/__tests__/guardian-action-sweep.test.ts +9 -8
  216. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -1
  217. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +21 -118
  218. package/src/__tests__/guardian-dispatch.test.ts +14 -11
  219. package/src/__tests__/guardian-grant-minting.test.ts +9 -15
  220. package/src/__tests__/guardian-outbound-http.test.ts +71 -106
  221. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -2
  222. package/src/__tests__/guardian-routing-invariants.test.ts +34 -90
  223. package/src/__tests__/guardian-routing-state.test.ts +14 -22
  224. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -2
  225. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +253 -0
  226. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +8 -4
  227. package/src/__tests__/heartbeat-service.test.ts +39 -21
  228. package/src/__tests__/helpers/call-route-handler.ts +72 -0
  229. package/src/__tests__/helpers/channel-test-adapter.ts +161 -0
  230. package/src/__tests__/helpers/gateway-classify-mock.ts +67 -0
  231. package/src/__tests__/helpers/mock-logger.ts +36 -0
  232. package/src/__tests__/history-repair-pipeline.test.ts +1 -1
  233. package/src/__tests__/home-state-routes.test.ts +10 -31
  234. package/src/__tests__/host-browser-e2e-cloud.test.ts +2 -1
  235. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +12 -2
  236. package/src/__tests__/host-browser-routes.test.ts +36 -91
  237. package/src/__tests__/host-browser-ws-events-e2e.test.ts +10 -2
  238. package/src/__tests__/host-proxy-interface.test.ts +3 -3
  239. package/src/__tests__/host-shell-tool.test.ts +2 -4
  240. package/src/__tests__/host-transfer-pending-interactions.test.ts +160 -0
  241. package/src/__tests__/host-transfer-proxy.test.ts +733 -0
  242. package/src/__tests__/http-conversation-lineage.test.ts +3 -2
  243. package/src/__tests__/http-user-message-parity.test.ts +20 -11
  244. package/src/__tests__/inbound-invite-redemption.test.ts +3 -2
  245. package/src/__tests__/injector-chain.test.ts +16 -17
  246. package/src/__tests__/inline-skill-load-permissions.test.ts +41 -206
  247. package/src/__tests__/install-skill-routing.test.ts +1 -1
  248. package/src/__tests__/invite-redemption-service.test.ts +2 -1
  249. package/src/__tests__/invite-routes-http.test.ts +80 -12
  250. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +2 -1
  251. package/src/__tests__/jobs-store-upsert-debounced.test.ts +2 -1
  252. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +157 -0
  253. package/src/__tests__/list-messages-attachments.test.ts +52 -55
  254. package/src/__tests__/list-messages-page-latest.test.ts +283 -0
  255. package/src/__tests__/list-messages-tool-merge.test.ts +16 -17
  256. package/src/__tests__/llm-call-pipeline.test.ts +7 -8
  257. package/src/__tests__/llm-context-normalization.test.ts +69 -4
  258. package/src/__tests__/llm-context-route-provider.test.ts +39 -113
  259. package/src/__tests__/llm-request-log-turn-query.test.ts +2 -1
  260. package/src/__tests__/llm-resolver.test.ts +211 -0
  261. package/src/__tests__/llm-schema.test.ts +57 -1
  262. package/src/__tests__/llm-usage-store.test.ts +2 -1
  263. package/src/__tests__/log-export-workspace.test.ts +28 -17
  264. package/src/__tests__/mcp-abort-signal.test.ts +2 -3
  265. package/src/__tests__/mcp-client-auth.test.ts +2 -3
  266. package/src/__tests__/memory-admin-recall.test.ts +221 -0
  267. package/src/__tests__/memory-recall-log-store.test.ts +2 -1
  268. package/src/__tests__/memory-retrieval-pipeline.test.ts +6 -8
  269. package/src/__tests__/memory-upsert-concurrency.test.ts +2 -1
  270. package/src/__tests__/migration-cross-version-compatibility.test.ts +14 -13
  271. package/src/__tests__/migration-export-http.test.ts +17 -17
  272. package/src/__tests__/migration-export-to-gcs.test.ts +491 -0
  273. package/src/__tests__/migration-import-commit-http.test.ts +16 -16
  274. package/src/__tests__/migration-import-from-gcs.test.ts +533 -0
  275. package/src/__tests__/migration-import-from-url.test.ts +16 -23
  276. package/src/__tests__/migration-import-preflight-http.test.ts +13 -13
  277. package/src/__tests__/migration-jobs-status.test.ts +164 -0
  278. package/src/__tests__/migration-validate-http.test.ts +48 -83
  279. package/src/__tests__/mock-gateway-ipc.ts +32 -62
  280. package/src/__tests__/model-intents.test.ts +15 -2
  281. package/src/__tests__/nl-approval-parser.test.ts +13 -17
  282. package/src/__tests__/non-member-access-request.test.ts +13 -5
  283. package/src/__tests__/notification-guardian-path.test.ts +15 -8
  284. package/src/__tests__/notification-schedule-notify-dedup.test.ts +2 -1
  285. package/src/__tests__/notification-telegram-adapter.test.ts +57 -55
  286. package/src/__tests__/oauth-apps-routes.test.ts +76 -122
  287. package/src/__tests__/oauth-cli.test.ts +14 -1
  288. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  289. package/src/__tests__/oauth-provider-visibility.test.ts +3 -1
  290. package/src/__tests__/oauth-providers-routes.test.ts +78 -101
  291. package/src/__tests__/oauth-store.test.ts +3 -1
  292. package/src/__tests__/oauth2-gateway-transport.test.ts +6 -3
  293. package/src/__tests__/openai-provider.test.ts +105 -6
  294. package/src/__tests__/openai-responses-provider.test.ts +146 -4
  295. package/src/__tests__/openrouter-provider-only.test.ts +22 -4
  296. package/src/__tests__/overflow-reduce-pipeline.test.ts +4 -9
  297. package/src/__tests__/permission-types.test.ts +3 -18
  298. package/src/__tests__/persistence-pipeline.test.ts +3 -2
  299. package/src/__tests__/pipeline-runner.test.ts +1 -1
  300. package/src/__tests__/platform-bash-auto-approve.test.ts +27 -20
  301. package/src/__tests__/platform.test.ts +11 -63
  302. package/src/__tests__/playbook-execution.test.ts +2 -1
  303. package/src/__tests__/playbook-tools.test.ts +2 -1
  304. package/src/__tests__/plugin-bootstrap.test.ts +51 -5
  305. package/src/__tests__/plugin-registry.test.ts +30 -0
  306. package/src/__tests__/plugin-route-contribution.test.ts +17 -11
  307. package/src/__tests__/plugin-skill-contribution.test.ts +3 -3
  308. package/src/__tests__/plugin-tool-contribution.test.ts +10 -4
  309. package/src/__tests__/plugin-types.test.ts +1 -1
  310. package/src/__tests__/pricing.test.ts +151 -2
  311. package/src/__tests__/profiler-routes.test.ts +112 -177
  312. package/src/__tests__/provider-send-message-override-profile.test.ts +223 -0
  313. package/src/__tests__/proxy-approval-callback.test.ts +6 -554
  314. package/src/__tests__/qdrant-collection-migration.test.ts +7 -7
  315. package/src/__tests__/reaction-persistence.test.ts +3 -2
  316. package/src/__tests__/rebuild-index-graph-nodes.test.ts +1 -1
  317. package/src/__tests__/recording-handler.test.ts +0 -2
  318. package/src/__tests__/registry.test.ts +1 -0
  319. package/src/__tests__/relay-server.test.ts +19 -4
  320. package/src/__tests__/require-fresh-approval.test.ts +19 -168
  321. package/src/__tests__/resolve-trust-class.test.ts +2 -1
  322. package/src/__tests__/retry-thinking-tool-choice.test.ts +19 -7
  323. package/src/__tests__/retry-verbosity-normalization.test.ts +139 -0
  324. package/src/__tests__/runtime-attachment-metadata.test.ts +26 -6
  325. package/src/__tests__/runtime-events-sse-parity.test.ts +12 -13
  326. package/src/__tests__/runtime-events-sse.test.ts +13 -21
  327. package/src/__tests__/schedule-routes.test.ts +226 -129
  328. package/src/__tests__/schedule-store.test.ts +119 -1
  329. package/src/__tests__/schedule-tools.test.ts +2 -1
  330. package/src/__tests__/scheduler-recurrence.test.ts +2 -1
  331. package/src/__tests__/scheduler-reuse-conversation.test.ts +2 -1
  332. package/src/__tests__/scheduler-wake.test.ts +356 -0
  333. package/src/__tests__/scoped-approval-grants.test.ts +2 -1
  334. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -1
  335. package/src/__tests__/secret-detection-handler.test.ts +2 -9
  336. package/src/__tests__/secret-ingress-http.test.ts +38 -21
  337. package/src/__tests__/secret-routes-managed-proxy.test.ts +46 -102
  338. package/src/__tests__/secret-scanner-executor.test.ts +1 -2
  339. package/src/__tests__/send-endpoint-busy.test.ts +38 -25
  340. package/src/__tests__/sequence-store.test.ts +2 -1
  341. package/src/__tests__/server-history-render.test.ts +2 -2
  342. package/src/__tests__/service-contracts-import-guard.test.ts +185 -0
  343. package/src/__tests__/set-permission-mode.test.ts +0 -10
  344. package/src/__tests__/settings-routes.test.ts +35 -68
  345. package/src/__tests__/skill-boundary-guard.test.ts +105 -0
  346. package/src/__tests__/skill-load-inline-command.test.ts +2 -2
  347. package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
  348. package/src/__tests__/skill-runtime-path.test.ts +64 -0
  349. package/src/__tests__/skills-file-content-endpoint.test.ts +0 -2
  350. package/src/__tests__/slack-inbound-verification.test.ts +11 -2
  351. package/src/__tests__/slack-messaging-token-resolution.test.ts +1 -3
  352. package/src/__tests__/slack-reaction-approvals.test.ts +4 -4
  353. package/src/__tests__/slack-share-routes.test.ts +37 -72
  354. package/src/__tests__/subagent-call-site-routing.test.ts +79 -0
  355. package/src/__tests__/subagent-fork-spawn.test.ts +20 -28
  356. package/src/__tests__/subagent-notify-parent.test.ts +6 -29
  357. package/src/__tests__/subagent-role-registry.test.ts +3 -3
  358. package/src/__tests__/subagent-spawn-tool-fork.test.ts +52 -104
  359. package/src/__tests__/subagent-tools.test.ts +0 -1
  360. package/src/__tests__/suggestion-routes.test.ts +55 -62
  361. package/src/__tests__/task-compiler.test.ts +2 -1
  362. package/src/__tests__/task-management-tools.test.ts +2 -1
  363. package/src/__tests__/task-memory-cleanup.test.ts +2 -1
  364. package/src/__tests__/task-scheduler.test.ts +2 -1
  365. package/src/__tests__/telegram-config.test.ts +0 -1
  366. package/src/__tests__/terminal-tools.test.ts +5 -314
  367. package/src/__tests__/test-preload.ts +0 -11
  368. package/src/__tests__/thread-backfill.test.ts +3 -2
  369. package/src/__tests__/token-estimate-pipeline.test.ts +68 -15
  370. package/src/__tests__/tool-approval-handler.test.ts +21 -63
  371. package/src/__tests__/tool-audit-listener.test.ts +3 -3
  372. package/src/__tests__/tool-domain-event-publisher.test.ts +3 -3
  373. package/src/__tests__/tool-error-pipeline.test.ts +6 -6
  374. package/src/__tests__/tool-execute-pipeline.test.ts +6 -8
  375. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +64 -1
  376. package/src/__tests__/tool-executor-lifecycle-events.test.ts +28 -56
  377. package/src/__tests__/tool-executor.test.ts +322 -1633
  378. package/src/__tests__/tool-grant-request-escalation.test.ts +90 -311
  379. package/src/__tests__/tool-result-truncate-pipeline.test.ts +1 -1
  380. package/src/__tests__/trust-context-guards.test.ts +1 -1
  381. package/src/__tests__/trusted-contact-approval-notifier.test.ts +7 -15
  382. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +178 -354
  383. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +3 -2
  384. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -2
  385. package/src/__tests__/trusted-contact-verification.test.ts +2 -1
  386. package/src/__tests__/turn-boundary-resolution.test.ts +2 -1
  387. package/src/__tests__/twilio-routes.test.ts +25 -66
  388. package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -7
  389. package/src/__tests__/usage-routes.test.ts +73 -90
  390. package/src/__tests__/user-plugin-loader.test.ts +54 -12
  391. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +2 -2
  392. package/src/__tests__/verification-control-plane-policy.test.ts +95 -14
  393. package/src/__tests__/voice-ingress-preflight.test.ts +5 -5
  394. package/src/__tests__/voice-invite-redemption.test.ts +2 -1
  395. package/src/__tests__/voice-scoped-grant-consumer.test.ts +3 -3
  396. package/src/__tests__/voice-session-bridge.test.ts +285 -106
  397. package/src/__tests__/volume-security-guard.test.ts +0 -2
  398. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -1
  399. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +3 -1
  400. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +2 -1
  401. package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +1 -1
  402. package/src/__tests__/workspace-migration-052-seed-default-inference-profiles.test.ts +260 -0
  403. package/src/__tests__/workspace-migration-053-release-notes-acp-codex.test.ts +225 -0
  404. package/src/__tests__/workspace-migration-054-seed-recall-callsite.test.ts +235 -0
  405. package/src/__tests__/workspace-migration-055-release-notes-agentic-recall.test.ts +128 -0
  406. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +232 -0
  407. package/src/__tests__/workspace-migration-acp-sessions-ui.test.ts +144 -0
  408. package/src/__tests__/workspace-migration-drop-user-md.test.ts +1 -1
  409. package/src/__tests__/workspace-migration-memory-v2-init.test.ts +274 -0
  410. package/src/acp/__tests__/client-handler.test.ts +64 -0
  411. package/src/acp/__tests__/helpers/acp-config-stub.ts +62 -0
  412. package/src/acp/__tests__/helpers/which-stub.ts +45 -0
  413. package/src/acp/__tests__/session-manager-persistence.test.ts +366 -0
  414. package/src/acp/__tests__/session-manager-startup.test.ts +159 -0
  415. package/src/acp/__tests__/session-manager.test.ts +83 -0
  416. package/src/acp/client-handler.ts +23 -139
  417. package/src/acp/resolve-agent.test.ts +291 -0
  418. package/src/acp/resolve-agent.ts +176 -0
  419. package/src/acp/session-manager.ts +166 -7
  420. package/src/acp/types.ts +2 -50
  421. package/src/agent/loop.ts +37 -14
  422. package/src/agent/message-types.ts +0 -2
  423. package/src/approvals/AGENTS.md +1 -1
  424. package/src/approvals/__tests__/guardian-feed-event.test.ts +1 -9
  425. package/src/approvals/approval-primitive.ts +3 -20
  426. package/src/approvals/guardian-decision-primitive.ts +37 -68
  427. package/src/approvals/guardian-request-resolvers.ts +29 -103
  428. package/src/avatar/character-components.ts +6 -6
  429. package/src/{config/bundled-skills/settings/tools → avatar}/identity-avatar.ts +1 -1
  430. package/src/backup/__tests__/backup-worker.test.ts +0 -2
  431. package/src/backup/__tests__/paths.test.ts +3 -2
  432. package/src/backup/backup-worker.ts +1 -10
  433. package/src/backup/paths.ts +2 -18
  434. package/src/backup/restore.ts +7 -11
  435. package/src/browser/__tests__/operations.test.ts +0 -35
  436. package/src/browser/operations.ts +1 -47
  437. package/src/bundler/package-resolver.ts +2 -6
  438. package/src/calls/active-call-lease.ts +1 -1
  439. package/src/calls/call-constants.ts +1 -1
  440. package/src/calls/call-controller.ts +1 -5
  441. package/src/calls/call-domain.ts +14 -14
  442. package/src/calls/call-pointer-messages.ts +4 -9
  443. package/src/calls/call-store.ts +2 -1
  444. package/src/calls/guardian-action-sweep.ts +9 -25
  445. package/src/calls/guardian-dispatch.ts +1 -20
  446. package/src/calls/media-stream-audio-transcode.ts +2 -41
  447. package/src/calls/media-stream-server.ts +2 -3
  448. package/src/calls/media-stream-stt-session.ts +1 -3
  449. package/src/calls/relay-access-wait.ts +5 -8
  450. package/src/calls/relay-server.ts +15 -18
  451. package/src/calls/relay-setup-router.ts +2 -2
  452. package/src/calls/relay-verification.ts +4 -4
  453. package/src/calls/twilio-rest.ts +1 -1
  454. package/src/calls/twilio-routes.ts +160 -78
  455. package/src/calls/voice-control-protocol.ts +10 -10
  456. package/src/calls/voice-ingress-preflight.ts +2 -2
  457. package/src/calls/voice-session-bridge.ts +137 -42
  458. package/src/channels/__tests__/types.test.ts +25 -3
  459. package/src/channels/permission-profiles.ts +2 -72
  460. package/src/channels/types.ts +42 -26
  461. package/src/cli/AGENTS.md +1 -0
  462. package/src/cli/__tests__/notifications.test.ts +12 -10
  463. package/src/cli/commands/__tests__/attachment.test.ts +14 -8
  464. package/src/cli/commands/__tests__/backup.test.ts +3 -14
  465. package/src/cli/commands/__tests__/browser.test.ts +36 -31
  466. package/src/cli/commands/__tests__/cache.test.ts +23 -18
  467. package/src/cli/commands/__tests__/memory-v2.test.ts +396 -0
  468. package/src/cli/commands/__tests__/task.test.ts +36 -35
  469. package/src/cli/commands/__tests__/trust.test.ts +602 -0
  470. package/src/cli/commands/__tests__/ui-confirm.test.ts +14 -14
  471. package/src/cli/commands/__tests__/ui.test.ts +17 -17
  472. package/src/cli/commands/__tests__/watchers.test.ts +29 -29
  473. package/src/cli/commands/__tests__/webhooks.test.ts +544 -0
  474. package/src/cli/commands/attachment.ts +12 -8
  475. package/src/cli/commands/auth.ts +1 -1
  476. package/src/cli/commands/avatar.ts +192 -9
  477. package/src/cli/commands/backup.ts +14 -44
  478. package/src/cli/commands/browser.ts +52 -4
  479. package/src/cli/commands/cache.ts +7 -5
  480. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  481. package/src/cli/commands/clients.ts +11 -12
  482. package/src/cli/commands/completions.ts +1 -1
  483. package/src/cli/commands/contacts.ts +10 -10
  484. package/src/cli/commands/conversations-defer.ts +364 -0
  485. package/src/cli/commands/conversations-import.ts +2 -3
  486. package/src/cli/commands/conversations.ts +63 -53
  487. package/src/cli/commands/credential-execution.ts +1 -1
  488. package/src/cli/commands/credentials.ts +139 -5
  489. package/src/cli/commands/default-action.ts +1 -1
  490. package/src/cli/commands/domain.ts +2 -2
  491. package/src/cli/commands/email.ts +7 -7
  492. package/src/cli/commands/image-generation.ts +1 -1
  493. package/src/cli/commands/keys.ts +2 -2
  494. package/src/cli/commands/mcp.ts +1 -1
  495. package/src/cli/commands/memory-v2.ts +343 -0
  496. package/src/cli/commands/memory.ts +8 -8
  497. package/src/cli/commands/notifications.ts +21 -20
  498. package/src/cli/commands/oauth/__tests__/connect.test.ts +23 -5
  499. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +1 -1
  500. package/src/cli/commands/oauth/__tests__/mode.test.ts +1 -1
  501. package/src/cli/commands/oauth/__tests__/status.test.ts +1 -1
  502. package/src/cli/commands/oauth/__tests__/token.test.ts +1 -1
  503. package/src/cli/commands/oauth/connect.ts +2 -2
  504. package/src/cli/commands/oauth/shared.ts +29 -2
  505. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -6
  506. package/src/cli/commands/platform/__tests__/connect.test.ts +23 -11
  507. package/src/cli/commands/platform/__tests__/disconnect.test.ts +22 -10
  508. package/src/cli/commands/platform/__tests__/status.test.ts +22 -10
  509. package/src/cli/commands/platform/connect.ts +3 -3
  510. package/src/cli/commands/platform/disconnect.ts +4 -6
  511. package/src/cli/commands/platform/index.ts +12 -10
  512. package/src/cli/commands/routes.ts +7 -1
  513. package/src/cli/commands/sequence.ts +7 -7
  514. package/src/cli/commands/skills.ts +188 -82
  515. package/src/cli/commands/task.ts +12 -10
  516. package/src/cli/commands/trust.ts +460 -162
  517. package/src/cli/commands/ui.ts +3 -3
  518. package/src/cli/commands/usage.ts +10 -5
  519. package/src/cli/commands/watchers.ts +8 -8
  520. package/src/cli/commands/webhooks.ts +270 -0
  521. package/src/cli/lib/daemon-avatar-client.ts +37 -0
  522. package/src/cli/lib/daemon-credential-client.ts +27 -189
  523. package/src/cli/lib/ipc-params.ts +22 -0
  524. package/src/cli/program.ts +4 -0
  525. package/src/cli.ts +1 -61
  526. package/src/config/acp-defaults.test.ts +57 -0
  527. package/src/config/acp-defaults.ts +40 -0
  528. package/src/config/acp-schema.ts +1 -1
  529. package/src/config/assistant-feature-flags.ts +18 -142
  530. package/src/config/bundled-skills/acp/SKILL.md +44 -16
  531. package/src/config/bundled-skills/acp/TOOLS.json +45 -1
  532. package/src/config/bundled-skills/acp/tools/acp-list-agents.ts +12 -0
  533. package/src/config/bundled-skills/acp/tools/acp-steer.ts +12 -0
  534. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +14 -14
  535. package/src/config/bundled-skills/contacts/tools/contact-search.ts +1 -4
  536. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +11 -6
  537. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +6 -6
  538. package/src/config/bundled-skills/media-processing/services/reduce.ts +0 -13
  539. package/src/config/bundled-skills/messaging/tools/gmail-mime-helpers.ts +1 -1
  540. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  541. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -1
  542. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +1 -1
  543. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -1
  544. package/src/config/bundled-skills/settings/SKILL.md +2 -17
  545. package/src/config/bundled-skills/settings/TOOLS.json +0 -56
  546. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  547. package/src/config/bundled-tool-registry.ts +4 -6
  548. package/src/config/env.ts +7 -8
  549. package/src/config/feature-flag-registry.json +16 -24
  550. package/src/config/llm-resolver.ts +51 -33
  551. package/src/config/loader.ts +12 -15
  552. package/src/config/schema.ts +5 -72
  553. package/src/config/schemas/__tests__/filing.test.ts +58 -0
  554. package/src/config/schemas/__tests__/memory-v2.test.ts +186 -0
  555. package/src/config/schemas/filing.ts +12 -0
  556. package/src/config/schemas/host-browser.ts +2 -2
  557. package/src/config/schemas/inference.ts +0 -2
  558. package/src/config/schemas/ingress.ts +1 -1
  559. package/src/config/schemas/llm.ts +51 -9
  560. package/src/config/schemas/memory-storage.ts +1 -1
  561. package/src/config/schemas/memory-v2.ts +176 -0
  562. package/src/config/schemas/memory.ts +2 -0
  563. package/src/config/schemas/security.ts +0 -60
  564. package/src/config/schemas/services.ts +46 -7
  565. package/src/config/skills.ts +1 -1
  566. package/src/config/types.ts +0 -41
  567. package/src/contacts/contact-store.ts +2 -2
  568. package/src/contacts/contacts-write.ts +0 -38
  569. package/src/contacts/types.ts +8 -10
  570. package/src/context/token-estimator.ts +1 -1
  571. package/src/context/tool-result-truncation.ts +1 -1
  572. package/src/context/window-manager.ts +1 -1
  573. package/src/credential-execution/approval-bridge.ts +7 -69
  574. package/src/credential-execution/client.ts +17 -422
  575. package/src/credential-execution/feature-gates.ts +1 -2
  576. package/src/credential-execution/managed-catalog.ts +1 -1
  577. package/src/credential-health/credential-health-service.ts +1 -1
  578. package/src/daemon/__tests__/conversation-feed-event.test.ts +0 -13
  579. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  580. package/src/daemon/__tests__/daemon-skill-host.test.ts +272 -0
  581. package/src/daemon/__tests__/meet-host-supervisor.test.ts +587 -0
  582. package/src/daemon/__tests__/meet-manifest-loader.test.ts +463 -0
  583. package/src/daemon/approval-generators.ts +2 -14
  584. package/src/daemon/classifier.ts +0 -106
  585. package/src/daemon/config-watcher.ts +14 -54
  586. package/src/daemon/connection-policy.ts +0 -14
  587. package/src/daemon/conversation-agent-loop-handlers.ts +37 -6
  588. package/src/daemon/conversation-agent-loop.ts +164 -53
  589. package/src/daemon/conversation-attachments.ts +5 -81
  590. package/src/daemon/conversation-error.ts +9 -5
  591. package/src/daemon/conversation-history.ts +1 -1
  592. package/src/daemon/conversation-launch.ts +1 -1
  593. package/src/daemon/conversation-messaging.ts +1 -1
  594. package/src/daemon/conversation-notifiers.ts +1 -1
  595. package/src/daemon/conversation-process.ts +9 -13
  596. package/src/daemon/conversation-runtime-assembly.ts +26 -88
  597. package/src/daemon/conversation-slash.ts +4 -160
  598. package/src/daemon/conversation-store.ts +368 -0
  599. package/src/daemon/conversation-surfaces.ts +5 -4
  600. package/src/daemon/conversation-tool-setup.ts +23 -172
  601. package/src/daemon/conversation.ts +46 -182
  602. package/src/daemon/daemon-control.ts +3 -3
  603. package/src/daemon/daemon-skill-host.ts +262 -0
  604. package/src/daemon/external-plugins-bootstrap.ts +67 -13
  605. package/src/daemon/handlers/config-channels.ts +2 -2
  606. package/src/daemon/handlers/config-embeddings.ts +1 -1
  607. package/src/daemon/handlers/config-ingress.ts +24 -2
  608. package/src/daemon/handlers/config-model.test.ts +17 -0
  609. package/src/daemon/handlers/config-model.ts +7 -52
  610. package/src/daemon/handlers/config-telegram.ts +6 -53
  611. package/src/daemon/handlers/config-voice.ts +1 -1
  612. package/src/daemon/handlers/conversations.ts +22 -156
  613. package/src/daemon/handlers/recording.ts +1 -1
  614. package/src/daemon/handlers/shared.ts +34 -35
  615. package/src/daemon/handlers/skills.ts +15 -23
  616. package/src/daemon/host-transfer-proxy.ts +500 -0
  617. package/src/daemon/lifecycle.ts +23 -258
  618. package/src/daemon/meet-host-startup.ts +51 -0
  619. package/src/daemon/meet-host-supervisor.ts +781 -0
  620. package/src/daemon/meet-manifest-loader.ts +410 -0
  621. package/src/daemon/memory-v2-startup.ts +35 -0
  622. package/src/daemon/message-protocol.ts +4 -7
  623. package/src/daemon/message-types/acp.ts +1 -0
  624. package/src/daemon/message-types/conversations.ts +16 -2
  625. package/src/daemon/message-types/host-transfer.ts +41 -0
  626. package/src/daemon/message-types/integrations.ts +6 -0
  627. package/src/daemon/message-types/messages.ts +14 -14
  628. package/src/daemon/message-types/schedules.ts +1 -0
  629. package/src/daemon/message-types/settings.ts +0 -6
  630. package/src/daemon/message-types/shared.ts +5 -2
  631. package/src/daemon/message-types/subagents.ts +2 -1
  632. package/src/daemon/message-types/workspace.ts +0 -2
  633. package/src/daemon/pkb-reminder-builder.test.ts +13 -12
  634. package/src/daemon/pkb-reminder-builder.ts +8 -16
  635. package/src/daemon/process-message.ts +616 -0
  636. package/src/daemon/providers-setup.ts +14 -6
  637. package/src/daemon/server.ts +79 -1274
  638. package/src/daemon/shutdown-handlers.ts +1 -1
  639. package/src/daemon/startup-error.ts +1 -1
  640. package/src/daemon/trust-context.ts +32 -0
  641. package/src/daemon/wake-target-adapter.ts +223 -0
  642. package/src/email/feature-gate.ts +1 -1
  643. package/src/events/domain-events.ts +1 -8
  644. package/src/events/tool-audit-listener.ts +2 -8
  645. package/src/events/tool-metrics-listener.ts +1 -4
  646. package/src/filing/filing-service.ts +194 -54
  647. package/src/followups/followup-store.ts +3 -71
  648. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +89 -21
  649. package/src/heartbeat/heartbeat-service.ts +32 -11
  650. package/src/home/__tests__/phase5-exit-criteria.test.ts +18 -1
  651. package/src/home/__tests__/rollup-producer.test.ts +67 -2
  652. package/src/home/assistant-feed-authoring.ts +8 -1
  653. package/src/home/feed-types.ts +1 -1
  654. package/src/home/relationship-state-writer.ts +1 -1
  655. package/src/home/rewrite-feed-title.ts +58 -0
  656. package/src/home/rollup-producer.ts +16 -3
  657. package/src/inbound/platform-callback-registration.ts +1 -17
  658. package/src/ipc/__tests__/attachment-ipc.test.ts +128 -66
  659. package/src/ipc/__tests__/browser-ipc.test.ts +75 -51
  660. package/src/ipc/__tests__/cache-ipc.test.ts +52 -107
  661. package/src/ipc/__tests__/cli-ipc.test.ts +9 -6
  662. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +254 -0
  663. package/src/ipc/__tests__/skill-server.test.ts +182 -0
  664. package/src/ipc/__tests__/socket-path.test.ts +69 -23
  665. package/src/ipc/__tests__/ui-request-route.test.ts +241 -216
  666. package/src/ipc/__tests__/watcher-ipc.test.ts +33 -33
  667. package/src/ipc/assistant-server.ts +450 -0
  668. package/src/ipc/cli-client.ts +3 -3
  669. package/src/ipc/gateway-client.test.ts +131 -0
  670. package/src/ipc/gateway-client.ts +98 -123
  671. package/src/ipc/ipc-framing.ts +281 -0
  672. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +152 -0
  673. package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +219 -0
  674. package/src/ipc/routes/db-proxy.ts +73 -0
  675. package/src/ipc/routes/route-adapter.ts +32 -0
  676. package/src/ipc/routes/trust-rules.test.ts +218 -0
  677. package/src/ipc/skill-ipc-types.ts +13 -0
  678. package/src/ipc/skill-routes/__tests__/config.test.ts +146 -0
  679. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +402 -0
  680. package/src/ipc/skill-routes/__tests__/identity.test.ts +81 -0
  681. package/src/ipc/skill-routes/__tests__/log.test.ts +133 -0
  682. package/src/ipc/skill-routes/__tests__/memory.test.ts +178 -0
  683. package/src/ipc/skill-routes/__tests__/platform.test.ts +111 -0
  684. package/src/ipc/skill-routes/__tests__/providers.test.ts +265 -0
  685. package/src/ipc/skill-routes/__tests__/registries.test.ts +361 -0
  686. package/src/ipc/skill-routes/config.ts +47 -0
  687. package/src/ipc/skill-routes/events.ts +131 -0
  688. package/src/ipc/skill-routes/identity.ts +34 -0
  689. package/src/ipc/skill-routes/index.ts +37 -0
  690. package/src/ipc/skill-routes/log.ts +40 -0
  691. package/src/ipc/skill-routes/memory.ts +76 -0
  692. package/src/ipc/skill-routes/platform.ts +39 -0
  693. package/src/ipc/skill-routes/providers.ts +163 -0
  694. package/src/ipc/skill-routes/registries.ts +393 -0
  695. package/src/ipc/skill-server.ts +771 -0
  696. package/src/ipc/skill-socket-path.ts +20 -0
  697. package/src/ipc/socket-cleanup.ts +92 -0
  698. package/src/ipc/socket-path.ts +63 -32
  699. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +374 -0
  700. package/src/live-voice/__tests__/live-voice-archive.test.ts +525 -0
  701. package/src/live-voice/__tests__/live-voice-events.test.ts +473 -0
  702. package/src/live-voice/__tests__/live-voice-integration.test.ts +359 -0
  703. package/src/live-voice/__tests__/live-voice-metrics.test.ts +179 -0
  704. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +349 -0
  705. package/src/live-voice/__tests__/live-voice-stt.test.ts +244 -0
  706. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +337 -0
  707. package/src/live-voice/__tests__/live-voice-tts.test.ts +337 -0
  708. package/src/live-voice/__tests__/protocol.test.ts +295 -0
  709. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +421 -0
  710. package/src/live-voice/live-voice-archive.ts +758 -0
  711. package/src/live-voice/live-voice-metrics.ts +472 -0
  712. package/src/live-voice/live-voice-session-manager.ts +222 -0
  713. package/src/live-voice/live-voice-session.ts +1144 -0
  714. package/src/live-voice/live-voice-tts.ts +260 -0
  715. package/src/live-voice/protocol.ts +524 -0
  716. package/src/mcp/client.ts +2 -2
  717. package/src/media/types.ts +4 -4
  718. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +4 -28
  719. package/src/memory/__tests__/auto-analysis-guard.test.ts +2 -2
  720. package/src/memory/__tests__/conversation-analyze-job.test.ts +7 -62
  721. package/src/memory/__tests__/conversation-group-migration.test.ts +2 -2
  722. package/src/memory/__tests__/find-analysis-conversation.test.ts +2 -1
  723. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +235 -0
  724. package/src/memory/admin.ts +65 -7
  725. package/src/memory/app-git-service.ts +0 -14
  726. package/src/memory/attachments-store.ts +14 -16
  727. package/src/memory/auto-analysis-enqueue.ts +2 -17
  728. package/src/memory/canonical-guardian-store.ts +2 -1
  729. package/src/memory/channel-verification-sessions.ts +1 -1
  730. package/src/memory/checkpoints.ts +1 -1
  731. package/src/memory/context-search/agent-protocol.ts +424 -0
  732. package/src/memory/context-search/agent-runner.ts +1295 -0
  733. package/src/memory/context-search/format.ts +160 -0
  734. package/src/memory/context-search/limits.ts +106 -0
  735. package/src/memory/context-search/search.ts +387 -0
  736. package/src/memory/context-search/sources/conversations.ts +278 -0
  737. package/src/memory/context-search/sources/memory.ts +90 -0
  738. package/src/memory/context-search/sources/pkb.ts +468 -0
  739. package/src/memory/context-search/sources/workspace.ts +1255 -0
  740. package/src/memory/context-search/types.ts +49 -0
  741. package/src/memory/conversation-analyze-job.ts +3 -24
  742. package/src/memory/conversation-attention-store.ts +1 -1
  743. package/src/memory/conversation-bootstrap.ts +1 -1
  744. package/src/memory/conversation-crud.ts +69 -127
  745. package/src/memory/conversation-directories.ts +1 -11
  746. package/src/memory/conversation-display-order-migration.ts +11 -2
  747. package/src/memory/conversation-group-migration.ts +20 -4
  748. package/src/memory/conversation-key-store.ts +3 -4
  749. package/src/memory/conversation-queries.ts +13 -26
  750. package/src/memory/conversation-starter-validation.ts +88 -0
  751. package/src/memory/conversation-starters-cadence.ts +1 -1
  752. package/src/memory/conversation-title-service.ts +2 -1
  753. package/src/memory/db-init.ts +14 -4
  754. package/src/memory/db-maintenance.ts +1 -1
  755. package/src/memory/delivery-channels.ts +1 -14
  756. package/src/memory/delivery-crud.ts +2 -32
  757. package/src/memory/delivery-status.ts +1 -1
  758. package/src/memory/embedding-gemini.test.ts +4 -4
  759. package/src/memory/external-conversation-store.ts +1 -1
  760. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +412 -0
  761. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +225 -0
  762. package/src/memory/graph/bootstrap.test.ts +2 -7
  763. package/src/memory/graph/bootstrap.ts +2 -1
  764. package/src/memory/graph/capability-seed.ts +3 -3
  765. package/src/memory/graph/compaction.ts +1 -1
  766. package/src/memory/graph/consolidation.ts +13 -10
  767. package/src/memory/graph/conversation-graph-memory.ts +151 -1
  768. package/src/memory/graph/decay.ts +1 -1
  769. package/src/memory/graph/extraction.ts +53 -21
  770. package/src/memory/graph/graph-memory-state-store.ts +1 -1
  771. package/src/memory/graph/graph-search.test.ts +94 -2
  772. package/src/memory/graph/graph-search.ts +22 -7
  773. package/src/memory/graph/image-ref-utils.ts +1 -1
  774. package/src/memory/graph/retriever.test.ts +158 -4
  775. package/src/memory/graph/retriever.ts +17 -5
  776. package/src/memory/graph/store.test.ts +2 -1
  777. package/src/memory/graph/store.ts +1 -1
  778. package/src/memory/graph/tool-handlers.ts +73 -247
  779. package/src/memory/graph/tools.ts +35 -53
  780. package/src/memory/group-crud.ts +1 -2
  781. package/src/memory/guardian-action-store.ts +2 -1
  782. package/src/memory/guardian-approvals.ts +1 -1
  783. package/src/memory/guardian-rate-limits.ts +1 -1
  784. package/src/memory/indexer.ts +43 -17
  785. package/src/memory/invite-store.ts +1 -1
  786. package/src/memory/job-handlers/backfill.ts +1 -1
  787. package/src/memory/job-handlers/cleanup.ts +2 -1
  788. package/src/memory/job-handlers/conversation-starters.ts +18 -10
  789. package/src/memory/job-handlers/embedding.test.ts +2 -1
  790. package/src/memory/job-handlers/embedding.ts +1 -1
  791. package/src/memory/job-handlers/index-maintenance.ts +1 -1
  792. package/src/memory/job-handlers/summarization.ts +3 -3
  793. package/src/memory/job-utils.ts +3 -3
  794. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +362 -0
  795. package/src/memory/jobs/embed-concept-page.ts +210 -0
  796. package/src/memory/jobs/embed-pkb-file.test.ts +2 -1
  797. package/src/memory/jobs-store.ts +10 -2
  798. package/src/memory/jobs-worker.ts +58 -5
  799. package/src/memory/lifecycle-events-store.ts +1 -1
  800. package/src/memory/llm-request-log-store.ts +1 -1
  801. package/src/memory/llm-usage-store.ts +1 -1
  802. package/src/memory/media-store.ts +1 -1
  803. package/src/memory/memory-recall-log-store.ts +1 -1
  804. package/src/memory/migrations/038-actor-token-records.ts +3 -0
  805. package/src/memory/migrations/039-actor-refresh-token-records.ts +3 -0
  806. package/src/memory/migrations/226-schedule-wake-conversation-id.ts +11 -0
  807. package/src/memory/migrations/227-add-conversation-inference-profile.ts +18 -0
  808. package/src/memory/migrations/228-rename-inference-profile-snake-case.ts +27 -0
  809. package/src/memory/migrations/229-delete-private-conversations.test.ts +1087 -0
  810. package/src/memory/migrations/229-delete-private-conversations.ts +210 -0
  811. package/src/memory/migrations/230-acp-session-history.ts +41 -0
  812. package/src/memory/migrations/231-repair-memory-graph-event-dates.ts +128 -0
  813. package/src/memory/migrations/232-activation-state.ts +38 -0
  814. package/src/memory/migrations/index.ts +10 -0
  815. package/src/memory/migrations/registry.ts +7 -0
  816. package/src/memory/pkb/pkb-index.test.ts +4 -5
  817. package/src/memory/pkb/pkb-reconcile.test.ts +4 -5
  818. package/src/memory/pkb/pkb-search.test.ts +83 -3
  819. package/src/memory/pkb/pkb-search.ts +27 -14
  820. package/src/memory/published-pages-store.ts +1 -1
  821. package/src/memory/schema/acp.ts +30 -0
  822. package/src/memory/schema/conversations.ts +1 -1
  823. package/src/memory/schema/index.ts +1 -0
  824. package/src/memory/schema/infrastructure.ts +1 -32
  825. package/src/memory/schema/memory-graph.ts +36 -14
  826. package/src/memory/scoped-approval-grants.ts +2 -1
  827. package/src/memory/search/semantic.ts +2 -2
  828. package/src/memory/shared-app-links-store.ts +2 -1
  829. package/src/memory/tool-usage-store.ts +1 -1
  830. package/src/memory/trace-event-store.ts +2 -1
  831. package/src/memory/turn-events-store.ts +1 -1
  832. package/src/memory/v2/__tests__/activation-store.test.ts +202 -0
  833. package/src/memory/v2/__tests__/activation.test.ts +956 -0
  834. package/src/memory/v2/__tests__/backfill-jobs.test.ts +610 -0
  835. package/src/memory/v2/__tests__/consolidation-job.test.ts +395 -0
  836. package/src/memory/v2/__tests__/edges.test.ts +435 -0
  837. package/src/memory/v2/__tests__/injection.test.ts +792 -0
  838. package/src/memory/v2/__tests__/migration.test.ts +812 -0
  839. package/src/memory/v2/__tests__/page-store.test.ts +334 -0
  840. package/src/memory/v2/__tests__/qdrant.test.ts +438 -0
  841. package/src/memory/v2/__tests__/sim.test.ts +549 -0
  842. package/src/memory/v2/__tests__/skill-content.test.ts +85 -0
  843. package/src/memory/v2/__tests__/skill-qdrant.test.ts +657 -0
  844. package/src/memory/v2/__tests__/skill-store.test.ts +351 -0
  845. package/src/memory/v2/__tests__/sweep-job.test.ts +441 -0
  846. package/src/memory/v2/activation-store.ts +109 -0
  847. package/src/memory/v2/activation.ts +490 -0
  848. package/src/memory/v2/backfill-jobs.ts +442 -0
  849. package/src/memory/v2/consolidation-job.ts +304 -0
  850. package/src/memory/v2/edges.ts +217 -0
  851. package/src/memory/v2/injection.ts +307 -0
  852. package/src/memory/v2/migration.ts +654 -0
  853. package/src/memory/v2/now-text.ts +38 -0
  854. package/src/memory/v2/page-store.ts +245 -0
  855. package/src/memory/v2/prompts/consolidation.ts +185 -0
  856. package/src/memory/v2/prompts/sweep.ts +56 -0
  857. package/src/memory/v2/qdrant.ts +342 -0
  858. package/src/memory/v2/sim.ts +206 -0
  859. package/src/memory/v2/skill-content.ts +42 -0
  860. package/src/memory/v2/skill-qdrant.ts +395 -0
  861. package/src/memory/v2/skill-store.ts +128 -0
  862. package/src/memory/v2/sweep-job.ts +298 -0
  863. package/src/memory/v2/types.ts +116 -0
  864. package/src/memory/validation.ts +1 -1
  865. package/src/messaging/providers/index.ts +262 -0
  866. package/src/messaging/providers/slack/api.ts +242 -0
  867. package/src/messaging/providers/slack/message-metadata.ts +1 -1
  868. package/src/messaging/providers/slack/send.ts +383 -0
  869. package/src/messaging/providers/telegram-bot/adapter.ts +4 -42
  870. package/src/messaging/providers/telegram-bot/api.ts +253 -0
  871. package/src/messaging/providers/telegram-bot/client.ts +17 -58
  872. package/src/messaging/providers/telegram-bot/send.ts +232 -0
  873. package/src/messaging/providers/whatsapp/adapter.ts +4 -36
  874. package/src/messaging/providers/whatsapp/api.ts +319 -0
  875. package/src/messaging/providers/whatsapp/client.ts +4 -48
  876. package/src/messaging/providers/whatsapp/send.ts +209 -0
  877. package/src/notifications/adapters/slack.ts +5 -23
  878. package/src/notifications/adapters/telegram.ts +8 -29
  879. package/src/notifications/conversation-candidates.ts +1 -1
  880. package/src/notifications/conversation-seed-composer.ts +12 -6
  881. package/src/notifications/copy-composer.ts +1 -1
  882. package/src/notifications/decision-engine.ts +1 -1
  883. package/src/notifications/decisions-store.ts +1 -1
  884. package/src/notifications/deliveries-store.ts +2 -1
  885. package/src/notifications/deterministic-checks.ts +1 -1
  886. package/src/notifications/events-store.ts +1 -13
  887. package/src/notifications/preferences-store.ts +1 -1
  888. package/src/notifications/signal.ts +0 -9
  889. package/src/oauth/connection-resolver.ts +11 -2
  890. package/src/oauth/oauth-store.ts +2 -1
  891. package/src/outbound-proxy/index.ts +0 -1
  892. package/src/permissions/approval-policy.test.ts +149 -132
  893. package/src/permissions/approval-policy.ts +65 -91
  894. package/src/permissions/checker.test.ts +632 -0
  895. package/src/permissions/checker.ts +266 -459
  896. package/src/permissions/gateway-threshold-reader.ts +28 -47
  897. package/src/permissions/ipc-risk-types.ts +95 -0
  898. package/src/permissions/prompter.ts +4 -9
  899. package/src/permissions/risk-types.ts +24 -210
  900. package/src/permissions/types.ts +17 -47
  901. package/src/permissions/workspace-policy.ts +2 -4
  902. package/src/playbooks/playbook-compiler.ts +1 -1
  903. package/src/plugins/defaults/index.ts +1 -1
  904. package/src/plugins/defaults/injectors.ts +18 -21
  905. package/src/plugins/defaults/llm-call.ts +6 -9
  906. package/src/plugins/defaults/memory-retrieval.ts +1 -6
  907. package/src/plugins/defaults/overflow-reduce.ts +9 -5
  908. package/src/plugins/defaults/token-estimate.ts +2 -3
  909. package/src/plugins/registry.ts +61 -1
  910. package/src/plugins/types.ts +6 -7
  911. package/src/plugins/user-loader.ts +36 -10
  912. package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +197 -0
  913. package/src/prompts/persona-resolver.ts +2 -4
  914. package/src/prompts/system-prompt.ts +39 -0
  915. package/src/prompts/templates/SOUL.md +3 -1
  916. package/src/providers/__tests__/provider-env-vars.test.ts +0 -21
  917. package/src/providers/__tests__/retry-callsite.test.ts +3 -6
  918. package/src/providers/anthropic/client.ts +71 -19
  919. package/src/providers/call-site-routing.ts +7 -3
  920. package/src/providers/fireworks/client.ts +3 -0
  921. package/src/providers/gemini/client.ts +96 -22
  922. package/src/providers/managed-proxy/context.ts +0 -12
  923. package/src/providers/model-catalog.ts +83 -8
  924. package/src/providers/model-intents.ts +7 -8
  925. package/src/providers/openai/chat-completions-provider.ts +37 -7
  926. package/src/providers/openai/responses-provider.ts +39 -4
  927. package/src/providers/openrouter/client.ts +4 -5
  928. package/src/providers/provider-env-vars.ts +4 -12
  929. package/src/providers/provider-send-message.ts +16 -11
  930. package/src/providers/registry.ts +1 -1
  931. package/src/providers/retry.ts +52 -23
  932. package/src/providers/speech-to-text/openai-whisper-stream.ts +1 -1
  933. package/src/providers/speech-to-text/openai-whisper.ts +3 -6
  934. package/src/providers/speech-to-text/provider-catalog.ts +75 -0
  935. package/src/providers/speech-to-text/xai.ts +5 -5
  936. package/src/providers/thinking-config.ts +34 -0
  937. package/src/providers/types.ts +22 -10
  938. package/src/runtime/AGENTS.md +10 -9
  939. package/src/runtime/__tests__/agent-wake.test.ts +33 -9
  940. package/src/runtime/__tests__/client-registry.test.ts +5 -27
  941. package/src/runtime/__tests__/interactive-ui.test.ts +157 -246
  942. package/src/runtime/access-request-helper.ts +9 -20
  943. package/src/runtime/actor-trust-resolver.ts +2 -2
  944. package/src/runtime/agent-wake.ts +174 -68
  945. package/src/runtime/approval-conversation-turn.ts +2 -15
  946. package/src/runtime/approval-message-composer.ts +11 -60
  947. package/src/runtime/assistant-event.ts +18 -66
  948. package/src/runtime/auth/__tests__/guard-tests.test.ts +6 -30
  949. package/src/runtime/auth/__tests__/middleware.test.ts +10 -10
  950. package/src/runtime/auth/__tests__/route-policy.test.ts +0 -8
  951. package/src/runtime/auth/context.ts +9 -0
  952. package/src/runtime/auth/middleware.ts +4 -4
  953. package/src/runtime/auth/route-policy.ts +195 -4
  954. package/src/runtime/auth/token-service.ts +1 -100
  955. package/src/runtime/capability-tokens.ts +89 -313
  956. package/src/runtime/channel-approval-types.ts +1 -6
  957. package/src/runtime/channel-approvals.ts +7 -79
  958. package/src/runtime/channel-readiness-service.ts +2 -2
  959. package/src/runtime/channel-reply-delivery.ts +2 -8
  960. package/src/runtime/channel-retry-sweep.ts +20 -17
  961. package/src/runtime/client-registry.ts +21 -28
  962. package/src/runtime/confirmation-request-guardian-bridge.ts +2 -7
  963. package/src/runtime/gateway-client.ts +37 -378
  964. package/src/runtime/guardian-action-grant-minter.ts +2 -3
  965. package/src/runtime/guardian-action-message-composer.ts +11 -52
  966. package/src/runtime/guardian-action-service.ts +19 -7
  967. package/src/runtime/guardian-decision-types.ts +4 -65
  968. package/src/runtime/guardian-reply-router.ts +10 -19
  969. package/src/runtime/guardian-vellum-migration.ts +5 -64
  970. package/src/runtime/http-errors.ts +3 -0
  971. package/src/runtime/http-router.ts +50 -7
  972. package/src/runtime/http-server.ts +345 -1110
  973. package/src/runtime/http-types.ts +15 -98
  974. package/src/runtime/interactive-ui-types.ts +145 -0
  975. package/src/runtime/interactive-ui.ts +38 -196
  976. package/src/runtime/invite-redemption-service.ts +1 -1
  977. package/src/runtime/invite-redemption-templates.ts +1 -1
  978. package/src/runtime/local-actor-identity.ts +13 -43
  979. package/src/runtime/message-composer-types.ts +134 -0
  980. package/src/runtime/middleware/rate-limiter.ts +1 -1
  981. package/src/runtime/middleware/request-logger.ts +5 -2
  982. package/src/runtime/migrations/__tests__/job-registry.test.ts +346 -0
  983. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +16 -0
  984. package/src/runtime/migrations/job-registry.ts +281 -0
  985. package/src/runtime/migrations/vbundle-builder.ts +3 -4
  986. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  987. package/src/runtime/migrations/vbundle-streaming-importer.ts +0 -13
  988. package/src/runtime/migrations/vbundle-tar-stream.ts +11 -3
  989. package/src/runtime/nl-approval-parser.ts +16 -21
  990. package/src/runtime/pending-interactions.ts +29 -12
  991. package/src/runtime/routes/__tests__/acp-routes.test.ts +395 -0
  992. package/src/runtime/routes/__tests__/backup-routes.test.ts +204 -320
  993. package/src/runtime/routes/__tests__/home-feed-routes.test.ts +72 -4
  994. package/src/runtime/routes/__tests__/stt-routes.test.ts +182 -223
  995. package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +230 -0
  996. package/src/{ipc/__tests__/task-ipc.test.ts → runtime/routes/__tests__/task-routes.test.ts} +116 -96
  997. package/src/runtime/routes/__tests__/tts-routes.test.ts +185 -289
  998. package/src/runtime/routes/access-request-decision.ts +25 -50
  999. package/src/runtime/routes/acp-routes.test.ts +371 -0
  1000. package/src/runtime/routes/acp-routes.ts +392 -166
  1001. package/src/runtime/routes/app-management-routes.ts +464 -660
  1002. package/src/runtime/routes/app-routes.ts +192 -177
  1003. package/src/runtime/routes/approval-routes.ts +116 -434
  1004. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +24 -84
  1005. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +3 -10
  1006. package/src/runtime/routes/attachment-routes.ts +409 -253
  1007. package/src/runtime/routes/audio-routes.ts +51 -18
  1008. package/src/runtime/routes/avatar-routes.ts +82 -75
  1009. package/src/runtime/routes/background-tool-routes.ts +94 -0
  1010. package/src/runtime/routes/backup-routes.ts +154 -336
  1011. package/src/runtime/routes/brain-graph-routes.ts +83 -110
  1012. package/src/runtime/routes/browser-routes.ts +141 -0
  1013. package/src/runtime/routes/btw-routes.ts +62 -106
  1014. package/src/runtime/routes/cache-routes.ts +96 -0
  1015. package/src/runtime/routes/call-routes.ts +208 -247
  1016. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +1 -1
  1017. package/src/runtime/routes/channel-delivery-routes.ts +25 -27
  1018. package/src/runtime/routes/channel-readiness-routes.ts +83 -120
  1019. package/src/runtime/routes/channel-route-definitions.ts +62 -0
  1020. package/src/runtime/routes/channel-route-shared.ts +14 -18
  1021. package/src/runtime/routes/channel-verification-routes.ts +207 -187
  1022. package/src/runtime/routes/client-routes.ts +48 -0
  1023. package/src/runtime/routes/contact-routes.ts +533 -407
  1024. package/src/runtime/routes/conversation-analysis-routes.ts +48 -49
  1025. package/src/runtime/routes/conversation-attention-routes.ts +55 -67
  1026. package/src/runtime/routes/conversation-list-routes.ts +265 -0
  1027. package/src/runtime/routes/conversation-management-routes.ts +626 -715
  1028. package/src/runtime/routes/conversation-query-routes.ts +510 -460
  1029. package/src/runtime/routes/conversation-routes.ts +456 -368
  1030. package/src/runtime/routes/conversation-starter-routes.ts +121 -71
  1031. package/src/runtime/routes/credential-prompt-routes.ts +124 -0
  1032. package/src/runtime/routes/debug-routes.ts +34 -39
  1033. package/src/runtime/routes/defer-routes.ts +230 -0
  1034. package/src/runtime/routes/diagnostics-routes.ts +79 -70
  1035. package/src/runtime/routes/documents-routes.ts +117 -106
  1036. package/src/runtime/routes/errors.ts +132 -0
  1037. package/src/runtime/routes/events-routes.ts +97 -58
  1038. package/src/runtime/routes/filing-routes.ts +65 -78
  1039. package/src/runtime/routes/global-search-routes.ts +51 -57
  1040. package/src/runtime/routes/group-routes.ts +199 -181
  1041. package/src/runtime/routes/guardian-action-routes.ts +103 -169
  1042. package/src/runtime/routes/guardian-approval-interception.ts +27 -58
  1043. package/src/runtime/routes/guardian-approval-prompt.ts +10 -21
  1044. package/src/runtime/routes/guardian-approval-reply-helpers.ts +2 -6
  1045. package/src/runtime/routes/guardian-expiry-sweep.ts +19 -36
  1046. package/src/runtime/routes/heartbeat-routes.ts +194 -209
  1047. package/src/runtime/routes/home-feed-routes.ts +85 -187
  1048. package/src/runtime/routes/home-state-routes.ts +27 -24
  1049. package/src/runtime/routes/host-bash-routes.ts +42 -52
  1050. package/src/runtime/routes/host-browser-routes.ts +38 -69
  1051. package/src/runtime/routes/host-cu-routes.ts +74 -70
  1052. package/src/runtime/routes/host-file-routes.ts +50 -60
  1053. package/src/runtime/routes/host-transfer-routes.ts +220 -0
  1054. package/src/runtime/routes/http-adapter.ts +172 -0
  1055. package/src/runtime/routes/identity-routes.ts +83 -79
  1056. package/src/runtime/routes/inbound-conversation.ts +11 -18
  1057. package/src/runtime/routes/inbound-message-handler.ts +74 -110
  1058. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +79 -138
  1059. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +2 -3
  1060. package/src/runtime/routes/inbound-stages/background-dispatch.ts +54 -90
  1061. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +25 -50
  1062. package/src/runtime/routes/inbound-stages/edit-intercept.ts +7 -7
  1063. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  1064. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +5 -6
  1065. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -24
  1066. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -10
  1067. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -4
  1068. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +3 -3
  1069. package/src/runtime/routes/inbound-stages/verification-intercept.ts +19 -26
  1070. package/src/runtime/routes/index.ts +197 -0
  1071. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +25 -32
  1072. package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +22 -31
  1073. package/src/runtime/routes/integrations/slack/channel.ts +69 -66
  1074. package/src/runtime/routes/integrations/slack/share.ts +49 -58
  1075. package/src/runtime/routes/integrations/telegram.ts +91 -74
  1076. package/src/runtime/routes/integrations/twilio.ts +163 -240
  1077. package/src/runtime/routes/integrations/vercel.ts +57 -54
  1078. package/src/runtime/routes/interface-routes.ts +43 -0
  1079. package/src/runtime/routes/internal-oauth-routes.ts +56 -0
  1080. package/src/runtime/routes/internal-twilio-routes.ts +46 -0
  1081. package/src/runtime/routes/llm-context-normalization.ts +4 -2
  1082. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  1083. package/src/runtime/routes/log-export-routes.ts +90 -100
  1084. package/src/runtime/routes/memory-item-routes.test.ts +152 -175
  1085. package/src/runtime/routes/memory-item-routes.ts +243 -323
  1086. package/src/runtime/routes/memory-v2-routes.ts +193 -0
  1087. package/src/runtime/routes/migration-rollback-routes.ts +167 -212
  1088. package/src/runtime/routes/migration-routes.ts +877 -374
  1089. package/src/runtime/routes/notification-routes.ts +199 -70
  1090. package/src/runtime/routes/oauth-apps.ts +254 -251
  1091. package/src/runtime/routes/oauth-providers.ts +66 -57
  1092. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +60 -120
  1093. package/src/runtime/routes/playground/__tests__/guard.test.ts +34 -54
  1094. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +107 -151
  1095. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +41 -117
  1096. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +95 -138
  1097. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +115 -217
  1098. package/src/runtime/routes/playground/__tests__/state.test.ts +41 -90
  1099. package/src/runtime/routes/playground/conversation-not-found.ts +9 -11
  1100. package/src/runtime/routes/playground/force-compact.ts +41 -54
  1101. package/src/runtime/routes/playground/guard.ts +18 -19
  1102. package/src/runtime/routes/playground/helpers.ts +103 -0
  1103. package/src/runtime/routes/playground/index.ts +15 -25
  1104. package/src/runtime/routes/playground/inject-failures.ts +48 -64
  1105. package/src/runtime/routes/playground/reset-circuit.ts +31 -57
  1106. package/src/runtime/routes/playground/seed-conversation.ts +66 -92
  1107. package/src/runtime/routes/playground/seeded-conversations.ts +60 -64
  1108. package/src/runtime/routes/playground/state.ts +23 -24
  1109. package/src/runtime/routes/profiler-routes.ts +132 -167
  1110. package/src/runtime/routes/ps-routes.ts +120 -0
  1111. package/src/runtime/routes/recording-routes.ts +197 -258
  1112. package/src/runtime/routes/rename-conversation-routes.ts +89 -0
  1113. package/src/runtime/routes/schedule-routes.ts +238 -242
  1114. package/src/runtime/routes/secret-routes.ts +219 -265
  1115. package/src/runtime/routes/secrets-deps.ts +24 -0
  1116. package/src/runtime/routes/settings-routes.ts +361 -441
  1117. package/src/runtime/routes/skills-routes.ts +434 -469
  1118. package/src/runtime/routes/stt-routes.ts +196 -206
  1119. package/src/runtime/routes/subagents-routes.ts +125 -141
  1120. package/src/runtime/routes/suggest-trust-rule-routes.ts +244 -0
  1121. package/src/runtime/routes/surface-action-routes.ts +135 -190
  1122. package/src/runtime/routes/surface-content-routes.ts +84 -118
  1123. package/src/runtime/routes/task-routes.ts +354 -0
  1124. package/src/runtime/routes/telemetry-routes.ts +33 -49
  1125. package/src/runtime/routes/trace-event-routes.ts +55 -74
  1126. package/src/runtime/routes/trust-rules-routes.ts +147 -239
  1127. package/src/runtime/routes/tts-routes.ts +187 -169
  1128. package/src/runtime/routes/types.ts +139 -0
  1129. package/src/{ipc/routes/ui-request.ts → runtime/routes/ui-request-routes.ts} +23 -17
  1130. package/src/runtime/routes/upgrade-broadcast-routes.ts +156 -197
  1131. package/src/runtime/routes/usage-routes.ts +143 -169
  1132. package/src/runtime/routes/user-routes.ts +102 -18
  1133. package/src/runtime/routes/wake-conversation-routes.ts +49 -0
  1134. package/src/{ipc/routes/watcher.ts → runtime/routes/watcher-routes.ts} +84 -39
  1135. package/src/runtime/routes/wipe-conversation-routes.ts +89 -0
  1136. package/src/runtime/routes/work-items-routes.test.ts +10 -20
  1137. package/src/runtime/routes/work-items-routes.ts +418 -433
  1138. package/src/runtime/routes/workspace-commit-routes.ts +30 -61
  1139. package/src/runtime/routes/workspace-routes.test.ts +254 -381
  1140. package/src/runtime/routes/workspace-routes.ts +238 -246
  1141. package/src/runtime/runtime-mode.ts +8 -1
  1142. package/src/runtime/services/__tests__/analyze-conversation.test.ts +80 -118
  1143. package/src/runtime/services/analyze-conversation.ts +14 -41
  1144. package/src/runtime/services/conversation-serializer.ts +181 -0
  1145. package/src/runtime/trust-context-resolver.ts +3 -2
  1146. package/src/runtime/verification-outbound-actions.ts +13 -49
  1147. package/src/schedule/schedule-store.ts +64 -2
  1148. package/src/schedule/scheduler.ts +101 -0
  1149. package/src/security/ces-credential-client.ts +32 -169
  1150. package/src/security/ces-rpc-credential-backend.ts +1 -1
  1151. package/src/security/credential-backend.ts +6 -6
  1152. package/src/security/oauth-completion-page.ts +1 -1
  1153. package/src/security/oauth2.ts +3 -6
  1154. package/src/sequence/analytics.ts +1 -1
  1155. package/src/sequence/guardrails.ts +3 -3
  1156. package/src/sequence/store.ts +2 -1
  1157. package/src/signals/bash.ts +1 -1
  1158. package/src/signals/event-stream.ts +1 -1
  1159. package/src/skills/catalog-cache.ts +7 -0
  1160. package/src/skills/catalog-files.ts +0 -5
  1161. package/src/skills/catalog-install.ts +28 -18
  1162. package/src/skills/category-inference.ts +0 -11
  1163. package/src/skills/clawhub.ts +2 -2
  1164. package/src/skills/managed-store.ts +2 -2
  1165. package/src/skills/remote-skill-policy.ts +6 -7
  1166. package/src/subagent/index.ts +2 -6
  1167. package/src/subagent/manager.ts +27 -23
  1168. package/src/subagent/types.ts +9 -0
  1169. package/src/tasks/SPEC.md +2 -2
  1170. package/src/tasks/task-compiler.ts +1 -1
  1171. package/src/tasks/task-runner.ts +2 -22
  1172. package/src/tasks/task-store.ts +1 -1
  1173. package/src/tools/acp/list-agents.test.ts +115 -0
  1174. package/src/tools/acp/list-agents.ts +31 -0
  1175. package/src/tools/acp/spawn.test.ts +379 -0
  1176. package/src/tools/acp/spawn.ts +142 -62
  1177. package/src/tools/acp/steer.test.ts +101 -0
  1178. package/src/tools/acp/steer.ts +38 -0
  1179. package/src/tools/background-tool-registry.ts +98 -0
  1180. package/src/tools/browser/browser-execution.ts +34 -7
  1181. package/src/tools/browser/browser-manager.ts +1 -8
  1182. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +1 -1
  1183. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +3 -1
  1184. package/src/tools/browser/cdp-client/types.ts +4 -1
  1185. package/src/tools/computer-use/definitions.ts +1 -1
  1186. package/src/tools/credential-execution/make-authenticated-request.ts +2 -2
  1187. package/src/tools/credential-execution/manage-secure-command-tool.ts +1 -1
  1188. package/src/tools/credential-execution/run-authenticated-command.ts +2 -2
  1189. package/src/tools/credentials/broker-types.ts +2 -1
  1190. package/src/tools/document/editor-template.ts +1 -1
  1191. package/src/tools/execution-timeout.ts +1 -1
  1192. package/src/tools/executor.ts +10 -15
  1193. package/src/tools/host-filesystem/transfer.test.ts +268 -0
  1194. package/src/tools/host-filesystem/transfer.ts +234 -0
  1195. package/src/tools/host-terminal/host-shell.ts +189 -11
  1196. package/src/tools/mcp/mcp-tool-factory.ts +1 -1
  1197. package/src/tools/memory/register.test.ts +161 -1
  1198. package/src/tools/memory/register.ts +19 -34
  1199. package/src/tools/permission-checker.ts +18 -219
  1200. package/src/tools/policy-context.ts +1 -8
  1201. package/src/tools/registry.ts +16 -1
  1202. package/src/tools/secret-detection-handler.ts +13 -103
  1203. package/src/tools/shared/shell-output.ts +4 -1
  1204. package/src/tools/side-effects.ts +2 -2
  1205. package/src/tools/skills/execute.ts +1 -1
  1206. package/src/tools/subagent/spawn.ts +35 -11
  1207. package/src/tools/terminal/safe-env.ts +9 -1
  1208. package/src/tools/terminal/shell.ts +161 -31
  1209. package/src/tools/tool-approval-handler.ts +4 -70
  1210. package/src/tools/tool-input-summary.ts +10 -0
  1211. package/src/tools/types.ts +143 -163
  1212. package/src/tools/ui-surface/definitions.ts +2 -2
  1213. package/src/util/debounce.ts +0 -21
  1214. package/src/util/errors.ts +0 -8
  1215. package/src/util/log-redact.ts +0 -1
  1216. package/src/util/platform.ts +85 -124
  1217. package/src/util/pricing.ts +109 -6
  1218. package/src/watcher/engine.ts +42 -20
  1219. package/src/watcher/watcher-store.ts +2 -1
  1220. package/src/work-items/work-item-store.ts +1 -1
  1221. package/src/workspace/git-service.ts +1 -6
  1222. package/src/workspace/migrations/006-services-config.ts +10 -1
  1223. package/src/workspace/migrations/017-seed-persona-dirs.ts +1 -1
  1224. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +1 -1
  1225. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +1 -1
  1226. package/src/workspace/migrations/031-drop-user-md.ts +1 -1
  1227. package/src/workspace/migrations/045-release-notes-meet-avatar.ts +3 -4
  1228. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +150 -0
  1229. package/src/workspace/migrations/053-release-notes-acp-codex.ts +107 -0
  1230. package/src/workspace/migrations/054-seed-recall-callsite.ts +102 -0
  1231. package/src/workspace/migrations/055-release-notes-agentic-recall.ts +63 -0
  1232. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +65 -0
  1233. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +98 -0
  1234. package/src/workspace/migrations/058-release-notes-acp-sessions-ui.ts +71 -0
  1235. package/src/workspace/migrations/059-move-pid-to-workspace.ts +53 -0
  1236. package/src/workspace/migrations/060-memory-v2-init.ts +53 -0
  1237. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +1 -1
  1238. package/src/workspace/migrations/registry.ts +18 -0
  1239. package/src/workspace/migrations/runner.ts +2 -2
  1240. package/src/workspace/provider-commit-message-generator.ts +1 -1
  1241. package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +0 -471
  1242. package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +0 -436
  1243. package/src/__tests__/cli-command-risk-guard.test.ts +0 -368
  1244. package/src/__tests__/config-watcher-feature-flags.test.ts +0 -211
  1245. package/src/__tests__/conversation-approval-overrides.test.ts +0 -207
  1246. package/src/__tests__/conversation-host-access-routes.test.ts +0 -229
  1247. package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +0 -226
  1248. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +0 -167
  1249. package/src/__tests__/ephemeral-permissions.test.ts +0 -474
  1250. package/src/__tests__/extension-id-sync-guard.test.ts +0 -241
  1251. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +0 -374
  1252. package/src/__tests__/native-host-marker-sync-guard.test.ts +0 -157
  1253. package/src/__tests__/pairing-concurrent.test.ts +0 -84
  1254. package/src/__tests__/pairing-routes.test.ts +0 -181
  1255. package/src/__tests__/parser.test.ts +0 -595
  1256. package/src/__tests__/permission-checker-host-gate.test.ts +0 -488
  1257. package/src/__tests__/permission-controls-v2-flag.test.ts +0 -55
  1258. package/src/__tests__/permission-mode.test.ts +0 -89
  1259. package/src/__tests__/provider-env-vars-scope.test.ts +0 -52
  1260. package/src/__tests__/risk-classifier-parity.test.ts +0 -230
  1261. package/src/__tests__/shell-identity.test.ts +0 -236
  1262. package/src/__tests__/shell-parser-fuzz.test.ts +0 -629
  1263. package/src/__tests__/shell-parser-property.test.ts +0 -936
  1264. package/src/__tests__/starter-bundle.test.ts +0 -173
  1265. package/src/__tests__/stt-catalog-parity.test.ts +0 -282
  1266. package/src/__tests__/task-runner.test.ts +0 -224
  1267. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -354
  1268. package/src/__tests__/trust-store-pattern-matches.test.ts +0 -29
  1269. package/src/__tests__/trust-store.test.ts +0 -2013
  1270. package/src/__tests__/v2-consent-policy.test.ts +0 -103
  1271. package/src/browser/identifiers.ts +0 -51
  1272. package/src/cli/db.ts +0 -1
  1273. package/src/config/bundled-skills/settings/tools/avatar-get.ts +0 -40
  1274. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +0 -64
  1275. package/src/config/bundled-skills/settings/tools/avatar-update.ts +0 -88
  1276. package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +0 -127
  1277. package/src/daemon/approved-devices-store.ts +0 -110
  1278. package/src/daemon/external-skills-bootstrap.ts +0 -41
  1279. package/src/daemon/message-types/trust.ts +0 -71
  1280. package/src/daemon/pairing-store.ts +0 -229
  1281. package/src/ipc/cli-server.ts +0 -252
  1282. package/src/ipc/routes/attachment.ts +0 -114
  1283. package/src/ipc/routes/browser-context.ts +0 -63
  1284. package/src/ipc/routes/browser.ts +0 -97
  1285. package/src/ipc/routes/cache.ts +0 -96
  1286. package/src/ipc/routes/get-contact.ts +0 -16
  1287. package/src/ipc/routes/index.ts +0 -35
  1288. package/src/ipc/routes/list-clients.ts +0 -31
  1289. package/src/ipc/routes/merge-contacts.ts +0 -17
  1290. package/src/ipc/routes/notification.ts +0 -133
  1291. package/src/ipc/routes/rename-conversation.ts +0 -59
  1292. package/src/ipc/routes/search-contacts.ts +0 -19
  1293. package/src/ipc/routes/task-queue.ts +0 -226
  1294. package/src/ipc/routes/task.ts +0 -173
  1295. package/src/ipc/routes/upsert-contact.ts +0 -25
  1296. package/src/ipc/routes/wake-conversation.ts +0 -19
  1297. package/src/memory/db.ts +0 -23
  1298. package/src/permissions/arg-parser.test.ts +0 -161
  1299. package/src/permissions/arg-parser.ts +0 -141
  1300. package/src/permissions/bash-risk-classifier.test.ts +0 -1620
  1301. package/src/permissions/bash-risk-classifier.ts +0 -950
  1302. package/src/permissions/command-registry.test.ts +0 -774
  1303. package/src/permissions/command-registry.ts +0 -1005
  1304. package/src/permissions/defaults.ts +0 -314
  1305. package/src/permissions/file-risk-classifier.test.ts +0 -535
  1306. package/src/permissions/file-risk-classifier.ts +0 -274
  1307. package/src/permissions/permission-mode.ts +0 -24
  1308. package/src/permissions/schedule-risk-classifier.test.ts +0 -129
  1309. package/src/permissions/schedule-risk-classifier.ts +0 -85
  1310. package/src/permissions/shell-identity.ts +0 -297
  1311. package/src/permissions/skill-risk-classifier.test.ts +0 -311
  1312. package/src/permissions/skill-risk-classifier.ts +0 -214
  1313. package/src/permissions/trust-client.ts +0 -359
  1314. package/src/permissions/trust-store-interface.ts +0 -100
  1315. package/src/permissions/trust-store.ts +0 -1330
  1316. package/src/permissions/v2-consent-policy.ts +0 -87
  1317. package/src/permissions/web-risk-classifier.test.ts +0 -170
  1318. package/src/permissions/web-risk-classifier.ts +0 -89
  1319. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +0 -715
  1320. package/src/runtime/__tests__/capability-tokens.test.ts +0 -258
  1321. package/src/runtime/actor-refresh-token-store.ts +0 -156
  1322. package/src/runtime/actor-token-store.ts +0 -207
  1323. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -264
  1324. package/src/runtime/auth/credential-service.ts +0 -352
  1325. package/src/runtime/conversation-approval-overrides.ts +0 -86
  1326. package/src/runtime/routes/browser-extension-pair-routes.ts +0 -575
  1327. package/src/runtime/routes/channel-routes.ts +0 -112
  1328. package/src/runtime/routes/contact-routes.test.ts +0 -298
  1329. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -175
  1330. package/src/runtime/routes/guardian-refresh-routes.ts +0 -79
  1331. package/src/runtime/routes/invite-routes.ts +0 -280
  1332. package/src/runtime/routes/pairing-routes.ts +0 -431
  1333. package/src/runtime/routes/playground/deps.ts +0 -56
  1334. package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +0 -67
  1335. package/src/runtime/services/analyze-deps-singleton.ts +0 -32
  1336. package/src/tasks/ephemeral-permissions.ts +0 -55
  1337. package/src/tools/terminal/parser.ts +0 -623
  1338. package/src/types/qrcode.d.ts +0 -13
  1339. package/src/util/network-info.ts +0 -55
  1340. /package/node_modules/@vellumai/{ces-contracts → ces-client}/tsconfig.json +0 -0
  1341. /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/__tests__/grants.test.ts +0 -0
  1342. /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/error.ts +0 -0
  1343. /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/grants.ts +0 -0
  1344. /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/handles.ts +0 -0
  1345. /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/rendering.ts +0 -0
  1346. /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/rpc.ts +0 -0
@@ -1,2013 +0,0 @@
1
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { beforeEach, describe, expect, mock, test } from "bun:test";
4
-
5
- import { ruleScope } from "@vellumai/ces-contracts";
6
-
7
- // Create a temp directory for the trust file
8
- const testDir = process.env.VELLUM_WORKSPACE_DIR!;
9
-
10
- // Point the file-based trust backend at the test temp dir so
11
- // getGatewaySecurityDir() (which checks this env var first) writes
12
- // trust.json under the test directory instead of ~/.vellum/protected.
13
- process.env.GATEWAY_SECURITY_DIR = join(testDir, "protected");
14
-
15
- // Mock platform module so trust-store writes to temp dir instead of ~/.vellum
16
- // Mock logger to suppress output during tests
17
- mock.module("../util/logger.js", () => ({
18
- getLogger: () => ({
19
- info: () => {},
20
- warn: () => {},
21
- error: () => {},
22
- debug: () => {},
23
- trace: () => {},
24
- fatal: () => {},
25
- child: () => ({
26
- info: () => {},
27
- warn: () => {},
28
- error: () => {},
29
- debug: () => {},
30
- }),
31
- }),
32
- }));
33
-
34
- import { getDefaultRuleTemplates } from "../permissions/defaults.js";
35
- import {
36
- addRule,
37
- clearAllRules,
38
- clearCache,
39
- findDenyRule,
40
- findHighestPriorityRule,
41
- findMatchingRule,
42
- getAllRules,
43
- removeRule,
44
- updateRule,
45
- } from "../permissions/trust-store.js";
46
-
47
- const trustPath = join(testDir, "protected", "trust.json");
48
- const DEFAULT_TEMPLATES = getDefaultRuleTemplates();
49
- const NUM_DEFAULTS = DEFAULT_TEMPLATES.length;
50
- const DEFAULT_PRIORITY_BY_ID = new Map(
51
- DEFAULT_TEMPLATES.map((t) => [t.id, t.priority]),
52
- );
53
-
54
- describe("Trust Store", () => {
55
- beforeEach(() => {
56
- // Clear cached rules and remove the trust file between tests
57
- clearCache();
58
- try {
59
- rmSync(trustPath);
60
- } catch {
61
- /* may not exist */
62
- }
63
- });
64
-
65
- // Intentionally do not remove `testDir` in afterAll.
66
- // A late async log flush can still attempt to open `test.log` under this dir,
67
- // which intermittently causes an unhandled ENOENT in CI if the dir is removed.
68
- // ── addRule ─────────────────────────────────────────────────────
69
-
70
- describe("addRule", () => {
71
- test("adds a rule and returns it", () => {
72
- const rule = addRule("bash", "git *", "/home/user/project");
73
- expect(rule.id).toBeDefined();
74
- expect(rule.tool).toBe("bash");
75
- expect(rule.pattern).toBe("git *");
76
- expect(rule.scope).toBe("/home/user/project");
77
- expect(rule.decision).toBe("allow");
78
- expect(rule.priority).toBe(100);
79
- expect(rule.createdAt).toBeGreaterThan(0);
80
- });
81
-
82
- test("assigns unique IDs to each rule", () => {
83
- const rule1 = addRule("bash", "npm *", "/tmp");
84
- const rule2 = addRule("bash", "bun *", "/tmp");
85
- expect(rule1.id).not.toBe(rule2.id);
86
- });
87
-
88
- test("persists rule to disk", () => {
89
- addRule("bash", "git push", "/home/user");
90
- const raw = readFileSync(trustPath, "utf-8");
91
- const data = JSON.parse(raw);
92
- expect(data.version).toBe(3);
93
- expect(data.rules).toHaveLength(1 + NUM_DEFAULTS);
94
- const userRule = data.rules.find(
95
- (r: { pattern: string }) => r.pattern === "git push",
96
- );
97
- expect(userRule).toBeDefined();
98
- expect(userRule.priority).toBe(100);
99
- });
100
-
101
- test("multiple rules accumulate", () => {
102
- addRule("bash", "git *", "/tmp");
103
- addRule("file_write", "/tmp/*", "/tmp");
104
- addRule("bash", "npm *", "/tmp");
105
- expect(getAllRules()).toHaveLength(3 + NUM_DEFAULTS);
106
- });
107
-
108
- test("default priority is 100", () => {
109
- const rule = addRule("bash", "git *", "/tmp");
110
- expect(rule.priority).toBe(100);
111
- });
112
-
113
- test("custom priority is respected", () => {
114
- const rule = addRule("bash", "git *", "/tmp", "allow", 5);
115
- expect(rule.priority).toBe(5);
116
- });
117
-
118
- test("rules are sorted by priority descending in getAllRules", () => {
119
- addRule("bash", "low *", "/tmp", "allow", 0);
120
- addRule("bash", "high *", "/tmp", "allow", 2);
121
- addRule("bash", "med *", "/tmp", "allow", 1);
122
- const rules = getAllRules();
123
- // Default ask rules have higher priority than user rules
124
- const maxDefaultPriority = Math.max(
125
- ...DEFAULT_TEMPLATES.map((t) => t.priority),
126
- );
127
- expect(rules[0].priority).toBe(maxDefaultPriority);
128
- const userRules = rules.filter((r) => !r.id.startsWith("default:"));
129
- expect(userRules[0].priority).toBe(2);
130
- expect(userRules[1].priority).toBe(1);
131
- expect(userRules[2].priority).toBe(0);
132
- });
133
-
134
- test("allowHighRisk option is stripped during normalization", () => {
135
- // allowHighRisk is no longer persisted — the parser strips it.
136
- const rule = addRule("bash", "sudo *", "everywhere", "allow", 100);
137
- // Verify the field is not on the rule
138
- expect(
139
- (rule as unknown as Record<string, unknown>).allowHighRisk,
140
- ).toBeUndefined();
141
- });
142
-
143
- test("at same priority deny rules sort before allow rules", () => {
144
- addRule("bash", "allow *", "/tmp", "allow", 100);
145
- addRule("bash", "deny *", "/tmp", "deny", 100);
146
- const userRules = getAllRules().filter(
147
- (r) => !r.id.startsWith("default:"),
148
- );
149
- expect(userRules[0].decision).toBe("deny");
150
- expect(userRules[1].decision).toBe("allow");
151
- });
152
-
153
- test("accepts executionTarget option and persists it", () => {
154
- const rule = addRule("skill_tool", "skill_tool:*", "/tmp", "allow", 100, {
155
- executionTarget: "sandbox",
156
- });
157
- expect(rule.executionTarget).toBe("sandbox");
158
-
159
- // Verify persistence to disk
160
- clearCache();
161
- const rules = getAllRules();
162
- const found = rules.find((r) => r.id === rule.id);
163
- expect(found).toBeDefined();
164
- expect(found!.executionTarget).toBe("sandbox");
165
- });
166
-
167
- test("accepts executionTarget option (allowHighRisk no longer supported)", () => {
168
- const rule = addRule(
169
- "risky_tool",
170
- "risky_tool:*",
171
- "everywhere",
172
- "allow",
173
- 100,
174
- {
175
- executionTarget: "host",
176
- },
177
- );
178
- expect(rule.executionTarget).toBe("host");
179
-
180
- // Verify on disk — allowHighRisk should not appear
181
- const raw = JSON.parse(readFileSync(trustPath, "utf-8"));
182
- const diskRule = raw.rules.find((r: { id: string }) => r.id === rule.id);
183
- expect(diskRule).toBeDefined();
184
- expect(diskRule).not.toHaveProperty("allowHighRisk");
185
- expect(diskRule.executionTarget).toBe("host");
186
- });
187
-
188
- test("addRule without options does not set optional fields", () => {
189
- const rule = addRule("bash", "echo *", "/tmp");
190
- expect(rule.executionTarget).toBeUndefined();
191
-
192
- // Verify on disk
193
- const raw = JSON.parse(readFileSync(trustPath, "utf-8"));
194
- const diskRule = raw.rules.find((r: { id: string }) => r.id === rule.id);
195
- expect(diskRule).toBeDefined();
196
- expect(diskRule).not.toHaveProperty("executionTarget");
197
- });
198
- });
199
-
200
- // ── removeRule ──────────────────────────────────────────────────
201
-
202
- describe("removeRule", () => {
203
- test("removes an existing rule", () => {
204
- const rule = addRule("bash", "git *", "/tmp");
205
- expect(removeRule(rule.id)).toBe(true);
206
- expect(getAllRules()).toHaveLength(NUM_DEFAULTS);
207
- });
208
-
209
- test("returns false for non-existent ID", () => {
210
- expect(removeRule("non-existent-id")).toBe(false);
211
- });
212
-
213
- test("persists removal to disk", () => {
214
- const rule = addRule("bash", "npm *", "/tmp");
215
- removeRule(rule.id);
216
- // Reload from disk to verify
217
- clearCache();
218
- expect(getAllRules()).toHaveLength(NUM_DEFAULTS);
219
- });
220
-
221
- test("only removes the targeted rule", () => {
222
- const rule1 = addRule("bash", "git *", "/tmp");
223
- const rule2 = addRule("bash", "npm *", "/tmp");
224
- removeRule(rule1.id);
225
- const remaining = getAllRules();
226
- expect(remaining).toHaveLength(1 + NUM_DEFAULTS);
227
- expect(remaining.find((r) => r.id === rule2.id)).toBeDefined();
228
- });
229
- });
230
-
231
- // ── updateRule ─────────────────────────────────────────────────
232
-
233
- describe("updateRule", () => {
234
- test("updates pattern on an existing rule", () => {
235
- const rule = addRule("bash", "git *", "/tmp");
236
- const updated = updateRule(rule.id, { pattern: "git push *" });
237
- expect(updated.pattern).toBe("git push *");
238
- expect(updated.id).toBe(rule.id);
239
- expect(updated.tool).toBe("bash");
240
- });
241
-
242
- test("updates multiple fields at once", () => {
243
- const rule = addRule("bash", "npm *", "/tmp");
244
- const updated = updateRule(rule.id, {
245
- tool: "file_write",
246
- scope: "/home",
247
- decision: "deny",
248
- priority: 50,
249
- });
250
- expect(updated.tool).toBe("file_write");
251
- expect(updated.scope).toBe("/home");
252
- expect(updated.decision).toBe("deny");
253
- expect(updated.priority).toBe(50);
254
- });
255
-
256
- test("throws for non-existent rule ID", () => {
257
- expect(() => updateRule("non-existent-id", { pattern: "test" })).toThrow(
258
- "Trust rule not found: non-existent-id",
259
- );
260
- });
261
-
262
- test("persists update to disk", () => {
263
- const rule = addRule("bash", "git *", "/tmp");
264
- updateRule(rule.id, { pattern: "git status" });
265
- clearCache();
266
- const rules = getAllRules();
267
- const found = rules.find((r) => r.id === rule.id);
268
- expect(found).toBeDefined();
269
- expect(found!.pattern).toBe("git status");
270
- });
271
-
272
- test("re-sorts rules after priority change", () => {
273
- const rule1 = addRule("bash", "low *", "/tmp", "allow", 10);
274
- const rule2 = addRule("bash", "high *", "/tmp", "allow", 200);
275
- // rule2 should be first (higher priority)
276
- let userRules = getAllRules().filter((r) => !r.id.startsWith("default:"));
277
- expect(userRules[0].id).toBe(rule2.id);
278
- // Update rule1 to have higher priority
279
- updateRule(rule1.id, { priority: 300 });
280
- userRules = getAllRules().filter((r) => !r.id.startsWith("default:"));
281
- expect(userRules[0].id).toBe(rule1.id);
282
- });
283
-
284
- test("leaves unchanged fields intact", () => {
285
- const rule = addRule("bash", "git *", "/home/user", "allow", 100);
286
- updateRule(rule.id, { pattern: "git push *" });
287
- const updated = getAllRules().find((r) => r.id === rule.id)!;
288
- expect(updated.tool).toBe("bash");
289
- expect(updated.scope).toBe("/home/user");
290
- expect(updated.decision).toBe("allow");
291
- expect(updated.priority).toBe(100);
292
- expect(updated.createdAt).toBe(rule.createdAt);
293
- });
294
-
295
- test("does not set userModifiedAt on non-default rules", () => {
296
- const rule = addRule("bash", "git *", "/tmp");
297
- const updated = updateRule(rule.id, { decision: "deny" });
298
- expect(updated.userModifiedAt).toBeUndefined();
299
- });
300
-
301
- test("sets userModifiedAt when updating a default rule", () => {
302
- const before = Date.now();
303
- const updated = updateRule("default:ask-host_bash-global", {
304
- decision: "allow",
305
- });
306
- expect(updated.userModifiedAt).toBeGreaterThanOrEqual(before);
307
- expect(updated.userModifiedAt).toBeLessThanOrEqual(Date.now());
308
- });
309
-
310
- test("persists userModifiedAt to disk for default rules", () => {
311
- updateRule("default:ask-host_bash-global", { decision: "allow" });
312
- clearCache();
313
- const rules = getAllRules();
314
- const found = rules.find((r) => r.id === "default:ask-host_bash-global");
315
- expect(found).toBeDefined();
316
- expect(found!.userModifiedAt).toBeGreaterThan(0);
317
- });
318
-
319
- test("does not set userModifiedAt on no-op default rule update", () => {
320
- // Updating a default rule with values identical to the template
321
- // should NOT set userModifiedAt — the rule hasn't actually diverged.
322
- const updated = updateRule("default:ask-host_bash-global", {
323
- decision: "ask",
324
- });
325
- expect(updated.userModifiedAt).toBeUndefined();
326
- });
327
-
328
- test("clears userModifiedAt when default rule is reset to template values", () => {
329
- // First, modify the rule to diverge from the template
330
- updateRule("default:ask-host_bash-global", { decision: "allow" });
331
- let found = getAllRules().find(
332
- (r) => r.id === "default:ask-host_bash-global",
333
- )!;
334
- expect(found.userModifiedAt).toBeGreaterThan(0);
335
-
336
- // Now reset it back to the template value
337
- updateRule("default:ask-host_bash-global", { decision: "ask" });
338
- found = getAllRules().find(
339
- (r) => r.id === "default:ask-host_bash-global",
340
- )!;
341
- // userModifiedAt should be cleared since rule matches template again
342
- expect(found.userModifiedAt).toBeUndefined();
343
- });
344
- });
345
-
346
- // ── findMatchingRule ────────────────────────────────────────────
347
-
348
- describe("findMatchingRule", () => {
349
- test("finds exact match", () => {
350
- addRule("bash", "git push", "/tmp");
351
- const match = findMatchingRule("bash", "git push", "/tmp");
352
- expect(match).not.toBeNull();
353
- expect(match!.pattern).toBe("git push");
354
- });
355
-
356
- test("finds glob wildcard match", () => {
357
- addRule("bash", "git *", "/tmp");
358
- const match = findMatchingRule("bash", "git push origin main", "/tmp");
359
- expect(match).not.toBeNull();
360
- });
361
-
362
- test("returns null when tool does not match", () => {
363
- addRule("file_write", "file_write:/tmp/*", "/tmp");
364
- // host_file_read default is 'ask' so findMatchingRule (allow-only) won't find it
365
- const match = findMatchingRule(
366
- "host_file_read",
367
- "host_file_read:/etc/hosts",
368
- "/tmp",
369
- );
370
- expect(match).toBeNull();
371
- });
372
-
373
- test("returns null when pattern does not match", () => {
374
- addRule("host_file_read", "host_file_read:/etc/hosts", "/tmp");
375
- const match = findMatchingRule(
376
- "host_file_read",
377
- "host_file_read:/var/log/syslog",
378
- "/tmp",
379
- );
380
- expect(match).toBeNull();
381
- });
382
-
383
- // Scope matching
384
- describe("scope matching", () => {
385
- test("matches when scope equals rule scope", () => {
386
- addRule("bash", "npm *", "/home/user/project");
387
- const match = findMatchingRule(
388
- "bash",
389
- "npm install",
390
- "/home/user/project",
391
- );
392
- expect(match).not.toBeNull();
393
- });
394
-
395
- test("matches when scope is under rule scope (prefix)", () => {
396
- addRule("bash", "npm *", "/home/user");
397
- const match = findMatchingRule(
398
- "bash",
399
- "npm install",
400
- "/home/user/project/sub",
401
- );
402
- expect(match).not.toBeNull();
403
- });
404
-
405
- test("does not match when scope is outside rule scope", () => {
406
- addRule(
407
- "host_file_read",
408
- "host_file_read:/home/user/project/*",
409
- "/home/user/project",
410
- );
411
- const match = findMatchingRule(
412
- "host_file_read",
413
- "host_file_read:/home/user/project/file.txt",
414
- "/home/other",
415
- );
416
- expect(match).toBeNull();
417
- });
418
-
419
- test("everywhere scope matches any directory", () => {
420
- addRule("bash", "git *", "everywhere");
421
- const match = findMatchingRule(
422
- "bash",
423
- "git status",
424
- "/any/random/path",
425
- );
426
- expect(match).not.toBeNull();
427
- });
428
-
429
- test("everywhere scope matches root", () => {
430
- addRule("bash", "ls", "everywhere");
431
- const match = findMatchingRule("bash", "ls", "/");
432
- expect(match).not.toBeNull();
433
- });
434
-
435
- test("does not match sibling path with shared prefix", () => {
436
- addRule(
437
- "host_file_read",
438
- "host_file_read:/home/user/project/*",
439
- "/home/user/project",
440
- );
441
- const match = findMatchingRule(
442
- "host_file_read",
443
- "host_file_read:/home/user/project/file.txt",
444
- "/home/user/project-evil",
445
- );
446
- expect(match).toBeNull();
447
- });
448
-
449
- test("matches exact scope with trailing slash on working dir", () => {
450
- addRule("bash", "npm *", "/home/user/project");
451
- const match = findMatchingRule(
452
- "bash",
453
- "npm install",
454
- "/home/user/project/",
455
- );
456
- expect(match).not.toBeNull();
457
- });
458
-
459
- test("matches when rule scope has trailing slash", () => {
460
- addRule("bash", "npm *", "/home/user/project/");
461
- const match = findMatchingRule(
462
- "bash",
463
- "npm install",
464
- "/home/user/project",
465
- );
466
- expect(match).not.toBeNull();
467
- });
468
-
469
- test("does not match sibling with glob-suffixed scope", () => {
470
- addRule(
471
- "host_file_read",
472
- "host_file_read:/home/user/project/*",
473
- "/home/user/project*",
474
- );
475
- const match = findMatchingRule(
476
- "host_file_read",
477
- "host_file_read:/home/user/project/file.txt",
478
- "/home/user/project-evil",
479
- );
480
- expect(match).toBeNull();
481
- });
482
- });
483
-
484
- // Pattern matching with minimatch
485
- describe("pattern matching", () => {
486
- test("matches * wildcard", () => {
487
- addRule("bash", "npm *", "/tmp");
488
- expect(findMatchingRule("bash", "npm install", "/tmp")).not.toBeNull();
489
- expect(findMatchingRule("bash", "npm test", "/tmp")).not.toBeNull();
490
- });
491
-
492
- test("matches exact string", () => {
493
- addRule("host_file_read", "host_file_read:/etc/hosts", "/tmp");
494
- expect(
495
- findMatchingRule(
496
- "host_file_read",
497
- "host_file_read:/etc/hosts",
498
- "/tmp",
499
- ),
500
- ).not.toBeNull();
501
- expect(
502
- findMatchingRule(
503
- "host_file_read",
504
- "host_file_read:/etc/passwd",
505
- "/tmp",
506
- ),
507
- ).toBeNull();
508
- });
509
-
510
- test("matches file path pattern", () => {
511
- addRule("file_write", "/tmp/*", "/tmp");
512
- expect(
513
- findMatchingRule("file_write", "/tmp/file.txt", "/tmp"),
514
- ).not.toBeNull();
515
- });
516
-
517
- test("star pattern matches single-segment strings", () => {
518
- addRule("file_write", "*", "/tmp");
519
- // minimatch '*' matches strings without path separators
520
- expect(
521
- findMatchingRule("file_write", "file.txt", "/tmp"),
522
- ).not.toBeNull();
523
- });
524
-
525
- test("star pattern does not match paths with slashes", () => {
526
- addRule("file_write", "*", "/tmp");
527
- // minimatch '*' does not cross '/' boundaries
528
- expect(
529
- findMatchingRule("file_write", "/any/path/file.txt", "/tmp"),
530
- ).toBeNull();
531
- });
532
- });
533
- });
534
-
535
- // ── findHighestPriorityRule ──────────────────────────────────────
536
-
537
- describe("findHighestPriorityRule", () => {
538
- test("returns highest priority matching rule", () => {
539
- addRule("bash", "rm *", "/tmp", "allow", 0);
540
- addRule("bash", "rm *", "/tmp", "deny", 100);
541
- const match = findHighestPriorityRule("bash", ["rm file.txt"], "/tmp");
542
- expect(match).not.toBeNull();
543
- expect(match!.decision).toBe("deny");
544
- expect(match!.priority).toBe(100);
545
- });
546
-
547
- test("higher priority allow beats lower priority deny", () => {
548
- addRule("bash", "rm *", "/tmp", "deny", 0);
549
- addRule("bash", "rm *", "/tmp", "allow", 100);
550
- const match = findHighestPriorityRule("bash", ["rm file.txt"], "/tmp");
551
- expect(match).not.toBeNull();
552
- expect(match!.decision).toBe("allow");
553
- });
554
-
555
- test("same priority: deny beats allow", () => {
556
- addRule("bash", "rm *", "/tmp", "allow", 100);
557
- addRule("bash", "rm *", "/tmp", "deny", 100);
558
- const match = findHighestPriorityRule("bash", ["rm file.txt"], "/tmp");
559
- expect(match).not.toBeNull();
560
- expect(match!.decision).toBe("deny");
561
- });
562
-
563
- test("checks multiple command candidates", () => {
564
- addRule("web_fetch", "web_fetch:https://example.com/*", "/tmp", "allow");
565
- const match = findHighestPriorityRule(
566
- "web_fetch",
567
- [
568
- "web_fetch:https://example.com/page",
569
- "web_fetch:https://example.com/*",
570
- ],
571
- "/tmp",
572
- );
573
- expect(match).not.toBeNull();
574
- });
575
-
576
- test("returns null when no rule matches", () => {
577
- // Use file_read with a non-workspace path — file_read defaults only
578
- // cover specific workspace files, so /tmp paths won't match any default.
579
- addRule("file_read", "file_read:/specific/*", "/tmp", "allow");
580
- const match = findHighestPriorityRule(
581
- "file_read",
582
- ["file_read:/other/path"],
583
- "/tmp",
584
- );
585
- expect(match).toBeNull();
586
- });
587
-
588
- test("respects scope matching", () => {
589
- // Use file_read — bash has a global default allow rule that matches everywhere.
590
- addRule(
591
- "file_read",
592
- "file_read:/home/user/project/*",
593
- "/home/user/project",
594
- "deny",
595
- );
596
- expect(
597
- findHighestPriorityRule(
598
- "file_read",
599
- ["file_read:/home/user/project/file.txt"],
600
- "/home/user/project/sub",
601
- ),
602
- ).not.toBeNull();
603
- expect(
604
- findHighestPriorityRule(
605
- "file_read",
606
- ["file_read:/home/user/project/file.txt"],
607
- "/home/other",
608
- ),
609
- ).toBeNull();
610
- });
611
-
612
- test("everywhere scope matches any directory", () => {
613
- addRule("bash", "git *", "everywhere", "allow");
614
- const match = findHighestPriorityRule(
615
- "bash",
616
- ["git status"],
617
- "/any/random/path",
618
- );
619
- expect(match).not.toBeNull();
620
- });
621
- });
622
-
623
- // ── getAllRules ─────────────────────────────────────────────────
624
-
625
- describe("getAllRules", () => {
626
- test("returns default rules when no user rules exist", () => {
627
- const rules = getAllRules();
628
- expect(rules).toHaveLength(NUM_DEFAULTS);
629
- expect(rules.every((r) => r.id.startsWith("default:"))).toBe(true);
630
- });
631
-
632
- test("returns a copy (not the internal array)", () => {
633
- addRule("bash", "git *", "/tmp");
634
- const rules1 = getAllRules();
635
- const rules2 = getAllRules();
636
- expect(rules1).toEqual(rules2);
637
- expect(rules1).not.toBe(rules2); // different references
638
- });
639
- });
640
-
641
- // ── clearCache ─────────────────────────────────────────────────
642
-
643
- describe("clearCache", () => {
644
- test("forces reload from disk on next access", () => {
645
- addRule("bash", "git *", "/tmp");
646
- expect(getAllRules()).toHaveLength(1 + NUM_DEFAULTS);
647
- clearCache();
648
- // After clearing cache, rules are reloaded from disk
649
- expect(getAllRules()).toHaveLength(1 + NUM_DEFAULTS);
650
- });
651
- });
652
-
653
- // ── persistence ─────────────────────────────────────────────────
654
-
655
- describe("persistence", () => {
656
- test("rules survive cache clear (loaded from disk)", () => {
657
- const rule = addRule("bash", "npm *", "/tmp");
658
- clearCache();
659
- const rules = getAllRules();
660
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
661
- expect(rules.find((r) => r.id === rule.id)).toBeDefined();
662
- });
663
-
664
- test("trust file has correct structure", () => {
665
- addRule("bash", "git *", "/tmp");
666
- const data = JSON.parse(readFileSync(trustPath, "utf-8"));
667
- expect(data).toHaveProperty("version", 3);
668
- expect(data).toHaveProperty("rules");
669
- expect(Array.isArray(data.rules)).toBe(true);
670
- const userRule = data.rules.find(
671
- (r: { pattern: string }) => r.pattern === "git *",
672
- );
673
- expect(userRule).toHaveProperty("priority", 100);
674
- });
675
- });
676
-
677
- // ── deny rules ─────────────────────────────────────────────────
678
-
679
- describe("deny rules", () => {
680
- test("addRule with deny decision creates a deny rule", () => {
681
- const rule = addRule("bash", "rm -rf *", "/tmp", "deny");
682
- expect(rule.decision).toBe("deny");
683
- expect(rule.tool).toBe("bash");
684
- expect(rule.pattern).toBe("rm -rf *");
685
- });
686
-
687
- test("deny rule persists to disk", () => {
688
- addRule("bash", "rm *", "/tmp", "deny");
689
- clearCache();
690
- const rules = getAllRules();
691
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
692
- const userRule = rules.find((r) => r.pattern === "rm *");
693
- expect(userRule).toBeDefined();
694
- expect(userRule!.decision).toBe("deny");
695
- });
696
-
697
- test("findDenyRule finds deny rules", () => {
698
- addRule("bash", "rm *", "/tmp", "deny");
699
- const match = findDenyRule("bash", "rm file.txt", "/tmp");
700
- expect(match).not.toBeNull();
701
- expect(match!.decision).toBe("deny");
702
- });
703
-
704
- test("findDenyRule ignores allow rules", () => {
705
- addRule("bash", "rm *", "/tmp", "allow");
706
- const match = findDenyRule("bash", "rm file.txt", "/tmp");
707
- expect(match).toBeNull();
708
- });
709
-
710
- test("findMatchingRule ignores deny rules", () => {
711
- // Use host_file_read — it has an 'ask' default so findMatchingRule (allow-only) won't find it.
712
- addRule("host_file_read", "host_file_read:/etc/*", "/tmp", "deny");
713
- const match = findMatchingRule(
714
- "host_file_read",
715
- "host_file_read:/etc/hosts",
716
- "/tmp",
717
- );
718
- expect(match).toBeNull();
719
- });
720
-
721
- test("deny and allow rules coexist", () => {
722
- addRule("bash", "git *", "/tmp", "allow");
723
- addRule("bash", "git push --force *", "/tmp", "deny");
724
- expect(findMatchingRule("bash", "git status", "/tmp")).not.toBeNull();
725
- expect(
726
- findDenyRule("bash", "git push --force origin", "/tmp"),
727
- ).not.toBeNull();
728
- });
729
-
730
- test("deny rule with scope matching", () => {
731
- addRule("bash", "rm *", "/home/user/project", "deny");
732
- expect(
733
- findDenyRule("bash", "rm file.txt", "/home/user/project/sub"),
734
- ).not.toBeNull();
735
- expect(findDenyRule("bash", "rm file.txt", "/home/other")).toBeNull();
736
- });
737
-
738
- test("deny rule with everywhere scope", () => {
739
- addRule("bash", "rm -rf *", "everywhere", "deny");
740
- expect(findDenyRule("bash", "rm -rf /", "/any/path")).not.toBeNull();
741
- });
742
-
743
- test("removeRule works for deny rules", () => {
744
- const rule = addRule("bash", "rm *", "/tmp", "deny");
745
- expect(removeRule(rule.id)).toBe(true);
746
- expect(findDenyRule("bash", "rm file.txt", "/tmp")).toBeNull();
747
- });
748
- });
749
-
750
- // ── default rules ─────────────────────────────────────────────
751
-
752
- describe("default rules", () => {
753
- test("backfills default rules on first load", () => {
754
- const rules = getAllRules();
755
- const defaults = rules.filter((r) => r.id.startsWith("default:"));
756
- expect(defaults).toHaveLength(NUM_DEFAULTS);
757
- for (const rule of defaults) {
758
- expect(rule.priority).toBe(DEFAULT_PRIORITY_BY_ID.get(rule.id)!);
759
- if (
760
- rule.id === "default:allow-bash-rm-bootstrap" ||
761
- rule.id === "default:allow-bash-rm-updates"
762
- ) {
763
- expect(ruleScope(rule)).toBe(testDir);
764
- } else {
765
- // Non-scoped tool families (managed skill tools, skill_load) no
766
- // longer carry an explicit scope field after normalization, but
767
- // ruleScope() returns "everywhere" for rules without scope.
768
- expect(ruleScope(rule)).toBe("everywhere");
769
- }
770
- }
771
- });
772
-
773
- test("default rules cover file, host file, host shell, and workspace prompt tools", () => {
774
- const rules = getAllRules();
775
- const defaultTools = [
776
- ...new Set(
777
- rules.filter((r) => r.id.startsWith("default:")).map((r) => r.tool),
778
- ),
779
- ].sort();
780
- expect(defaultTools).toEqual([
781
- "bash",
782
- "computer_use_click",
783
- "computer_use_drag",
784
- "computer_use_key",
785
- "computer_use_observe",
786
- "computer_use_open_app",
787
- "computer_use_run_applescript",
788
- "computer_use_scroll",
789
- "computer_use_type_text",
790
- "computer_use_wait",
791
- "delete_managed_skill",
792
- "file_edit",
793
- "file_read",
794
- "file_write",
795
- "host_bash",
796
- "host_file_edit",
797
- "host_file_read",
798
- "host_file_write",
799
- "recall",
800
- "scaffold_managed_skill",
801
- "skill_execute",
802
- "skill_load",
803
- "ui_dismiss",
804
- "ui_show",
805
- "ui_update",
806
- ]);
807
- });
808
-
809
- test("default rules are not duplicated on reload", () => {
810
- getAllRules(); // first load
811
- clearCache();
812
- const rules = getAllRules(); // second load
813
- const defaults = rules.filter((r) => r.id.startsWith("default:"));
814
- expect(defaults).toHaveLength(NUM_DEFAULTS);
815
- });
816
-
817
- test("default rules persist to disk", () => {
818
- getAllRules(); // triggers backfill + save
819
- const data = JSON.parse(readFileSync(trustPath, "utf-8"));
820
- const defaults = data.rules.filter((r: { id: string }) =>
821
- r.id.startsWith("default:"),
822
- );
823
- expect(defaults).toHaveLength(NUM_DEFAULTS);
824
- });
825
-
826
- test("removed default rule is re-backfilled on next load", () => {
827
- // First load backfills defaults
828
- getAllRules();
829
- // Remove one default rule by editing trust.json directly on disk
830
- // (removeRule() throws for default rules, so we simulate external editing)
831
- const raw = JSON.parse(readFileSync(trustPath, "utf-8"));
832
- raw.rules = raw.rules.filter(
833
- (r: { id: string }) => r.id !== "default:ask-host_file_read-global",
834
- );
835
- writeFileSync(trustPath, JSON.stringify(raw, null, 2));
836
- // After reload, the rule is re-backfilled (defaults are always present)
837
- clearCache();
838
- const rules = getAllRules();
839
- expect(
840
- rules.find((r) => r.id === "default:ask-host_file_read-global"),
841
- ).toBeDefined();
842
- });
843
-
844
- test("findHighestPriorityRule matches default ask for host_file_read", () => {
845
- const match = findHighestPriorityRule(
846
- "host_file_read",
847
- ["host_file_read:/etc/hosts"],
848
- "/tmp",
849
- );
850
- expect(match).not.toBeNull();
851
- expect(match!.id).toBe("default:ask-host_file_read-global");
852
- expect(match!.decision).toBe("ask");
853
- expect(match!.priority).toBe(
854
- DEFAULT_PRIORITY_BY_ID.get("default:ask-host_file_read-global")!,
855
- );
856
- });
857
-
858
- test("findHighestPriorityRule matches default ask for host_file_write", () => {
859
- const match = findHighestPriorityRule(
860
- "host_file_write",
861
- ["host_file_write:/etc/hosts"],
862
- "/tmp",
863
- );
864
- expect(match).not.toBeNull();
865
- expect(match!.id).toBe("default:ask-host_file_write-global");
866
- expect(match!.decision).toBe("ask");
867
- expect(match!.priority).toBe(
868
- DEFAULT_PRIORITY_BY_ID.get("default:ask-host_file_write-global")!,
869
- );
870
- });
871
-
872
- test("findHighestPriorityRule matches default ask for host_file_edit", () => {
873
- const match = findHighestPriorityRule(
874
- "host_file_edit",
875
- ["host_file_edit:/etc/hosts"],
876
- "/tmp",
877
- );
878
- expect(match).not.toBeNull();
879
- expect(match!.id).toBe("default:ask-host_file_edit-global");
880
- expect(match!.decision).toBe("ask");
881
- expect(match!.priority).toBe(
882
- DEFAULT_PRIORITY_BY_ID.get("default:ask-host_file_edit-global")!,
883
- );
884
- });
885
-
886
- test("findHighestPriorityRule matches default ask for host_bash", () => {
887
- const match = findHighestPriorityRule("host_bash", ["ls"], "/tmp");
888
- expect(match).not.toBeNull();
889
- expect(match!.id).toBe("default:ask-host_bash-global");
890
- expect(match!.decision).toBe("ask");
891
- expect(match!.priority).toBe(
892
- DEFAULT_PRIORITY_BY_ID.get("default:ask-host_bash-global")!,
893
- );
894
- });
895
-
896
- test("findHighestPriorityRule matches default ask for computer_use_click", () => {
897
- const match = findHighestPriorityRule(
898
- "computer_use_click",
899
- ["computer_use_click:"],
900
- "/tmp",
901
- );
902
- expect(match).not.toBeNull();
903
- expect(match!.id).toBe("default:ask-computer_use_click-global");
904
- expect(match!.decision).toBe("ask");
905
- expect(match!.priority).toBe(
906
- DEFAULT_PRIORITY_BY_ID.get("default:ask-computer_use_click-global")!,
907
- );
908
- });
909
-
910
- test("findHighestPriorityRule matches default ask for computer_use_observe", () => {
911
- const match = findHighestPriorityRule(
912
- "computer_use_observe",
913
- ["computer_use_observe:"],
914
- "/tmp",
915
- );
916
- expect(match).not.toBeNull();
917
- expect(match!.id).toBe("default:ask-computer_use_observe-global");
918
- expect(match!.decision).toBe("ask");
919
- expect(match!.priority).toBe(
920
- DEFAULT_PRIORITY_BY_ID.get("default:ask-computer_use_observe-global")!,
921
- );
922
- });
923
-
924
- test("bootstrap delete rule matches only when workingDir is the workspace dir", () => {
925
- const workspaceDir = testDir;
926
- // Should match when workingDir is the workspace directory — the bootstrap
927
- // rule (priority 100) outranks the global default allow (priority 50).
928
- const match = findHighestPriorityRule(
929
- "bash",
930
- ["rm BOOTSTRAP.md"],
931
- workspaceDir,
932
- );
933
- expect(match).not.toBeNull();
934
- expect(match!.id).toBe("default:allow-bash-rm-bootstrap");
935
- expect(match!.decision).toBe("allow");
936
- // Outside workspace, the bootstrap rule doesn't match — without
937
- // IS_CONTAINERIZED there is no catch-all bash allow rule either.
938
- const other = findHighestPriorityRule(
939
- "bash",
940
- ["rm BOOTSTRAP.md"],
941
- "/tmp/other-project",
942
- );
943
- expect(other).toBeNull();
944
- });
945
-
946
- test("updates delete rule matches only when workingDir is the workspace dir", () => {
947
- const workspaceDir = testDir;
948
- const match = findHighestPriorityRule(
949
- "bash",
950
- ["rm UPDATES.md"],
951
- workspaceDir,
952
- );
953
- expect(match).not.toBeNull();
954
- expect(match!.id).toBe("default:allow-bash-rm-updates");
955
- expect(match!.decision).toBe("allow");
956
- // Outside workspace, should NOT match the updates rule — without
957
- // IS_CONTAINERIZED there is no catch-all bash allow rule either.
958
- const other = findHighestPriorityRule(
959
- "bash",
960
- ["rm UPDATES.md"],
961
- "/tmp/other-project",
962
- );
963
- expect(other).toBeNull();
964
- });
965
-
966
- test("default ask does not affect files outside protected directory", () => {
967
- const safePath = join(testDir, "data", "assistant.db");
968
- const match = findHighestPriorityRule(
969
- "file_read",
970
- [`file_read:${safePath}`],
971
- "/tmp",
972
- );
973
- // Should not match a default deny rule
974
- expect(match == null || !match.id.startsWith("default:")).toBe(true);
975
- });
976
-
977
- test("default rules are backfilled after malformed JSON in trust file", () => {
978
- mkdirSync(dirname(trustPath), { recursive: true });
979
- writeFileSync(trustPath, "NOT VALID JSON {{{");
980
- clearCache();
981
- const rules = getAllRules();
982
- const defaults = rules.filter((r) => r.id.startsWith("default:"));
983
- expect(defaults).toHaveLength(NUM_DEFAULTS);
984
- });
985
-
986
- test("default rules are backfilled in-memory after unknown file version without overwriting disk", () => {
987
- mkdirSync(dirname(trustPath), { recursive: true });
988
- const originalContent = JSON.stringify({
989
- version: 9999,
990
- rules: [
991
- {
992
- id: "future-rule",
993
- tool: "bash",
994
- pattern: "future *",
995
- scope: "everywhere",
996
- decision: "allow",
997
- priority: 50,
998
- createdAt: 1000,
999
- },
1000
- ],
1001
- });
1002
- writeFileSync(trustPath, originalContent);
1003
- clearCache();
1004
- const rules = getAllRules();
1005
- // Defaults should be present in-memory
1006
- const defaults = rules.filter((r) => r.id.startsWith("default:"));
1007
- expect(defaults).toHaveLength(NUM_DEFAULTS);
1008
- // The on-disk file must NOT be overwritten — it preserves the unknown format
1009
- const diskContent = readFileSync(trustPath, "utf-8");
1010
- expect(diskContent).toBe(originalContent);
1011
- });
1012
-
1013
- test("clearAllRules preserves default rules", () => {
1014
- addRule("bash", "git *", "/tmp");
1015
- clearAllRules();
1016
- const rules = getAllRules();
1017
- // User rules should be gone, but defaults should remain
1018
- expect(rules.filter((r) => !r.id.startsWith("default:"))).toHaveLength(0);
1019
- const defaults = rules.filter((r) => r.id.startsWith("default:"));
1020
- expect(defaults).toHaveLength(NUM_DEFAULTS);
1021
- });
1022
-
1023
- // ── skill source mutation rules ────────────────────────────────
1024
-
1025
- test("default rules include ask rules for file_write on skill source paths", () => {
1026
- const rules = getAllRules();
1027
- const managed = rules.find(
1028
- (r) => r.id === "default:ask-file_write-managed-skills",
1029
- );
1030
- expect(managed).toBeDefined();
1031
- expect(managed!.tool).toBe("file_write");
1032
- expect(managed!.decision).toBe("ask");
1033
- expect(managed!.priority).toBe(50);
1034
- expect(managed!.pattern).toContain("skills/**");
1035
-
1036
- const bundled = rules.find(
1037
- (r) => r.id === "default:ask-file_write-bundled-skills",
1038
- );
1039
- expect(bundled).toBeDefined();
1040
- expect(bundled!.tool).toBe("file_write");
1041
- expect(bundled!.decision).toBe("ask");
1042
- expect(bundled!.priority).toBe(50);
1043
- });
1044
-
1045
- test("default rules include ask rules for file_edit on skill source paths", () => {
1046
- const rules = getAllRules();
1047
- const managed = rules.find(
1048
- (r) => r.id === "default:ask-file_edit-managed-skills",
1049
- );
1050
- expect(managed).toBeDefined();
1051
- expect(managed!.tool).toBe("file_edit");
1052
- expect(managed!.decision).toBe("ask");
1053
- expect(managed!.priority).toBe(50);
1054
- expect(managed!.pattern).toContain("skills/**");
1055
-
1056
- const bundled = rules.find(
1057
- (r) => r.id === "default:ask-file_edit-bundled-skills",
1058
- );
1059
- expect(bundled).toBeDefined();
1060
- expect(bundled!.tool).toBe("file_edit");
1061
- expect(bundled!.decision).toBe("ask");
1062
- expect(bundled!.priority).toBe(50);
1063
- });
1064
-
1065
- // ── default allow: skill_load ────────────────────────────────
1066
-
1067
- test("skill_load default allow rule exists in templates", () => {
1068
- const templates = getDefaultRuleTemplates();
1069
- const skillLoadRule = templates.find(
1070
- (t) => t.id === "default:allow-skill_load-global",
1071
- );
1072
- expect(skillLoadRule).toBeDefined();
1073
- expect(skillLoadRule!.tool).toBe("skill_load");
1074
- expect(skillLoadRule!.pattern).toBe("skill_load:*");
1075
- expect(skillLoadRule!.decision).toBe("allow");
1076
- // skill_load is a non-scoped tool — template omits scope
1077
- expect(skillLoadRule!.scope).toBeUndefined();
1078
- });
1079
-
1080
- test("findHighestPriorityRule matches default allow for skill_load", () => {
1081
- const match = findHighestPriorityRule(
1082
- "skill_load",
1083
- ["skill_load:browser"],
1084
- "/tmp",
1085
- );
1086
- expect(match).not.toBeNull();
1087
- expect(match!.id).toBe("default:allow-skill_load-global");
1088
- expect(match!.decision).toBe("allow");
1089
- expect(match!.priority).toBe(100);
1090
- });
1091
-
1092
- test("findHighestPriorityRule matches default allow for skill_load with any skill name", () => {
1093
- const match = findHighestPriorityRule(
1094
- "skill_load",
1095
- ["skill_load:some-random-skill"],
1096
- "/tmp",
1097
- );
1098
- expect(match).not.toBeNull();
1099
- expect(match!.id).toBe("default:allow-skill_load-global");
1100
- expect(match!.decision).toBe("allow");
1101
- });
1102
-
1103
- test("no default ask rules exist for file_read on skill source paths", () => {
1104
- const rules = getAllRules();
1105
- // There should be no default rules with IDs matching file_read for skill sources
1106
- const readManagedSkill = rules.find(
1107
- (r) => r.id === "default:ask-file_read-managed-skills",
1108
- );
1109
- const readBundledSkill = rules.find(
1110
- (r) => r.id === "default:ask-file_read-bundled-skills",
1111
- );
1112
- expect(readManagedSkill).toBeUndefined();
1113
- expect(readBundledSkill).toBeUndefined();
1114
- });
1115
-
1116
- test("findHighestPriorityRule matches default ask for file_write on managed skill path", () => {
1117
- const skillFile = join(testDir, "skills", "my-skill", "SKILL.md");
1118
- const match = findHighestPriorityRule(
1119
- "file_write",
1120
- [`file_write:${skillFile}`],
1121
- "/tmp",
1122
- );
1123
- expect(match).not.toBeNull();
1124
- expect(match!.id).toBe("default:ask-file_write-managed-skills");
1125
- expect(match!.decision).toBe("ask");
1126
- });
1127
-
1128
- test("findHighestPriorityRule matches default ask for file_edit on managed skill path", () => {
1129
- const skillFile = join(testDir, "skills", "my-skill", "tools.ts");
1130
- const match = findHighestPriorityRule(
1131
- "file_edit",
1132
- [`file_edit:${skillFile}`],
1133
- "/tmp",
1134
- );
1135
- expect(match).not.toBeNull();
1136
- expect(match!.id).toBe("default:ask-file_edit-managed-skills");
1137
- expect(match!.decision).toBe("ask");
1138
- });
1139
-
1140
- // ── userModifiedAt and backfill migration ──────────────────────
1141
-
1142
- test("default rules without userModifiedAt are migrated when template changes", () => {
1143
- // First load backfills defaults
1144
- getAllRules();
1145
- // Manually alter a default rule on disk to simulate a template change
1146
- // (the rule on disk has old values, template has new ones)
1147
- const raw = JSON.parse(readFileSync(trustPath, "utf-8"));
1148
- const idx = raw.rules.findIndex(
1149
- (r: { id: string }) => r.id === "default:ask-host_bash-global",
1150
- );
1151
- expect(idx).toBeGreaterThanOrEqual(0);
1152
- // Manually set an old priority to simulate the rule diverging from template
1153
- raw.rules[idx].priority = 9999;
1154
- writeFileSync(trustPath, JSON.stringify(raw, null, 2));
1155
- clearCache();
1156
- const rules = getAllRules();
1157
- const found = rules.find((r) => r.id === "default:ask-host_bash-global");
1158
- expect(found).toBeDefined();
1159
- // Should be migrated back to the template priority (50)
1160
- expect(found!.priority).toBe(50);
1161
- });
1162
-
1163
- test("default rules with userModifiedAt are preserved during backfill migration", () => {
1164
- // First load backfills defaults
1165
- getAllRules();
1166
- // Modify the rule via updateRule to set userModifiedAt
1167
- updateRule("default:ask-host_bash-global", { decision: "allow" });
1168
- // Verify userModifiedAt is set
1169
- let rules = getAllRules();
1170
- let found = rules.find((r) => r.id === "default:ask-host_bash-global");
1171
- expect(found).toBeDefined();
1172
- expect(found!.userModifiedAt).toBeGreaterThan(0);
1173
- expect(found!.decision).toBe("allow");
1174
-
1175
- // Now simulate a template change by altering what the template expects:
1176
- // on disk the rule has decision=allow + userModifiedAt, but the template
1177
- // would try to migrate it back to decision=ask. Since userModifiedAt is
1178
- // set, backfillDefaults should skip it.
1179
- clearCache();
1180
- rules = getAllRules();
1181
- found = rules.find((r) => r.id === "default:ask-host_bash-global");
1182
- expect(found).toBeDefined();
1183
- // The user's override should be preserved
1184
- expect(found!.decision).toBe("allow");
1185
- expect(found!.userModifiedAt).toBeGreaterThan(0);
1186
- });
1187
-
1188
- test("userModifiedAt survives round-trip through disk", () => {
1189
- getAllRules(); // backfill
1190
- updateRule("default:ask-host_bash-global", { priority: 999 });
1191
- const before = getAllRules().find(
1192
- (r) => r.id === "default:ask-host_bash-global",
1193
- )!;
1194
- expect(before.userModifiedAt).toBeGreaterThan(0);
1195
-
1196
- // Round-trip through disk
1197
- clearCache();
1198
- const after = getAllRules().find(
1199
- (r) => r.id === "default:ask-host_bash-global",
1200
- )!;
1201
- expect(after.userModifiedAt).toBe(before.userModifiedAt);
1202
- expect(after.priority).toBe(999);
1203
- });
1204
- });
1205
-
1206
- // ── trust rule schema v3 (PR 14) ──────────────────────────────
1207
-
1208
- describe("trust rule schema v3 (PR 14)", () => {
1209
- test("new rules can include v3 optional fields (executionTarget)", () => {
1210
- const rule = addRule("bash", "git *", "/tmp");
1211
- // Manually set v3 optional field on the rule and persist
1212
- rule.executionTarget = "/usr/local/bin/node";
1213
- // Re-persist the updated rules
1214
- const rules = getAllRules().map((r) => (r.id === rule.id ? rule : r));
1215
- // Write directly to verify round-trip
1216
- const trustData = { version: 3, rules };
1217
- writeFileSync(trustPath, JSON.stringify(trustData, null, 2));
1218
- clearCache();
1219
- const reloaded = getAllRules();
1220
- const found = reloaded.find((r) => r.id === rule.id);
1221
- expect(found).toBeDefined();
1222
- expect(found!.executionTarget).toBe("/usr/local/bin/node");
1223
- });
1224
-
1225
- test("trust file persists with version 3", () => {
1226
- addRule("bash", "echo *", "/tmp");
1227
- const data = JSON.parse(readFileSync(trustPath, "utf-8"));
1228
- expect(data.version).toBe(3);
1229
- });
1230
- });
1231
-
1232
- // ── loadFromDisk resilience (misc) ──────────────────────────────
1233
-
1234
- describe("loadFromDisk resilience (misc)", () => {
1235
- test("malformed file (valid JSON but null) is handled gracefully", () => {
1236
- mkdirSync(dirname(trustPath), { recursive: true });
1237
- writeFileSync(trustPath, "null");
1238
- clearCache();
1239
- const rules = getAllRules();
1240
- // Accessing null.version throws TypeError, caught by try/catch,
1241
- // falls through to backfill defaults
1242
- expect(rules).toHaveLength(NUM_DEFAULTS);
1243
- });
1244
-
1245
- test("v3 file with optional fields is loaded correctly without re-migration", () => {
1246
- mkdirSync(dirname(trustPath), { recursive: true });
1247
- const v3Rules = [
1248
- {
1249
- id: "v3-with-options",
1250
- tool: "bash",
1251
- pattern: "skill-cmd *",
1252
- scope: "/tmp",
1253
- decision: "allow",
1254
- priority: 100,
1255
- createdAt: 7000,
1256
- executionTarget: "/usr/bin/node",
1257
- },
1258
- {
1259
- id: "v3-without-options",
1260
- tool: "bash",
1261
- pattern: "git *",
1262
- scope: "/tmp",
1263
- decision: "allow",
1264
- priority: 100,
1265
- createdAt: 7001,
1266
- },
1267
- ];
1268
- writeFileSync(trustPath, JSON.stringify({ version: 3, rules: v3Rules }));
1269
- clearCache();
1270
- const rules = getAllRules();
1271
-
1272
- // Rule with optional fields should have them preserved
1273
- const withOptions = rules.find((r) => r.id === "v3-with-options");
1274
- expect(withOptions).toBeDefined();
1275
- expect(withOptions!.executionTarget).toBe("/usr/bin/node");
1276
-
1277
- // Rule without optional fields should remain without them
1278
- const withoutOptions = rules.find((r) => r.id === "v3-without-options");
1279
- expect(withoutOptions).toBeDefined();
1280
- expect(withoutOptions).not.toHaveProperty("executionTarget");
1281
- });
1282
-
1283
- test("legacy v2 version migrates rules and persists as v3", () => {
1284
- mkdirSync(dirname(trustPath), { recursive: true });
1285
- writeFileSync(
1286
- trustPath,
1287
- JSON.stringify({
1288
- version: 2,
1289
- rules: [
1290
- {
1291
- id: "old-version-rule",
1292
- tool: "bash",
1293
- pattern: "git *",
1294
- scope: "/tmp",
1295
- decision: "allow",
1296
- priority: 100,
1297
- createdAt: 5000,
1298
- },
1299
- ],
1300
- }),
1301
- );
1302
- clearCache();
1303
- const rules = getAllRules();
1304
- const migratedRule = rules.find((r) => r.id === "old-version-rule");
1305
- expect(migratedRule).toBeDefined();
1306
- expect(migratedRule!.decision).toBe("allow");
1307
- expect(rules).toHaveLength(1 + NUM_DEFAULTS);
1308
-
1309
- // File should be persisted to the current schema version.
1310
- const data = JSON.parse(readFileSync(trustPath, "utf-8"));
1311
- expect(data.version).toBe(3);
1312
- expect(
1313
- data.rules.some((r: { id: string }) => r.id === "old-version-rule"),
1314
- ).toBe(true);
1315
- });
1316
-
1317
- test("legacy v1 version migrates rules and persists as v3", () => {
1318
- mkdirSync(dirname(trustPath), { recursive: true });
1319
- writeFileSync(
1320
- trustPath,
1321
- JSON.stringify({
1322
- version: 1,
1323
- rules: [
1324
- {
1325
- id: "v1-rule",
1326
- tool: "bash",
1327
- pattern: "rm *",
1328
- scope: "everywhere",
1329
- decision: "deny",
1330
- priority: 200,
1331
- createdAt: 4000,
1332
- },
1333
- ],
1334
- }),
1335
- );
1336
-
1337
- clearCache();
1338
- const rules = getAllRules();
1339
- const migratedRule = rules.find((r) => r.id === "v1-rule");
1340
- expect(migratedRule).toBeDefined();
1341
- expect(migratedRule!.decision).toBe("deny");
1342
-
1343
- const data = JSON.parse(readFileSync(trustPath, "utf-8"));
1344
- expect(data.version).toBe(3);
1345
- expect(data.rules.some((r: { id: string }) => r.id === "v1-rule")).toBe(
1346
- true,
1347
- );
1348
- });
1349
- });
1350
-
1351
- // ── executionTarget-aware rule matching ──────────────────────
1352
-
1353
- describe("executionTarget-aware rule matching", () => {
1354
- /**
1355
- * Helper: write a v3 trust file with the given rules directly to disk,
1356
- * then clear the cache so the next getRules() call picks them up.
1357
- */
1358
- function seedRules(rules: Array<Record<string, unknown>>): void {
1359
- mkdirSync(dirname(trustPath), { recursive: true });
1360
- writeFileSync(trustPath, JSON.stringify({ version: 3, rules }));
1361
- clearCache();
1362
- }
1363
-
1364
- // ── wildcard semantics (no executionTarget on rule) ──────────
1365
-
1366
- describe("wildcard semantics — rules without executionTarget", () => {
1367
- test("rule with no executionTarget matches when no context is provided", () => {
1368
- addRule("bash", "git *", "/tmp", "allow", 200);
1369
- const match = findHighestPriorityRule("bash", ["git status"], "/tmp");
1370
- expect(match).not.toBeNull();
1371
- expect(match!.decision).toBe("allow");
1372
- });
1373
-
1374
- test("rule with no executionTarget matches any execution target", () => {
1375
- addRule("bash", "git *", "/tmp", "allow", 200);
1376
- const match = findHighestPriorityRule("bash", ["git status"], "/tmp", {
1377
- executionTarget: "/usr/bin/node",
1378
- });
1379
- expect(match).not.toBeNull();
1380
- expect(match!.decision).toBe("allow");
1381
- });
1382
- });
1383
-
1384
- // ── executionTarget matching ──────────────────────────────────
1385
-
1386
- describe("executionTarget matching", () => {
1387
- test("rule with executionTarget matches exact target", () => {
1388
- seedRules([
1389
- {
1390
- id: "et-exact",
1391
- tool: "bash",
1392
- pattern: "run *",
1393
- scope: "everywhere",
1394
- decision: "allow",
1395
- priority: 200,
1396
- createdAt: Date.now(),
1397
- executionTarget: "/usr/local/bin/node",
1398
- },
1399
- ]);
1400
- const match = findHighestPriorityRule(
1401
- "bash",
1402
- ["run script.js"],
1403
- "/tmp",
1404
- {
1405
- executionTarget: "/usr/local/bin/node",
1406
- },
1407
- );
1408
- expect(match).not.toBeNull();
1409
- expect(match!.id).toBe("et-exact");
1410
- });
1411
-
1412
- test("rule with executionTarget does NOT match different target", () => {
1413
- seedRules([
1414
- {
1415
- id: "et-diff",
1416
- tool: "bash",
1417
- pattern: "run *",
1418
- scope: "everywhere",
1419
- decision: "allow",
1420
- priority: 200,
1421
- createdAt: Date.now(),
1422
- executionTarget: "/usr/local/bin/node",
1423
- },
1424
- ]);
1425
- const match = findHighestPriorityRule(
1426
- "bash",
1427
- ["run script.js"],
1428
- "/tmp",
1429
- {
1430
- executionTarget: "/usr/local/bin/bun",
1431
- },
1432
- );
1433
- expect(match == null || match.id !== "et-diff").toBe(true);
1434
- });
1435
-
1436
- test("rule with executionTarget does NOT match when no target in context", () => {
1437
- seedRules([
1438
- {
1439
- id: "et-no-ctx",
1440
- tool: "bash",
1441
- pattern: "run *",
1442
- scope: "everywhere",
1443
- decision: "allow",
1444
- priority: 200,
1445
- createdAt: Date.now(),
1446
- executionTarget: "/usr/local/bin/node",
1447
- },
1448
- ]);
1449
- const match = findHighestPriorityRule(
1450
- "bash",
1451
- ["run script.js"],
1452
- "/tmp",
1453
- {},
1454
- );
1455
- expect(match == null || match.id !== "et-no-ctx").toBe(true);
1456
- });
1457
-
1458
- test("rule WITHOUT executionTarget matches any target (wildcard)", () => {
1459
- addRule("bash", "run *", "/tmp", "allow", 200);
1460
- const match = findHighestPriorityRule(
1461
- "bash",
1462
- ["run script.js"],
1463
- "/tmp",
1464
- {
1465
- executionTarget: "/any/path/to/runtime",
1466
- },
1467
- );
1468
- expect(match).not.toBeNull();
1469
- expect(match!.pattern).toBe("run *");
1470
- });
1471
- });
1472
-
1473
- // ── optional ctx parameter ────────────────────────────────────
1474
-
1475
- describe("optional ctx parameter", () => {
1476
- test("callers without ctx parameter still work", () => {
1477
- addRule("bash", "git *", "/tmp", "allow", 200);
1478
- // Calling without the 4th argument — must still match
1479
- const match = findHighestPriorityRule("bash", ["git status"], "/tmp");
1480
- expect(match).not.toBeNull();
1481
- expect(match!.pattern).toBe("git *");
1482
- });
1483
-
1484
- test("empty PolicyContext object behaves the same as no context", () => {
1485
- addRule("bash", "ls *", "/tmp", "allow", 200);
1486
- const matchNoCtx = findHighestPriorityRule("bash", ["ls -la"], "/tmp");
1487
- const matchEmptyCtx = findHighestPriorityRule(
1488
- "bash",
1489
- ["ls -la"],
1490
- "/tmp",
1491
- {},
1492
- );
1493
- expect(matchNoCtx).not.toBeNull();
1494
- expect(matchEmptyCtx).not.toBeNull();
1495
- expect(matchNoCtx!.id).toBe(matchEmptyCtx!.id);
1496
- });
1497
- });
1498
- });
1499
-
1500
- // ── network_request trust rule matching ────────────────────────
1501
-
1502
- describe("network_request trust rules", () => {
1503
- test("exact origin rule matches network_request candidates", () => {
1504
- addRule(
1505
- "network_request",
1506
- "network_request:https://api.example.com/*",
1507
- "everywhere",
1508
- );
1509
- const rule = findHighestPriorityRule(
1510
- "network_request",
1511
- [
1512
- "network_request:https://api.example.com/v1/data",
1513
- "network_request:https://api.example.com/*",
1514
- ],
1515
- "/tmp",
1516
- );
1517
- expect(rule).not.toBeNull();
1518
- expect(rule!.decision).toBe("allow");
1519
- });
1520
-
1521
- test("exact url rule matches only that url candidate", () => {
1522
- addRule(
1523
- "network_request",
1524
- "network_request:https://api.example.com/v1/data",
1525
- "everywhere",
1526
- );
1527
- const match = findHighestPriorityRule(
1528
- "network_request",
1529
- [
1530
- "network_request:https://api.example.com/v1/data",
1531
- "network_request:https://api.example.com/*",
1532
- ],
1533
- "/tmp",
1534
- );
1535
- expect(match).not.toBeNull();
1536
-
1537
- const noMatch = findHighestPriorityRule(
1538
- "network_request",
1539
- ["network_request:https://api.example.com/v2/other"],
1540
- "/tmp",
1541
- );
1542
- expect(noMatch).toBeNull();
1543
- });
1544
-
1545
- test("globstar rule matches any network_request candidate", () => {
1546
- // minimatch treats standalone "**" as globstar (matching "/"), but
1547
- // "network_request:*" uses single "*" which doesn't cross slashes.
1548
- // The tool field is already filtered by findHighestPriorityRule, so
1549
- // "**" is the correct catch-all pattern.
1550
- addRule("network_request", "**", "everywhere");
1551
- const rule = findHighestPriorityRule(
1552
- "network_request",
1553
- ["network_request:https://any-host.example.org/path"],
1554
- "/tmp",
1555
- );
1556
- expect(rule).not.toBeNull();
1557
- });
1558
-
1559
- test("single-star wildcard matches flat candidates only", () => {
1560
- // "network_request:*" won't match URLs with slashes — consistent
1561
- // with the behavior of web_fetch:* patterns.
1562
- addRule("network_request", "network_request:*", "everywhere");
1563
- const noSlashMatch = findHighestPriorityRule(
1564
- "network_request",
1565
- ["network_request:flat-target"],
1566
- "/tmp",
1567
- );
1568
- expect(noSlashMatch).not.toBeNull();
1569
-
1570
- const slashNoMatch = findHighestPriorityRule(
1571
- "network_request",
1572
- ["network_request:https://example.com/path"],
1573
- "/tmp",
1574
- );
1575
- // Single "*" does not match "/" so this URL candidate won't match.
1576
- expect(slashNoMatch).toBeNull();
1577
- });
1578
-
1579
- test("network_request rule does not match web_fetch tool", () => {
1580
- addRule(
1581
- "network_request",
1582
- "network_request:https://api.example.com/*",
1583
- "everywhere",
1584
- );
1585
- const rule = findHighestPriorityRule(
1586
- "web_fetch",
1587
- [
1588
- "web_fetch:https://api.example.com/v1/data",
1589
- "web_fetch:https://api.example.com/*",
1590
- ],
1591
- "/tmp",
1592
- );
1593
- expect(rule).toBeNull();
1594
- });
1595
-
1596
- test("web_fetch rule does not match network_request tool", () => {
1597
- addRule("web_fetch", "web_fetch:https://api.example.com/*", "everywhere");
1598
- const rule = findHighestPriorityRule(
1599
- "network_request",
1600
- [
1601
- "network_request:https://api.example.com/v1/data",
1602
- "network_request:https://api.example.com/*",
1603
- ],
1604
- "/tmp",
1605
- );
1606
- expect(rule).toBeNull();
1607
- });
1608
-
1609
- test("deny rule takes precedence over allow at same priority", () => {
1610
- addRule(
1611
- "network_request",
1612
- "network_request:https://api.example.com/*",
1613
- "everywhere",
1614
- "allow",
1615
- 100,
1616
- );
1617
- addRule(
1618
- "network_request",
1619
- "network_request:https://api.example.com/*",
1620
- "everywhere",
1621
- "deny",
1622
- 100,
1623
- );
1624
- const rule = findHighestPriorityRule(
1625
- "network_request",
1626
- [
1627
- "network_request:https://api.example.com/v1/data",
1628
- "network_request:https://api.example.com/*",
1629
- ],
1630
- "/tmp",
1631
- );
1632
- expect(rule).not.toBeNull();
1633
- expect(rule!.decision).toBe("deny");
1634
- });
1635
-
1636
- test("higher-priority allow overrides lower-priority deny", () => {
1637
- addRule(
1638
- "network_request",
1639
- "network_request:https://api.example.com/*",
1640
- "everywhere",
1641
- "deny",
1642
- 50,
1643
- );
1644
- addRule(
1645
- "network_request",
1646
- "network_request:https://api.example.com/*",
1647
- "everywhere",
1648
- "allow",
1649
- 100,
1650
- );
1651
- const rule = findHighestPriorityRule(
1652
- "network_request",
1653
- [
1654
- "network_request:https://api.example.com/v1/data",
1655
- "network_request:https://api.example.com/*",
1656
- ],
1657
- "/tmp",
1658
- );
1659
- expect(rule).not.toBeNull();
1660
- expect(rule!.decision).toBe("allow");
1661
- });
1662
-
1663
- test("network_request rules match regardless of working directory (URL tools ignore scope)", () => {
1664
- addRule(
1665
- "network_request",
1666
- "network_request:https://api.example.com/*",
1667
- "/home/user/project",
1668
- );
1669
- const inScope = findHighestPriorityRule(
1670
- "network_request",
1671
- ["network_request:https://api.example.com/*"],
1672
- "/home/user/project",
1673
- );
1674
- expect(inScope).not.toBeNull();
1675
-
1676
- // URL tools (network_request) do not support scope — the rule matches
1677
- // regardless of working directory because scope is stripped during
1678
- // normalization.
1679
- const outOfScope = findHighestPriorityRule(
1680
- "network_request",
1681
- ["network_request:https://api.example.com/*"],
1682
- "/tmp/other",
1683
- );
1684
- expect(outOfScope).not.toBeNull();
1685
- });
1686
- });
1687
- });
1688
-
1689
- describe("computer-use tool trust rule matching", () => {
1690
- test("actionable CU tools have default ask trust rules", () => {
1691
- // Actionable CU tools (those that perform screen interactions) should
1692
- // have default "ask" rules so strict mode prompts before use.
1693
- const actionableCuTools = ["computer_use_click", "computer_use_type_text"];
1694
-
1695
- for (const name of actionableCuTools) {
1696
- const rule = findHighestPriorityRule(name, [name], "/tmp/test");
1697
- expect(rule).not.toBeNull();
1698
- expect(rule!.decision).toBe("ask");
1699
- }
1700
- });
1701
-
1702
- test("terminal CU tools (done/respond) have no default trust rules", () => {
1703
- // computer_use_done and computer_use_respond are terminal signal tools
1704
- // with RiskLevel.Low — they should not have ask rules since they don't
1705
- // perform any screen action.
1706
- const terminalCuTools = ["computer_use_done", "computer_use_respond"];
1707
-
1708
- for (const name of terminalCuTools) {
1709
- const defaultRule = DEFAULT_TEMPLATES.find((t) => t.tool === name);
1710
- expect(defaultRule).toBeUndefined();
1711
- }
1712
- });
1713
- });
1714
-
1715
- // ── canonical parser normalization-on-load ─────────────────────────────────
1716
-
1717
- describe("canonical parser normalization-on-load", () => {
1718
- beforeEach(() => {
1719
- clearCache();
1720
- try {
1721
- rmSync(trustPath);
1722
- } catch {
1723
- /* may not exist */
1724
- }
1725
- });
1726
-
1727
- test("URL rule with executionTarget is stripped on load and re-saved", () => {
1728
- // A URL rule (web_fetch) should not carry executionTarget — the canonical
1729
- // parser strips it and marks the file for re-save.
1730
- mkdirSync(dirname(trustPath), { recursive: true });
1731
- writeFileSync(
1732
- trustPath,
1733
- JSON.stringify({
1734
- version: 3,
1735
- rules: [
1736
- {
1737
- id: "url-rule-with-et",
1738
- tool: "web_fetch",
1739
- pattern: "web_fetch:https://example.com/*",
1740
- scope: "everywhere",
1741
- decision: "allow",
1742
- priority: 100,
1743
- createdAt: 1000,
1744
- executionTarget: "/usr/bin/node",
1745
- },
1746
- ],
1747
- }),
1748
- );
1749
- clearCache();
1750
- const rules = getAllRules();
1751
- const found = rules.find((r) => r.id === "url-rule-with-et");
1752
- expect(found).toBeDefined();
1753
- // executionTarget should have been stripped by the canonical parser
1754
- expect(found).not.toHaveProperty("executionTarget");
1755
-
1756
- // Verify the re-save persisted the normalized rule
1757
- const disk = JSON.parse(readFileSync(trustPath, "utf-8"));
1758
- const diskRule = disk.rules.find(
1759
- (r: { id: string }) => r.id === "url-rule-with-et",
1760
- );
1761
- expect(diskRule).toBeDefined();
1762
- expect(diskRule).not.toHaveProperty("executionTarget");
1763
- });
1764
-
1765
- test("URL rule with allowHighRisk is stripped on load (normalized)", () => {
1766
- mkdirSync(dirname(trustPath), { recursive: true });
1767
- writeFileSync(
1768
- trustPath,
1769
- JSON.stringify({
1770
- version: 3,
1771
- rules: [
1772
- {
1773
- id: "url-rule-with-ahr",
1774
- tool: "web_fetch",
1775
- pattern: "**",
1776
- scope: "everywhere",
1777
- decision: "allow",
1778
- priority: 100,
1779
- createdAt: 2000,
1780
- allowHighRisk: true,
1781
- },
1782
- ],
1783
- }),
1784
- );
1785
- clearCache();
1786
- const rules = getAllRules();
1787
- const found = rules.find((r) => r.id === "url-rule-with-ahr");
1788
- expect(found).toBeDefined();
1789
- // allowHighRisk is stripped during normalization
1790
- expect(
1791
- (found as unknown as Record<string, unknown>).allowHighRisk,
1792
- ).toBeUndefined();
1793
- });
1794
-
1795
- test("scoped rule preserves executionTarget but strips allowHighRisk on load", () => {
1796
- mkdirSync(dirname(trustPath), { recursive: true });
1797
- writeFileSync(
1798
- trustPath,
1799
- JSON.stringify({
1800
- version: 3,
1801
- rules: [
1802
- {
1803
- id: "scoped-rule-with-opts",
1804
- tool: "bash",
1805
- pattern: "npm *",
1806
- scope: "/tmp",
1807
- decision: "allow",
1808
- priority: 100,
1809
- createdAt: 3000,
1810
- executionTarget: "/usr/local/bin/node",
1811
- allowHighRisk: true,
1812
- },
1813
- ],
1814
- }),
1815
- );
1816
- clearCache();
1817
- const rules = getAllRules();
1818
- const found = rules.find((r) => r.id === "scoped-rule-with-opts");
1819
- expect(found).toBeDefined();
1820
- expect((found as { executionTarget?: string }).executionTarget).toBe(
1821
- "/usr/local/bin/node",
1822
- );
1823
- // allowHighRisk is stripped during normalization
1824
- expect(
1825
- (found as unknown as Record<string, unknown>).allowHighRisk,
1826
- ).toBeUndefined();
1827
- });
1828
-
1829
- test("normalization on v2 file triggers re-save as v3", () => {
1830
- mkdirSync(dirname(trustPath), { recursive: true });
1831
- writeFileSync(
1832
- trustPath,
1833
- JSON.stringify({
1834
- version: 2,
1835
- rules: [
1836
- {
1837
- id: "v2-url-rule",
1838
- tool: "network_request",
1839
- pattern: "network_request:https://api.test.com/*",
1840
- scope: "everywhere",
1841
- decision: "allow",
1842
- priority: 100,
1843
- createdAt: 4000,
1844
- executionTarget: "stale-value",
1845
- },
1846
- ],
1847
- }),
1848
- );
1849
- clearCache();
1850
- getAllRules();
1851
-
1852
- // File should be re-saved as v3 with normalized rules
1853
- const disk = JSON.parse(readFileSync(trustPath, "utf-8"));
1854
- expect(disk.version).toBe(3);
1855
- const diskRule = disk.rules.find(
1856
- (r: { id: string }) => r.id === "v2-url-rule",
1857
- );
1858
- expect(diskRule).toBeDefined();
1859
- expect(diskRule).not.toHaveProperty("executionTarget");
1860
- });
1861
- });
1862
-
1863
- // ── optional-scope matching fallback ──────────────────────────────────────
1864
-
1865
- describe("optional-scope matching fallback", () => {
1866
- beforeEach(() => {
1867
- clearCache();
1868
- try {
1869
- rmSync(trustPath);
1870
- } catch {
1871
- /* may not exist */
1872
- }
1873
- });
1874
-
1875
- test("rule with missing scope is normalized to everywhere and matches any dir", () => {
1876
- // Simulate a persisted rule that somehow lacks a scope field —
1877
- // the canonical parser normalizes it to "everywhere".
1878
- mkdirSync(dirname(trustPath), { recursive: true });
1879
- writeFileSync(
1880
- trustPath,
1881
- JSON.stringify({
1882
- version: 3,
1883
- rules: [
1884
- {
1885
- id: "no-scope-rule",
1886
- tool: "bash",
1887
- pattern: "echo *",
1888
- decision: "allow",
1889
- priority: 200,
1890
- createdAt: 5000,
1891
- },
1892
- ],
1893
- }),
1894
- );
1895
- clearCache();
1896
- const rules = getAllRules();
1897
- const found = rules.find((r) => r.id === "no-scope-rule");
1898
- expect(found).toBeDefined();
1899
- expect(found!.scope).toBe("everywhere");
1900
-
1901
- // Should match any working directory since scope defaults to everywhere
1902
- const match = findHighestPriorityRule(
1903
- "bash",
1904
- ["echo hello"],
1905
- "/any/random/dir",
1906
- );
1907
- expect(match).not.toBeNull();
1908
- expect(match!.id).toBe("no-scope-rule");
1909
- });
1910
-
1911
- test("rule with empty-string scope is normalized to everywhere", () => {
1912
- mkdirSync(dirname(trustPath), { recursive: true });
1913
- writeFileSync(
1914
- trustPath,
1915
- JSON.stringify({
1916
- version: 3,
1917
- rules: [
1918
- {
1919
- id: "empty-scope-rule",
1920
- tool: "bash",
1921
- pattern: "ls *",
1922
- scope: "",
1923
- decision: "allow",
1924
- priority: 200,
1925
- createdAt: 6000,
1926
- },
1927
- ],
1928
- }),
1929
- );
1930
- clearCache();
1931
- const rules = getAllRules();
1932
- const found = rules.find((r) => r.id === "empty-scope-rule");
1933
- expect(found).toBeDefined();
1934
- // The ruleScope helper treats "" as "everywhere"
1935
- const match = findHighestPriorityRule(
1936
- "bash",
1937
- ["ls -la"],
1938
- "/some/other/dir",
1939
- );
1940
- expect(match).not.toBeNull();
1941
- expect(match!.id).toBe("empty-scope-rule");
1942
- });
1943
- });
1944
-
1945
- // ── unknown-version no-overwrite semantics ────────────────────────────────
1946
-
1947
- describe("unknown-version no-overwrite semantics", () => {
1948
- beforeEach(() => {
1949
- clearCache();
1950
- try {
1951
- rmSync(trustPath);
1952
- } catch {
1953
- /* may not exist */
1954
- }
1955
- });
1956
-
1957
- test("unknown version file is never overwritten even if normalization would apply", () => {
1958
- mkdirSync(dirname(trustPath), { recursive: true });
1959
- // A future version file with rules that would normally be normalized —
1960
- // the unknown-version guard must prevent any disk writes.
1961
- const originalContent = JSON.stringify({
1962
- version: 9999,
1963
- rules: [
1964
- {
1965
- id: "future-url-rule",
1966
- tool: "web_fetch",
1967
- pattern: "web_fetch:https://future.io/*",
1968
- scope: "everywhere",
1969
- decision: "allow",
1970
- priority: 50,
1971
- createdAt: 7000,
1972
- executionTarget: "should-be-stripped-but-file-not-overwritten",
1973
- },
1974
- ],
1975
- });
1976
- writeFileSync(trustPath, originalContent);
1977
- clearCache();
1978
- const rules = getAllRules();
1979
- // Defaults should be present in-memory
1980
- const defaults = rules.filter((r) => r.id.startsWith("default:"));
1981
- expect(defaults).toHaveLength(NUM_DEFAULTS);
1982
- // The on-disk file must NOT be overwritten
1983
- const diskContent = readFileSync(trustPath, "utf-8");
1984
- expect(diskContent).toBe(originalContent);
1985
- });
1986
-
1987
- test("unknown version returns only in-memory defaults, not file rules", () => {
1988
- mkdirSync(dirname(trustPath), { recursive: true });
1989
- writeFileSync(
1990
- trustPath,
1991
- JSON.stringify({
1992
- version: 42,
1993
- rules: [
1994
- {
1995
- id: "v42-rule",
1996
- tool: "bash",
1997
- pattern: "dangerous *",
1998
- scope: "everywhere",
1999
- decision: "allow",
2000
- priority: 500,
2001
- createdAt: 8000,
2002
- },
2003
- ],
2004
- }),
2005
- );
2006
- clearCache();
2007
- const rules = getAllRules();
2008
- // The file's rules should NOT appear in the loaded set
2009
- expect(rules.find((r) => r.id === "v42-rule")).toBeUndefined();
2010
- // Only defaults should be present
2011
- expect(rules.every((r) => r.id.startsWith("default:"))).toBe(true);
2012
- });
2013
- });