@koda-sl/baker-bridge 0.109.0-dev.afb6a264b → 0.109.0-dev.d6e55699d

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 (315) hide show
  1. package/README.md +89 -47
  2. package/dist/broker/baker-api.d.ts +23 -0
  3. package/dist/broker/baker-api.d.ts.map +1 -0
  4. package/dist/broker/baker-api.js +55 -0
  5. package/dist/broker/baker-api.js.map +1 -0
  6. package/dist/broker/broker-client.d.ts +10 -0
  7. package/dist/broker/broker-client.d.ts.map +1 -0
  8. package/dist/broker/broker-client.js +60 -0
  9. package/dist/broker/broker-client.js.map +1 -0
  10. package/dist/broker/broker-process.d.ts +10 -0
  11. package/dist/broker/broker-process.d.ts.map +1 -0
  12. package/dist/broker/broker-process.js +20 -0
  13. package/dist/broker/broker-process.js.map +1 -0
  14. package/dist/broker/broker-process.test.d.ts +2 -0
  15. package/dist/broker/broker-process.test.d.ts.map +1 -0
  16. package/dist/broker/broker-process.test.js +13 -0
  17. package/dist/broker/broker-process.test.js.map +1 -0
  18. package/dist/broker/ca.d.ts +38 -0
  19. package/dist/broker/ca.d.ts.map +1 -0
  20. package/dist/broker/ca.js +80 -0
  21. package/dist/broker/ca.js.map +1 -0
  22. package/dist/broker/ca.test.d.ts +2 -0
  23. package/dist/broker/ca.test.d.ts.map +1 -0
  24. package/dist/broker/ca.test.js +58 -0
  25. package/dist/broker/ca.test.js.map +1 -0
  26. package/dist/broker/credential-cache.d.ts +24 -0
  27. package/dist/broker/credential-cache.d.ts.map +1 -0
  28. package/dist/broker/credential-cache.js +76 -0
  29. package/dist/broker/credential-cache.js.map +1 -0
  30. package/dist/broker/credential-cache.test.d.ts +2 -0
  31. package/dist/broker/credential-cache.test.d.ts.map +1 -0
  32. package/dist/broker/credential-cache.test.js +136 -0
  33. package/dist/broker/credential-cache.test.js.map +1 -0
  34. package/dist/broker/credential-text.d.ts +45 -0
  35. package/dist/broker/credential-text.d.ts.map +1 -0
  36. package/dist/broker/credential-text.js +103 -0
  37. package/dist/broker/credential-text.js.map +1 -0
  38. package/dist/broker/credential-text.test.d.ts +2 -0
  39. package/dist/broker/credential-text.test.d.ts.map +1 -0
  40. package/dist/broker/credential-text.test.js +54 -0
  41. package/dist/broker/credential-text.test.js.map +1 -0
  42. package/dist/broker/env.d.ts +21 -0
  43. package/dist/broker/env.d.ts.map +1 -0
  44. package/dist/broker/env.js +20 -0
  45. package/dist/broker/env.js.map +1 -0
  46. package/dist/broker/main.d.ts +3 -0
  47. package/dist/broker/main.d.ts.map +1 -0
  48. package/dist/broker/main.js +57 -0
  49. package/dist/broker/main.js.map +1 -0
  50. package/dist/broker/proxy.d.ts +24 -0
  51. package/dist/broker/proxy.d.ts.map +1 -0
  52. package/dist/broker/proxy.integration.test.d.ts +2 -0
  53. package/dist/broker/proxy.integration.test.d.ts.map +1 -0
  54. package/dist/broker/proxy.integration.test.js +197 -0
  55. package/dist/broker/proxy.integration.test.js.map +1 -0
  56. package/dist/broker/proxy.js +147 -0
  57. package/dist/broker/proxy.js.map +1 -0
  58. package/dist/broker/rewrite.d.ts +44 -0
  59. package/dist/broker/rewrite.d.ts.map +1 -0
  60. package/dist/broker/rewrite.js +291 -0
  61. package/dist/broker/rewrite.js.map +1 -0
  62. package/dist/broker/rewrite.test.d.ts +2 -0
  63. package/dist/broker/rewrite.test.d.ts.map +1 -0
  64. package/dist/broker/rewrite.test.js +171 -0
  65. package/dist/broker/rewrite.test.js.map +1 -0
  66. package/dist/broker/socket-service.d.ts +18 -0
  67. package/dist/broker/socket-service.d.ts.map +1 -0
  68. package/dist/broker/socket-service.js +113 -0
  69. package/dist/broker/socket-service.js.map +1 -0
  70. package/dist/broker/socket-service.test.d.ts +2 -0
  71. package/dist/broker/socket-service.test.d.ts.map +1 -0
  72. package/dist/broker/socket-service.test.js +80 -0
  73. package/dist/broker/socket-service.test.js.map +1 -0
  74. package/dist/broker/supervisor-main.d.ts +3 -0
  75. package/dist/broker/supervisor-main.d.ts.map +1 -0
  76. package/dist/broker/supervisor-main.js +70 -0
  77. package/dist/broker/supervisor-main.js.map +1 -0
  78. package/dist/broker/supervisor.d.ts +25 -0
  79. package/dist/broker/supervisor.d.ts.map +1 -0
  80. package/dist/broker/supervisor.js +88 -0
  81. package/dist/broker/supervisor.js.map +1 -0
  82. package/dist/broker/supervisor.test.d.ts +2 -0
  83. package/dist/broker/supervisor.test.d.ts.map +1 -0
  84. package/dist/broker/supervisor.test.js +161 -0
  85. package/dist/broker/supervisor.test.js.map +1 -0
  86. package/dist/broker/token-bucket.d.ts +9 -0
  87. package/dist/broker/token-bucket.d.ts.map +1 -0
  88. package/dist/broker/token-bucket.js +18 -0
  89. package/dist/broker/token-bucket.js.map +1 -0
  90. package/dist/broker/usage-reporter.d.ts +18 -0
  91. package/dist/broker/usage-reporter.d.ts.map +1 -0
  92. package/dist/broker/usage-reporter.js +31 -0
  93. package/dist/broker/usage-reporter.js.map +1 -0
  94. package/dist/broker/usage-reporter.test.d.ts +2 -0
  95. package/dist/broker/usage-reporter.test.d.ts.map +1 -0
  96. package/dist/broker/usage-reporter.test.js +20 -0
  97. package/dist/broker/usage-reporter.test.js.map +1 -0
  98. package/dist/cli.d.ts.map +1 -1
  99. package/dist/cli.js +2 -0
  100. package/dist/cli.js.map +1 -1
  101. package/dist/hono/agent-session.d.ts +3 -0
  102. package/dist/hono/agent-session.d.ts.map +1 -1
  103. package/dist/hono/agent-session.js +15 -0
  104. package/dist/hono/agent-session.js.map +1 -1
  105. package/dist/hono/agent-tasks.js +4 -4
  106. package/dist/hono/agent-tasks.js.map +1 -1
  107. package/dist/hono/agent.d.ts +2 -0
  108. package/dist/hono/agent.d.ts.map +1 -1
  109. package/dist/hono/agent.js +11 -2
  110. package/dist/hono/agent.js.map +1 -1
  111. package/dist/hono/agent.test.js +8 -2
  112. package/dist/hono/agent.test.js.map +1 -1
  113. package/dist/hono/avatar-accent.d.ts +23 -5
  114. package/dist/hono/avatar-accent.d.ts.map +1 -1
  115. package/dist/hono/avatar-accent.js +54 -63
  116. package/dist/hono/avatar-accent.js.map +1 -1
  117. package/dist/hono/avatar-accent.test.js +64 -35
  118. package/dist/hono/avatar-accent.test.js.map +1 -1
  119. package/dist/hono/avatar-casting.d.ts +1 -0
  120. package/dist/hono/avatar-casting.d.ts.map +1 -1
  121. package/dist/hono/avatar-casting.js +18 -4
  122. package/dist/hono/avatar-casting.js.map +1 -1
  123. package/dist/hono/avatar-casting.test.js +5 -0
  124. package/dist/hono/avatar-casting.test.js.map +1 -1
  125. package/dist/hono/avatar-face-question.d.ts +23 -6
  126. package/dist/hono/avatar-face-question.d.ts.map +1 -1
  127. package/dist/hono/avatar-face-question.js +48 -59
  128. package/dist/hono/avatar-face-question.js.map +1 -1
  129. package/dist/hono/avatar-face-question.test.js +46 -101
  130. package/dist/hono/avatar-face-question.test.js.map +1 -1
  131. package/dist/hono/baker-ui-tool-schemas.test.js +30 -4
  132. package/dist/hono/baker-ui-tool-schemas.test.js.map +1 -1
  133. package/dist/hono/blocking-card.d.ts +12 -0
  134. package/dist/hono/blocking-card.d.ts.map +1 -0
  135. package/dist/hono/blocking-card.js +12 -0
  136. package/dist/hono/blocking-card.js.map +1 -0
  137. package/dist/hono/bridge-job.d.ts +48 -2
  138. package/dist/hono/bridge-job.d.ts.map +1 -1
  139. package/dist/hono/bridge-job.js +202 -34
  140. package/dist/hono/bridge-job.js.map +1 -1
  141. package/dist/hono/bridge-job.test.js +344 -20
  142. package/dist/hono/bridge-job.test.js.map +1 -1
  143. package/dist/hono/check-content.d.ts +49 -0
  144. package/dist/hono/check-content.d.ts.map +1 -0
  145. package/dist/hono/check-content.js +182 -0
  146. package/dist/hono/check-content.js.map +1 -0
  147. package/dist/hono/check-content.test.d.ts +2 -0
  148. package/dist/hono/check-content.test.d.ts.map +1 -0
  149. package/dist/hono/check-content.test.js +87 -0
  150. package/dist/hono/check-content.test.js.map +1 -0
  151. package/dist/hono/connection-input.d.ts +170 -12
  152. package/dist/hono/connection-input.d.ts.map +1 -1
  153. package/dist/hono/connection-input.js +166 -14
  154. package/dist/hono/connection-input.js.map +1 -1
  155. package/dist/hono/connection-input.test.js +112 -9
  156. package/dist/hono/connection-input.test.js.map +1 -1
  157. package/dist/hono/convex.d.ts +8 -0
  158. package/dist/hono/convex.d.ts.map +1 -1
  159. package/dist/hono/convex.js +8 -0
  160. package/dist/hono/convex.js.map +1 -1
  161. package/dist/hono/convex.test.js +4 -1
  162. package/dist/hono/convex.test.js.map +1 -1
  163. package/dist/hono/credential-input.d.ts +39 -0
  164. package/dist/hono/credential-input.d.ts.map +1 -0
  165. package/dist/hono/credential-input.js +78 -0
  166. package/dist/hono/credential-input.js.map +1 -0
  167. package/dist/hono/credential-scrub.d.ts +42 -0
  168. package/dist/hono/credential-scrub.d.ts.map +1 -0
  169. package/dist/hono/credential-scrub.js +173 -0
  170. package/dist/hono/credential-scrub.js.map +1 -0
  171. package/dist/hono/credential-scrub.test.d.ts +2 -0
  172. package/dist/hono/credential-scrub.test.d.ts.map +1 -0
  173. package/dist/hono/credential-scrub.test.js +130 -0
  174. package/dist/hono/credential-scrub.test.js.map +1 -0
  175. package/dist/hono/env.d.ts +1 -0
  176. package/dist/hono/env.d.ts.map +1 -1
  177. package/dist/hono/env.js +2 -0
  178. package/dist/hono/env.js.map +1 -1
  179. package/dist/hono/flow-crypto.d.ts +2 -1
  180. package/dist/hono/flow-crypto.d.ts.map +1 -1
  181. package/dist/hono/flow-crypto.js +50 -26
  182. package/dist/hono/flow-crypto.js.map +1 -1
  183. package/dist/hono/flow-crypto.test.js +32 -0
  184. package/dist/hono/flow-crypto.test.js.map +1 -1
  185. package/dist/hono/flow-input-card-fields.test.d.ts +10 -0
  186. package/dist/hono/flow-input-card-fields.test.d.ts.map +1 -0
  187. package/dist/hono/flow-input-card-fields.test.js +26 -0
  188. package/dist/hono/flow-input-card-fields.test.js.map +1 -0
  189. package/dist/hono/flow-input-fields.test.js +1 -1
  190. package/dist/hono/flow-input-fields.test.js.map +1 -1
  191. package/dist/hono/flow-input.d.ts +23 -2
  192. package/dist/hono/flow-input.d.ts.map +1 -1
  193. package/dist/hono/flow-input.js +63 -14
  194. package/dist/hono/flow-input.js.map +1 -1
  195. package/dist/hono/flow-input.test.js +67 -11
  196. package/dist/hono/flow-input.test.js.map +1 -1
  197. package/dist/hono/jev.d.ts +16 -0
  198. package/dist/hono/jev.d.ts.map +1 -0
  199. package/dist/hono/jev.js +60 -0
  200. package/dist/hono/jev.js.map +1 -0
  201. package/dist/hono/jev.test.d.ts +2 -0
  202. package/dist/hono/jev.test.d.ts.map +1 -0
  203. package/dist/hono/jev.test.js +15 -0
  204. package/dist/hono/jev.test.js.map +1 -0
  205. package/dist/hono/knowledge-content.d.ts.map +1 -1
  206. package/dist/hono/knowledge-content.js +4 -2
  207. package/dist/hono/knowledge-content.js.map +1 -1
  208. package/dist/hono/knowledge-content.test.js +6 -5
  209. package/dist/hono/knowledge-content.test.js.map +1 -1
  210. package/dist/hono/mcp-servers.d.ts +8 -1
  211. package/dist/hono/mcp-servers.d.ts.map +1 -1
  212. package/dist/hono/mcp-servers.js +17 -6
  213. package/dist/hono/mcp-servers.js.map +1 -1
  214. package/dist/hono/mcp-servers.test.js +25 -0
  215. package/dist/hono/mcp-servers.test.js.map +1 -1
  216. package/dist/hono/model-capabilities.d.ts.map +1 -1
  217. package/dist/hono/model-capabilities.js +20 -9
  218. package/dist/hono/model-capabilities.js.map +1 -1
  219. package/dist/hono/model-capabilities.test.js +16 -8
  220. package/dist/hono/model-capabilities.test.js.map +1 -1
  221. package/dist/hono/oom-priority.agent.test.d.ts +2 -0
  222. package/dist/hono/oom-priority.agent.test.d.ts.map +1 -0
  223. package/dist/hono/oom-priority.agent.test.js +138 -0
  224. package/dist/hono/oom-priority.agent.test.js.map +1 -0
  225. package/dist/hono/oom-priority.d.ts +39 -0
  226. package/dist/hono/oom-priority.d.ts.map +1 -0
  227. package/dist/hono/oom-priority.js +138 -0
  228. package/dist/hono/oom-priority.js.map +1 -0
  229. package/dist/hono/oom-priority.test.d.ts +2 -0
  230. package/dist/hono/oom-priority.test.d.ts.map +1 -0
  231. package/dist/hono/oom-priority.test.js +52 -0
  232. package/dist/hono/oom-priority.test.js.map +1 -0
  233. package/dist/hono/openrouter-session.d.ts.map +1 -1
  234. package/dist/hono/openrouter-session.js +12 -1
  235. package/dist/hono/openrouter-session.js.map +1 -1
  236. package/dist/hono/openrouter-session.test.js +9 -3
  237. package/dist/hono/openrouter-session.test.js.map +1 -1
  238. package/dist/hono/prompt-context.d.ts +8 -1
  239. package/dist/hono/prompt-context.d.ts.map +1 -1
  240. package/dist/hono/prompt-context.js +45 -7
  241. package/dist/hono/prompt-context.js.map +1 -1
  242. package/dist/hono/prompt-context.test.js +32 -1
  243. package/dist/hono/prompt-context.test.js.map +1 -1
  244. package/dist/hono/publish-recovery.d.ts +1 -1
  245. package/dist/hono/publish-recovery.d.ts.map +1 -1
  246. package/dist/hono/publish-recovery.js +9 -6
  247. package/dist/hono/publish-recovery.js.map +1 -1
  248. package/dist/hono/publish-recovery.test.js +11 -2
  249. package/dist/hono/publish-recovery.test.js.map +1 -1
  250. package/dist/hono/relay.d.ts +10 -1
  251. package/dist/hono/relay.d.ts.map +1 -1
  252. package/dist/hono/relay.js +6 -0
  253. package/dist/hono/relay.js.map +1 -1
  254. package/dist/hono/sandbox-content.d.ts +2 -0
  255. package/dist/hono/sandbox-content.d.ts.map +1 -1
  256. package/dist/hono/sandbox-content.js +8 -4
  257. package/dist/hono/sandbox-content.js.map +1 -1
  258. package/dist/hono/sandbox-content.test.js +20 -1
  259. package/dist/hono/sandbox-content.test.js.map +1 -1
  260. package/dist/hono/server.d.ts.map +1 -1
  261. package/dist/hono/server.js +32 -0
  262. package/dist/hono/server.js.map +1 -1
  263. package/dist/hono/server.test.js +21 -1
  264. package/dist/hono/server.test.js.map +1 -1
  265. package/dist/hono/session-continuity.integration.test.js +10 -1
  266. package/dist/hono/session-continuity.integration.test.js.map +1 -1
  267. package/dist/hono/tag-fields.d.ts.map +1 -1
  268. package/dist/hono/tag-fields.js +2 -0
  269. package/dist/hono/tag-fields.js.map +1 -1
  270. package/dist/hono/tag-input.d.ts +124 -3
  271. package/dist/hono/tag-input.d.ts.map +1 -1
  272. package/dist/hono/tag-input.js +27 -17
  273. package/dist/hono/tag-input.js.map +1 -1
  274. package/dist/hono/tag-input.test.js +19 -1
  275. package/dist/hono/tag-input.test.js.map +1 -1
  276. package/dist/hono/tool-mentions.d.ts.map +1 -1
  277. package/dist/hono/tool-mentions.js +1 -0
  278. package/dist/hono/tool-mentions.js.map +1 -1
  279. package/dist/hono/tool-mentions.test.js +5 -0
  280. package/dist/hono/tool-mentions.test.js.map +1 -1
  281. package/dist/hono/types.d.ts +8 -0
  282. package/dist/hono/types.d.ts.map +1 -1
  283. package/dist/hono/workspace-script.d.ts +10 -0
  284. package/dist/hono/workspace-script.d.ts.map +1 -0
  285. package/dist/hono/workspace-script.js +19 -0
  286. package/dist/hono/workspace-script.js.map +1 -0
  287. package/dist/hono/workspace-settle.d.ts +1 -1
  288. package/dist/hono/workspace-settle.d.ts.map +1 -1
  289. package/dist/hono/workspace-settle.js +12 -8
  290. package/dist/hono/workspace-settle.js.map +1 -1
  291. package/dist/review/digest.d.ts +8 -1
  292. package/dist/review/digest.d.ts.map +1 -1
  293. package/dist/review/digest.js +10 -43
  294. package/dist/review/digest.js.map +1 -1
  295. package/dist/review/digest.test.js +11 -6
  296. package/dist/review/digest.test.js.map +1 -1
  297. package/dist/review/evidence-block.js +1 -1
  298. package/dist/review/evidence-block.js.map +1 -1
  299. package/dist/review/evidence-block.test.js +0 -10
  300. package/dist/review/evidence-block.test.js.map +1 -1
  301. package/dist/review/shell-policy.d.ts +6 -0
  302. package/dist/review/shell-policy.d.ts.map +1 -1
  303. package/dist/review/shell-policy.js +240 -72
  304. package/dist/review/shell-policy.js.map +1 -1
  305. package/dist/review/shell-policy.test.js +114 -0
  306. package/dist/review/shell-policy.test.js.map +1 -1
  307. package/package.json +9 -5
  308. package/dist/hono/latitude-telemetry.d.ts +0 -17
  309. package/dist/hono/latitude-telemetry.d.ts.map +0 -1
  310. package/dist/hono/latitude-telemetry.js +0 -52
  311. package/dist/hono/latitude-telemetry.js.map +0 -1
  312. package/dist/hono/latitude-telemetry.test.d.ts +0 -2
  313. package/dist/hono/latitude-telemetry.test.d.ts.map +0 -1
  314. package/dist/hono/latitude-telemetry.test.js +0 -18
  315. package/dist/hono/latitude-telemetry.test.js.map +0 -1
@@ -11,15 +11,18 @@ import { avatarAwaitingItsFace, avatarCreatedInAssistantMessage, avatarHandleFro
11
11
  import { accentContradictsUser } from "./avatar-accent.js";
12
12
  import { AVATAR_CASTING_TOOL_NAME, normalizeAvatarCastingToolInput, } from "./avatar-casting.js";
13
13
  import { asksForAvatarFace, FACE_QUESTION_MESSAGE } from "./avatar-face-question.js";
14
- import { CONNECTION_INPUT_TOOL_NAME, CONNECTION_PLATFORMS, normalizeConnectionInputToolInput, } from "./connection-input.js";
14
+ import { CONNECTION_INPUT_TOOL_NAME, CONNECTION_PLATFORMS, connectedButNotLoaded, normalizeConnectionInputToolInput, toolsConnected, } from "./connection-input.js";
15
15
  import { messageContextTokens, pickContextWindow, streamEventContextUsage, turnContextUsage, } from "./context-usage.js";
16
- import { FLOW_INPUT_TOOL_NAME, nodeTargetRefusals, normalizeFlowInputToolInput, readRawFlowTree, sanitizeFlowRequests, } from "./flow-input.js";
16
+ import { CREDENTIAL_INPUT_TOOL_NAME, normalizeCredentialInputToolInput, } from "./credential-input.js";
17
+ import { credentialScrubHooks } from "./credential-scrub.js";
18
+ import { agentStatedFlowMessage, FLOW_INPUT_TOOL_NAME, nodeTargetRefusals, normalizeFlowInputToolInput, readRawFlowTree, sanitizeFlowRequests, unknownFlowPrefillMessage, } from "./flow-input.js";
17
19
  import { writesInventedLogo } from "./invented-logo.js";
18
- import { installedLatitudeHook, latitudeTelemetryFor } from "./latitude-telemetry.js";
20
+ import { jevJudge } from "./jev.js";
19
21
  import { composioScopesToInvalidate, failedCustomServers, reportMcpInvalid, serversConnectedWithNoTools, } from "./mcp-servers.js";
20
22
  import { agentModelOverride, autoCompactWindowForModel, betasForModel, contextWindowForModel, } from "./model-capabilities.js";
23
+ import { agentShellPrefixEnv, killFirstWrapper } from "./oom-priority.js";
21
24
  import { BEDROCK_SERVABLE_CAPABILITIES, buildAgentExtraBody, EXTRA_BODY_ENV, MODEL_CAPABILITIES_ENV, } from "./openrouter-session.js";
22
- import { currentReferenceReminder } from "./prompt-context.js";
25
+ import { credentialReminder, currentReferenceReminder } from "./prompt-context.js";
23
26
  import { bridgeRuntimeFiles } from "./runtime-files.js";
24
27
  import { applyCurrentSenderAttribution, buildCleanBoundary, buildRehydratedPrompt, compactionRetryAttempt, emptyInProgressSession, isContextOverflowError, isResumeSessionError, observeInProgressSession, prepareSessionAttempt, stallContinuationAttempt, } from "./session-continuity.js";
25
28
  import { readSessionSkills } from "./session-skills.js";
@@ -44,6 +47,35 @@ export function parsePlanDecision(value) {
44
47
  }
45
48
  return null;
46
49
  }
50
+ /**
51
+ * How long a mid-turn MCP reload may hold the card's answer. Connecting a
52
+ * handful of HTTP servers takes seconds; past this the tools arrive next
53
+ * message instead, and the turn goes on.
54
+ */
55
+ const MCP_RELOAD_TIMEOUT_MS = 30_000;
56
+ /** The card's resolution, with what the reload did: ready now, or which servers would not start. */
57
+ function withReload(resolution, reload) {
58
+ const missing = reload?.ready ? connectedButNotLoaded(resolution, reload.loaded) : [];
59
+ const failed = [...(reload?.failed ?? []), ...missing];
60
+ return {
61
+ ...resolution,
62
+ toolsReady: reload?.ready === true && missing.length === 0,
63
+ ...(failed.length > 0 ? { failedToLoad: failed } : {}),
64
+ };
65
+ }
66
+ /** The promise's value, or `undefined` once `ms` pass without one. */
67
+ async function settleWithin(promise, ms) {
68
+ let timer;
69
+ const timedOut = new Promise((resolve) => {
70
+ timer = setTimeout(() => resolve(undefined), ms);
71
+ });
72
+ try {
73
+ return await Promise.race([promise, timedOut]);
74
+ }
75
+ finally {
76
+ clearTimeout(timer);
77
+ }
78
+ }
47
79
  /**
48
80
  * The Agent SDK matches AskUserQuestion answers to their questions by the
49
81
  * question TEXT (see `AskUserQuestionInput.answers`), but the dashboard submits
@@ -118,11 +150,10 @@ const BASE_DISALLOWED_TOOLS = [
118
150
  //
119
151
  // Since CLI 2.1.233 the CLI additionally withholds these from
120
152
  // Sonnet-5-and-newer models — so on Opus 5.5 this list is now
121
- // belt-and-braces. It is NOT redundant, because development runs on
122
- // Haiku 4.5, which predates that gate: read off the session's own
123
- // `init` tool list on 2.1.239, Haiku is offered all four and Opus 5
124
- // none. Removing them would hand the dev agent the backlog the
125
- // production agent cannot see.
153
+ // belt-and-braces. It is NOT redundant: Haiku 4.5 predates that gate
154
+ // (read off the session's own `init` tool list on 2.1.239, it is offered
155
+ // all four and Opus 5 none), and any override naming it would hand that
156
+ // agent the backlog the production agent cannot see.
126
157
  //
127
158
  // `TodoWrite` is gone from both — it is absent from an SDK session's
128
159
  // tool list on either model, so in-turn planning no longer has a tool
@@ -191,6 +222,16 @@ export function reviewQueryOptions(job) {
191
222
  ? { outputFormat: { type: "json_schema", schema: job.review.outputSchema }, hooks: reviewOutputHooks() }
192
223
  : {};
193
224
  }
225
+ /**
226
+ * Every job's tool results pass through the credential scrub, appended after
227
+ * any other PostToolUse hook so its rewrite is the one the SDK keeps.
228
+ */
229
+ function withCredentialScrub(options) {
230
+ const scrub = credentialScrubHooks();
231
+ const hooks = { ...(options.hooks ?? {}) };
232
+ hooks.PostToolUse = [...(hooks.PostToolUse ?? []), ...(scrub.PostToolUse ?? [])];
233
+ return { ...options, hooks };
234
+ }
194
235
  /**
195
236
  * The review runs on Baker's own OpenRouter credential and its own read-only
196
237
  * key, both replacing whatever the Runtime was started with — the client never
@@ -217,6 +258,12 @@ export const REVIEW_DEBUG_LOG = "/tmp/baker-review-cli.jsonl";
217
258
  // briefly; model activity cancels it (the CLI clearly isn't idle). Turns that
218
259
  // never ran a task skip the delay entirely.
219
260
  const DEFAULT_TURN_END_SETTLE_MS = 1_000;
261
+ // When the last task settles after the clean result, the CLI wakes the model on
262
+ // it and streams nothing until the woken turn's first token — seconds, and 30+
263
+ // on a slow route. Ending input inside that silence strands the woken turn on a
264
+ // closed channel, and the CLI's exit at its result kills anything it backgrounds
265
+ // (dev, Oct 2026: a render). A settle that wakes nobody waits this long once.
266
+ const DEFAULT_TURN_END_WAKE_MS = 45_000;
220
267
  // After input ends, a healthy idle CLI exits within seconds. If the stream is
221
268
  // still open past this window with no messages arriving, the CLI is wedged —
222
269
  // close the query so the turn can finish instead of hanging to the watchdog.
@@ -244,10 +291,10 @@ const UNATTENDED_PROMPT_MESSAGE = "This run is unattended — it was started by
244
291
  * asked for in a text prompt renders a question nobody can answer. In each case the user
245
292
  * is left waiting for something that was never going to come.
246
293
  */
247
- function refuseMalformedCall(toolName, input, userText, priorUserText) {
294
+ async function refuseMalformedCall(toolName, input, judge, userText, priorUserText) {
248
295
  if (toolName === "Bash") {
249
296
  const command = typeof input?.command === "string" ? input.command : undefined;
250
- return toolRunAsCommand(command) ?? accentContradictsUser(command, userText, priorUserText);
297
+ return toolRunAsCommand(command) ?? (await accentContradictsUser(command, userText, priorUserText, judge));
251
298
  }
252
299
  if (toolName === "Agent" || toolName === "Task") {
253
300
  return blockingToolDelegated(typeof input?.prompt === "string" ? input.prompt : undefined);
@@ -255,11 +302,18 @@ function refuseMalformedCall(toolName, input, userText, priorUserText) {
255
302
  if (toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
256
303
  return writesInventedLogo(typeof input?.file_path === "string" ? input.file_path : undefined);
257
304
  }
258
- if (toolName === "AskUserQuestion" && asksForAvatarFace(input)) {
305
+ if (toolName === "AskUserQuestion" && (await asksForAvatarFace(input, judge))) {
259
306
  return FACE_QUESTION_MESSAGE;
260
307
  }
261
308
  return null;
262
309
  }
310
+ /**
311
+ * Whether this Runtime has Credentials: the backend sets `BAKER_CREDENTIALS=1`
312
+ * alongside the credential broker's proxy env. Read per turn, not at import.
313
+ */
314
+ function credentialsEnabled() {
315
+ return process.env.BAKER_CREDENTIALS === "1";
316
+ }
263
317
  /** Blocking prompts: every tool that would leave a card the backend parks on. */
264
318
  function deniedUnattendedPrompt(toolName) {
265
319
  return (toolName === "AskUserQuestion" ||
@@ -267,6 +321,7 @@ function deniedUnattendedPrompt(toolName) {
267
321
  toolName === TAG_INPUT_TOOL_NAME ||
268
322
  toolName === FLOW_INPUT_TOOL_NAME ||
269
323
  toolName === CONNECTION_INPUT_TOOL_NAME ||
324
+ toolName === CREDENTIAL_INPUT_TOOL_NAME ||
270
325
  toolName === AVATAR_CASTING_TOOL_NAME);
271
326
  }
272
327
  export class StreamStallError extends Error {
@@ -343,8 +398,7 @@ function enrichImageError(message) {
343
398
  return `${message} — likely a file whose extension lies about its format (e.g. PNG renamed .jpg) or a corrupted download. Run the image validator and check magic bytes with \`file <path>\`.`;
344
399
  }
345
400
  /**
346
- * Which hooks a job runs. The client's agent runs the workspace's own and is
347
- * traced to Latitude. A Session review runs none: every workspace hook is
401
+ * Which hooks a job runs. The client's agent runs the workspace's own. A Session review runs none: every workspace hook is
348
402
  * written for the client's agent, and in the reviewer they speak about "this
349
403
  * Session" with the review's read-scoped key. The SessionStart probes are
350
404
  * refused on full-scope routes, and the reviewer filed its own "status unknown"
@@ -361,12 +415,11 @@ function hookSettingsForJob(job) {
361
415
  if (job.review) {
362
416
  return { disableAllHooks: true, permissions: { ask: ["Bash"] } };
363
417
  }
364
- const hooks = latitudeTelemetryFor(process.env, installedLatitudeHook());
365
- return hooks ? { hooks } : {};
418
+ return {};
366
419
  }
367
420
  // Carry a Runtime-wide model override down to the fan-out workers.
368
421
  //
369
- // The shared subagents pin themselves to Sonnet 5 in their own frontmatter (see
422
+ // The shared subagents pin themselves to Sonnet 5.5 in their own frontmatter (see
370
423
  // `DEFAULT_MODEL`). Since CLI 2.1.251 `CLAUDE_CODE_SUBAGENT_MODEL` is only the
371
424
  // default beneath that pin; `CLAUDE_CODE_SUBAGENT_MODEL_FORCE` (2.1.257) makes it
372
425
  // win over the frontmatter and any per-spawn model again. Absent (production),
@@ -660,6 +713,11 @@ export function createStallDetector(input) {
660
713
  timer.unref?.();
661
714
  return {
662
715
  observeMessage: (msg) => {
716
+ // A tool's progress says nothing about the model: a background task's
717
+ // heartbeat arrives while the model's stream is stalled.
718
+ if (msg.type === "tool_progress") {
719
+ return;
720
+ }
663
721
  lastMessageAt = input.now();
664
722
  trackPendingToolUses(pendingToolUses, msg);
665
723
  awaitingModel = (msg.type === "user" || msg.type === "stream_event") && pendingToolUses.size === 0;
@@ -788,7 +846,7 @@ beforeEndInput) {
788
846
  }, timings.exitGuardMs);
789
847
  exitGuard.unref?.();
790
848
  };
791
- const scheduleEnd = () => {
849
+ const scheduleEnd = (delayMs = timings.settleMs) => {
792
850
  if (settleTimer) {
793
851
  return;
794
852
  }
@@ -800,7 +858,7 @@ beforeEndInput) {
800
858
  endInput();
801
859
  return;
802
860
  }
803
- settleTimer = setTimeout(endInput, timings.settleMs);
861
+ settleTimer = setTimeout(endInput, delayMs);
804
862
  settleTimer.unref?.();
805
863
  };
806
864
  return {
@@ -866,7 +924,7 @@ beforeEndInput) {
866
924
  // second result) would strand the turn until the watchdog. An emptied
867
925
  // background_tasks_changed level counts as a settle the same way.
868
926
  if (lastWasTaskLifecycle && cleanResultSeen && !hasInFlightWork()) {
869
- scheduleEnd();
927
+ scheduleEnd(timings.wakeMs);
870
928
  }
871
929
  },
872
930
  hasInFlightWork,
@@ -959,12 +1017,14 @@ export class BridgeJobExecutor {
959
1017
  stallCheckIntervalMs;
960
1018
  recordTurnEvent;
961
1019
  turnEndSettleMs;
1020
+ turnEndWakeMs;
962
1021
  turnEndExitGuardMs;
963
1022
  now;
964
1023
  readUsageBaseline;
965
1024
  stalledCliExitMs;
966
1025
  readFlowTree;
967
1026
  settleWorkspace;
1027
+ judge;
968
1028
  constructor(dependencies) {
969
1029
  this.sdk = dependencies.sdk;
970
1030
  this.relay = dependencies.relay;
@@ -977,6 +1037,7 @@ export class BridgeJobExecutor {
977
1037
  this.stallCheckIntervalMs = dependencies.stallCheckIntervalMs ?? DEFAULT_STALL_CHECK_INTERVAL_MS;
978
1038
  this.recordTurnEvent = dependencies.recordTurnEvent ?? recordTurnEvent;
979
1039
  this.turnEndSettleMs = dependencies.turnEndSettleMs ?? DEFAULT_TURN_END_SETTLE_MS;
1040
+ this.turnEndWakeMs = dependencies.turnEndWakeMs ?? DEFAULT_TURN_END_WAKE_MS;
980
1041
  this.turnEndExitGuardMs = dependencies.turnEndExitGuardMs ?? DEFAULT_TURN_END_EXIT_GUARD_MS;
981
1042
  this.now = dependencies.now ?? Date.now;
982
1043
  this.readUsageBaseline =
@@ -986,6 +1047,7 @@ export class BridgeJobExecutor {
986
1047
  this.settleWorkspace = dependencies.settleWorkspace ?? (() => settleWorkspaceAfterTasks());
987
1048
  this.readFlowTree =
988
1049
  dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
1050
+ this.judge = dependencies.judge ?? jevJudge(process.env.OPENROUTER_API_KEY);
989
1051
  }
990
1052
  async execute(job, controls) {
991
1053
  const state = createBridgeJobState();
@@ -1058,7 +1120,11 @@ export class BridgeJobExecutor {
1058
1120
  const verbatim = verbatimSlashCommand(job.prompt) ??
1059
1121
  leadingSkillCommand(job.prompt, (await readSessionSkills(bridgeRuntimeFiles.projectRoot())) ?? []);
1060
1122
  let requestPrompt = verbatim ?? applyCurrentSenderAttribution(job.prompt, job.currentSender);
1061
- const reminder = verbatim === null ? currentReferenceReminder(job.currentReference) : undefined;
1123
+ const reminders = [
1124
+ verbatim === null ? currentReferenceReminder(job.currentReference) : undefined,
1125
+ credentialReminder(job.credentials ?? [], credentialsEnabled()),
1126
+ ].filter((text) => text !== undefined);
1127
+ const reminder = reminders.length > 0 ? reminders.join("\n\n") : undefined;
1062
1128
  if (job.attachments && job.attachments.length > 0) {
1063
1129
  requestPrompt += await this.prepareAttachments(job.attachments, "message");
1064
1130
  }
@@ -1315,6 +1381,39 @@ export class BridgeJobExecutor {
1315
1381
  return undefined;
1316
1382
  }
1317
1383
  }
1384
+ /**
1385
+ * Load what the user just connected into the running query, so its tools
1386
+ * answer in this turn instead of the next. Asks Convex for the whole set
1387
+ * afresh (the same set the next turn starts from, so the tool list changes
1388
+ * once, not twice) and hands it to the CLI with `baker_ui` kept — the SDK
1389
+ * drops any in-process server missing from the call, and `baker_ui` is the
1390
+ * server whose tool call is resolving right now. Never throws. A server the
1391
+ * CLI could not start is named, because it will not start next message
1392
+ * either; anything else that goes wrong here loads next message.
1393
+ */
1394
+ async reloadMcpServers(job, query) {
1395
+ const notReady = { ready: false, loaded: [], failed: [] };
1396
+ if (!this.mcpServersProvider || !query.setMcpServers) {
1397
+ return notReady;
1398
+ }
1399
+ try {
1400
+ const servers = await this.mcpServersProvider(job.threadId, job.currentSender?.userId, { fresh: true });
1401
+ if (!servers) {
1402
+ return notReady;
1403
+ }
1404
+ const result = await settleWithin(query.setMcpServers({ ...servers, baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) }), MCP_RELOAD_TIMEOUT_MS);
1405
+ const failed = Object.keys(result?.errors ?? {});
1406
+ if (failed.length > 0) {
1407
+ console.error(`[mcp] reload after a connection could not start ${failed.join(", ")}`);
1408
+ }
1409
+ const loaded = Object.keys(servers).filter((name) => !failed.includes(name));
1410
+ return { ready: result !== undefined && failed.length === 0, loaded, failed };
1411
+ }
1412
+ catch (error) {
1413
+ console.error(`[mcp] reload after a connection failed: ${error instanceof Error ? error.message : String(error)}`);
1414
+ return notReady;
1415
+ }
1416
+ }
1318
1417
  reportMcpFailures(job, msg) {
1319
1418
  for (const scope of composioScopesToInvalidate(msg)) {
1320
1419
  void reportMcpInvalid(job.threadId, { scope }, job.currentSender?.userId);
@@ -1334,7 +1433,7 @@ export class BridgeJobExecutor {
1334
1433
  * handlers — except on an unattended turn, where there is nobody to answer and
1335
1434
  * every one of them is refused up front (see `UNATTENDED_PROMPT_MESSAGE`).
1336
1435
  */
1337
- routeToolUse(job, controls, toolName, input, opts) {
1436
+ async routeToolUse(job, controls, toolName, input, opts) {
1338
1437
  const refusedShell = reviewShellRefusal(job, toolName, input);
1339
1438
  if (refusedShell) {
1340
1439
  return Promise.resolve({ behavior: "deny", message: refusedShell });
@@ -1350,9 +1449,9 @@ export class BridgeJobExecutor {
1350
1449
  });
1351
1450
  return Promise.resolve({ behavior: "deny", message: UNATTENDED_PROMPT_MESSAGE });
1352
1451
  }
1353
- const malformed = refuseMalformedCall(toolName, input, job.prompt, userTurnsBefore(job));
1452
+ const malformed = await refuseMalformedCall(toolName, input, this.judge, job.prompt, userTurnsBefore(job));
1354
1453
  if (malformed)
1355
- return Promise.resolve({ behavior: "deny", message: malformed });
1454
+ return { behavior: "deny", message: malformed };
1356
1455
  // Cast what is owed FIRST, whatever comes next — including another create. Checked
1357
1456
  // the other way round, a second create (the agent retrying after a rejected flag,
1358
1457
  // which is what happens in practice) would overwrite the handle and the first
@@ -1405,6 +1504,15 @@ export class BridgeJobExecutor {
1405
1504
  if (toolName === CONNECTION_INPUT_TOOL_NAME) {
1406
1505
  return this.handleConnectionInputRequest(job, controls, input, opts.toolUseID, opts.signal);
1407
1506
  }
1507
+ if (toolName === CREDENTIAL_INPUT_TOOL_NAME) {
1508
+ if (!credentialsEnabled()) {
1509
+ return Promise.resolve({
1510
+ behavior: "deny",
1511
+ message: "Saved credentials are not available in this workspace. Ask the user to share what you need another way, and never ask them to paste a password or key into the chat.",
1512
+ });
1513
+ }
1514
+ return this.handleCredentialInputRequest(job, controls, input, opts.toolUseID, opts.signal);
1515
+ }
1408
1516
  if (toolName === AVATAR_CASTING_TOOL_NAME) {
1409
1517
  return this.handleAvatarCastingRequest(job, controls, input, opts.toolUseID, opts.signal);
1410
1518
  }
@@ -1443,6 +1551,10 @@ export class BridgeJobExecutor {
1443
1551
  // warning to the user. Pinning it takes that threshold out of remote control.
1444
1552
  // See `autoCompactWindowForModel` for the full chain and its ceiling.
1445
1553
  ...(autoCompactWindow !== undefined ? { autoCompactWindow } : {}),
1554
+ // Subagents, workflows and helper requests default to a 5-minute prompt
1555
+ // cache outside a Claude subscription, so a subagent waiting on a render
1556
+ // longer than that rewrites its whole prefix on the next call.
1557
+ subagentPromptCacheTtl: "1h",
1446
1558
  // SDK rejects autoMemoryDirectory from project settings for security; must use settings tier. Gate on BAKER_CHAT_ID so local dev never writes into the monorepo.
1447
1559
  ...(process.env.BAKER_CHAT_ID
1448
1560
  ? {
@@ -1475,11 +1587,8 @@ export class BridgeJobExecutor {
1475
1587
  settingSources: ["project"],
1476
1588
  // `baker_ui` hosts in-process interactive dashboard tools (secure tag
1477
1589
  // form). Always present; HTTP (Composio) servers merge alongside it.
1478
- mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer() },
1479
- // Only send the key when there is something in it: the SDK forwards
1480
- // `settings` to the CLI whenever it is present, so an empty object still
1481
- // becomes a `--settings {}` flag tier for the CLI to parse.
1482
- ...(Object.keys(settings).length > 0 ? { settings } : {}),
1590
+ mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) },
1591
+ settings,
1483
1592
  includePartialMessages: true,
1484
1593
  // Only the workspace's own skills. Omitting the option is NOT "skills
1485
1594
  // off" — the CLI's defaults still load its built-in developer skills
@@ -1488,8 +1597,11 @@ export class BridgeJobExecutor {
1488
1597
  // the workspace could not be read; omit rather than allowlist nothing.
1489
1598
  ...(skills ? { skills } : {}),
1490
1599
  disallowedTools: disallowedToolsFor(job),
1491
- ...reviewQueryOptions(job),
1492
- ...(job.planMode ? { permissionMode: "plan" } : {}),
1600
+ ...withCredentialScrub(reviewQueryOptions(job)),
1601
+ // Never omitted: since SDK 0.3.286 an omitted mode starts a third-party-provider
1602
+ // session (OpenRouter) in auto mode, where a classifier rather than `canUseTool`
1603
+ // decides tool calls.
1604
+ permissionMode: job.planMode ? "plan" : "default",
1493
1605
  canUseTool: (toolName, input, opts) => this.routeToolUse(job, controls, toolName, input, opts),
1494
1606
  // No `fallbackModel`, and no server-side refusal `fallbacks` in the extra
1495
1607
  // body: the CLI's refusal fallback is gated on a first-party base URL (see
@@ -1513,6 +1625,13 @@ export class BridgeJobExecutor {
1513
1625
  // system-reminder repeatedly pushed the agent toward that ephemeral
1514
1626
  // backlog instead of the product's own Tasks (`baker actions`).
1515
1627
  CLAUDE_CODE_ENABLE_TASKS: "0",
1628
+ // Every Bash command runs through a wrapper that makes it, not the agent
1629
+ // or the credential broker, what the OOM killer takes first (oom-priority.ts).
1630
+ ...agentShellPrefixEnv(killFirstWrapper(), process.env),
1631
+ // The CLI clamps any Bash timeout to 10 minutes unless told otherwise;
1632
+ // the workspace's `inject-timeouts` hook gives a render up to 30, and at
1633
+ // the clamp the render was moved to the background mid-repair.
1634
+ BASH_MAX_TIMEOUT_MS: String(30 * 60_000),
1516
1635
  // `citty` writes ANSI escapes into `baker --help` unconditionally —
1517
1636
  // they are never TTY-gated, so every help read the agent does inside
1518
1637
  // the sandbox pays ~5-10% of its tokens on colour codes it can't see.
@@ -1534,6 +1653,7 @@ export class BridgeJobExecutor {
1534
1653
  ...attempt.resumeOptions,
1535
1654
  };
1536
1655
  const query = this.sdk.query({ prompt: attempt.prompt, reminder: attempt.reminder, options });
1656
+ controls.reloadMcpServers = () => this.reloadMcpServers(job, query);
1537
1657
  let queryClosed = false;
1538
1658
  const closeQuery = () => {
1539
1659
  if (queryClosed) {
@@ -1555,6 +1675,7 @@ export class BridgeJobExecutor {
1555
1675
  throw error;
1556
1676
  }
1557
1677
  finally {
1678
+ controls.reloadMcpServers = undefined;
1558
1679
  controls.abortSignal.removeEventListener("abort", closeQuery);
1559
1680
  // Release the held-open input if the turn-end policy never fired (the
1560
1681
  // stream ended on its own, or an error path closed the query). Idempotent.
@@ -1585,7 +1706,7 @@ export class BridgeJobExecutor {
1585
1706
  // A reading that answers after the query drained (past its timeout) belongs
1586
1707
  // to a query already folded into the turn — and a retry reuses `state`.
1587
1708
  let drained = false;
1588
- const turnEnd = createTurnEndController(job, query, closeQuery, { settleMs: this.turnEndSettleMs, exitGuardMs: this.turnEndExitGuardMs }, this.recordTurnEvent, async () => {
1709
+ const turnEnd = createTurnEndController(job, query, closeQuery, { settleMs: this.turnEndSettleMs, wakeMs: this.turnEndWakeMs, exitGuardMs: this.turnEndExitGuardMs }, this.recordTurnEvent, async () => {
1589
1710
  const reading = await query.usage?.();
1590
1711
  if (reading && !drained) {
1591
1712
  settledCostUsd = recordCumulativeUsage(state, reading);
@@ -1693,6 +1814,7 @@ export class BridgeJobExecutor {
1693
1814
  controls.pendingTagInputs.size > 0 ||
1694
1815
  controls.pendingFlowInputs.size > 0 ||
1695
1816
  controls.pendingConnectionInputs.size > 0 ||
1817
+ controls.pendingCredentialInputs.size > 0 ||
1696
1818
  controls.pendingAvatarCastings.size > 0,
1697
1819
  onStall: () => {
1698
1820
  console.error(`[BridgeJob] no stream data for ${this.stallTimeoutMs}ms while awaiting the model — closing query for retry (thread ${job.threadId})`);
@@ -2121,6 +2243,16 @@ export class BridgeJobExecutor {
2121
2243
  // Strip any secret values the model tried to prefill; their names still reach
2122
2244
  // the user as requested fields. The backend strips again.
2123
2245
  const sanitizedRequests = sanitizeFlowRequests(requests);
2246
+ // A key the card does not render is dropped there without a word, and the
2247
+ // person retypes what the agent already knew — refuse it before the card opens.
2248
+ const stated = agentStatedFlowMessage(sanitizedRequests);
2249
+ if (stated) {
2250
+ return { behavior: "deny", message: stated };
2251
+ }
2252
+ const unknownPrefill = unknownFlowPrefillMessage(sanitizedRequests);
2253
+ if (unknownPrefill) {
2254
+ return { behavior: "deny", message: unknownPrefill };
2255
+ }
2124
2256
  const posted = await this.relay.flowInputRequest(target, { toolUseId, requests: sanitizedRequests });
2125
2257
  if (!posted) {
2126
2258
  console.error(`[request_flow_input] failed to post flow-input-request for ${toolUseId} — denying to avoid a hang`);
@@ -2171,7 +2303,7 @@ export class BridgeJobExecutor {
2171
2303
  if (connections.length === 0) {
2172
2304
  return {
2173
2305
  behavior: "deny",
2174
- message: `request_connection requires \`connections: [...]\` with at least one entry { platform, reason }. Platform must be one of ${CONNECTION_PLATFORMS.join(", ")}. Retry with that shape.`,
2306
+ message: `request_connection requires \`connections: [...]\` with at least one entry: { platform, reason } where platform is one of ${CONNECTION_PLATFORMS.join(", ")}; { server: { name, url, auth?, keyHeader?, scope? }, reason } for a remote MCP server; or { app, reason } for a managed app from \`baker mcp catalog\`. Retry with that shape.`,
2175
2307
  };
2176
2308
  }
2177
2309
  const posted = await this.relay.connectionInputRequest(target, { toolUseId, connections });
@@ -2193,7 +2325,43 @@ export class BridgeJobExecutor {
2193
2325
  return { behavior: "deny", message: PROMPT_UNANSWERED_MESSAGE };
2194
2326
  }
2195
2327
  void this.relay.connectionInputResolved(target);
2196
- return { behavior: "allow", updatedInput: { connections, resolution: decision.value } };
2328
+ // Loaded before the tool answers, so the model's next request already
2329
+ // carries the new tools. Only when an app or server came through: a
2330
+ // platform adds no tool, and reloading for nothing rewrites the tool list.
2331
+ const resolution = toolsConnected(decision.value)
2332
+ ? withReload(decision.value, await controls.reloadMcpServers?.())
2333
+ : decision.value;
2334
+ return { behavior: "allow", updatedInput: { connections, resolution } };
2335
+ }
2336
+ async handleCredentialInputRequest(job, controls, input, toolUseId, signal) {
2337
+ const target = { threadId: job.threadId, turnId: job.turnId };
2338
+ const request = normalizeCredentialInputToolInput(input);
2339
+ if (!request) {
2340
+ return {
2341
+ behavior: "deny",
2342
+ message: "request_credential requires { label, reason, allowedHosts: [...] } (and optionally a lowercase `name`). Retry with that shape.",
2343
+ };
2344
+ }
2345
+ const posted = await this.relay.credentialInputRequest(target, { toolUseId, ...request });
2346
+ if (!posted) {
2347
+ console.error(`[request_credential] failed to post credential-input-request for ${toolUseId} — denying`);
2348
+ return {
2349
+ behavior: "deny",
2350
+ message: "The credential request could not be delivered to the dashboard — retry request_credential in a moment.",
2351
+ };
2352
+ }
2353
+ const decision = await this.awaitPromptDecision({
2354
+ label: "request_credential",
2355
+ signal,
2356
+ controls,
2357
+ pending: controls.pendingCredentialInputs,
2358
+ toolUseId,
2359
+ });
2360
+ if (decision.kind === "unanswered") {
2361
+ return { behavior: "deny", message: PROMPT_UNANSWERED_MESSAGE };
2362
+ }
2363
+ void this.relay.credentialInputResolved(target);
2364
+ return { behavior: "allow", updatedInput: { ...request, resolution: decision.value } };
2197
2365
  }
2198
2366
  async handlePlanReview(job, controls, input, toolUseId, signal) {
2199
2367
  const target = { threadId: job.threadId, turnId: job.turnId };