@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
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Telephony synthesis-language resolution.
3
+ *
4
+ * The pin half of this resolver is gated on whether the provider transcribing
5
+ * the call honors manual language selection, so these cover which provider
6
+ * that gate reads: the telephony role's, which is what the call dials.
7
+ */
8
+ import { afterEach, describe, expect, test } from "bun:test";
9
+
10
+ import { setConfig } from "../../__tests__/helpers/set-config.js";
11
+ import { resolveTelephonySynthesisLanguage } from "../telephony-synthesis-language.js";
12
+
13
+ function seedStt(stt: Record<string, unknown>): void {
14
+ setConfig("services", { stt });
15
+ }
16
+
17
+ afterEach(() => {
18
+ setConfig("services", {});
19
+ });
20
+
21
+ describe("resolveTelephonySynthesisLanguage", () => {
22
+ test("a detected caller language outranks the configured pin", () => {
23
+ seedStt({ provider: "deepgram", language: "es" });
24
+
25
+ expect(resolveTelephonySynthesisLanguage("hi-IN")).toBe("hi");
26
+ });
27
+
28
+ test("reads the pin gate from the telephony role, not the global provider", () => {
29
+ // Whisper auto-detects and ignores services.stt.language, so reading the
30
+ // global provider drops the pin. The call is transcribed by the telephony
31
+ // role's deepgram, which is pinned to Hindi and honors it.
32
+ seedStt({
33
+ provider: "openai-whisper",
34
+ language: "hi",
35
+ roles: { telephony: { provider: "deepgram" } },
36
+ });
37
+
38
+ expect(resolveTelephonySynthesisLanguage()).toBe("hi");
39
+ });
40
+
41
+ test("a telephony role that auto-detects drops a pin the global would honor", () => {
42
+ // The inverse: the global honors the pin but no call reaches it, so
43
+ // hinting Hindi would contradict what the caller is actually heard by.
44
+ seedStt({
45
+ provider: "deepgram",
46
+ language: "hi",
47
+ roles: { telephony: { provider: "openai-whisper" } },
48
+ });
49
+
50
+ expect(resolveTelephonySynthesisLanguage()).toBeUndefined();
51
+ });
52
+
53
+ test("an unset telephony role reads the global provider", () => {
54
+ seedStt({
55
+ provider: "deepgram",
56
+ language: "hi",
57
+ roles: { liveVoice: { provider: "openai-whisper" } },
58
+ });
59
+
60
+ expect(resolveTelephonySynthesisLanguage()).toBe("hi");
61
+ });
62
+
63
+ test('"multi" is auto-detection, not a pin', () => {
64
+ seedStt({
65
+ provider: "openai-whisper",
66
+ language: "multi",
67
+ roles: { telephony: { provider: "deepgram" } },
68
+ });
69
+
70
+ expect(resolveTelephonySynthesisLanguage()).toBeUndefined();
71
+ });
72
+
73
+ test("no configured language yields no hint", () => {
74
+ seedStt({
75
+ provider: "openai-whisper",
76
+ roles: { telephony: { provider: "deepgram" } },
77
+ });
78
+
79
+ expect(resolveTelephonySynthesisLanguage()).toBeUndefined();
80
+ });
81
+ });
@@ -390,6 +390,7 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
390
390
  );
391
391
  expect(fake.lastPersistOpts()?.metadata).toEqual({
392
392
  voiceSessionTurn: true,
393
+ scripted: true,
393
394
  hidden: true,
394
395
  messageKind: VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND,
395
396
  });
@@ -404,8 +405,11 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
404
405
 
405
406
  await startVoiceTurn(makeTurnOptions()); // content: CALL_OPENING_MARKER
406
407
 
408
+ // Visible AND scripted: the opener is shown, but the assistant wrote it,
409
+ // so it is not the user taking a turn.
407
410
  expect(fake.lastPersistOpts()?.metadata).toEqual({
408
411
  voiceSessionTurn: true,
412
+ scripted: true,
409
413
  });
410
414
  });
411
415
 
@@ -425,6 +429,7 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
425
429
  // analytics already read.
426
430
  expect(fake.lastPersistOpts()?.metadata).toEqual({
427
431
  voiceSessionTurn: true,
432
+ scripted: true,
428
433
  client: {
429
434
  voice: true,
430
435
  voice_session_id: "session-123",
@@ -444,10 +449,41 @@ describe("startVoiceTurn escalation-continuation persistence", () => {
444
449
 
445
450
  expect(fake.lastPersistOpts()?.metadata).toEqual({
446
451
  voiceSessionTurn: true,
452
+ scripted: true,
447
453
  client: { voice: true, voice_session_id: "session-123" },
448
454
  });
449
455
  });
450
456
 
457
+ test("a turn the user really spoke is left unmarked, not marked false", async () => {
458
+ // Absent means UNKNOWN and falls through to the legacy classifier, which
459
+ // is the safe answer here. Stamping `false` would assert this turn was
460
+ // typed by the user, and a wrong `false` is trusted downstream.
461
+ const fake = makeFakeConversation({ processing: false });
462
+ fakeConversation = fake.conversation;
463
+
464
+ await startVoiceTurn({
465
+ ...makeTurnOptions(),
466
+ content: "what is on my calendar",
467
+ });
468
+
469
+ expect(fake.lastPersistOpts()?.metadata).not.toHaveProperty("scripted");
470
+ });
471
+
472
+ test("scripted is not derived from hidden", async () => {
473
+ // The two answer different questions, and the opener is the case that
474
+ // separates them: shown to the user, written by the assistant. Deriving
475
+ // one from the other lets every visible-but-scripted turn count as
476
+ // activation, which is the largest single source of funnel inflation.
477
+ const fake = makeFakeConversation({ processing: false });
478
+ fakeConversation = fake.conversation;
479
+
480
+ await startVoiceTurn(makeTurnOptions()); // content: CALL_OPENING_MARKER
481
+
482
+ const metadata = fake.lastPersistOpts()?.metadata;
483
+ expect(metadata?.scripted).toBe(true);
484
+ expect(metadata).not.toHaveProperty("hidden");
485
+ });
486
+
451
487
  test("a phone turn carries no client bag", async () => {
452
488
  const fake = makeFakeConversation({ processing: false });
453
489
  fakeConversation = fake.conversation;
@@ -501,6 +537,7 @@ describe("startVoiceTurn hiddenSyntheticPrompt", () => {
501
537
  expect(fake.lastPersistOpts()?.content).toBe(SYNTHETIC_CONTENT);
502
538
  expect(fake.lastPersistOpts()?.metadata).toEqual({
503
539
  voiceSessionTurn: true,
540
+ scripted: true,
504
541
  hidden: true,
505
542
  });
506
543
  expect(echoes).toHaveLength(0);
@@ -634,6 +671,26 @@ describe("default call protocol numbered rules", () => {
634
671
  }
635
672
  });
636
673
 
674
+ test("the pre-speech pin reads the telephony role, not the global provider", async () => {
675
+ // Whisper ignores services.stt.language, so reading the global provider
676
+ // drops the pin and opens in English. The caller is transcribed by the
677
+ // telephony role's deepgram, which is listening in Spanish.
678
+ setConfig("services", {
679
+ stt: {
680
+ provider: "openai-whisper",
681
+ language: "es",
682
+ roles: { telephony: { provider: "deepgram" } },
683
+ },
684
+ });
685
+ try {
686
+ const installed = captureInstalledPrompt();
687
+ await startVoiceTurn(makeTurnOptions());
688
+ expect(installed()).toContain('configured listening language ("es")');
689
+ } finally {
690
+ setConfig("services", {});
691
+ }
692
+ });
693
+
637
694
  test("a language pin on an auto-detecting provider keeps the English fallback", () => {
638
695
  // google-gemini and openai-whisper ignore services.stt.language entirely
639
696
  // (languageSelection: "auto"), so a persisted "es" pin must not force a
@@ -432,6 +432,7 @@ export class MediaStreamSttSession {
432
432
  let transcriber: StreamingTranscriber | null = null;
433
433
  try {
434
434
  transcriber = await resolveStreamingTranscriber({
435
+ role: "telephony",
435
436
  sampleRate: STREAMING_SAMPLE_RATE,
436
437
  utteranceBoundaryFinals: true,
437
438
  utteranceEndMs: getConfig().calls.voice.utteranceEndMs,
@@ -688,7 +689,7 @@ export class MediaStreamSttSession {
688
689
  // Resolve a batch transcriber for the configured provider.
689
690
  let transcriber;
690
691
  try {
691
- transcriber = await resolveBatchTranscriber();
692
+ transcriber = await resolveBatchTranscriber({ role: "telephony" });
692
693
  } catch (err) {
693
694
  if (this.disposed) {
694
695
  return;
@@ -7,14 +7,15 @@
7
7
  * is a no-op for providers that cannot). Resolution mirrors live voice's
8
8
  * per-utterance turn language (live-voice-session.ts): the caller's
9
9
  * latest STT-detected language when the transcriber tags finals, else a
10
- * monolingual `services.stt.language` pin when the configured provider
11
- * honors manual language selection, else undefined (no hint, provider
12
- * default behavior).
10
+ * monolingual `services.stt.language` pin when the provider serving the
11
+ * telephony role honors manual language selection, else undefined (no hint,
12
+ * provider default behavior).
13
13
  */
14
14
 
15
15
  import { getConfig } from "../config/loader.js";
16
16
  import type { AssistantConfig } from "../config/types.js";
17
17
  import { pinnedListeningLanguage } from "../providers/speech-to-text/provider-catalog.js";
18
+ import { sttCatalogKeyForRole } from "../stt/roles.js";
18
19
  import { resolveLanguageVoiceOverride } from "../tts/language-voices.js";
19
20
  import { resolveTtsConfig } from "../tts/tts-config-resolver.js";
20
21
  import type { TtsProviderId } from "../tts/types.js";
@@ -37,7 +38,7 @@ export function resolveTelephonySynthesisLanguage(
37
38
  return detected;
38
39
  }
39
40
 
40
- let stt: { provider: string; language?: string };
41
+ let stt: AssistantConfig["services"]["stt"];
41
42
  try {
42
43
  stt = getConfig().services.stt;
43
44
  } catch {
@@ -45,7 +46,14 @@ export function resolveTelephonySynthesisLanguage(
45
46
  return undefined;
46
47
  }
47
48
 
48
- return pinnedListeningLanguage(stt.provider, stt.language);
49
+ // The pin only counts when the provider transcribing the call honors
50
+ // manual language selection, so the gate reads the telephony role rather
51
+ // than the global provider: those differ whenever a role override is set,
52
+ // and it is the role's transcriber the caller is actually heard by.
53
+ return pinnedListeningLanguage(
54
+ sttCatalogKeyForRole(stt, "telephony"),
55
+ stt.language,
56
+ );
49
57
  }
50
58
 
51
59
  /**
@@ -43,6 +43,7 @@ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
43
43
  import { getCurrentSeq } from "../runtime/assistant-stream-state.js";
44
44
  import { publishConversationMessagesChanged } from "../runtime/sync/resource-sync-events.js";
45
45
  import { computeToolApprovalDigest } from "../security/tool-approval-digest.js";
46
+ import { sttCatalogKeyForRole } from "../stt/roles.js";
46
47
  import { getAllTools } from "../tools/registry.js";
47
48
  import { sensitiveToolReach } from "../tools/tool-approval-handler.js";
48
49
  import { createAbortReason } from "../util/abort-reasons.js";
@@ -478,12 +479,13 @@ const PHONE_NO_SETUP_FLOWS_RULE =
478
479
  * caller's language (the transcriber is already listening in it, see
479
480
  * media-stream-stt-session.ts and providers/speech-to-text/resolve.ts), so it
480
481
  * outranks the English default; "multi" and unset mean auto-detect, where
481
- * English remains the fallback. The pin only counts when the active provider
482
+ * English remains the fallback. The pin only counts when the provider
482
483
  * honors manual language selection (see pinnedListeningLanguage):
483
484
  * auto-detecting providers (gemini, whisper) ignore a persisted language
484
485
  * entirely, so greeting in it would contradict what the transcriber
485
- * actually hears. Exported for tests: the default test config exercises
486
- * only the auto-detect branch.
486
+ * actually hears. `sttProvider` is the telephony role's catalog key, since
487
+ * that is the transcriber the caller is heard by. Exported for tests: the
488
+ * default test config exercises only the auto-detect branch.
487
489
  */
488
490
  export function preSpeechLanguageRuleFragment(
489
491
  sttLanguage: string | undefined,
@@ -590,7 +592,7 @@ function buildVoiceCallControlPrompt(opts: {
590
592
  "9. After the opening greeting turn, treat the Task field as background context only — do not re-execute its instructions on subsequent turns.",
591
593
  '10. Do not make up information. If you are unsure, use [ASK_GUARDIAN: your question] to consult your guardian. For tool permission requests, use [ASK_GUARDIAN_APPROVAL: {"question":"...","toolName":"...","input":{...}}].',
592
594
  `11. Your text is sent directly to a text-to-speech engine. Never use markdown formatting (asterisks, headers, backticks, links) or emojis in your spoken responses. Write plain conversational text only. Protocol markers like ${opts.isCallerGuardian ? "[END_CALL]" : "[ASK_GUARDIAN: ...] and [END_CALL]"} are not spoken text and should still be used normally.`,
593
- `12. Speak the caller's language: reply in the language of the caller's most recent actual speech, and follow them if they switch languages mid-call. Synthetic user turns (parenthetical markers like the call-connected and verification-completed notices) are not caller speech and never set the language. Before the caller has spoken, such as on the opening greeting turn, ${preSpeechLanguageRuleFragment(config.services.stt.language, config.services.stt.provider)}.`,
595
+ `12. Speak the caller's language: reply in the language of the caller's most recent actual speech, and follow them if they switch languages mid-call. Synthetic user turns (parenthetical markers like the call-connected and verification-completed notices) are not caller speech and never set the language. Before the caller has spoken, such as on the opening greeting turn, ${preSpeechLanguageRuleFragment(config.services.stt.language, sttCatalogKeyForRole(config.services.stt, "telephony"))}.`,
594
596
  `13. ${PHONE_NO_SETUP_FLOWS_RULE}`,
595
597
  );
596
598
 
@@ -1029,6 +1031,19 @@ export async function startVoiceTurn(
1029
1031
  // Durable "this turn came from an open voice session" marker; see
1030
1032
  // `isVoiceSessionUserMessage` for why the channel fields cannot carry it.
1031
1033
  voiceSessionTurn: true,
1034
+ // Auto-sent on the user's behalf rather than spoken or typed by them,
1035
+ // so activation metrics exclude it (see the `scripted` contract on the
1036
+ // send route). Keyed off the synthetic set, NOT off
1037
+ // `isHiddenSyntheticPrompt`: the two answer different questions, and
1038
+ // deriving one from the other is what lets a visible-but-scripted turn
1039
+ // through. `hidden` decides whether a human sees the row; `scripted`
1040
+ // decides whether it counts as the user taking a turn.
1041
+ //
1042
+ // Only ever `true` here. A genuine voice turn is left absent rather
1043
+ // than stamped `false`, because absent means UNKNOWN and falls through
1044
+ // to the legacy classifier, while a wrong `false` is trusted
1045
+ // downstream.
1046
+ ...(isSyntheticVoicePrompt ? { scripted: true } : {}),
1032
1047
  ...(isHiddenSyntheticPrompt ? { hidden: true } : {}),
1033
1048
  ...(isEscalationContinuation
1034
1049
  ? { messageKind: VOICE_ESCALATION_CONTINUATION_MESSAGE_KIND }
@@ -6,7 +6,7 @@
6
6
  * to compile until a policy is added below.
7
7
  */
8
8
 
9
- import type { ChannelId } from "./types.js";
9
+ import { CHANNEL_IDS, type ChannelId } from "./types.js";
10
10
 
11
11
  export type ConversationStrategy =
12
12
  | "start_new_conversation"
@@ -77,12 +77,12 @@ const CHANNEL_POLICIES = {
77
77
  notification: {
78
78
  // Replies to an inbound Discord message do not read this flag: they
79
79
  // route by `replyCallbackUrl` through the channel transport. This gates
80
- // proactive notification only, which needs a guardian binding to
81
- // address and a destination resolver to reach it. Discord has neither,
82
- // and `NotificationChannel` is derived from this flag, so enabling it
83
- // early would let the decision engine pick a channel that resolves to
84
- // nothing.
85
- deliveryEnabled: false,
80
+ // proactive notification, which addresses the verified guardian
81
+ // directly: the adapter resolves a DM channel from the guardian
82
+ // binding's user snowflake, so a notification can never land in a
83
+ // guild room whose id merely looks like a DM's (both are bare
84
+ // snowflakes).
85
+ deliveryEnabled: true,
86
86
  conversationStrategy: "continue_existing_conversation",
87
87
  },
88
88
  },
@@ -110,20 +110,31 @@ export function getChannelPolicy(
110
110
  return CHANNEL_POLICIES[channelId];
111
111
  }
112
112
 
113
+ /**
114
+ * The channels whose policy enables proactive delivery, derived from the
115
+ * registry's literal flags: flipping a channel's deliveryEnabled changes
116
+ * this union, and every exhaustive switch over it, at compile time.
117
+ */
118
+ export type DeliverableChannelId = {
119
+ [K in keyof ChannelPolicies]: ChannelPolicies[K]["notification"]["deliveryEnabled"] extends true
120
+ ? K
121
+ : never;
122
+ }[keyof ChannelPolicies];
123
+
113
124
  /**
114
125
  * Returns the list of channels where notification delivery is enabled.
115
126
  *
116
127
  * The return type is derived from the registry so downstream consumers
117
128
  * get a narrow union rather than the full ChannelId set.
118
129
  */
119
- export function getDeliverableChannels(): ChannelId[] {
120
- return (Object.keys(CHANNEL_POLICIES) as ChannelId[]).filter(
121
- (id) => CHANNEL_POLICIES[id].notification.deliveryEnabled,
122
- );
130
+ export function getDeliverableChannels(): DeliverableChannelId[] {
131
+ return CHANNEL_IDS.filter(isNotificationDeliverable);
123
132
  }
124
133
 
125
134
  /** Whether notification delivery is enabled for the given channel. */
126
- export function isNotificationDeliverable(channelId: ChannelId): boolean {
135
+ export function isNotificationDeliverable(
136
+ channelId: ChannelId,
137
+ ): channelId is DeliverableChannelId {
127
138
  return CHANNEL_POLICIES[channelId].notification.deliveryEnabled;
128
139
  }
129
140
 
package/src/cli/AGENTS.md CHANGED
@@ -12,12 +12,14 @@ Commands here operate on a **single running assistant's** local state — config
12
12
 
13
13
  For commands that manage the **lifecycle of assistant instances** (create, start, stop, delete), see `cli/AGENTS.md`.
14
14
 
15
- Examples: `config`, `contacts`, `memory`, `autonomy`, `conversations` belong here. `hatch`, `wake`, `sleep`, `retire`, `ps`, `ssh` belong in `cli/`.
15
+ Examples: `config`, `contacts` (reads), `memory`, `autonomy`, `conversations` belong here. `hatch`, `wake`, `sleep`, `retire`, `ps`, `ssh` belong in `cli/`. Contact ACL writes belong on `gateway contacts`.
16
16
 
17
17
  ## Full CRUD for Resource Namespaces
18
18
 
19
19
  Every namespace that manages a resource (schedules, contacts, tasks, …) must expose the full CRUD surface — create, get/list, update, delete — each wired to a daemon route. Intentional exceptions (e.g. create restricted to one mode) must be documented inline in the namespace help text so agents know the gap is deliberate. Partial surfaces are not a smaller scope, they are a hazard: when a CRUD verb is missing, the model bypasses the CLI and hand-writes SQLite rows instead.
20
20
 
21
+ **No write CLI for gateway-owned contact ACL.** Do not add `assistant contacts` verbs that persist contact ACL (risk ceilings, and any new ACL write). Those writes live on `gateway contacts`. `assistant contacts` may list and inspect.
22
+
21
23
  ## Conventions
22
24
 
23
25
  - Commands use [Commander.js](https://github.com/tj/commander.js) and follow the `registerXCommand(program: Command)` pattern.
@@ -11,8 +11,8 @@ trust rules, hooks, the SQLite database) as a .vbundle file. Credentials are
11
11
  NOT included — they live in the OS keychain / CES and users re-authenticate
12
12
  integrations after a restore (via the gateway). The automated worker runs on a configurable
13
13
  interval and writes to a local pool under ~/.vellum/backups/local/, optionally
14
- mirroring each snapshot to one or more offsite destinations (iCloud Drive by
15
- default).
14
+ mirroring each snapshot to one or more offsite destinations. macOS uses
15
+ encrypted iCloud Drive by default. Windows and Linux require an explicit path.
16
16
 
17
17
  Offsite destinations can be per-destination encrypted (AES-256-GCM) or
18
18
  plaintext — plaintext only makes sense when the user owns physical access to
@@ -70,11 +70,9 @@ Examples:
70
70
  description: "Manage offsite backup destinations",
71
71
  helpText: `
72
72
  Offsite destinations are absolute paths the backup worker writes a copy of
73
- each snapshot to after the local write succeeds. The default destination is
74
- the iCloud Drive VellumAssistant folder, and it is used implicitly until an
75
- explicit destinations array is configured. The first 'destinations add' or
76
- 'destinations remove' materializes the iCloud default before applying the
77
- change, so the default is never lost on an accidental "clear all".
73
+ each snapshot to after the local write succeeds. macOS uses the iCloud Drive
74
+ VellumAssistant folder until an explicit destinations array is configured.
75
+ Windows and Linux have no implicit offsite destination.
78
76
 
79
77
  Each destination has an 'encrypt' flag. When true (the default), snapshots
80
78
  are written as .vbundle.enc (AES-256-GCM). When false, snapshots are copied
@@ -90,9 +88,9 @@ Examples:
90
88
  name: "list",
91
89
  description: "List configured offsite destinations",
92
90
  helpText: `
93
- Resolves the current destinations array (materializing the iCloud default if
94
- no explicit array is configured) and prints a table with the path and
95
- encryption flag per row.
91
+ Resolves the current destinations array and prints each path and encryption
92
+ flag. On macOS this includes the implicit iCloud default when configured paths
93
+ are null.
96
94
 
97
95
  Examples:
98
96
  $ assistant backup destinations list`,
@@ -114,9 +112,8 @@ Arguments:
114
112
  caller controls; the backup worker writes files inside this
115
113
  directory, not the directory itself.
116
114
 
117
- If backup.offsite.destinations is currently null (the implicit iCloud default),
118
- the iCloud default is materialized first so the new entry appends to a
119
- 2-element array rather than replacing the default.
115
+ On macOS, adding the first explicit destination preserves the implicit iCloud
116
+ destination. Windows and Linux start with only the destination supplied here.
120
117
 
121
118
  Examples:
122
119
  $ assistant backup destinations add /Volumes/BackupSSD/vellum --plaintext
@@ -75,9 +75,10 @@ Examples:
75
75
  Arguments:
76
76
  id UUID of the contact to retrieve. Run 'assistant contacts list' to find IDs.
77
77
 
78
- Returns the full contact record including role, display name, and all
79
- channel memberships (phone numbers, Telegram IDs, email addresses, etc.).
80
- For assistant-type contacts, additional assistant metadata is included.
78
+ Returns the full contact record including role, display name, assistant
79
+ access ceiling, and all channel memberships (phone numbers, Telegram
80
+ IDs, email addresses, etc.). For assistant-type contacts, additional
81
+ assistant metadata is included.
81
82
 
82
83
  Examples:
83
84
  $ assistant contacts get 7a3b1c2d-4e5f-6789-abcd-ef0123456789
@@ -120,11 +121,22 @@ Examples:
120
121
  "How long to wait for the user to submit (ms). Defaults to match the server-side prompt timeout.",
121
122
  defaultValue: String(310_000),
122
123
  },
124
+ {
125
+ flags: "--verify",
126
+ description:
127
+ "Mark the submitted channel verified (same attest as Contacts Verify me)",
128
+ },
123
129
  ],
124
130
  helpText: `
125
131
  Opens a contact address prompt in the user's app. The user enters a channel
126
132
  address (phone number, email, Telegram ID, etc.). The address is saved with
127
- status "unverified". Verification is a separate step.
133
+ status "unverified" unless --verify is set, which attests the channel the
134
+ same way Contacts Verify me does.
135
+
136
+ Examples:
137
+ $ assistant contacts prompt --channel phone --role guardian
138
+ $ assistant contacts prompt --channel imessage --role guardian --verify \\
139
+ --label "Your iMessage number" --placeholder "+15551234567"
128
140
 
129
141
  Run \`assistant contacts prompt --help\` for full option details.`,
130
142
  },
@@ -35,6 +35,7 @@ interface ContactWithChannels {
35
35
  createdAt: string | number;
36
36
  updatedAt: string | number;
37
37
  interactionCount: number | null;
38
+ autoApproveThreshold?: string | null;
38
39
  channels: ContactChannel[];
39
40
  }
40
41
 
@@ -131,6 +132,7 @@ function formatContactDetail(
131
132
  lines.push(`Role: ${c.role}`);
132
133
  }
133
134
  lines.push(`Type: ${c.contactType}`);
135
+ lines.push(`Access: ${c.autoApproveThreshold ?? "inherit"}`);
134
136
  if (c.notes) {
135
137
  lines.push(`Notes: ${c.notes}`);
136
138
  }
@@ -262,6 +264,7 @@ export function registerContactsCommand(program: Command): void {
262
264
  label?: string;
263
265
  description?: string;
264
266
  timeout?: string;
267
+ verify?: boolean;
265
268
  },
266
269
  cmd: Command,
267
270
  ) => {
@@ -276,6 +279,7 @@ export function registerContactsCommand(program: Command): void {
276
279
  role: opts.role ?? "unknown",
277
280
  label: opts.label,
278
281
  description: opts.description,
282
+ verify: opts.verify === true,
279
283
  },
280
284
  },
281
285
  { timeoutMs },
@@ -302,7 +306,7 @@ export function registerContactsCommand(program: Command): void {
302
306
  `Registered ${result.channelType} channel: ${result.address}\n` +
303
307
  ` Channel ID: ${result.channelId}\n` +
304
308
  ` Contact ID: ${result.contactId}\n` +
305
- ` Status: unverified\n`,
309
+ ` Status: ${opts.verify ? "verified" : "unverified"}\n`,
306
310
  );
307
311
  }
308
312
  },
@@ -178,7 +178,7 @@ Arguments:
178
178
  id (optional) Credential UUID for lookup by ID
179
179
 
180
180
  Shows everything known about a credential without revealing the secret value.
181
- The secret is masked to show only the last 4 characters (e.g. ****c123).
181
+ The secret is masked to show only the first 4 characters (e.g. cfat****).
182
182
 
183
183
  Displayed fields include: label, creation/update timestamps, allowed tools,
184
184
  allowed domains, OAuth2 scopes, account info, and injection template count.
@@ -20,7 +20,6 @@
20
20
  import type { Command } from "commander";
21
21
 
22
22
  import { cliIpcCall } from "../../ipc/cli-client.js";
23
- import type { OAuth2Config } from "../../security/oauth2.js";
24
23
  import { subcommand } from "../lib/cli-command-help.js";
25
24
  import { writeCliError } from "../lib/cli-output.js";
26
25
  import { openInHostBrowser } from "../lib/open-browser.js";
@@ -448,19 +447,6 @@ function attachDeleteSubcommand(parent: Command): void {
448
447
  );
449
448
  }
450
449
 
451
- // ---------------------------------------------------------------------------
452
- // OpenAI Codex OAuth config (PKCE, no client secret)
453
- // ---------------------------------------------------------------------------
454
-
455
- const OPENAI_CODEX_OAUTH_CONFIG: OAuth2Config = {
456
- authorizeUrl: "https://auth.openai.com/oauth/authorize",
457
- tokenExchangeUrl: "https://auth.openai.com/oauth/token",
458
- clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
459
- scopes: ["openid", "profile", "email", "offline_access"],
460
- scopeSeparator: " ",
461
- authorizeParams: { id_token_add_organizations: "true" },
462
- };
463
-
464
450
  // ---------------------------------------------------------------------------
465
451
  // Subcommand: login-chatgpt
466
452
  // ---------------------------------------------------------------------------
@@ -469,10 +455,18 @@ function attachLoginChatgptSubcommand(providers: Command): void {
469
455
  subcommand(providers, "login-chatgpt").action(
470
456
  async (opts: { json?: boolean }) => {
471
457
  try {
472
- // Deferred: loads the OAuth and secure-key graphs on demand.
473
- const [{ startOAuth2Flow }, { setSecureKeyAsync }] = await Promise.all([
458
+ // Deferred: loads the OAuth and credential graphs on demand.
459
+ const [
460
+ { startOAuth2Flow },
461
+ {
462
+ CHATGPT_SUBSCRIPTION_AUTH_INPUT,
463
+ CHATGPT_SUBSCRIPTION_CONNECTION_NAME,
464
+ OPENAI_OAUTH_CONFIG,
465
+ storeChatgptSubscriptionCredentials,
466
+ },
467
+ ] = await Promise.all([
474
468
  import("../../security/oauth2.js"),
475
- import("../../security/secure-keys.js"),
469
+ import("../../providers/inference/chatgpt-subscription-credentials.js"),
476
470
  ]);
477
471
  // Step 1: Run browser-based PKCE OAuth flow
478
472
  if (!opts.json) {
@@ -481,7 +475,7 @@ function attachLoginChatgptSubcommand(providers: Command): void {
481
475
  );
482
476
  }
483
477
  const result = await startOAuth2Flow(
484
- OPENAI_CODEX_OAUTH_CONFIG,
478
+ OPENAI_OAUTH_CONFIG,
485
479
  {
486
480
  openUrl: (url) => {
487
481
  openInHostBrowser(url);
@@ -496,43 +490,14 @@ function attachLoginChatgptSubcommand(providers: Command): void {
496
490
  loopbackCallbackPath: "/auth/callback",
497
491
  },
498
492
  );
499
- const tokens = result.tokens;
500
-
501
493
  // Step 2: Store tokens in CES
502
- const accessStored = await setSecureKeyAsync(
503
- "credential/chatgpt/access_token",
504
- tokens.accessToken,
505
- );
506
- if (!accessStored) {
507
- writeCliError("Failed to store access token", opts.json);
508
- return;
509
- }
510
-
511
- if (tokens.refreshToken) {
512
- const refreshStored = await setSecureKeyAsync(
513
- "credential/chatgpt/refresh_token",
514
- tokens.refreshToken,
515
- );
516
- if (!refreshStored) {
517
- writeCliError("Failed to store refresh token", opts.json);
518
- return;
519
- }
520
- }
521
-
522
- if (tokens.expiresIn) {
523
- const expiresAt = Math.floor(Date.now() / 1000 + tokens.expiresIn);
524
- await setSecureKeyAsync(
525
- "credential/chatgpt/expires_at",
526
- String(expiresAt),
527
- );
528
- }
494
+ await storeChatgptSubscriptionCredentials(result.tokens);
529
495
 
530
- // Step 3: Create (or update) the provider entry via IPC
531
- const connectionName = "chatgpt-subscription";
532
- const authInput = {
533
- type: "oauth_subscription",
534
- credential: "credential/chatgpt/access_token",
535
- };
496
+ // Step 3: Create (or update) the provider entry via IPC. The CLI runs
497
+ // outside the daemon and has no database handle, so the connection
498
+ // upsert goes over IPC rather than through the shared store helper.
499
+ const connectionName = CHATGPT_SUBSCRIPTION_CONNECTION_NAME;
500
+ const authInput = CHATGPT_SUBSCRIPTION_AUTH_INPUT;
536
501
 
537
502
  // Try to update first; if the entry doesn't exist, create it.
538
503
  const updateResult = await cliIpcCall<ProviderConnection>(
@@ -97,8 +97,8 @@ Examples:
97
97
  },
98
98
  {
99
99
  flags: "-r, --risk <level>",
100
- description: "Default risk level: low, medium, or high",
101
- defaultValue: "high",
100
+ description:
101
+ "Risk level tools from this server start at: low, medium, or high",
102
102
  },
103
103
  ],
104
104
  helpText: `
@@ -110,9 +110,10 @@ Transport-specific requirements:
110
110
  sse Requires --url pointing to the SSE endpoint
111
111
  streamable-http Requires --url pointing to the HTTP endpoint
112
112
 
113
- The --risk flag sets the default risk level for all tools from this server
114
- (defaults to "high" if not specified). The server starts enabled unless
115
- --disabled is passed.
113
+ The --risk flag sets the risk level tools from this server start at. Omit it
114
+ and the server tracks the shipped default, "medium". A tool's own MCP
115
+ annotations move it one step from there: destructiveHint up, readOnlyHint
116
+ down. The server starts enabled unless --disabled is passed.
116
117
 
117
118
  The --header (-H) flag adds custom HTTP headers to sse/streamable-http
118
119
  transports. Use it for Bearer Token or API Key authentication. The flag