@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,58 @@
1
+ /**
2
+ * Guards over the schedule worker entrypoint's feature flag bootstrap.
3
+ *
4
+ * The override cache is process-local, so this worker has to populate its own.
5
+ * When it does not, every flag check inside it resolves to the registry default
6
+ * and ignores remote values and local overrides, and this is the process that
7
+ * decides at fire time whether a schedule runs. The entrypoint installs signal
8
+ * handlers and calls `process.exit`, so it cannot be imported into a test
9
+ * process; these assertions read its source the same way
10
+ * `src/__tests__/worker-entrypoint-guards.test.ts` does.
11
+ */
12
+
13
+ import { readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { describe, expect, test } from "bun:test";
16
+
17
+ const INIT_CALL = "await initFeatureFlagOverrides(";
18
+ const REFRESH_CALL = "refreshOverridesFromGateway(";
19
+ const REFRESH_INTERVAL = "FLAG_REFRESH_INTERVAL_MS";
20
+
21
+ /** The worker's first call that can execute a schedule. */
22
+ const WORK_START = "void tick()";
23
+
24
+ function readWorkerSource(): string {
25
+ return readFileSync(join(process.cwd(), "src/schedule/worker.ts"), "utf8");
26
+ }
27
+
28
+ describe("schedule worker feature flags", () => {
29
+ test("loads flag overrides before the first schedule tick", () => {
30
+ const source = readWorkerSource();
31
+ const initAt = source.indexOf(INIT_CALL);
32
+ const workAt = source.indexOf(WORK_START);
33
+
34
+ expect(
35
+ initAt,
36
+ `The schedule worker must \`${INIT_CALL})\` at startup. Without it ` +
37
+ "its process-local override cache stays unset and fire-time flag " +
38
+ "checks fall back to registry defaults.",
39
+ ).toBeGreaterThanOrEqual(0);
40
+ expect(workAt).toBeGreaterThanOrEqual(0);
41
+ expect(
42
+ initAt < workAt,
43
+ "The flag load must complete before the first tick, so the schedules " +
44
+ "that tick fires are gated on real flag values.",
45
+ ).toBe(true);
46
+ });
47
+
48
+ test("re-reads the flag cache on a timer while running", () => {
49
+ const source = readWorkerSource();
50
+
51
+ expect(
52
+ source.includes(REFRESH_CALL) && source.includes(REFRESH_INTERVAL),
53
+ `This process outlives any single flag value, so it must call ` +
54
+ `${REFRESH_CALL}) on the ${REFRESH_INTERVAL} timer rather than ` +
55
+ "holding whatever was cached at startup.",
56
+ ).toBe(true);
57
+ });
58
+ });
@@ -41,15 +41,19 @@ export function decideRetry(
41
41
  export async function applyRetryDecision(params: {
42
42
  job: RetryPolicyJob;
43
43
  isOneShot: boolean;
44
- errorMsg: string;
45
44
  decision: RetryDecision;
46
45
  scheduleRetry: (id: string, nextRetryAt: number) => void | Promise<void>;
47
46
  failOneShotPermanently: (id: string) => void | Promise<void>;
48
47
  resetRetryCount: (id: string) => void | Promise<void>;
49
- emitAlert: (title: string, summary: string, dedupKey: string) => void;
48
+ /**
49
+ * Fired exactly once, when retries are exhausted. This is the single
50
+ * user-facing alert for a failed schedule (per-attempt failures are
51
+ * retried silently), and the caller owns its content and dedupe key.
52
+ */
53
+ emitAlert: () => void;
50
54
  log: Logger;
51
55
  }): Promise<void> {
52
- const { job, isOneShot, errorMsg, decision } = params;
56
+ const { job, isOneShot, decision } = params;
53
57
 
54
58
  if (decision.action === "retry") {
55
59
  await params.scheduleRetry(job.id, decision.nextRetryAt);
@@ -69,11 +73,7 @@ export async function applyRetryDecision(params: {
69
73
  } else {
70
74
  await params.resetRetryCount(job.id);
71
75
  }
72
- params.emitAlert(
73
- `${job.name}: Retries exhausted`,
74
- `Failed after ${job.retryCount + 1} attempt(s): ${errorMsg}`,
75
- `schedule-retries-exhausted:${job.id}:${Date.now()}`,
76
- );
76
+ params.emitAlert();
77
77
  params.log.warn(
78
78
  { jobId: job.id, name: job.name, attempts: job.retryCount + 1 },
79
79
  "Schedule retries exhausted",
@@ -1,4 +1,8 @@
1
1
  import { buildSanitizedEnv } from "../tools/terminal/safe-env.js";
2
+ import {
3
+ buildShellInvocation,
4
+ terminateProcessTree,
5
+ } from "../util/host-process.js";
2
6
  import { getLogger } from "../util/logger.js";
3
7
  import { getWorkspaceDir } from "../util/platform.js";
4
8
 
@@ -34,14 +38,13 @@ export interface ScriptResult {
34
38
  /**
35
39
  * Run a shell command and capture its output.
36
40
  *
37
- * Uses Bun.spawn with /bin/sh so the command string supports pipes,
38
- * redirects, and shell builtins. Output is truncated to
39
- * {@link MAX_OUTPUT_BYTES} to keep schedule_runs rows bounded.
41
+ * Uses the platform shell so the command string supports pipes, redirects,
42
+ * and shell builtins. Windows uses PowerShell and POSIX hosts use Bash. Output
43
+ * is truncated to {@link MAX_OUTPUT_BYTES} to keep schedule_runs rows bounded.
40
44
  *
41
- * The command runs in its own process group, and the whole group is killed
42
- * on timeout and swept after exit, so background children cannot outlive the
43
- * run. A child that daemonizes itself with setsid leaves the group and
44
- * deliberately survives.
45
+ * The command runs in its own process group or Windows process tree. The tree
46
+ * is terminated on timeout and swept after exit so background children cannot
47
+ * outlive the run.
45
48
  */
46
49
  export async function runScript(
47
50
  command: string,
@@ -57,7 +60,8 @@ export async function runScript(
57
60
 
58
61
  log.info({ command, cwd, timeoutMs }, "Running script");
59
62
 
60
- const proc = Bun.spawn(["sh", "-c", command], {
63
+ const shell = buildShellInvocation(command);
64
+ const proc = Bun.spawn([shell.command, ...shell.args], {
61
65
  cwd,
62
66
  detached: true,
63
67
  stdout: "pipe",
@@ -82,7 +86,7 @@ export async function runScript(
82
86
  const timeoutPromise = new Promise<never>((_, reject) => {
83
87
  const timer = setTimeout(() => {
84
88
  timedOut = true;
85
- killProcessGroup(proc.pid);
89
+ terminateProcessTree(proc);
86
90
  reject(new Error(`Script timed out after ${timeoutMs}ms`));
87
91
  }, timeoutMs);
88
92
  timer.unref();
@@ -116,7 +120,7 @@ export async function runScript(
116
120
  // Sweep anything the script left running. This reaps orphaned background
117
121
  // children and closes their copies of the pipe fds so the stream reads
118
122
  // below can reach EOF.
119
- killProcessGroup(proc.pid);
123
+ terminateProcessTree(proc);
120
124
 
121
125
  const [stdoutStr, stderrStr] = await drainStreams(
122
126
  stdoutCollector,
@@ -133,19 +137,6 @@ export async function runScript(
133
137
  return { exitCode, stdout, stderr };
134
138
  }
135
139
 
136
- /**
137
- * SIGKILL every process in the script's process group. The group is often
138
- * already empty, in which case the signal fails with ESRCH and there is
139
- * nothing to reap.
140
- */
141
- function killProcessGroup(pid: number): void {
142
- try {
143
- process.kill(-pid, "SIGKILL");
144
- } catch {
145
- // Process group may have already exited.
146
- }
147
- }
148
-
149
140
  interface StreamCollector {
150
141
  /** Resolves with the text read so far once the stream ends or is cancelled. */
151
142
  promise: Promise<string>;
@@ -52,7 +52,6 @@ export async function recoverStaleSchedules(): Promise<number> {
52
52
  await applyRetryDecision({
53
53
  job,
54
54
  isOneShot,
55
- errorMsg,
56
55
  decision,
57
56
  scheduleRetry,
58
57
  failOneShotPermanently,
@@ -1,4 +1,5 @@
1
1
  import { refreshBackgroundWakeIntent } from "../background-wake/publisher.js";
2
+ import { resolveSingleRouteProfileKey } from "../config/llm-resolver.js";
2
3
  import { getConfig } from "../config/loader.js";
3
4
  import {
4
5
  checkDiskPressureBackgroundGate,
@@ -7,13 +8,18 @@ import {
7
8
  } from "../daemon/disk-pressure-background-gate.js";
8
9
  import { processMessage } from "../daemon/process-message.js";
9
10
  import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../daemon/trust-context.js";
11
+ import { emitBackgroundFailureSignal } from "../notifications/background-failure-signal.js";
10
12
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
11
13
  import { getConversation } from "../persistence/conversation-crud.js";
12
14
  import { isLifecycleQuiesced } from "../persistence/lifecycle-quiesce.js";
13
15
  import { invalidateAssistantInferredItemsForConversation } from "../plugins/defaults/memory/task-memory-cleanup.js";
16
+ import { dispatchProviderResolvable } from "../providers/provider-resolvability.js";
14
17
  import { wakeAgentForOpportunity } from "../runtime/agent-wake.js";
15
18
  import { broadcastMessage } from "../runtime/assistant-event-hub.js";
16
- import { runBackgroundJob } from "../runtime/background-job-runner.js";
19
+ import {
20
+ type BackgroundJobErrorKind,
21
+ runBackgroundJob,
22
+ } from "../runtime/background-job-runner.js";
17
23
  import { publishConversationListChanged } from "../runtime/sync/resource-sync-events.js";
18
24
  import { runSequencesOnce } from "../sequence/engine.js";
19
25
  import type { TurnFailure } from "../telemetry/turn-outcome.js";
@@ -223,27 +229,84 @@ async function handleExecutionFailure(params: {
223
229
  job: ScheduleJob;
224
230
  errorMsg: string;
225
231
  isOneShot: boolean;
232
+ /**
233
+ * Classified failure cause from the runner, when the failing path had one.
234
+ * Keys the exhaustion alert's dedupe so same-cause failures across jobs
235
+ * collapse; absent means a generic exception, which keys per schedule.
236
+ */
237
+ errorKind?: BackgroundJobErrorKind;
238
+ /**
239
+ * The failing turn's carried classification, when the failure was a
240
+ * non-throwing turn failure. The exhaustion alert renders its authored
241
+ * message and scopes by the route that actually served the runs.
242
+ */
243
+ turnFailure?: TurnFailure;
226
244
  }): Promise<void> {
227
245
  const decision = decideRetry(params.job);
228
246
  await applyRetryDecision({
229
247
  job: params.job,
230
248
  isOneShot: params.isOneShot,
231
- errorMsg: params.errorMsg,
232
249
  decision,
233
250
  scheduleRetry,
234
251
  failOneShotPermanently,
235
252
  resetRetryCount,
236
- emitAlert: (_title, _summary, dedupKey) =>
237
- emitScheduleActivityFailed({
238
- jobId: params.job.id,
239
- jobName: params.job.name,
253
+ emitAlert: () => {
254
+ const fallbackProviderScope = resolveScheduleProviderScope(params.job);
255
+ const carried = params.turnFailure;
256
+ emitBackgroundFailureSignal({
257
+ jobName: `schedule:${params.job.id}`,
258
+ displayName: `schedule:${params.job.name}`,
259
+ sourceChannel: "scheduler",
260
+ sourceContextId: params.job.id,
261
+ errorKind: params.errorKind ?? "exception",
240
262
  errorMessage: params.errorMsg,
241
- dedupKey,
242
- }),
263
+ ...(carried?.failureCode !== undefined
264
+ ? { failureCode: carried.failureCode }
265
+ : {}),
266
+ ...(carried?.userMessage !== undefined
267
+ ? { failureSummary: carried.userMessage }
268
+ : {}),
269
+ ...(carried?.errorCategory !== undefined
270
+ ? { errorCategory: carried.errorCategory }
271
+ : {}),
272
+ ...(carried?.connectionName !== undefined
273
+ ? { connectionName: carried.connectionName }
274
+ : {}),
275
+ ...(carried?.profileName !== undefined
276
+ ? { profileName: carried.profileName }
277
+ : {}),
278
+ ...(fallbackProviderScope !== undefined
279
+ ? { fallbackProviderScope }
280
+ : {}),
281
+ });
282
+ },
243
283
  log,
244
284
  });
245
285
  }
246
286
 
287
+ /**
288
+ * The provider scope a schedule's failing runs resolved through: its pinned
289
+ * profile when that names a single provider route, else the route the
290
+ * mainAgent winner actually served (a deleted pin falls through to it). A
291
+ * mix pin, no named winner, or an unreadable config yield no scope, so the
292
+ * exhaustion alert keys per schedule rather than claiming one scope for
293
+ * arms that can span providers.
294
+ */
295
+ function resolveScheduleProviderScope(job: ScheduleJob): string | undefined {
296
+ try {
297
+ return resolveSingleRouteProfileKey("mainAgent", getConfig().llm, {
298
+ ...(job.inferenceProfile
299
+ ? { overrideProfile: job.inferenceProfile }
300
+ : {}),
301
+ // Dispatch's own resolvability predicate, so a stale pin dispatch
302
+ // rejected (force-deleted connection) is not accepted as the scope.
303
+ isResolvableProvider: dispatchProviderResolvable,
304
+ });
305
+ } catch {
306
+ return undefined;
307
+ }
308
+ }
309
+
247
310
  /** The running scheduler, retained so shutdown can stop it. */
248
311
  let instance: SchedulerHandle | null = null;
249
312
 
@@ -896,6 +959,8 @@ export async function runDueSchedulesOnce(
896
959
  let conversationId: string;
897
960
  let ok: boolean;
898
961
  let errorMsg: string | undefined;
962
+ let errorKind: BackgroundJobErrorKind | undefined;
963
+ let failedTurn: TurnFailure | undefined;
899
964
  const conversationReused = reusedConversationId != null;
900
965
  let runConversationId = reusedConversationId;
901
966
  const runId = await createScheduleRun(job.id, reusedConversationId);
@@ -929,17 +994,20 @@ export async function runDueSchedulesOnce(
929
994
  if (turnFailure) {
930
995
  ok = false;
931
996
  errorMsg = describeTurnFailure(turnFailure);
997
+ errorKind = "model_provider";
998
+ failedTurn = turnFailure;
932
999
  } else {
933
1000
  ok = true;
934
1001
  }
935
1002
  } catch (err) {
936
1003
  ok = false;
937
1004
  errorMsg = err instanceof Error ? err.message : String(err);
1005
+ errorKind = "exception";
938
1006
  }
939
1007
  } else {
940
- // Fresh-bootstrap path: route through the shared runner so failures
941
- // surface via `activity.failed` and we get the standard timeout +
942
- // error-classification policy applied to every background producer.
1008
+ // Fresh-bootstrap path: route through the shared runner for the
1009
+ // standard timeout + error-classification policy applied to every
1010
+ // background producer.
943
1011
  // The runner fires `onConversationCreated` synchronously after bootstrap
944
1012
  // (before `processMessage` starts) so the macOS sidebar gets the new
945
1013
  // conversation immediately rather than after the up-to-30-min job ends.
@@ -962,7 +1030,12 @@ export async function runDueSchedulesOnce(
962
1030
  groupId: resolveScheduleConversationGroupId(job),
963
1031
  conversationType: "scheduled",
964
1032
  scheduleJobId: job.id,
965
- suppressFailureNotifications: job.quiet === true,
1033
+ // The retry policy owns the single user-facing alert for a failing
1034
+ // schedule (fired once, when retries are exhausted). Letting the
1035
+ // runner also emit per attempt would notify for failures that a
1036
+ // retry may yet recover, and pair every exhaustion alert with a
1037
+ // duplicate.
1038
+ suppressFailureNotifications: true,
966
1039
  onConversationCreated: async (newConversationId) => {
967
1040
  runConversationId = newConversationId;
968
1041
  await setScheduleRunConversationId(runId, newConversationId);
@@ -988,6 +1061,8 @@ export async function runDueSchedulesOnce(
988
1061
  }
989
1062
  ok = result.ok;
990
1063
  errorMsg = result.error?.message;
1064
+ errorKind = result.errorKind;
1065
+ failedTurn = result.turnFailure;
991
1066
  }
992
1067
 
993
1068
  if (ok) {
@@ -1016,6 +1091,8 @@ export async function runDueSchedulesOnce(
1016
1091
  job,
1017
1092
  errorMsg: errorMsg ?? "Schedule run failed",
1018
1093
  isOneShot,
1094
+ ...(errorKind !== undefined ? { errorKind } : {}),
1095
+ ...(failedTurn !== undefined ? { turnFailure: failedTurn } : {}),
1019
1096
  });
1020
1097
 
1021
1098
  // Only skip invalidation when the conversation was *actually* reused,
@@ -1039,42 +1116,3 @@ export async function runDueSchedulesOnce(
1039
1116
 
1040
1117
  return result;
1041
1118
  }
1042
-
1043
- /**
1044
- * Emit an `activity.failed` notification for a schedule whose retries have
1045
- * been exhausted. Fires once when the retry policy has given up, so the
1046
- * dedupeKey caller is the per-attempt key passed in by `applyRetryDecision`
1047
- * (already includes the job id and a timestamp).
1048
- */
1049
- function emitScheduleActivityFailed(args: {
1050
- jobId: string;
1051
- jobName: string;
1052
- errorMessage: string;
1053
- dedupKey: string;
1054
- }): void {
1055
- emitNotificationSignal({
1056
- sourceChannel: "scheduler",
1057
- sourceContextId: args.jobId,
1058
- sourceEventName: "activity.failed",
1059
- dedupeKey: args.dedupKey,
1060
- contextPayload: {
1061
- jobName: `schedule:${args.jobName}`,
1062
- errorMessage: args.errorMessage,
1063
- errorKind: "exception",
1064
- },
1065
- attentionHints: {
1066
- requiresAction: false,
1067
- urgency: "medium",
1068
- isAsyncBackground: true,
1069
- visibleInSourceNow: false,
1070
- },
1071
- }).catch((emitErr) => {
1072
- log.warn(
1073
- {
1074
- err: emitErr instanceof Error ? emitErr.message : String(emitErr),
1075
- jobId: args.jobId,
1076
- },
1077
- "Failed to emit activity.failed notification for exhausted schedule",
1078
- );
1079
- });
1080
- }
@@ -13,8 +13,13 @@
13
13
  */
14
14
  import { writeFileSync } from "node:fs";
15
15
 
16
+ import {
17
+ initFeatureFlagOverrides,
18
+ refreshOverridesFromGateway,
19
+ } from "../config/assistant-feature-flags.js";
16
20
  import { getConfig } from "../config/loader.js";
17
21
  import { rehydratePlatformCredentials } from "../config/platform-rehydration.js";
22
+ import { startConversationEvictor } from "../daemon/conversation-evictor.js";
18
23
  import { resetDb } from "../persistence/db-connection.js";
19
24
  import { registerWorkerPluginSurface } from "../plugins/worker-plugin-surface.js";
20
25
  import { disableStreamSeqStamping } from "../runtime/assistant-stream-state.js";
@@ -32,6 +37,9 @@ const log = getLogger("schedule-worker-process");
32
37
  /** Same cadence as the daemon scheduler's tick. */
33
38
  const TICK_INTERVAL_MS = 15_000;
34
39
 
40
+ /** How often this process re-reads flag overrides from the gateway. */
41
+ const FLAG_REFRESH_INTERVAL_MS = 60_000;
42
+
35
43
  async function main(): Promise<void> {
36
44
  // Only the daemon stamps SSE seqs and writes the shared reservation file.
37
45
  disableStreamSeqStamping();
@@ -44,6 +52,29 @@ async function main(): Promise<void> {
44
52
  writeFileSync(pidPath, String(process.pid), { flag: "w" });
45
53
  log.info({ pid: process.pid, pidPath }, "Schedule worker process started");
46
54
 
55
+ // The override cache is per-process, so this worker has to populate its own
56
+ // even though the daemon already populated the daemon's. Without it every
57
+ // flag check here resolves to its registry default and ignores both remote
58
+ // values and local overrides, which decides at fire time whether a schedule
59
+ // runs at all. Awaited so the first tick reads real values, and best-effort:
60
+ // a gateway that never answers leaves the cache unset and the worker falls
61
+ // back to registry defaults rather than failing to start.
62
+ // Bounded to one short attempt: a gateway that accepts the socket but never
63
+ // answers would otherwise hold the first tick for the full production retry
64
+ // schedule. A miss here leaves the cache unset (registry defaults) and the
65
+ // periodic refresh below converges to real values within a minute.
66
+ try {
67
+ await initFeatureFlagOverrides({
68
+ retryBackoffsMs: [],
69
+ callTimeoutMs: 2_000,
70
+ });
71
+ } catch (err) {
72
+ log.warn(
73
+ { err },
74
+ "Failed to load feature flag overrides in schedule worker; continuing on registry defaults",
75
+ );
76
+ }
77
+
47
78
  // Rehydrate the platform base URL and IDs from the credential store before
48
79
  // the first tick. The daemon does this in initializeProvidersAndTools(); this
49
80
  // standalone process must do it itself so getPlatformBaseUrl() resolves to
@@ -71,9 +102,15 @@ async function main(): Promise<void> {
71
102
  );
72
103
  }
73
104
 
105
+ // Sweep idle conversations out of the in-memory pool. The daemon starts
106
+ // this at startup; this process hosts conversations too, so without it
107
+ // every scheduled run's conversation is retained for the process lifetime.
108
+ startConversationEvictor();
109
+
74
110
  let stopped = false;
75
111
  let tickRunning = false;
76
112
  let timer: ReturnType<typeof setInterval> | null = null;
113
+ let flagRefreshTimer: ReturnType<typeof setInterval> | null = null;
77
114
  let disposePidGuard: (() => void) | null = null;
78
115
  const shutdown = (signal: string) => {
79
116
  log.info({ signal }, "Schedule worker process shutting down");
@@ -81,6 +118,9 @@ async function main(): Promise<void> {
81
118
  if (timer != null) {
82
119
  clearInterval(timer);
83
120
  }
121
+ if (flagRefreshTimer != null) {
122
+ clearInterval(flagRefreshTimer);
123
+ }
84
124
  disposePidGuard?.();
85
125
  cleanupWorkerPidFile(pidPath);
86
126
  process.exit(0);
@@ -127,6 +167,20 @@ async function main(): Promise<void> {
127
167
  }, TICK_INTERVAL_MS);
128
168
  void tick();
129
169
 
170
+ // This process outlives any single flag value, so poll the gateway instead of
171
+ // holding whatever was cached at startup. The daemon's gateway flag listener
172
+ // is not reusable here: it also reconciles managed profiles and broadcasts
173
+ // sync events, both of which belong to the daemon alone. The refresh swaps the
174
+ // cache atomically, so a tick reading a flag mid-refresh still sees the last
175
+ // known values. Unref'd so the tick interval above stays the one thing
176
+ // keeping this process alive.
177
+ flagRefreshTimer = setInterval(() => {
178
+ void refreshOverridesFromGateway().catch((err) => {
179
+ log.warn({ err }, "Failed to refresh feature flag overrides");
180
+ });
181
+ }, FLAG_REFRESH_INTERVAL_MS);
182
+ flagRefreshTimer.unref();
183
+
130
184
  process.on("SIGUSR1", () => {
131
185
  log.info("Received SIGUSR1 — refreshing database connections");
132
186
  resetDb();
@@ -155,6 +209,9 @@ async function main(): Promise<void> {
155
209
  if (timer != null) {
156
210
  clearInterval(timer);
157
211
  }
212
+ if (flagRefreshTimer != null) {
213
+ clearInterval(flagRefreshTimer);
214
+ }
158
215
  cleanupWorkerPidFile(getScheduleWorkerPidPath());
159
216
  });
160
217
  }