@vellumai/assistant 0.11.4 → 0.11.5-staging.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (358) hide show
  1. package/AGENTS.md +2 -2
  2. package/ARCHITECTURE.md +29 -4
  3. package/README.md +1 -1
  4. package/docs/architecture/memory.md +17 -5
  5. package/docs/architecture/security.md +96 -152
  6. package/docs/guardian-request-flow.md +13 -2
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  11. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  15. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  16. package/node_modules/@vellumai/gateway-client/src/http-delivery.ts +7 -14
  17. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  18. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +29 -31
  19. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  21. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  22. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/openapi.yaml +193 -3
  24. package/package.json +1 -1
  25. package/src/__tests__/anthropic-provider.test.ts +10 -3
  26. package/src/__tests__/approval-interception-trust-gates.test.ts +40 -0
  27. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  28. package/src/__tests__/channel-approval-routes.test.ts +4 -0
  29. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -6
  30. package/src/__tests__/channel-reply-delivery.test.ts +26 -13
  31. package/src/__tests__/checker.test.ts +20 -314
  32. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  33. package/src/__tests__/client-os-metadata-persistence.test.ts +16 -0
  34. package/src/__tests__/compactor-retained-image-validation.test.ts +142 -0
  35. package/src/__tests__/container-cpu-sampler.test.ts +153 -0
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/conversation-attachments.test.ts +0 -1
  38. package/src/__tests__/conversation-error.test.ts +17 -0
  39. package/src/__tests__/conversation-lifecycle.test.ts +20 -26
  40. package/src/__tests__/conversation-pairing.test.ts +186 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +21 -2
  42. package/src/__tests__/credential-broker-server-use.test.ts +24 -5
  43. package/src/__tests__/credential-routes.test.ts +22 -3
  44. package/src/__tests__/credential-security-invariants.test.ts +2 -1
  45. package/src/__tests__/daemon-credential-client.test.ts +88 -0
  46. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  47. package/src/__tests__/dm-backfill.test.ts +63 -0
  48. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  49. package/src/__tests__/edit-propagation.test.ts +107 -4
  50. package/src/__tests__/events-client-registration.test.ts +28 -0
  51. package/src/__tests__/file-write-tool.test.ts +4 -2
  52. package/src/__tests__/guardian-prompt-notice-privacy.test.ts +133 -0
  53. package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +143 -97
  54. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  55. package/src/__tests__/host-proxy-interface.test.ts +11 -1
  56. package/src/__tests__/host-shell-tool.test.ts +23 -4
  57. package/src/__tests__/image-conversion.test.ts +143 -1
  58. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  59. package/src/__tests__/input-repairs.test.ts +207 -0
  60. package/src/__tests__/live-workspace-guard.test.ts +75 -0
  61. package/src/__tests__/mcp-abort-signal.test.ts +1 -1
  62. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  63. package/src/__tests__/mcp-tool-annotations-risk.test.ts +1 -1
  64. package/src/__tests__/media-resolve-image-validation.test.ts +310 -0
  65. package/src/__tests__/mtime-cache.test.ts +2 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +124 -1
  67. package/src/__tests__/openai-provider.test.ts +22 -0
  68. package/src/__tests__/personal-memory-auth-bypass.test.ts +22 -6
  69. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  70. package/src/__tests__/platform.test.ts +16 -1
  71. package/src/__tests__/plugin-api-resolve-credential.test.ts +22 -13
  72. package/src/__tests__/plugin-api-shim.test.ts +5 -0
  73. package/src/__tests__/plugin-api-store-credential.test.ts +268 -0
  74. package/src/__tests__/plugin-config-data-migration.test.ts +8 -1
  75. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  76. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  77. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  78. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -1
  79. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
  80. package/src/__tests__/reaction-persistence.test.ts +200 -7
  81. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  82. package/src/__tests__/resource-pressure-guard.test.ts +428 -0
  83. package/src/__tests__/resource-pressure-routes.test.ts +113 -0
  84. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  85. package/src/__tests__/run-conversation-turn-persistence.test.ts +27 -1
  86. package/src/__tests__/secret-routes-platform-proxy.test.ts +7 -0
  87. package/src/__tests__/skill-tool-factory.test.ts +161 -0
  88. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  89. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  90. package/src/__tests__/tool-executor.test.ts +63 -38
  91. package/src/__tests__/tool-policy.test.ts +97 -1
  92. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +7 -4
  93. package/src/__tests__/user-plugin-loader.test.ts +2 -0
  94. package/src/__tests__/validate-input.test.ts +243 -7
  95. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +97 -29
  97. package/src/acp/__tests__/prepare-agent-env.test.ts +376 -153
  98. package/src/acp/acp-claude-oauth.ts +38 -21
  99. package/src/acp/prepare-agent-env.ts +146 -55
  100. package/src/agent/loop-tool-dedup.test.ts +161 -0
  101. package/src/agent/loop.ts +68 -22
  102. package/src/api/constants/app-tools.ts +34 -0
  103. package/src/api/events/resource-pressure-status-changed.ts +53 -0
  104. package/src/api/index.ts +19 -0
  105. package/src/api/responses/resource-pressure-status.ts +25 -0
  106. package/src/approvals/guardian-channel-delivery.ts +70 -6
  107. package/src/approvals/guardian-request-resolvers.ts +32 -66
  108. package/src/channels/__tests__/message-audience.test.ts +49 -0
  109. package/src/channels/__tests__/types.test.ts +17 -3
  110. package/src/channels/message-audience.ts +40 -0
  111. package/src/channels/types.ts +16 -12
  112. package/src/cli/__tests__/catalog-search-help.test.ts +50 -0
  113. package/src/cli/commands/__tests__/channel-verification-sessions.test.ts +31 -0
  114. package/src/cli/commands/__tests__/conversations-search.test.ts +289 -0
  115. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  116. package/src/cli/commands/__tests__/inference-providers.test.ts +68 -0
  117. package/src/cli/commands/__tests__/keys.test.ts +89 -8
  118. package/src/cli/commands/__tests__/plugins.test.ts +57 -1
  119. package/src/cli/commands/channel-verification-sessions.help.ts +11 -9
  120. package/src/cli/commands/channel-verification-sessions.ts +11 -21
  121. package/src/cli/commands/conversations.help.ts +34 -0
  122. package/src/cli/commands/conversations.ts +125 -0
  123. package/src/cli/commands/inference-providers.ts +9 -4
  124. package/src/cli/commands/inference.help.ts +9 -4
  125. package/src/cli/commands/keys.help.ts +13 -1
  126. package/src/cli/commands/keys.ts +26 -15
  127. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  128. package/src/cli/commands/memory/index.help.ts +41 -27
  129. package/src/cli/commands/memory/index.ts +2 -0
  130. package/src/cli/commands/memory/memory-v2.ts +58 -54
  131. package/src/cli/commands/memory/memory-validate.ts +18 -0
  132. package/src/cli/commands/monitoring.ts +1 -0
  133. package/src/cli/commands/plugins.help.ts +28 -31
  134. package/src/cli/commands/plugins.ts +10 -0
  135. package/src/cli/commands/skills.help.ts +13 -16
  136. package/src/cli/commands/trust.ts +3 -14
  137. package/src/cli/lib/__tests__/install-from-github.test.ts +38 -0
  138. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +34 -0
  139. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +32 -1
  140. package/src/cli/lib/__tests__/toggle-plugin.test.ts +2 -1
  141. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +64 -0
  142. package/src/cli/lib/bundled-marketplace.json +13 -0
  143. package/src/cli/lib/daemon-credential-client.ts +25 -3
  144. package/src/cli/lib/install-from-github.ts +35 -24
  145. package/src/cli/lib/merge-plugin-tree.ts +22 -4
  146. package/src/cli/lib/plugin-surfaces.ts +27 -0
  147. package/src/config/feature-flag-registry.json +35 -19
  148. package/src/config/webhook-routing.ts +8 -0
  149. package/src/context/compactor.ts +31 -2
  150. package/src/daemon/__tests__/provider-rejection-log-fields.test.ts +272 -0
  151. package/src/daemon/conversation-agent-loop-handlers.ts +4 -1
  152. package/src/daemon/conversation-error.ts +13 -0
  153. package/src/daemon/conversation-messaging.ts +22 -2
  154. package/src/daemon/conversation-process.ts +11 -0
  155. package/src/daemon/conversation-store.ts +10 -0
  156. package/src/daemon/conversation-surfaces.ts +21 -8
  157. package/src/daemon/conversation.ts +12 -10
  158. package/src/daemon/lifecycle.ts +6 -2
  159. package/src/daemon/message-types/conversations.ts +5 -7
  160. package/src/daemon/provider-rejection-log-fields.ts +124 -0
  161. package/src/daemon/resource-pressure-guard-lifecycle.ts +66 -0
  162. package/src/daemon/resource-pressure-guard.ts +387 -0
  163. package/src/daemon/shutdown-handlers.ts +2 -0
  164. package/src/daemon/startup-error.ts +16 -0
  165. package/src/daemon/trust-context.ts +14 -10
  166. package/src/daemon/unsendable-image-notice.ts +76 -0
  167. package/src/hooks/registry.ts +65 -51
  168. package/src/ipc/__tests__/socket-path.test.ts +15 -7
  169. package/src/ipc/gateway-client.test.ts +1 -1
  170. package/src/ipc/gateway-client.ts +14 -21
  171. package/src/ipc/socket-cleanup.ts +2 -11
  172. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -1
  173. package/src/messaging/provider-message-metadata.ts +128 -0
  174. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +116 -68
  175. package/src/messaging/providers/channel-transport.ts +90 -13
  176. package/src/messaging/providers/discord/send.ts +16 -0
  177. package/src/messaging/providers/discord/transport.ts +11 -1
  178. package/src/messaging/providers/index.ts +109 -16
  179. package/src/messaging/providers/slack/message-metadata.ts +45 -0
  180. package/src/messaging/providers/slack/render-transcript.ts +1 -4
  181. package/src/messaging/providers/slack/send.test.ts +6 -9
  182. package/src/messaging/providers/slack/send.ts +40 -28
  183. package/src/messaging/providers/slack/transport.ts +22 -26
  184. package/src/messaging/providers/telegram-bot/send.test.ts +2 -8
  185. package/src/messaging/providers/telegram-bot/transport.ts +3 -6
  186. package/src/messaging/read-provider-metadata.test.ts +157 -0
  187. package/src/messaging/read-provider-metadata.ts +51 -0
  188. package/src/notifications/__tests__/assistant-reply-producer.test.ts +94 -0
  189. package/src/notifications/__tests__/edit-notification.test.ts +174 -2
  190. package/src/notifications/__tests__/home-feed-side-effect.test.ts +536 -4
  191. package/src/notifications/adapters/macos.ts +55 -0
  192. package/src/notifications/adapters/slack.ts +10 -5
  193. package/src/notifications/assistant-reply-producer.ts +37 -4
  194. package/src/notifications/conversation-pairing.ts +111 -21
  195. package/src/notifications/edit-notification.ts +38 -8
  196. package/src/notifications/emit-signal.ts +12 -14
  197. package/src/notifications/home-feed-side-effect.ts +218 -15
  198. package/src/notifications/types.ts +5 -0
  199. package/src/permissions/AGENTS.md +16 -0
  200. package/src/permissions/checker.test.ts +75 -122
  201. package/src/permissions/checker.ts +44 -560
  202. package/src/permissions/confirmation-guardian-request.test.ts +28 -3
  203. package/src/permissions/confirmation-guardian-request.ts +5 -1
  204. package/src/persistence/conversation-crud.ts +23 -20
  205. package/src/persistence/conversation-queries.ts +14 -1
  206. package/src/persistence/conversation-types.ts +22 -0
  207. package/src/persistence/delivery-crud.ts +122 -15
  208. package/src/plugin-api/__tests__/import-graph-partial-mock.test.ts +67 -0
  209. package/src/plugin-api/conversation-turn.ts +27 -0
  210. package/src/plugin-api/credential-scope.test.ts +15 -0
  211. package/src/plugin-api/credential-scope.ts +14 -0
  212. package/src/plugin-api/index.ts +19 -1
  213. package/src/plugin-api/resolve-credential.ts +15 -10
  214. package/src/plugin-api/store-credential.ts +148 -0
  215. package/src/plugin-api/system-card.ts +36 -0
  216. package/src/plugin-api/vision-support.test.ts +82 -0
  217. package/src/plugin-api/vision-support.ts +14 -2
  218. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +361 -110
  219. package/src/plugins/defaults/image-fallback/__tests__/vision-recovery.test.ts +4 -0
  220. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +58 -0
  221. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +64 -23
  222. package/src/plugins/defaults/image-recovery/detect.ts +24 -1
  223. package/src/plugins/defaults/main.ts +15 -0
  224. package/src/plugins/defaults/memory/AGENTS.md +11 -3
  225. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  226. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +9 -12
  227. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  228. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  229. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +114 -0
  230. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  231. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  232. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  233. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  234. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  235. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  236. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +1 -61
  237. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +68 -6
  238. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  239. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  240. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  241. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  242. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  243. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  244. package/src/plugins/defaults/memory/substrate/static-context.ts +0 -29
  245. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +3 -1
  246. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  247. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  248. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  249. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  250. package/src/plugins/mtime-cache.ts +1 -1
  251. package/src/plugins/pipeline.ts +14 -7
  252. package/src/plugins/plugin-execution-context.ts +35 -11
  253. package/src/plugins/plugin-tree-walk.ts +63 -4
  254. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  255. package/src/providers/anthropic/client.ts +332 -241
  256. package/src/providers/connection-resolution.ts +2 -1
  257. package/src/providers/content-blocks.ts +22 -0
  258. package/src/providers/gemini/client.ts +5 -2
  259. package/src/providers/inference/__tests__/adapter-factory-litellm.test.ts +47 -1
  260. package/src/providers/inference/__tests__/adapter-factory-ollama.test.ts +83 -0
  261. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +98 -1
  262. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +38 -0
  263. package/src/providers/inference/__tests__/base-url-security.test.ts +10 -0
  264. package/src/providers/inference/__tests__/connections-ollama.test.ts +90 -0
  265. package/src/providers/inference/__tests__/missing-credential-guard.test.ts +117 -0
  266. package/src/providers/inference/adapter-factory.ts +33 -2
  267. package/src/providers/inference/auth.ts +13 -2
  268. package/src/providers/inference/credential-usage.ts +37 -0
  269. package/src/providers/inference/missing-credential-guard.ts +110 -0
  270. package/src/providers/inference/resolve-auth.ts +7 -7
  271. package/src/providers/media-resolve.ts +176 -15
  272. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +576 -46
  273. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  274. package/src/providers/openai/chat-completions-provider.ts +322 -42
  275. package/src/routes/route-host-protocol.ts +7 -0
  276. package/src/routes/worker.ts +31 -10
  277. package/src/runtime/AGENTS.md +35 -0
  278. package/src/runtime/__tests__/runtime-http-port-collision.test.ts +131 -0
  279. package/src/runtime/__tests__/web-presence.test.ts +234 -0
  280. package/src/runtime/assistant-event-hub.ts +38 -0
  281. package/src/runtime/channel-reply-delivery.ts +54 -31
  282. package/src/runtime/channel-retry-sweep.ts +6 -4
  283. package/src/runtime/effective-capabilities.test.ts +19 -0
  284. package/src/runtime/effective-capabilities.ts +25 -0
  285. package/src/runtime/guardian-reply-router.ts +6 -2
  286. package/src/runtime/http-errors.ts +1 -0
  287. package/src/runtime/http-server.ts +20 -5
  288. package/src/runtime/routes/__tests__/client-routes.test.ts +150 -0
  289. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +133 -0
  290. package/src/runtime/routes/__tests__/credential-delete-in-use.test.ts +155 -0
  291. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +52 -1
  292. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +1 -0
  293. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +175 -0
  294. package/src/runtime/routes/__tests__/plugins-routes.test.ts +88 -35
  295. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  296. package/src/runtime/routes/__tests__/system-card-turn-termination.test.ts +107 -0
  297. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  298. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +75 -4
  299. package/src/runtime/routes/approval-routes.ts +37 -4
  300. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +16 -22
  301. package/src/runtime/routes/canned-message-complete.ts +68 -29
  302. package/src/runtime/routes/client-routes.ts +113 -0
  303. package/src/runtime/routes/conversation-list-routes.ts +22 -7
  304. package/src/runtime/routes/conversation-management-routes.ts +15 -9
  305. package/src/runtime/routes/conversation-routes.ts +36 -22
  306. package/src/runtime/routes/credential-in-use.ts +85 -0
  307. package/src/runtime/routes/credential-routes.ts +55 -85
  308. package/src/runtime/routes/guardian-approval-interception.ts +22 -22
  309. package/src/runtime/routes/guardian-approval-reply-helpers.ts +14 -12
  310. package/src/runtime/routes/identity-routes.ts +5 -121
  311. package/src/runtime/routes/inbound-message-handler.ts +71 -27
  312. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -17
  313. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +77 -50
  314. package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -97
  315. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -77
  316. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +17 -8
  317. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -7
  318. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -70
  319. package/src/runtime/routes/index.ts +2 -0
  320. package/src/runtime/routes/inference-provider-connection-routes.ts +9 -7
  321. package/src/runtime/routes/monitoring-routes.ts +9 -1
  322. package/src/runtime/routes/plugins-routes.ts +17 -5
  323. package/src/runtime/routes/resource-pressure-routes.ts +22 -0
  324. package/src/runtime/routes/secret-routes.ts +39 -8
  325. package/src/runtime/routes/settings-routes.ts +6 -6
  326. package/src/runtime/routes/surface-action-routes.ts +20 -19
  327. package/src/runtime/routes/user-route-dispatcher.ts +31 -2
  328. package/src/runtime/routes/user-route-resolution.ts +18 -1
  329. package/src/runtime/slack-reply-session.test.ts +23 -13
  330. package/src/runtime/slack-reply-session.ts +38 -55
  331. package/src/runtime/web-presence.ts +90 -0
  332. package/src/skills/validate-input.ts +177 -40
  333. package/src/subagent/__tests__/consult-context-gating.test.ts +6 -10
  334. package/src/subagent/consult-context.ts +7 -23
  335. package/src/tools/credentials/broker.ts +24 -79
  336. package/src/tools/credentials/ref-parse.ts +35 -0
  337. package/src/tools/credentials/resolve.ts +4 -10
  338. package/src/tools/credentials/store.ts +168 -0
  339. package/src/tools/credentials/tool-policy.ts +67 -0
  340. package/src/tools/executor.ts +53 -15
  341. package/src/tools/network/__tests__/web-search.test.ts +41 -1
  342. package/src/tools/network/url-safety.ts +9 -16
  343. package/src/tools/network/web-search.ts +21 -3
  344. package/src/tools/permission-checker.ts +43 -52
  345. package/src/tools/schema-transforms.ts +40 -5
  346. package/src/tools/shared/input-repairs.ts +160 -0
  347. package/src/tools/skills/skill-tool-factory.ts +18 -4
  348. package/src/tools/subagent/spawn.ts +0 -1
  349. package/src/tools/tool-approval-handler.ts +16 -10
  350. package/src/tools/tool-types.ts +26 -13
  351. package/src/tools/types.ts +6 -6
  352. package/src/util/__tests__/cgroup-memory.test.ts +3 -0
  353. package/src/util/cgroup-memory.ts +3 -0
  354. package/src/util/container-cpu-sampler.ts +250 -0
  355. package/src/util/image-conversion.ts +178 -14
  356. package/src/util/platform.ts +93 -10
  357. package/src/permissions/ipc-risk-types.ts +0 -143
  358. package/src/permissions/risk-types.ts +0 -76
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Log-field builder for provider rejections that carry no actionable
3
+ * classification of their own (unclassified 4xx).
4
+ *
5
+ * The extracted fields on `ProviderError` (`apiErrorCode`, `message`) drop the
6
+ * detail an operator needs to find the offending request: which request field,
7
+ * which message, which byte pattern. The verbatim upstream body carries it, so
8
+ * it rides the log record, bounded so a large provider error page cannot flood
9
+ * the log stream.
10
+ */
11
+
12
+ import { ProviderError } from "../util/errors.js";
13
+ import { redactLogString } from "../util/log-redact.js";
14
+
15
+ /**
16
+ * Ceiling for the logged upstream body. Bodies are already capped at 16 KiB
17
+ * when captured (`providers/openai/api-error-normalization.ts`); this second,
18
+ * tighter bound is what a log record carries.
19
+ */
20
+ export const MAX_LOGGED_UPSTREAM_BODY_CHARS = 4096;
21
+
22
+ /**
23
+ * Upstream error codes whose message names the offending request content part,
24
+ * e.g. `Invalid 'input[191].content[1].text': string too long`. Kept narrow:
25
+ * codes outside this set have no such pointer, so parsing their message would
26
+ * only produce coincidental matches.
27
+ */
28
+ const CONTENT_PART_POINTER_CODES = new Set([
29
+ "string_above_max_length",
30
+ "invalid_body",
31
+ ]);
32
+
33
+ /**
34
+ * Positional pointer into the request payload. `input[N]` is the Responses API
35
+ * shape, `messages[N]` the Chat Completions shape; both index the request's
36
+ * message array in send order, so `N` is an offset into the conversation
37
+ * history that produced the request.
38
+ */
39
+ const CONTENT_PART_POINTER = /\b(?:input|messages)\[(\d+)\]\.content\[(\d+)\]/i;
40
+
41
+ export interface ProviderRejectionLogFields {
42
+ /** Verbatim upstream non-2xx body, truncated to the logged ceiling. */
43
+ upstreamErrorBody: string | null;
44
+ /** True when the body was cut to fit the ceiling. */
45
+ upstreamErrorBodyTruncated?: boolean;
46
+ apiErrorCode?: string;
47
+ apiErrorType?: string;
48
+ apiErrorParam?: string;
49
+ requestId?: string;
50
+ /** Index of the offending message within the request's message array. */
51
+ offendingMessageIndex?: number;
52
+ /** Index of the offending content part within that message. */
53
+ offendingContentIndex?: number;
54
+ }
55
+
56
+ /**
57
+ * Extract the offending `input[N].content[M]` coordinates from an upstream
58
+ * error message, for the codes that carry such a pointer.
59
+ */
60
+ function parseContentPartPointer(
61
+ error: ProviderError,
62
+ ): Pick<
63
+ ProviderRejectionLogFields,
64
+ "offendingMessageIndex" | "offendingContentIndex"
65
+ > {
66
+ const code = error.apiErrorCode?.toLowerCase();
67
+ if (!code || !CONTENT_PART_POINTER_CODES.has(code)) {
68
+ return {};
69
+ }
70
+ const match = CONTENT_PART_POINTER.exec(
71
+ `${error.message} ${error.rawBody ?? ""}`,
72
+ );
73
+ if (!match) {
74
+ return {};
75
+ }
76
+ const messageIndex = Number(match[1]);
77
+ const contentIndex = Number(match[2]);
78
+ if (
79
+ !Number.isSafeInteger(messageIndex) ||
80
+ !Number.isSafeInteger(contentIndex)
81
+ ) {
82
+ return {};
83
+ }
84
+ return {
85
+ offendingMessageIndex: messageIndex,
86
+ offendingContentIndex: contentIndex,
87
+ };
88
+ }
89
+
90
+ /** Diagnostic fields describing what the provider actually rejected. */
91
+ export function buildProviderRejectionLogFields(
92
+ error: Error,
93
+ ): ProviderRejectionLogFields {
94
+ if (!(error instanceof ProviderError)) {
95
+ return { upstreamErrorBody: null };
96
+ }
97
+
98
+ // The body is arbitrary upstream text under a custom field name, so it is
99
+ // outside the reach of the pino `err`/`req`/`res` serializers and is scrubbed
100
+ // here before it reaches the record.
101
+ const rawBody =
102
+ error.rawBody === undefined ? undefined : redactLogString(error.rawBody);
103
+ const truncated =
104
+ rawBody !== undefined && rawBody.length > MAX_LOGGED_UPSTREAM_BODY_CHARS;
105
+
106
+ return {
107
+ upstreamErrorBody:
108
+ rawBody === undefined
109
+ ? null
110
+ : rawBody.slice(0, MAX_LOGGED_UPSTREAM_BODY_CHARS),
111
+ ...(truncated ? { upstreamErrorBodyTruncated: true } : {}),
112
+ ...(error.apiErrorCode !== undefined
113
+ ? { apiErrorCode: error.apiErrorCode }
114
+ : {}),
115
+ ...(error.apiErrorType !== undefined
116
+ ? { apiErrorType: error.apiErrorType }
117
+ : {}),
118
+ ...(error.apiErrorParam !== undefined
119
+ ? { apiErrorParam: error.apiErrorParam }
120
+ : {}),
121
+ ...(error.requestId !== undefined ? { requestId: error.requestId } : {}),
122
+ ...parseContentPartPointer(error),
123
+ };
124
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Resource-pressure guard wiring for the daemon lifecycle.
3
+ *
4
+ * Starts the guard at boot and defers the first sample onto a macrotask so it
5
+ * never blocks startup, and stops the guard (cancelling any pending deferred
6
+ * sample) on shutdown. When the guard is disabled (non-platform), no deferred
7
+ * sample is scheduled.
8
+ */
9
+ import { getLogger } from "../util/logger.js";
10
+ import {
11
+ evaluateResourcePressureNow,
12
+ startResourcePressureGuard,
13
+ stopResourcePressureGuard,
14
+ } from "./resource-pressure-guard.js";
15
+
16
+ const log = getLogger("resource-pressure-guard-lifecycle");
17
+
18
+ let resourcePressureStartupSampleTimer: ReturnType<typeof setTimeout> | null =
19
+ null;
20
+
21
+ function runDeferredResourcePressureStartupSample(): void {
22
+ resourcePressureStartupSampleTimer = null;
23
+ try {
24
+ const status = evaluateResourcePressureNow();
25
+ if (status.error) {
26
+ log.warn(
27
+ { error: status.error },
28
+ "Resource pressure guard sample failed during startup, continuing",
29
+ );
30
+ }
31
+ } catch (err) {
32
+ log.warn(
33
+ { err },
34
+ "Resource pressure guard failed during startup, continuing",
35
+ );
36
+ }
37
+ }
38
+
39
+ export function startResourcePressureGuardForLifecycle(): void {
40
+ try {
41
+ const startedStatus = startResourcePressureGuard();
42
+ if (!startedStatus.enabled) {
43
+ return;
44
+ }
45
+ if (!resourcePressureStartupSampleTimer) {
46
+ resourcePressureStartupSampleTimer = setTimeout(
47
+ runDeferredResourcePressureStartupSample,
48
+ 0,
49
+ );
50
+ (resourcePressureStartupSampleTimer as { unref?: () => void }).unref?.();
51
+ }
52
+ } catch (err) {
53
+ log.warn(
54
+ { err },
55
+ "Resource pressure guard failed during startup, continuing",
56
+ );
57
+ }
58
+ }
59
+
60
+ export function stopResourcePressureGuardForLifecycle(): void {
61
+ if (resourcePressureStartupSampleTimer) {
62
+ clearTimeout(resourcePressureStartupSampleTimer);
63
+ resourcePressureStartupSampleTimer = null;
64
+ }
65
+ stopResourcePressureGuard();
66
+ }
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Sustained CPU/memory pressure guard for platform-hosted assistants.
3
+ *
4
+ * Samples container CPU utilization and working-set memory against the
5
+ * container's allocation on a fixed cadence and reports an `elevated` state
6
+ * only when a signal stays over its threshold for nearly the whole sampling
7
+ * window (so a single heavy agent task never trips it). Clearing requires a
8
+ * sustained run of samples below a lower clear threshold (hysteresis), so the
9
+ * state never flaps while usage hovers near a threshold.
10
+ *
11
+ * The guard is platform-gated: off-platform there is no plan allocation to
12
+ * measure against, so the guard stays disabled and samples nothing.
13
+ */
14
+ import {
15
+ type ResourcePressureState,
16
+ type ResourcePressureStatus,
17
+ } from "../api/events/resource-pressure-status-changed.js";
18
+ import { getIsPlatform } from "../config/env-registry.js";
19
+ import { broadcastMessage } from "../runtime/assistant-event-hub.js";
20
+ import { getContainerCpuCores } from "../util/cgroup-cpu.js";
21
+ import {
22
+ getContainerMemoryLimitBytes,
23
+ getContainerMemoryStat,
24
+ getContainerMemoryUsageBytes,
25
+ } from "../util/cgroup-memory.js";
26
+ import { getAverageContainerCpuPercentOrNull } from "../util/container-cpu-sampler.js";
27
+ import { getLogger } from "../util/logger.js";
28
+
29
+ const log = getLogger("resource-pressure-guard");
30
+
31
+ const RESOURCE_PRESSURE_SAMPLE_INTERVAL_MS = 30_000;
32
+ // 20 samples at a 30s cadence: 10 minutes of history.
33
+ export const RESOURCE_PRESSURE_WINDOW_SAMPLES = 20;
34
+ // A signal must exceed its threshold in at least this many of the last
35
+ // RESOURCE_PRESSURE_WINDOW_SAMPLES samples, with the window full, to enter
36
+ // `elevated`.
37
+ export const RESOURCE_PRESSURE_ENTER_SAMPLES = 18;
38
+ // Consecutive samples below the clear threshold (5 minutes) required to leave
39
+ // `elevated`.
40
+ export const RESOURCE_PRESSURE_CLEAR_CONSECUTIVE_SAMPLES = 10;
41
+ const CPU_PRESSURE_THRESHOLD_PERCENT = 85;
42
+ const CPU_PRESSURE_CLEAR_THRESHOLD_PERCENT = 70;
43
+ const MEMORY_PRESSURE_THRESHOLD_PERCENT = 90;
44
+ const MEMORY_PRESSURE_CLEAR_THRESHOLD_PERCENT = 80;
45
+
46
+ export { type ResourcePressureState, type ResourcePressureStatus };
47
+
48
+ interface ResourcePressureMemorySample {
49
+ usageBytes: number;
50
+ limitBytes: number;
51
+ reclaimableBytes: number;
52
+ }
53
+
54
+ interface ResourcePressureSamplers {
55
+ sampleCpuPercent?: () => number | null;
56
+ sampleMemory?: () => ResourcePressureMemorySample | null;
57
+ }
58
+
59
+ interface SignalWindow {
60
+ /** Ring buffer of over-threshold results, oldest first. */
61
+ overThreshold: boolean[];
62
+ consecutiveBelowClear: number;
63
+ elevated: boolean;
64
+ }
65
+
66
+ interface ResourcePressureGuardState {
67
+ timer: ReturnType<typeof setInterval> | null;
68
+ status: ResourcePressureStatus;
69
+ cpuWindow: SignalWindow;
70
+ memoryWindow: SignalWindow;
71
+ }
72
+
73
+ const DISABLED_STATUS: ResourcePressureStatus = {
74
+ enabled: false,
75
+ state: "disabled",
76
+ cpuPercent: null,
77
+ memoryPercent: null,
78
+ cpuElevated: false,
79
+ memoryElevated: false,
80
+ cpuThresholdPercent: CPU_PRESSURE_THRESHOLD_PERCENT,
81
+ memoryThresholdPercent: MEMORY_PRESSURE_THRESHOLD_PERCENT,
82
+ lastCheckedAt: null,
83
+ error: null,
84
+ };
85
+
86
+ const OK_STATUS: ResourcePressureStatus = {
87
+ ...DISABLED_STATUS,
88
+ enabled: true,
89
+ state: "ok",
90
+ };
91
+
92
+ function emptySignalWindow(): SignalWindow {
93
+ return { overThreshold: [], consecutiveBelowClear: 0, elevated: false };
94
+ }
95
+
96
+ const state: ResourcePressureGuardState = {
97
+ timer: null,
98
+ status: cloneStatus(DISABLED_STATUS),
99
+ cpuWindow: emptySignalWindow(),
100
+ memoryWindow: emptySignalWindow(),
101
+ };
102
+
103
+ function cloneStatus(status: ResourcePressureStatus): ResourcePressureStatus {
104
+ return { ...status };
105
+ }
106
+
107
+ // The raw percents and timestamp change on every sample; broadcasting each
108
+ // tick would spam the SSE hub, so only substantive fields participate.
109
+ function statusFingerprint(status: ResourcePressureStatus): string {
110
+ const {
111
+ lastCheckedAt: _lastCheckedAt,
112
+ cpuPercent: _cpuPercent,
113
+ memoryPercent: _memoryPercent,
114
+ ...substantiveStatus
115
+ } = status;
116
+ return JSON.stringify(substantiveStatus);
117
+ }
118
+
119
+ function publishStatusChangedIfNeeded(previous: ResourcePressureStatus): void {
120
+ if (statusFingerprint(previous) === statusFingerprint(state.status)) {
121
+ return;
122
+ }
123
+ const status = cloneStatus(state.status);
124
+ broadcastMessage({
125
+ type: "resource_pressure_status_changed",
126
+ status,
127
+ });
128
+ }
129
+
130
+ function replaceStatus(next: ResourcePressureStatus): ResourcePressureStatus {
131
+ const previous = cloneStatus(state.status);
132
+ state.status = cloneStatus(next);
133
+ publishStatusChangedIfNeeded(previous);
134
+ return cloneStatus(state.status);
135
+ }
136
+
137
+ function ensureEnabledStatus(): void {
138
+ if (!state.status.enabled) {
139
+ state.status = cloneStatus(OK_STATUS);
140
+ }
141
+ }
142
+
143
+ function roundPercent(value: number): number {
144
+ return Math.round(value * 100) / 100;
145
+ }
146
+
147
+ function formatError(error: unknown): string {
148
+ return error instanceof Error ? error.message : String(error);
149
+ }
150
+
151
+ function sampleFailureStatus(errorMessage: string): ResourcePressureStatus {
152
+ return {
153
+ ...OK_STATUS,
154
+ state: "unknown",
155
+ lastCheckedAt: new Date().toISOString(),
156
+ error: errorMessage,
157
+ };
158
+ }
159
+
160
+ function resetSignalWindow(window: SignalWindow): void {
161
+ window.overThreshold = [];
162
+ window.consecutiveBelowClear = 0;
163
+ window.elevated = false;
164
+ }
165
+
166
+ /**
167
+ * Feed one sample into a signal's window and derive its elevated flag.
168
+ *
169
+ * An unavailable sample (null percent) resets the window: the signal cannot
170
+ * hold `elevated` without data, and a fresh full window is required before it
171
+ * can enter again.
172
+ */
173
+ function updateSignal(
174
+ window: SignalWindow,
175
+ percent: number | null,
176
+ thresholdPercent: number,
177
+ clearThresholdPercent: number,
178
+ ): boolean {
179
+ if (percent === null) {
180
+ resetSignalWindow(window);
181
+ return false;
182
+ }
183
+
184
+ window.overThreshold.push(percent >= thresholdPercent);
185
+ if (window.overThreshold.length > RESOURCE_PRESSURE_WINDOW_SAMPLES) {
186
+ window.overThreshold.shift();
187
+ }
188
+ if (percent < clearThresholdPercent) {
189
+ window.consecutiveBelowClear += 1;
190
+ } else {
191
+ window.consecutiveBelowClear = 0;
192
+ }
193
+
194
+ if (window.elevated) {
195
+ // Hysteresis: hold until enough consecutive samples land below the clear
196
+ // threshold.
197
+ if (
198
+ window.consecutiveBelowClear >=
199
+ RESOURCE_PRESSURE_CLEAR_CONSECUTIVE_SAMPLES
200
+ ) {
201
+ window.elevated = false;
202
+ }
203
+ } else {
204
+ const overCount = window.overThreshold.filter(Boolean).length;
205
+ window.elevated =
206
+ window.overThreshold.length >= RESOURCE_PRESSURE_WINDOW_SAMPLES &&
207
+ overCount >= RESOURCE_PRESSURE_ENTER_SAMPLES;
208
+ }
209
+
210
+ return window.elevated;
211
+ }
212
+
213
+ function defaultSampleCpuPercent(): number | null {
214
+ if (getContainerCpuCores() <= 0) {
215
+ return null;
216
+ }
217
+ // Average the rolling sampler's 5s windows across this guard's own 30s
218
+ // cadence: reading one instantaneous value would let a short spike that
219
+ // happens to align with the guard's ticks alias into "sustained" load
220
+ // (and an out-of-phase one be missed). Null while the sampler is still
221
+ // warming up or CPU accounting is unreadable: a placeholder 0% would look
222
+ // like a real measurement and mask the signal being unavailable.
223
+ return getAverageContainerCpuPercentOrNull(
224
+ RESOURCE_PRESSURE_SAMPLE_INTERVAL_MS,
225
+ );
226
+ }
227
+
228
+ function defaultSampleMemory(): ResourcePressureMemorySample | null {
229
+ const limitBytes = getContainerMemoryLimitBytes();
230
+ const usageBytes = getContainerMemoryUsageBytes();
231
+ if (limitBytes === null || limitBytes <= 0 || usageBytes === null) {
232
+ return null;
233
+ }
234
+ // Raw usage includes cache the kernel can drop under pressure; counting
235
+ // it would false-positive, so the working set subtracts the READILY
236
+ // reclaimable part: inactive file pages plus reclaimable slab. The full
237
+ // `file` counter is not used because active file pages (hot mmaps, a hot
238
+ // cache) are not disposable and belong in the working set. When the
239
+ // breakdown is unreadable (cgroups v1, where memory.stat lives
240
+ // elsewhere, or missing v2 counters) the working set is unknowable:
241
+ // report the signal unavailable rather than guessing.
242
+ const stat = getContainerMemoryStat();
243
+ const inactiveFileBytes = stat?.inactiveFileBytes ?? null;
244
+ if (inactiveFileBytes === null) {
245
+ return null;
246
+ }
247
+ return {
248
+ usageBytes,
249
+ limitBytes,
250
+ reclaimableBytes: inactiveFileBytes + (stat?.slabReclaimableBytes ?? 0),
251
+ };
252
+ }
253
+
254
+ export function startResourcePressureGuard(): ResourcePressureStatus {
255
+ if (!getIsPlatform()) {
256
+ return cloneStatus(state.status);
257
+ }
258
+
259
+ ensureEnabledStatus();
260
+
261
+ if (!state.timer) {
262
+ state.timer = setInterval(() => {
263
+ evaluateResourcePressureNow();
264
+ }, RESOURCE_PRESSURE_SAMPLE_INTERVAL_MS);
265
+ (state.timer as { unref?: () => void }).unref?.();
266
+ }
267
+
268
+ return cloneStatus(state.status);
269
+ }
270
+
271
+ export function stopResourcePressureGuard(): void {
272
+ if (!state.timer) {
273
+ return;
274
+ }
275
+ clearInterval(state.timer);
276
+ state.timer = null;
277
+ }
278
+
279
+ export function evaluateResourcePressureNow(
280
+ deps?: ResourcePressureSamplers,
281
+ ): ResourcePressureStatus {
282
+ if (!getIsPlatform()) {
283
+ return cloneStatus(state.status);
284
+ }
285
+
286
+ ensureEnabledStatus();
287
+
288
+ const sampleCpuPercent = deps?.sampleCpuPercent ?? defaultSampleCpuPercent;
289
+ const sampleMemory = deps?.sampleMemory ?? defaultSampleMemory;
290
+
291
+ // A sampler returning null means the signal is legitimately unavailable
292
+ // (e.g. no cgroup limit) and is not an error; only a throwing sampler
293
+ // contributes to sampleError.
294
+ const sampleErrors: string[] = [];
295
+
296
+ let cpuPercent: number | null = null;
297
+ try {
298
+ cpuPercent = sampleCpuPercent();
299
+ } catch (error) {
300
+ sampleErrors.push(`CPU sample failed: ${formatError(error)}`);
301
+ }
302
+
303
+ let memoryPercent: number | null = null;
304
+ try {
305
+ const memorySample = sampleMemory();
306
+ if (memorySample && memorySample.limitBytes > 0) {
307
+ const workingSetBytes = Math.max(
308
+ 0,
309
+ memorySample.usageBytes - memorySample.reclaimableBytes,
310
+ );
311
+ memoryPercent = roundPercent(
312
+ (workingSetBytes / memorySample.limitBytes) * 100,
313
+ );
314
+ }
315
+ } catch (error) {
316
+ sampleErrors.push(`Memory sample failed: ${formatError(error)}`);
317
+ }
318
+
319
+ const sampleError = sampleErrors.length > 0 ? sampleErrors.join("; ") : null;
320
+
321
+ if (cpuPercent === null && memoryPercent === null) {
322
+ resetSignalWindow(state.cpuWindow);
323
+ resetSignalWindow(state.memoryWindow);
324
+ return replaceStatus(
325
+ sampleFailureStatus(
326
+ sampleError ?? "Resource pressure samples unavailable",
327
+ ),
328
+ );
329
+ }
330
+
331
+ // Log on the transition into a failing state, not on every 30s sample. The
332
+ // status fingerprint includes `error`, so the SSE broadcast below reflects
333
+ // the same transition exactly once.
334
+ if (sampleError !== null && sampleError !== state.status.error) {
335
+ log.warn(
336
+ { error: sampleError },
337
+ "Resource pressure sampler failed; the remaining signal still evaluated",
338
+ );
339
+ }
340
+
341
+ const cpuElevated = updateSignal(
342
+ state.cpuWindow,
343
+ cpuPercent,
344
+ CPU_PRESSURE_THRESHOLD_PERCENT,
345
+ CPU_PRESSURE_CLEAR_THRESHOLD_PERCENT,
346
+ );
347
+ const memoryElevated = updateSignal(
348
+ state.memoryWindow,
349
+ memoryPercent,
350
+ MEMORY_PRESSURE_THRESHOLD_PERCENT,
351
+ MEMORY_PRESSURE_CLEAR_THRESHOLD_PERCENT,
352
+ );
353
+
354
+ return replaceStatus({
355
+ ...OK_STATUS,
356
+ state: cpuElevated || memoryElevated ? "elevated" : "ok",
357
+ cpuPercent,
358
+ memoryPercent,
359
+ cpuElevated,
360
+ memoryElevated,
361
+ lastCheckedAt: new Date().toISOString(),
362
+ error: sampleError,
363
+ });
364
+ }
365
+
366
+ export function getResourcePressureStatus(): ResourcePressureStatus {
367
+ if (!getIsPlatform()) {
368
+ return cloneStatus(DISABLED_STATUS);
369
+ }
370
+ if (!state.status.enabled) {
371
+ return cloneStatus(OK_STATUS);
372
+ }
373
+ return cloneStatus(state.status);
374
+ }
375
+
376
+ export function __resetResourcePressureGuardForTests(): void {
377
+ stopResourcePressureGuard();
378
+ state.status = cloneStatus(DISABLED_STATUS);
379
+ resetSignalWindow(state.cpuWindow);
380
+ resetSignalWindow(state.memoryWindow);
381
+ }
382
+
383
+ export function __getResourcePressureGuardTimerForTests(): ReturnType<
384
+ typeof setInterval
385
+ > | null {
386
+ return state.timer;
387
+ }
@@ -38,6 +38,7 @@ import { isStartupComplete } from "./daemon-readiness.js";
38
38
  import { stopDiskPressureGuardForLifecycle } from "./disk-pressure-guard-lifecycle.js";
39
39
  import { stopEventLoopWatchdog } from "./event-loop-watchdog.js";
40
40
  import { stopOrphanReaper } from "./orphan-reaper.js";
41
+ import { stopResourcePressureGuardForLifecycle } from "./resource-pressure-guard-lifecycle.js";
41
42
 
42
43
  const log = getLogger("lifecycle");
43
44
 
@@ -49,6 +50,7 @@ const log = getLogger("lifecycle");
49
50
  function stopBackgroundServicesAndCleanupPidFile(): void {
50
51
  stopGatewayFlagListener();
51
52
  stopDiskPressureGuardForLifecycle();
53
+ stopResourcePressureGuardForLifecycle();
52
54
  stopOrphanReaper();
53
55
  stopEventLoopWatchdog();
54
56
  cleanupPidFile();
@@ -23,6 +23,22 @@ export interface DaemonStartupError {
23
23
 
24
24
  const DAEMON_ERROR_PREFIX = "DAEMON_ERROR:";
25
25
 
26
+ /**
27
+ * Build an `EADDRINUSE`-coded error so callers (and {@link categorizeDaemonError})
28
+ * can branch on `err.code` and surface the structured "already running"
29
+ * guidance instead of a generic UNKNOWN. Used by every transport that detects
30
+ * an occupied address itself rather than receiving a kernel-coded error.
31
+ */
32
+ export function makeAddrInUseError(
33
+ message: string,
34
+ cause?: unknown,
35
+ ): NodeJS.ErrnoException {
36
+ const err = new Error(message, cause === undefined ? undefined : { cause });
37
+ const errno = err as NodeJS.ErrnoException;
38
+ errno.code = "EADDRINUSE";
39
+ return errno;
40
+ }
41
+
26
42
  /**
27
43
  * Inspect an error and return a categorized {@link DaemonStartupError}.
28
44
  */
@@ -7,7 +7,7 @@
7
7
  import type { ChannelConversationType } from "@vellumai/gateway-client";
8
8
 
9
9
  import { isHttpAuthDisabled } from "../config/env.js";
10
- import { shouldExposePersonalMemory } from "../plugins/defaults/memory/substrate/static-context.js";
10
+ import { canSeePersonalMemory } from "../runtime/effective-capabilities.js";
11
11
  import type { TrustClass } from "../runtime/trust-class.js";
12
12
  import type { TrustContext } from "./trust-context-types.js";
13
13
 
@@ -67,21 +67,25 @@ export function resolveTrustClass(
67
67
  * Whether personal-memory content may be surfaced for the actor described by
68
68
  * `trustContext`: the gate admits guardian-class actors and internal/local
69
69
  * flows (including turns with no trust context), and blocks remote untrusted
70
- * actors — see {@link shouldExposePersonalMemory} for the rationale.
70
+ * actors.
71
71
  *
72
72
  * This is THE personal-memory trust gate. Every surface that exposes private
73
73
  * user content — the v2 dynamic/static `<memory>` layers, PKB context, NOW.md,
74
74
  * memory-v3 cards/spotlight, and the `loadFromDb` rehydration of persisted
75
75
  * memory blocks — must call this one helper so the exposure rule cannot drift
76
- * between copies. It folds in {@link resolveTrustClass} so the dev-bypass
77
- * (HTTP auth disabled → guardian) applies uniformly at every call site.
76
+ * between copies.
77
+ *
78
+ * The trust-context binding lives here; the policy itself is
79
+ * {@link canSeePersonalMemory}, alongside the other capability-plus-context
80
+ * compositions. This adapter folds in {@link resolveTrustClass} so the
81
+ * dev-bypass (HTTP auth disabled → guardian) applies uniformly at every call
82
+ * site.
78
83
  */
79
84
  export function isPersonalMemoryAllowed(
80
85
  trustContext: TrustContext | undefined,
81
86
  ): boolean {
82
- return shouldExposePersonalMemory({
83
- sourceChannel: trustContext?.sourceChannel,
84
- isTrustedActor: resolveTrustClass(trustContext) === "guardian",
87
+ return canSeePersonalMemory({
88
+ trustClass: resolveTrustClass(trustContext),
85
89
  });
86
90
  }
87
91
 
@@ -109,9 +113,9 @@ export function mapChatTypeToConversationType(
109
113
  // it for the axis rather than inventing a native-looking word.
110
114
  case "dm":
111
115
  return "dm";
112
- // "mpim" is Slack's multi-party DM. The gateway normalizer currently
113
- // collapses mpim into "channel", so this arm matches nothing from Slack
114
- // today; it pins the correct mapping for the raw Slack vocabulary.
116
+ // "mpim" is Slack's multi-party DM, forwarded under its own name rather
117
+ // than reported as "im": a room with several people in it takes the
118
+ // tighter `private` cell and the group-chat etiquette, not the `dm` one.
115
119
  case "mpim":
116
120
  case "group": // Telegram group
117
121
  case "supergroup": // Telegram supergroup