@vellumai/assistant 0.11.6 → 0.11.7-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) hide show
  1. package/ARCHITECTURE.md +31 -19
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +3 -3
  4. package/docs/flux-turn-detection-spike.md +8 -7
  5. package/docs/runbook-trusted-contacts.md +14 -0
  6. package/docs/stt-provider-onboarding.md +8 -4
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
  9. package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
  10. package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
  11. package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
  12. package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
  13. package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
  14. package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
  15. package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  19. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  20. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  21. package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
  22. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  23. package/node_modules/@vellumai/environments/package.json +2 -1
  24. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  25. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  27. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  28. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  29. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  30. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  32. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  33. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  34. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  35. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  36. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  37. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  38. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  39. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  40. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  41. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  42. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  43. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  44. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  45. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  46. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  47. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  48. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  49. package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  50. package/openapi.yaml +419 -55
  51. package/package.json +4 -1
  52. package/scripts/sync-llm-catalog.ts +6 -0
  53. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  54. package/src/__tests__/approval-card-edit.test.ts +153 -0
  55. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  56. package/src/__tests__/available-skills.test.ts +54 -0
  57. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  58. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  59. package/src/__tests__/channel-policy.test.ts +1 -3
  60. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  61. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  63. package/src/__tests__/config-schema.test.ts +2 -3
  64. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  65. package/src/__tests__/conversation-attachments.test.ts +0 -1
  66. package/src/__tests__/conversation-error.test.ts +44 -0
  67. package/src/__tests__/conversation-evictor.test.ts +19 -0
  68. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  69. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  70. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  71. package/src/__tests__/conversation-slash.test.ts +9 -0
  72. package/src/__tests__/conversation-title-service.test.ts +97 -0
  73. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  74. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  75. package/src/__tests__/credential-broker.test.ts +43 -420
  76. package/src/__tests__/credential-record-write-through.test.ts +78 -0
  77. package/src/__tests__/credential-routes.test.ts +17 -1
  78. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  79. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  80. package/src/__tests__/delete-propagation.test.ts +92 -2
  81. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  82. package/src/__tests__/discord-channel-config.test.ts +237 -0
  83. package/src/__tests__/edit-propagation.test.ts +42 -2
  84. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  85. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  86. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  87. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  88. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  89. package/src/__tests__/image-conversion.test.ts +38 -4
  90. package/src/__tests__/install-skill-routing.test.ts +25 -0
  91. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  92. package/src/__tests__/llm-resolver.test.ts +74 -0
  93. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  94. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  95. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  96. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  97. package/src/__tests__/mcp-cli.test.ts +23 -1
  98. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  99. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  100. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  101. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  102. package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
  103. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  104. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  105. package/src/__tests__/platform.test.ts +19 -1
  106. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  107. package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
  108. package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
  109. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  110. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  111. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  112. package/src/__tests__/schedule-retry.test.ts +183 -4
  113. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  115. package/src/__tests__/skill-load-tool.test.ts +34 -0
  116. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  117. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  118. package/src/__tests__/skills.test.ts +25 -0
  119. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  120. package/src/__tests__/terminal-tools.test.ts +2 -0
  121. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  122. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  123. package/src/__tests__/tool-audit.test.ts +111 -40
  124. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  125. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  126. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  127. package/src/__tests__/voice-config-update.test.ts +43 -0
  128. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  129. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  130. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  131. package/src/agent/loop.ts +31 -2
  132. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  133. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  134. package/src/avatar/avatar-manifest.ts +48 -100
  135. package/src/avatar/avatar-store.ts +7 -7
  136. package/src/avatar/ensure-raster.ts +146 -0
  137. package/src/avatar/resvg-lazy.test.ts +0 -1
  138. package/src/avatar/traits-png-sync.ts +9 -10
  139. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  140. package/src/backup/__tests__/paths.test.ts +6 -2
  141. package/src/backup/paths.ts +6 -4
  142. package/src/bundler/app-compiler.ts +2 -2
  143. package/src/bundler/compiler-tools.test.ts +36 -0
  144. package/src/bundler/compiler-tools.ts +54 -18
  145. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  146. package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
  147. package/src/calls/media-stream-stt-session.ts +2 -1
  148. package/src/calls/telephony-synthesis-language.ts +13 -5
  149. package/src/calls/voice-session-bridge.ts +19 -4
  150. package/src/channels/config.ts +23 -12
  151. package/src/cli/AGENTS.md +3 -1
  152. package/src/cli/commands/backup.help.ts +10 -13
  153. package/src/cli/commands/contacts.help.ts +16 -4
  154. package/src/cli/commands/contacts.ts +5 -1
  155. package/src/cli/commands/credentials.help.ts +1 -1
  156. package/src/cli/commands/inference-providers.ts +18 -53
  157. package/src/cli/commands/mcp.help.ts +6 -5
  158. package/src/cli/commands/mcp.ts +1 -1
  159. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  160. package/src/cli/commands/stt.help.ts +2 -2
  161. package/src/config/__tests__/backup-schema.test.ts +1 -1
  162. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  163. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  164. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  165. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  166. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  167. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  168. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  169. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  170. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  171. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  172. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  173. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  174. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  175. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  176. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  177. package/src/config/default-profile-catalog.ts +195 -10
  178. package/src/config/default-profile-names.ts +41 -0
  179. package/src/config/feature-flag-registry.json +34 -2
  180. package/src/config/inference-profile-validation.ts +11 -2
  181. package/src/config/llm-resolver.ts +25 -0
  182. package/src/config/loader.ts +3 -0
  183. package/src/config/managed-speech-defaults.ts +160 -10
  184. package/src/config/schema.ts +2 -0
  185. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  186. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  187. package/src/config/schemas/backup.ts +1 -1
  188. package/src/config/schemas/channels.ts +32 -0
  189. package/src/config/schemas/live-voice.ts +13 -4
  190. package/src/config/schemas/llm.ts +263 -15
  191. package/src/config/schemas/mcp.ts +10 -2
  192. package/src/config/schemas/memory-retrospective.ts +1 -19
  193. package/src/config/schemas/services.ts +24 -0
  194. package/src/config/schemas/stt.ts +128 -5
  195. package/src/config/seed-inference-profiles.ts +32 -3
  196. package/src/config/skill-state.ts +5 -0
  197. package/src/config/skills.ts +21 -0
  198. package/src/contacts/notify-contacts-changed.ts +5 -2
  199. package/src/context/compactor.ts +74 -1
  200. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  201. package/src/daemon/config-watcher.ts +2 -1
  202. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  203. package/src/daemon/conversation-agent-loop.ts +86 -10
  204. package/src/daemon/conversation-error.ts +25 -0
  205. package/src/daemon/conversation-evictor.ts +2 -4
  206. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  207. package/src/daemon/conversation-skill-tools.ts +5 -1
  208. package/src/daemon/conversation-slash.ts +2 -2
  209. package/src/daemon/conversation-store.ts +8 -1
  210. package/src/daemon/conversation-tool-setup.ts +37 -0
  211. package/src/daemon/conversation.ts +0 -4
  212. package/src/daemon/handlers/channel-config-result.ts +30 -0
  213. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  214. package/src/daemon/handlers/config-ingress.ts +9 -0
  215. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  216. package/src/daemon/handlers/config-telegram.ts +2 -5
  217. package/src/daemon/handlers/conversations.ts +0 -2
  218. package/src/daemon/handlers/skills.ts +25 -7
  219. package/src/daemon/lifecycle.ts +3 -1
  220. package/src/daemon/message-types/messages.ts +0 -2
  221. package/src/daemon/tool-setup-types.ts +5 -3
  222. package/src/daemon/trust-context-types.test.ts +33 -0
  223. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  224. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  225. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  226. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  227. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  228. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
  229. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  230. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  231. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  232. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  233. package/src/live-voice/live-voice-session-manager.ts +39 -2
  234. package/src/live-voice/live-voice-session.ts +224 -9
  235. package/src/live-voice/protocol.ts +187 -1
  236. package/src/messaging/providers/discord/api.ts +2 -1
  237. package/src/messaging/providers/discord/withdraw.ts +62 -0
  238. package/src/messaging/read-provider-metadata.ts +32 -0
  239. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  240. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  241. package/src/monitoring/db-integrity-sample.ts +1 -0
  242. package/src/monitoring/file-descriptors.ts +39 -6
  243. package/src/monitoring/process-memory.ts +37 -5
  244. package/src/monitoring/resource-sampler.ts +25 -8
  245. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  246. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  247. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  248. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  249. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  250. package/src/notifications/activity-failed-dedupe.ts +95 -0
  251. package/src/notifications/adapters/discord.ts +132 -0
  252. package/src/notifications/adapters/shared.ts +16 -0
  253. package/src/notifications/adapters/slack.ts +6 -1
  254. package/src/notifications/adapters/telegram.ts +5 -8
  255. package/src/notifications/approval-card-data.ts +94 -1
  256. package/src/notifications/background-failure-signal.ts +116 -0
  257. package/src/notifications/broadcaster.ts +3 -9
  258. package/src/notifications/copy-composer.ts +41 -8
  259. package/src/notifications/decision-engine.ts +55 -0
  260. package/src/notifications/destination-resolver.ts +45 -5
  261. package/src/notifications/emit-signal.ts +19 -5
  262. package/src/notifications/guardian-question-mode.ts +57 -0
  263. package/src/notifications/types.ts +2 -8
  264. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  265. package/src/oauth/connect-orchestrator.ts +16 -0
  266. package/src/oauth/seed-providers.ts +262 -0
  267. package/src/permissions/checker.test.ts +21 -0
  268. package/src/permissions/checker.ts +6 -0
  269. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  270. package/src/permissions/gateway-threshold-reader.ts +144 -10
  271. package/src/permissions/prompter.ts +0 -6
  272. package/src/permissions/types.ts +5 -4
  273. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  274. package/src/persistence/bulk-write-gate.ts +9 -10
  275. package/src/persistence/conversation-crud.ts +88 -134
  276. package/src/persistence/conversation-title-service.ts +43 -29
  277. package/src/persistence/embeddings/embedding-local.ts +14 -85
  278. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  279. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  280. package/src/persistence/lifecycle-events-store.ts +75 -5
  281. package/src/platform/platform-patch-queue.test.ts +368 -0
  282. package/src/platform/platform-patch-queue.ts +190 -0
  283. package/src/platform/sync-avatar.test.ts +527 -0
  284. package/src/platform/sync-avatar.ts +209 -0
  285. package/src/platform/sync-identity.test.ts +75 -0
  286. package/src/platform/sync-identity.ts +17 -110
  287. package/src/plugin-api/model-profiles.test.ts +2 -2
  288. package/src/plugin-api/model-profiles.ts +7 -7
  289. package/src/plugin-api/transcription-session.ts +1 -1
  290. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  291. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  292. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  293. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  294. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  295. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  296. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  297. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  298. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  299. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  300. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  301. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  302. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  303. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  304. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  305. package/src/plugins/defaults/memory/worker.ts +7 -0
  306. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  307. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  308. package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
  309. package/src/providers/connection-resolution.ts +2 -17
  310. package/src/providers/fallback-breaker.ts +389 -0
  311. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  312. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  313. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  314. package/src/providers/inference/adapter-factory.ts +246 -3
  315. package/src/providers/inference/auth.ts +15 -14
  316. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  317. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  318. package/src/providers/inference/resolve-auth.ts +1 -1
  319. package/src/providers/media-resolve.ts +2 -3
  320. package/src/providers/model-catalog.ts +100 -0
  321. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  322. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  323. package/src/providers/openai/api-error-normalization.ts +13 -0
  324. package/src/providers/openai/chat-completions-provider.ts +84 -0
  325. package/src/providers/opencode/client.test.ts +150 -0
  326. package/src/providers/opencode/client.ts +61 -0
  327. package/src/providers/platform-proxy/constants.ts +5 -0
  328. package/src/providers/provider-resolvability.ts +20 -0
  329. package/src/providers/provider-secret-catalog.ts +3 -2
  330. package/src/providers/retry.ts +956 -68
  331. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  332. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  333. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  334. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  335. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  336. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  337. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  338. package/src/providers/speech-to-text/resolve.ts +132 -24
  339. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  340. package/src/providers/types.ts +39 -0
  341. package/src/providers/usage-tracking.ts +12 -1
  342. package/src/providers/vellum/client.ts +29 -0
  343. package/src/providers/vellum-model-routing.test.ts +2 -0
  344. package/src/providers/vellum-model-routing.ts +3 -3
  345. package/src/runtime/AGENTS.md +1 -2
  346. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  347. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  348. package/src/runtime/agent-wake.ts +44 -5
  349. package/src/runtime/background-job-runner.ts +68 -50
  350. package/src/runtime/channel-readiness-service.ts +0 -39
  351. package/src/runtime/http-server.ts +12 -4
  352. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  353. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  354. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  355. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  356. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  357. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  358. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  359. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  360. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  361. package/src/runtime/routes/approval-routes.ts +0 -12
  362. package/src/runtime/routes/avatar-routes.ts +17 -30
  363. package/src/runtime/routes/backup-routes.ts +2 -2
  364. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  365. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  366. package/src/runtime/routes/contact-routes.ts +20 -15
  367. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  368. package/src/runtime/routes/conversation-routes.ts +2 -2
  369. package/src/runtime/routes/credential-routes.ts +2 -1
  370. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  371. package/src/runtime/routes/inbound-message-handler.ts +47 -48
  372. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  373. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  374. package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
  375. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  376. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  377. package/src/runtime/routes/index.ts +2 -0
  378. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  379. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  380. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  381. package/src/runtime/routes/inference-send-routes.ts +2 -2
  382. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  383. package/src/runtime/routes/integrations/discord.ts +119 -0
  384. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  385. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  386. package/src/runtime/routes/migration-routes.ts +3 -1
  387. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  388. package/src/runtime/routes/secret-routes.ts +7 -0
  389. package/src/runtime/routes/stt-routes.ts +38 -6
  390. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  391. package/src/runtime/routes/watch-routes.ts +9 -5
  392. package/src/runtime/sync/resource-sync-events.ts +2 -0
  393. package/src/runtime/trust-verdict-consumer.ts +3 -3
  394. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  395. package/src/schedule/retry-policy.ts +8 -8
  396. package/src/schedule/run-script.ts +14 -23
  397. package/src/schedule/schedule-recovery.ts +0 -1
  398. package/src/schedule/scheduler.ts +89 -51
  399. package/src/schedule/worker.ts +57 -0
  400. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  401. package/src/security/ces-rpc-record-backend.ts +123 -0
  402. package/src/security/oauth2.ts +7 -0
  403. package/src/security/openai-device-auth.ts +364 -0
  404. package/src/security/secure-keys.ts +15 -0
  405. package/src/skills/available-skills.ts +10 -1
  406. package/src/skills/catalog-install.ts +19 -1
  407. package/src/skills/inline-command-runner.ts +6 -2
  408. package/src/skills/platform-compatibility.test.ts +49 -0
  409. package/src/skills/platform-compatibility.ts +65 -0
  410. package/src/skills/tool-manifest.ts +20 -0
  411. package/src/stt/__tests__/roles.test.ts +152 -0
  412. package/src/stt/daemon-batch-transcriber.ts +4 -1
  413. package/src/stt/roles.ts +180 -0
  414. package/src/stt/stt-stream-session.ts +3 -2
  415. package/src/stt/types.ts +33 -3
  416. package/src/subagent/manager.ts +12 -0
  417. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  418. package/src/telemetry/live-voice-funnel.ts +12 -2
  419. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  420. package/src/telemetry/tool-audit.ts +83 -30
  421. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  422. package/src/telemetry/turn-outcome.ts +75 -4
  423. package/src/tools/client-os.ts +115 -0
  424. package/src/tools/computer-use/definitions.ts +11 -7
  425. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  426. package/src/tools/credentials/broker-types.ts +0 -71
  427. package/src/tools/credentials/broker.ts +6 -201
  428. package/src/tools/credentials/metadata-store.ts +65 -16
  429. package/src/tools/credentials/store.ts +2 -0
  430. package/src/tools/executor.ts +2 -8
  431. package/src/tools/host-shell.test.ts +75 -0
  432. package/src/tools/host-shell.ts +24 -0
  433. package/src/tools/host-terminal/host-shell.ts +15 -44
  434. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  435. package/src/tools/permission-checker.ts +73 -21
  436. package/src/tools/policy-context.ts +1 -0
  437. package/src/tools/shared/filesystem/image-read.ts +1 -1
  438. package/src/tools/skills/find-similar-skills.ts +10 -2
  439. package/src/tools/skills/load.ts +41 -6
  440. package/src/tools/skills/sandbox-runner.ts +8 -16
  441. package/src/tools/skills/skill-tool-factory.ts +1 -0
  442. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  443. package/src/tools/terminal/safe-env.ts +39 -9
  444. package/src/tools/terminal/shell.ts +12 -31
  445. package/src/tools/tool-approval-handler.ts +64 -10
  446. package/src/tools/tool-types.ts +10 -0
  447. package/src/tools/types.ts +19 -3
  448. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  449. package/src/tools/workflows/manage-workflows.ts +2 -2
  450. package/src/tools/workflows/run-workflow.test.ts +2 -1
  451. package/src/util/__tests__/file-use.test.ts +61 -0
  452. package/src/util/__tests__/process-table.test.ts +66 -0
  453. package/src/util/__tests__/process-tree.test.ts +46 -0
  454. package/src/util/errors.ts +1 -0
  455. package/src/util/file-use.ts +91 -0
  456. package/src/util/host-process.test.ts +17 -0
  457. package/src/util/host-process.ts +75 -0
  458. package/src/util/image-conversion.ts +39 -7
  459. package/src/util/platform.ts +32 -13
  460. package/src/util/process-table.ts +249 -0
  461. package/src/util/process-tree.ts +85 -129
  462. package/src/util/provider-error-patterns.ts +20 -0
  463. package/src/util/spawn.ts +9 -2
  464. package/src/watch/__tests__/watch-retro.test.ts +48 -1
  465. package/src/watch/watch-retro.ts +46 -4
  466. package/src/workspace/git-service.ts +4 -14
  467. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  468. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  469. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  470. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  471. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  472. package/src/workspace/migrations/registry.ts +8 -0
  473. package/src/__tests__/permission-types.test.ts +0 -17
  474. package/src/channels/gateway-discord-admission.ts +0 -32
  475. package/src/persistence/fork-message-copy.ts +0 -239
  476. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  477. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  478. package/src/security/oauth2-device-code.ts +0 -312
@@ -103,13 +103,16 @@ const auditCalls = {
103
103
  denied: [] as any[],
104
104
  error: [] as any[],
105
105
  executed: [] as any[],
106
- prompted: [] as string[],
106
+ prompted: [] as any[],
107
+ decided: [] as any[],
107
108
  };
108
109
  mock.module("../telemetry/tool-audit.js", () => ({
109
110
  recordToolDenied: (e: any) => auditCalls.denied.push(e),
110
111
  recordToolError: (e: any) => auditCalls.error.push(e),
111
112
  recordToolExecuted: (e: any) => auditCalls.executed.push(e),
112
- recordToolPermissionPrompted: (n: string) => auditCalls.prompted.push(n),
113
+ recordToolPermissionPrompted: (e: any) => auditCalls.prompted.push(e),
114
+ recordToolPermissionDecided: (e: any, outcome: string) =>
115
+ auditCalls.decided.push({ entry: e, outcome }),
113
116
  }));
114
117
 
115
118
  function resetAuditCalls(): void {
@@ -117,6 +120,7 @@ function resetAuditCalls(): void {
117
120
  auditCalls.error.length = 0;
118
121
  auditCalls.executed.length = 0;
119
122
  auditCalls.prompted.length = 0;
123
+ auditCalls.decided.length = 0;
120
124
  }
121
125
 
122
126
  import { mkdtempSync, rmSync } from "node:fs";
@@ -946,6 +950,8 @@ describe("ToolApprovalHandler / approval cell lifts the sensitive-tool floor", (
946
950
  }
947
951
 
948
952
  // Running code in the workspace is how you write everything else in it.
953
+ // A room cell cannot lift bash. A contact-level ceiling can; that path
954
+ // is covered in the contact-ceiling describe below.
949
955
  test("sandbox bash", async () => {
950
956
  await expectFloored("bash", { command: "ls" });
951
957
  });
@@ -1024,6 +1030,144 @@ describe("ToolApprovalHandler / approval cell lifts the sensitive-tool floor", (
1024
1030
  });
1025
1031
  });
1026
1032
 
1033
+ describe("contact ceiling lifts sandbox bash", () => {
1034
+ beforeEach(() => {
1035
+ // A Full-access cell still cannot lift bash. These cases pass a
1036
+ // contact ceiling so the lift comes from the person, not the room.
1037
+ thresholdReaderMock.cell = cell("high");
1038
+ });
1039
+
1040
+ test.each(["low", "medium", "high"] as const)(
1041
+ "a %s contact ceiling lifts sandbox bash — no scoped grant required",
1042
+ async (threshold) => {
1043
+ thresholdReaderMock.contactThreshold = threshold;
1044
+ const result = await handler.checkPreExecutionGates(
1045
+ "bash",
1046
+ { command: "ls" },
1047
+ channelContext({ requesterContactId: "contact-1" }),
1048
+ "low",
1049
+ Date.now(),
1050
+ );
1051
+
1052
+ expect(result.allowed).toBe(true);
1053
+ if (!result.allowed) {
1054
+ return;
1055
+ }
1056
+ expect(result.grantConsumed).toBeFalsy();
1057
+ expect(auditCalls.denied.length).toBe(0);
1058
+ // Contact ceiling is already on the turn; the cell is not consulted.
1059
+ expect(thresholdReaderMock.cellLookups).toBe(0);
1060
+ },
1061
+ );
1062
+
1063
+ test("a none contact ceiling leaves sandbox bash on the floor", async () => {
1064
+ thresholdReaderMock.contactThreshold = "none";
1065
+ const result = await handler.checkPreExecutionGates(
1066
+ "bash",
1067
+ { command: "ls" },
1068
+ channelContext({ requesterContactId: "contact-1" }),
1069
+ "low",
1070
+ Date.now(),
1071
+ );
1072
+
1073
+ expect(result.allowed).toBe(false);
1074
+ if (result.allowed) {
1075
+ return;
1076
+ }
1077
+ expect(result.result.content).toContain("requires guardian approval");
1078
+ expect(thresholdReaderMock.cellLookups).toBe(0);
1079
+ });
1080
+
1081
+ test("an unset contact ceiling leaves sandbox bash on the floor", async () => {
1082
+ const result = await handler.checkPreExecutionGates(
1083
+ "bash",
1084
+ { command: "ls" },
1085
+ channelContext(),
1086
+ "low",
1087
+ Date.now(),
1088
+ );
1089
+
1090
+ expect(result.allowed).toBe(false);
1091
+ expect(thresholdReaderMock.cellLookups).toBe(0);
1092
+ });
1093
+
1094
+ test("a high contact ceiling does not lift host_bash", async () => {
1095
+ thresholdReaderMock.contactThreshold = "high";
1096
+ const result = await handler.checkPreExecutionGates(
1097
+ "host_bash",
1098
+ { command: "ls" },
1099
+ channelContext({ requesterContactId: "contact-1" }),
1100
+ "low",
1101
+ Date.now(),
1102
+ );
1103
+
1104
+ expect(result.allowed).toBe(false);
1105
+ if (result.allowed) {
1106
+ return;
1107
+ }
1108
+ expect(result.result.content).toContain("requires guardian approval");
1109
+ expect(thresholdReaderMock.cellLookups).toBe(0);
1110
+ });
1111
+
1112
+ test("a high contact ceiling does not lift a control-plane write", async () => {
1113
+ thresholdReaderMock.contactThreshold = "high";
1114
+ const result = await handler.checkPreExecutionGates(
1115
+ "file_write",
1116
+ { path: "SOUL.md", content: "x" },
1117
+ channelContext({ requesterContactId: "contact-1" }),
1118
+ "low",
1119
+ Date.now(),
1120
+ );
1121
+
1122
+ expect(result.allowed).toBe(false);
1123
+ if (result.allowed) {
1124
+ return;
1125
+ }
1126
+ expect(result.result.content).toContain("requires guardian approval");
1127
+ expect(thresholdReaderMock.cellLookups).toBe(0);
1128
+ });
1129
+
1130
+ test("a high contact ceiling lifts an ordinary workspace write even when the cell is Strict", async () => {
1131
+ thresholdReaderMock.cell = cell("none");
1132
+ thresholdReaderMock.contactThreshold = "high";
1133
+
1134
+ const result = await handler.checkPreExecutionGates(
1135
+ "file_write",
1136
+ { path: "notes/todo.md", content: "x" },
1137
+ channelContext({ requesterContactId: "contact-1" }),
1138
+ "low",
1139
+ Date.now(),
1140
+ );
1141
+
1142
+ expect(result.allowed).toBe(true);
1143
+ if (!result.allowed) {
1144
+ return;
1145
+ }
1146
+ expect(result.grantConsumed).toBeFalsy();
1147
+ expect(thresholdReaderMock.cellLookups).toBe(0);
1148
+ });
1149
+
1150
+ test("a none contact ceiling leaves an ordinary workspace write on the floor even when the cell is Full access", async () => {
1151
+ thresholdReaderMock.cell = cell("high");
1152
+ thresholdReaderMock.contactThreshold = "none";
1153
+
1154
+ const result = await handler.checkPreExecutionGates(
1155
+ "file_write",
1156
+ { path: "notes/todo.md", content: "x" },
1157
+ channelContext({ requesterContactId: "contact-1" }),
1158
+ "low",
1159
+ Date.now(),
1160
+ );
1161
+
1162
+ expect(result.allowed).toBe(false);
1163
+ if (result.allowed) {
1164
+ return;
1165
+ }
1166
+ expect(result.result.content).toContain("requires guardian approval");
1167
+ expect(thresholdReaderMock.cellLookups).toBe(0);
1168
+ });
1169
+ });
1170
+
1027
1171
  test("a public web_fetch is still lifted", async () => {
1028
1172
  thresholdReaderMock.cell = cell("low");
1029
1173
 
@@ -1,5 +1,6 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
+ import { buildApprovalNotificationBlocks } from "../notifications/adapters/slack.js";
3
4
  import type {
4
5
  ApprovalCardBlock,
5
6
  ApprovalCardFallbackBlock,
@@ -90,9 +91,11 @@ describe("buildToolApprovalSeedContentBlocks", () => {
90
91
  });
91
92
  });
92
93
 
93
- test("returns null for pending_question without toolName", () => {
94
+ test("pending_question without toolName renders as a question, not an approval", () => {
94
95
  const payload = { ...voiceToolApprovalPayload, toolName: undefined };
95
- expect(buildToolApprovalSeedContentBlocks(payload)).toBeNull();
96
+ const surface = surfaceBlock(buildToolApprovalSeedContentBlocks(payload)!);
97
+ expect(surface.title).toBe("Question");
98
+ expect(surface.data.subtitle).toBe("Waiting on your answer");
96
99
  });
97
100
 
98
101
  test("produces a ui_surface block and a text fallback block for tool_approval", () => {
@@ -317,3 +320,106 @@ describe("buildToolApprovalSeedContentBlocks", () => {
317
320
  expect(fallback.text).toContain("Approve tool: bash (requested by Bob)");
318
321
  });
319
322
  });
323
+
324
+ /**
325
+ * An `ask_question` prompt promoted to a guardian request. It renders as a
326
+ * question rather than an approval, and carries no buttons: the in-app surface
327
+ * route resolves approval actions only, so answering happens on the channel or
328
+ * by reply.
329
+ */
330
+ describe("ask_question card", () => {
331
+ const questionPayload: Record<string, unknown> = {
332
+ requestId: "req-ask-321",
333
+ requestCode: "08B619",
334
+ requestKind: "pending_question",
335
+ questionText: "What should I dig into?",
336
+ sourceChannel: "slack",
337
+ options: [
338
+ { id: "opt-thread", label: "This Slack thread" },
339
+ { id: "opt-pr", label: "The pull request" },
340
+ { id: "opt-ticket", label: "The Linear ticket" },
341
+ ],
342
+ };
343
+
344
+ test("the question leads the card rather than generic guardian copy", () => {
345
+ const surface = surfaceBlock(
346
+ buildToolApprovalSeedContentBlocks(questionPayload)!,
347
+ );
348
+ expect(surface.title).toBe("Question");
349
+ expect(surface.data.title).toBe("What should I dig into?");
350
+ });
351
+
352
+ test("a question card offers no buttons, not even Approve and Reject", () => {
353
+ const surface = surfaceBlock(
354
+ buildToolApprovalSeedContentBlocks(questionPayload)!,
355
+ );
356
+ // Empty rather than absent: the builder reads an absent action set as a
357
+ // request for its generic Approve/Reject pair, which is the wrong verb for
358
+ // a question and would resolve as "Yes" or a skip if tapped. The answer
359
+ // options are not offered here either, because the in-app surface route
360
+ // resolves only approval actions, so they would be inert.
361
+ expect(surface.actions).toEqual([]);
362
+ });
363
+
364
+ test("a channel without buttons still gets the question and its options", () => {
365
+ const fallback = fallbackBlock(
366
+ buildToolApprovalSeedContentBlocks(questionPayload)!,
367
+ );
368
+ expect(fallback.text).toContain("What should I dig into?");
369
+ expect(fallback.text).toContain("1. This Slack thread");
370
+ expect(fallback.text).toContain("3. The Linear ticket");
371
+ expect(fallback.text).toContain("08B619");
372
+ });
373
+
374
+ test("a question with no options still offers no buttons", () => {
375
+ const payload = { ...questionPayload, options: undefined };
376
+ const surface = surfaceBlock(buildToolApprovalSeedContentBlocks(payload)!);
377
+ expect(surface.data.title).toBe("What should I dig into?");
378
+ expect(surface.actions).toEqual([]);
379
+ });
380
+ });
381
+
382
+ /**
383
+ * The Slack card heads itself from the approval's stated intent, so a question
384
+ * is not titled as an approval. Producers resolve the intent; the adapter only
385
+ * draws it.
386
+ */
387
+ describe("Slack card intent", () => {
388
+ const base = {
389
+ sourceEventName: "guardian.question",
390
+ copy: { deliveryText: "What should I dig into?" },
391
+ };
392
+
393
+ test("a question card is headed Question", () => {
394
+ const blocks = buildApprovalNotificationBlocks(
395
+ {
396
+ ...base,
397
+ approvalContext: {
398
+ requestId: "req-ask-321",
399
+ actions: [{ id: "answer_0", label: "This Slack thread" }],
400
+ plainTextFallback: 'Reply "08B619 <your answer>".',
401
+ intent: "question",
402
+ },
403
+ } as never,
404
+ "What should I dig into?",
405
+ );
406
+ const card = blocks[0] as { title?: { text?: string } };
407
+ expect(card.title?.text).toBe("Question");
408
+ });
409
+
410
+ test("an approval with no tool details keeps its own heading", () => {
411
+ const blocks = buildApprovalNotificationBlocks(
412
+ {
413
+ ...base,
414
+ approvalContext: {
415
+ requestId: "req-appr-1",
416
+ actions: [{ id: "approve_once", label: "Approve" }],
417
+ plainTextFallback: 'Reply "ABC123 approve".',
418
+ },
419
+ } as never,
420
+ "Approve this",
421
+ );
422
+ const card = blocks[0] as { title?: { text?: string } };
423
+ expect(card.title?.text).toBe("Approval Request");
424
+ });
425
+ });
@@ -8,10 +8,18 @@ mock.module("../platform/consent-cache.js", () => ({
8
8
  getRawShareAnalytics: () => shareAnalytics,
9
9
  }));
10
10
 
11
- // Capture the audit rows the terminals would insert, and the lifecycle-event
12
- // telemetry strings they would record, without touching the database.
11
+ // Capture the audit rows the terminals would insert, and the lifecycle events
12
+ // they would record, without touching the database.
13
13
  const records: Array<Record<string, unknown>> = [];
14
- const lifecycleEvents: string[] = [];
14
+ const lifecycleEvents: Array<{
15
+ eventName: string;
16
+ attributes: Record<string, unknown>;
17
+ }> = [];
18
+ const lifecycleEventNames = (): string[] =>
19
+ lifecycleEvents.map((event) => event.eventName);
20
+ // Simulates the telemetry store failing (degraded DB) for the one test that
21
+ // asserts the terminals swallow it.
22
+ let lifecycleRecorderThrows = false;
15
23
 
16
24
  mock.module("../telemetry/tool-usage-store.js", () => ({
17
25
  recordToolInvocation: (record: Record<string, unknown>) =>
@@ -19,13 +27,22 @@ mock.module("../telemetry/tool-usage-store.js", () => ({
19
27
  }));
20
28
 
21
29
  mock.module("../persistence/lifecycle-events-store.js", () => ({
22
- recordLifecycleEvent: (eventName: string) => lifecycleEvents.push(eventName),
30
+ recordLifecycleEvent: (
31
+ eventName: string,
32
+ attributes: Record<string, unknown> = {},
33
+ ) => {
34
+ if (lifecycleRecorderThrows) {
35
+ throw new Error("telemetry db down");
36
+ }
37
+ lifecycleEvents.push({ eventName, attributes });
38
+ },
23
39
  }));
24
40
 
25
41
  import {
26
42
  recordToolDenied,
27
43
  recordToolError,
28
44
  recordToolExecuted,
45
+ recordToolPermissionDecided,
29
46
  recordToolPermissionPrompted,
30
47
  } from "../telemetry/tool-audit.js";
31
48
  import {
@@ -48,6 +65,7 @@ const ATTRIBUTION = {
48
65
  describe("tool audit terminals", () => {
49
66
  beforeEach(() => {
50
67
  shareAnalytics = true;
68
+ lifecycleRecorderThrows = false;
51
69
  records.length = 0;
52
70
  lifecycleEvents.length = 0;
53
71
  });
@@ -63,7 +81,6 @@ describe("tool audit terminals", () => {
63
81
  riskLevel: "low",
64
82
  durationMs: 12,
65
83
  attribution: null,
66
- wasPrompted: false,
67
84
  });
68
85
 
69
86
  expect(records).toHaveLength(1);
@@ -84,7 +101,6 @@ describe("tool audit terminals", () => {
84
101
  reason: "Blocked by deny rule: rm *",
85
102
  riskLevel: "high",
86
103
  durationMs: 20,
87
- wasPrompted: false,
88
104
  });
89
105
  recordToolDenied({
90
106
  conversationId: "conv-2",
@@ -93,7 +109,6 @@ describe("tool audit terminals", () => {
93
109
  reason: "Permission denied by user",
94
110
  riskLevel: "high",
95
111
  durationMs: 22,
96
- wasPrompted: false,
97
112
  });
98
113
 
99
114
  expect(records).toHaveLength(2);
@@ -118,7 +133,6 @@ describe("tool audit terminals", () => {
118
133
  riskLevel: "low",
119
134
  durationMs: 5,
120
135
  attribution: null,
121
- wasPrompted: false,
122
136
  });
123
137
 
124
138
  expect(records).toHaveLength(1);
@@ -141,7 +155,6 @@ describe("tool audit terminals", () => {
141
155
  riskLevel: "low",
142
156
  durationMs: 5,
143
157
  attribution: null,
144
- wasPrompted: false,
145
158
  });
146
159
  recordToolError({
147
160
  conversationId: "conv-input-redact",
@@ -160,7 +173,6 @@ describe("tool audit terminals", () => {
160
173
  reason: "Permission denied by user",
161
174
  riskLevel: "high",
162
175
  durationMs: 5,
163
- wasPrompted: false,
164
176
  });
165
177
 
166
178
  expect(records).toHaveLength(3);
@@ -192,7 +204,6 @@ describe("tool audit terminals", () => {
192
204
  riskLevel: "low",
193
205
  durationMs: 2,
194
206
  attribution: null,
195
- wasPrompted: false,
196
207
  });
197
208
 
198
209
  expect(records).toHaveLength(1);
@@ -215,7 +226,6 @@ describe("tool audit terminals", () => {
215
226
  riskLevel: "low",
216
227
  durationMs: 2,
217
228
  attribution: null,
218
- wasPrompted: false,
219
229
  });
220
230
 
221
231
  expect(records).toHaveLength(1);
@@ -246,7 +256,6 @@ describe("tool audit terminals", () => {
246
256
  riskLevel: "low",
247
257
  durationMs: 3,
248
258
  attribution: null,
249
- wasPrompted: false,
250
259
  });
251
260
 
252
261
  expect(records).toHaveLength(1);
@@ -289,7 +298,6 @@ describe("tool audit terminals", () => {
289
298
  riskLevel: "low",
290
299
  durationMs: 12,
291
300
  attribution: null,
292
- wasPrompted: false,
293
301
  });
294
302
 
295
303
  expect(records).toHaveLength(1);
@@ -316,7 +324,6 @@ describe("tool audit terminals", () => {
316
324
  riskLevel: "low",
317
325
  durationMs: 4,
318
326
  attribution: null,
319
- wasPrompted: false,
320
327
  });
321
328
 
322
329
  expect(records).toHaveLength(1);
@@ -339,7 +346,6 @@ describe("tool audit terminals", () => {
339
346
  riskLevel: "low",
340
347
  durationMs: 12,
341
348
  attribution: ATTRIBUTION,
342
- wasPrompted: false,
343
349
  });
344
350
  recordToolError({
345
351
  conversationId: "conv-opt-out",
@@ -395,7 +401,6 @@ describe("tool audit terminals", () => {
395
401
  riskLevel: "low",
396
402
  durationMs: 12,
397
403
  attribution: ATTRIBUTION,
398
- wasPrompted: false,
399
404
  });
400
405
 
401
406
  expect(records).toHaveLength(1);
@@ -420,7 +425,6 @@ describe("tool audit terminals", () => {
420
425
  riskLevel: "low",
421
426
  durationMs: 12,
422
427
  attribution: ATTRIBUTION,
423
- wasPrompted: false,
424
428
  });
425
429
  recordToolExecuted({
426
430
  conversationId: "conv-attr",
@@ -432,7 +436,6 @@ describe("tool audit terminals", () => {
432
436
  riskLevel: "low",
433
437
  durationMs: 3,
434
438
  attribution: null,
435
- wasPrompted: false,
436
439
  });
437
440
  recordToolDenied({
438
441
  conversationId: "conv-attr",
@@ -441,7 +444,6 @@ describe("tool audit terminals", () => {
441
444
  reason: "Permission denied by user",
442
445
  riskLevel: "high",
443
446
  durationMs: 1,
444
- wasPrompted: false,
445
447
  });
446
448
 
447
449
  expect(records).toHaveLength(3);
@@ -462,9 +464,7 @@ describe("tool audit terminals", () => {
462
464
  expect(records[2].resultBytes).toBeUndefined();
463
465
  });
464
466
 
465
- test("prompted decisions record permission lifecycle telemetry; unprompted do not", () => {
466
- recordToolPermissionPrompted("bash");
467
- // Prompted allow → executed records a decided event.
467
+ test("audit terminals record no permission lifecycle telemetry", () => {
468
468
  recordToolExecuted({
469
469
  conversationId: "conv-prompt",
470
470
  toolName: "bash",
@@ -475,9 +475,7 @@ describe("tool audit terminals", () => {
475
475
  riskLevel: "high",
476
476
  durationMs: 3,
477
477
  attribution: null,
478
- wasPrompted: true,
479
478
  });
480
- // Prompted denial → denied records a decided event.
481
479
  recordToolDenied({
482
480
  conversationId: "conv-prompt",
483
481
  toolName: "bash",
@@ -485,26 +483,99 @@ describe("tool audit terminals", () => {
485
483
  reason: "Permission denied by user",
486
484
  riskLevel: "high",
487
485
  durationMs: 1,
488
- wasPrompted: true,
489
486
  });
490
- // Unprompted (auto-approved) execution records nothing.
491
- recordToolExecuted({
487
+
488
+ expect(lifecycleEvents).toEqual([]);
489
+ });
490
+
491
+ test("prompt and decision carry the same grouping dimensions", () => {
492
+ const prompt = {
493
+ toolName: "bash",
494
+ riskLevel: "high",
495
+ riskThreshold: "low",
496
+ surface: "slack",
492
497
  conversationId: "conv-prompt",
493
- toolName: "file_read",
494
- input: { path: "/tmp/a" },
495
- resultContent: "ok",
496
- resultBytes: 2,
497
- decision: "allow",
498
- riskLevel: "low",
499
- durationMs: 1,
500
- attribution: null,
501
- wasPrompted: false,
502
- });
498
+ };
499
+
500
+ recordToolPermissionPrompted(prompt);
501
+ recordToolPermissionDecided(prompt, "deny");
503
502
 
504
- expect(lifecycleEvents).toEqual([
503
+ expect(lifecycleEventNames()).toEqual([
505
504
  "permission_prompt:bash",
506
- "permission_decided:bash:allow",
507
505
  "permission_decided:bash:deny",
508
506
  ]);
507
+ for (const event of lifecycleEvents) {
508
+ expect(event.attributes).toEqual({
509
+ toolName: "bash",
510
+ riskLevel: "high",
511
+ riskThreshold: "low",
512
+ surface: "slack",
513
+ conversationId: "conv-prompt",
514
+ });
515
+ }
516
+ });
517
+
518
+ test("optional grouping dimensions are left unset when unknown", () => {
519
+ recordToolPermissionPrompted({ toolName: "file_read", riskLevel: "low" });
520
+
521
+ const attributes = lifecycleEvents[0]!.attributes;
522
+ expect(attributes.toolName).toBe("file_read");
523
+ expect(attributes.riskLevel).toBe("low");
524
+ expect(attributes.riskThreshold).toBeUndefined();
525
+ expect(attributes.surface).toBeUndefined();
526
+ expect(attributes.conversationId).toBeUndefined();
527
+ });
528
+
529
+ test("records an abandoned decision distinctly from a user denial", () => {
530
+ const prompt = { toolName: "bash", riskLevel: "medium" };
531
+
532
+ recordToolPermissionPrompted(prompt);
533
+ recordToolPermissionDecided(prompt, "abandoned");
534
+
535
+ expect(lifecycleEventNames()).toEqual([
536
+ "permission_prompt:bash",
537
+ "permission_decided:bash:abandoned",
538
+ ]);
539
+ });
540
+
541
+ test("truncates a long tool name to the wire event-name bound", () => {
542
+ const toolName = `mcp__${"a".repeat(80)}__run`;
543
+ const prompt = { toolName, riskLevel: "high" };
544
+
545
+ recordToolPermissionPrompted(prompt);
546
+ recordToolPermissionDecided(prompt, "abandoned");
547
+
548
+ for (const event of lifecycleEvents) {
549
+ expect(event.eventName.length).toBeLessThanOrEqual(64);
550
+ // The full name still joins the pair.
551
+ expect(event.attributes.toolName).toBe(toolName);
552
+ }
553
+ expect(lifecycleEvents[1]!.eventName.endsWith(":abandoned")).toBe(true);
554
+ });
555
+
556
+ test("clamps a tool name past the wire tool_name bound", () => {
557
+ // A field over its bound fails validation, and a failed event is acked and
558
+ // discarded rather than retried.
559
+ const toolName = `mcp__${"a".repeat(300)}__run`;
560
+
561
+ recordToolPermissionPrompted({ toolName, riskLevel: "high" });
562
+
563
+ expect(lifecycleEvents[0]!.attributes.toolName).toBe(
564
+ toolName.slice(0, 255),
565
+ );
566
+ });
567
+
568
+ test("a failing lifecycle recorder never propagates out of the terminals", () => {
569
+ lifecycleRecorderThrows = true;
570
+
571
+ expect(() =>
572
+ recordToolPermissionPrompted({ toolName: "bash", riskLevel: "high" }),
573
+ ).not.toThrow();
574
+ expect(() =>
575
+ recordToolPermissionDecided(
576
+ { toolName: "bash", riskLevel: "high" },
577
+ "abandoned",
578
+ ),
579
+ ).not.toThrow();
509
580
  });
510
581
  });