@koda-sl/baker-bridge 0.110.0-dev.6a86a7961 → 0.110.0-dev.6beff6a4b

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 (338) hide show
  1. package/README.md +108 -65
  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 +4 -0
  102. package/dist/hono/agent-session.d.ts.map +1 -1
  103. package/dist/hono/agent-session.js +28 -2
  104. package/dist/hono/agent-session.js.map +1 -1
  105. package/dist/hono/agent-session.test.js +37 -0
  106. package/dist/hono/agent-session.test.js.map +1 -1
  107. package/dist/hono/agent-tasks.d.ts.map +1 -1
  108. package/dist/hono/agent-tasks.js +11 -7
  109. package/dist/hono/agent-tasks.js.map +1 -1
  110. package/dist/hono/agent-tasks.test.js +15 -0
  111. package/dist/hono/agent-tasks.test.js.map +1 -1
  112. package/dist/hono/agent.d.ts +2 -0
  113. package/dist/hono/agent.d.ts.map +1 -1
  114. package/dist/hono/agent.js +22 -10
  115. package/dist/hono/agent.js.map +1 -1
  116. package/dist/hono/agent.test.js +8 -2
  117. package/dist/hono/agent.test.js.map +1 -1
  118. package/dist/hono/attachments.d.ts +6 -1
  119. package/dist/hono/attachments.d.ts.map +1 -1
  120. package/dist/hono/attachments.js +9 -4
  121. package/dist/hono/attachments.js.map +1 -1
  122. package/dist/hono/attachments.test.js +5 -0
  123. package/dist/hono/attachments.test.js.map +1 -1
  124. package/dist/hono/avatar-accent.d.ts +23 -5
  125. package/dist/hono/avatar-accent.d.ts.map +1 -1
  126. package/dist/hono/avatar-accent.js +54 -63
  127. package/dist/hono/avatar-accent.js.map +1 -1
  128. package/dist/hono/avatar-accent.test.js +64 -35
  129. package/dist/hono/avatar-accent.test.js.map +1 -1
  130. package/dist/hono/avatar-casting.d.ts +1 -0
  131. package/dist/hono/avatar-casting.d.ts.map +1 -1
  132. package/dist/hono/avatar-casting.js +18 -4
  133. package/dist/hono/avatar-casting.js.map +1 -1
  134. package/dist/hono/avatar-casting.test.js +5 -0
  135. package/dist/hono/avatar-casting.test.js.map +1 -1
  136. package/dist/hono/avatar-face-question.d.ts +23 -6
  137. package/dist/hono/avatar-face-question.d.ts.map +1 -1
  138. package/dist/hono/avatar-face-question.js +48 -59
  139. package/dist/hono/avatar-face-question.js.map +1 -1
  140. package/dist/hono/avatar-face-question.test.js +46 -101
  141. package/dist/hono/avatar-face-question.test.js.map +1 -1
  142. package/dist/hono/baker-ui-tool-schemas.test.js +30 -4
  143. package/dist/hono/baker-ui-tool-schemas.test.js.map +1 -1
  144. package/dist/hono/blocking-card.d.ts +12 -0
  145. package/dist/hono/blocking-card.d.ts.map +1 -0
  146. package/dist/hono/blocking-card.js +12 -0
  147. package/dist/hono/blocking-card.js.map +1 -0
  148. package/dist/hono/bridge-job.d.ts +78 -13
  149. package/dist/hono/bridge-job.d.ts.map +1 -1
  150. package/dist/hono/bridge-job.js +333 -103
  151. package/dist/hono/bridge-job.js.map +1 -1
  152. package/dist/hono/bridge-job.test.js +571 -190
  153. package/dist/hono/bridge-job.test.js.map +1 -1
  154. package/dist/hono/check-content.d.ts +49 -0
  155. package/dist/hono/check-content.d.ts.map +1 -0
  156. package/dist/hono/check-content.js +182 -0
  157. package/dist/hono/check-content.js.map +1 -0
  158. package/dist/hono/check-content.test.d.ts +2 -0
  159. package/dist/hono/check-content.test.d.ts.map +1 -0
  160. package/dist/hono/check-content.test.js +87 -0
  161. package/dist/hono/check-content.test.js.map +1 -0
  162. package/dist/hono/connection-input.d.ts +177 -12
  163. package/dist/hono/connection-input.d.ts.map +1 -1
  164. package/dist/hono/connection-input.js +167 -14
  165. package/dist/hono/connection-input.js.map +1 -1
  166. package/dist/hono/connection-input.test.js +112 -10
  167. package/dist/hono/connection-input.test.js.map +1 -1
  168. package/dist/hono/convex.d.ts +8 -0
  169. package/dist/hono/convex.d.ts.map +1 -1
  170. package/dist/hono/convex.js +8 -0
  171. package/dist/hono/convex.js.map +1 -1
  172. package/dist/hono/convex.test.js +4 -1
  173. package/dist/hono/convex.test.js.map +1 -1
  174. package/dist/hono/credential-input.d.ts +39 -0
  175. package/dist/hono/credential-input.d.ts.map +1 -0
  176. package/dist/hono/credential-input.js +78 -0
  177. package/dist/hono/credential-input.js.map +1 -0
  178. package/dist/hono/credential-scrub.d.ts +42 -0
  179. package/dist/hono/credential-scrub.d.ts.map +1 -0
  180. package/dist/hono/credential-scrub.js +173 -0
  181. package/dist/hono/credential-scrub.js.map +1 -0
  182. package/dist/hono/credential-scrub.test.d.ts +2 -0
  183. package/dist/hono/credential-scrub.test.d.ts.map +1 -0
  184. package/dist/hono/credential-scrub.test.js +130 -0
  185. package/dist/hono/credential-scrub.test.js.map +1 -0
  186. package/dist/hono/env.d.ts +1 -0
  187. package/dist/hono/env.d.ts.map +1 -1
  188. package/dist/hono/env.js +2 -0
  189. package/dist/hono/env.js.map +1 -1
  190. package/dist/hono/flow-crypto.d.ts +2 -1
  191. package/dist/hono/flow-crypto.d.ts.map +1 -1
  192. package/dist/hono/flow-crypto.js +50 -26
  193. package/dist/hono/flow-crypto.js.map +1 -1
  194. package/dist/hono/flow-crypto.test.js +32 -0
  195. package/dist/hono/flow-crypto.test.js.map +1 -1
  196. package/dist/hono/flow-input-card-fields.test.d.ts +10 -0
  197. package/dist/hono/flow-input-card-fields.test.d.ts.map +1 -0
  198. package/dist/hono/flow-input-card-fields.test.js +26 -0
  199. package/dist/hono/flow-input-card-fields.test.js.map +1 -0
  200. package/dist/hono/flow-input-fields.test.js +1 -1
  201. package/dist/hono/flow-input-fields.test.js.map +1 -1
  202. package/dist/hono/flow-input.d.ts +23 -2
  203. package/dist/hono/flow-input.d.ts.map +1 -1
  204. package/dist/hono/flow-input.js +64 -14
  205. package/dist/hono/flow-input.js.map +1 -1
  206. package/dist/hono/flow-input.test.js +86 -11
  207. package/dist/hono/flow-input.test.js.map +1 -1
  208. package/dist/hono/jev.d.ts +16 -0
  209. package/dist/hono/jev.d.ts.map +1 -0
  210. package/dist/hono/jev.js +60 -0
  211. package/dist/hono/jev.js.map +1 -0
  212. package/dist/hono/jev.test.d.ts +2 -0
  213. package/dist/hono/jev.test.d.ts.map +1 -0
  214. package/dist/hono/jev.test.js +15 -0
  215. package/dist/hono/jev.test.js.map +1 -0
  216. package/dist/hono/knowledge-content.d.ts.map +1 -1
  217. package/dist/hono/knowledge-content.js +4 -2
  218. package/dist/hono/knowledge-content.js.map +1 -1
  219. package/dist/hono/knowledge-content.test.js +6 -5
  220. package/dist/hono/knowledge-content.test.js.map +1 -1
  221. package/dist/hono/mcp-servers.d.ts +8 -1
  222. package/dist/hono/mcp-servers.d.ts.map +1 -1
  223. package/dist/hono/mcp-servers.js +17 -6
  224. package/dist/hono/mcp-servers.js.map +1 -1
  225. package/dist/hono/mcp-servers.test.js +25 -0
  226. package/dist/hono/mcp-servers.test.js.map +1 -1
  227. package/dist/hono/model-capabilities.d.ts.map +1 -1
  228. package/dist/hono/model-capabilities.js +20 -9
  229. package/dist/hono/model-capabilities.js.map +1 -1
  230. package/dist/hono/model-capabilities.test.js +16 -8
  231. package/dist/hono/model-capabilities.test.js.map +1 -1
  232. package/dist/hono/oom-priority.agent.test.d.ts +2 -0
  233. package/dist/hono/oom-priority.agent.test.d.ts.map +1 -0
  234. package/dist/hono/oom-priority.agent.test.js +138 -0
  235. package/dist/hono/oom-priority.agent.test.js.map +1 -0
  236. package/dist/hono/oom-priority.d.ts +39 -0
  237. package/dist/hono/oom-priority.d.ts.map +1 -0
  238. package/dist/hono/oom-priority.js +138 -0
  239. package/dist/hono/oom-priority.js.map +1 -0
  240. package/dist/hono/oom-priority.test.d.ts +2 -0
  241. package/dist/hono/oom-priority.test.d.ts.map +1 -0
  242. package/dist/hono/oom-priority.test.js +52 -0
  243. package/dist/hono/oom-priority.test.js.map +1 -0
  244. package/dist/hono/openrouter-session.d.ts.map +1 -1
  245. package/dist/hono/openrouter-session.js +12 -1
  246. package/dist/hono/openrouter-session.js.map +1 -1
  247. package/dist/hono/openrouter-session.test.js +9 -3
  248. package/dist/hono/openrouter-session.test.js.map +1 -1
  249. package/dist/hono/prompt-context.d.ts +8 -1
  250. package/dist/hono/prompt-context.d.ts.map +1 -1
  251. package/dist/hono/prompt-context.js +45 -7
  252. package/dist/hono/prompt-context.js.map +1 -1
  253. package/dist/hono/prompt-context.test.js +32 -1
  254. package/dist/hono/prompt-context.test.js.map +1 -1
  255. package/dist/hono/publish-recovery.d.ts +1 -1
  256. package/dist/hono/publish-recovery.d.ts.map +1 -1
  257. package/dist/hono/publish-recovery.js +9 -6
  258. package/dist/hono/publish-recovery.js.map +1 -1
  259. package/dist/hono/publish-recovery.test.js +11 -2
  260. package/dist/hono/publish-recovery.test.js.map +1 -1
  261. package/dist/hono/relay.d.ts +10 -1
  262. package/dist/hono/relay.d.ts.map +1 -1
  263. package/dist/hono/relay.js +6 -0
  264. package/dist/hono/relay.js.map +1 -1
  265. package/dist/hono/sandbox-content.d.ts +2 -0
  266. package/dist/hono/sandbox-content.d.ts.map +1 -1
  267. package/dist/hono/sandbox-content.js +8 -4
  268. package/dist/hono/sandbox-content.js.map +1 -1
  269. package/dist/hono/sandbox-content.test.js +20 -1
  270. package/dist/hono/sandbox-content.test.js.map +1 -1
  271. package/dist/hono/server.d.ts.map +1 -1
  272. package/dist/hono/server.js +32 -0
  273. package/dist/hono/server.js.map +1 -1
  274. package/dist/hono/server.test.js +21 -1
  275. package/dist/hono/server.test.js.map +1 -1
  276. package/dist/hono/session-continuity.d.ts +0 -1
  277. package/dist/hono/session-continuity.d.ts.map +1 -1
  278. package/dist/hono/session-continuity.integration.test.js +10 -1
  279. package/dist/hono/session-continuity.integration.test.js.map +1 -1
  280. package/dist/hono/session-continuity.js +2 -5
  281. package/dist/hono/session-continuity.js.map +1 -1
  282. package/dist/hono/tag-fields.d.ts.map +1 -1
  283. package/dist/hono/tag-fields.js +4 -0
  284. package/dist/hono/tag-fields.js.map +1 -1
  285. package/dist/hono/tag-input.d.ts +125 -3
  286. package/dist/hono/tag-input.d.ts.map +1 -1
  287. package/dist/hono/tag-input.js +27 -17
  288. package/dist/hono/tag-input.js.map +1 -1
  289. package/dist/hono/tag-input.test.js +19 -1
  290. package/dist/hono/tag-input.test.js.map +1 -1
  291. package/dist/hono/tool-mentions.d.ts.map +1 -1
  292. package/dist/hono/tool-mentions.js +1 -0
  293. package/dist/hono/tool-mentions.js.map +1 -1
  294. package/dist/hono/tool-mentions.test.js +6 -2
  295. package/dist/hono/tool-mentions.test.js.map +1 -1
  296. package/dist/hono/types.d.ts +15 -6
  297. package/dist/hono/types.d.ts.map +1 -1
  298. package/dist/hono/workspace-script.d.ts +10 -0
  299. package/dist/hono/workspace-script.d.ts.map +1 -0
  300. package/dist/hono/workspace-script.js +19 -0
  301. package/dist/hono/workspace-script.js.map +1 -0
  302. package/dist/hono/workspace-settle.d.ts +20 -0
  303. package/dist/hono/workspace-settle.d.ts.map +1 -0
  304. package/dist/hono/workspace-settle.js +50 -0
  305. package/dist/hono/workspace-settle.js.map +1 -0
  306. package/dist/review/digest.d.ts +15 -2
  307. package/dist/review/digest.d.ts.map +1 -1
  308. package/dist/review/digest.js +90 -59
  309. package/dist/review/digest.js.map +1 -1
  310. package/dist/review/digest.test.js +94 -6
  311. package/dist/review/digest.test.js.map +1 -1
  312. package/dist/review/evidence-block.js +1 -1
  313. package/dist/review/evidence-block.js.map +1 -1
  314. package/dist/review/evidence-block.test.js +0 -10
  315. package/dist/review/evidence-block.test.js.map +1 -1
  316. package/dist/review/shell-policy.d.ts +6 -0
  317. package/dist/review/shell-policy.d.ts.map +1 -1
  318. package/dist/review/shell-policy.js +240 -72
  319. package/dist/review/shell-policy.js.map +1 -1
  320. package/dist/review/shell-policy.test.js +114 -0
  321. package/dist/review/shell-policy.test.js.map +1 -1
  322. package/package.json +10 -5
  323. package/dist/hono/latitude-telemetry.d.ts +0 -17
  324. package/dist/hono/latitude-telemetry.d.ts.map +0 -1
  325. package/dist/hono/latitude-telemetry.js +0 -52
  326. package/dist/hono/latitude-telemetry.js.map +0 -1
  327. package/dist/hono/latitude-telemetry.test.d.ts +0 -2
  328. package/dist/hono/latitude-telemetry.test.d.ts.map +0 -1
  329. package/dist/hono/latitude-telemetry.test.js +0 -18
  330. package/dist/hono/latitude-telemetry.test.js.map +0 -1
  331. package/dist/hono/main-sync.d.ts +0 -46
  332. package/dist/hono/main-sync.d.ts.map +0 -1
  333. package/dist/hono/main-sync.js +0 -422
  334. package/dist/hono/main-sync.js.map +0 -1
  335. package/dist/hono/main-sync.test.d.ts +0 -2
  336. package/dist/hono/main-sync.test.d.ts.map +0 -1
  337. package/dist/hono/main-sync.test.js +0 -288
  338. package/dist/hono/main-sync.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";
@@ -29,6 +32,7 @@ import { createBakerUiServer, normalizeTagInputToolInput, sanitizeTagChanges, TA
29
32
  import { blockingToolDelegated, toolRunAsCommand } from "./tool-in-shell.js";
30
33
  import { recordTurnEvent } from "./turn-events.js";
31
34
  import { addQueryModelUsage, queryModelUsage, turnModelUsageList } from "./turn-model-usage.js";
35
+ import { settleWorkspaceAfterTasks } from "./workspace-settle.js";
32
36
  export function parsePlanDecision(value) {
33
37
  if (!value || typeof value !== "object") {
34
38
  return null;
@@ -43,6 +47,35 @@ export function parsePlanDecision(value) {
43
47
  }
44
48
  return null;
45
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
+ }
46
79
  /**
47
80
  * The Agent SDK matches AskUserQuestion answers to their questions by the
48
81
  * question TEXT (see `AskUserQuestionInput.answers`), but the dashboard submits
@@ -117,11 +150,10 @@ const BASE_DISALLOWED_TOOLS = [
117
150
  //
118
151
  // Since CLI 2.1.233 the CLI additionally withholds these from
119
152
  // Sonnet-5-and-newer models — so on Opus 5.5 this list is now
120
- // belt-and-braces. It is NOT redundant, because development runs on
121
- // Haiku 4.5, which predates that gate: read off the session's own
122
- // `init` tool list on 2.1.239, Haiku is offered all four and Opus 5
123
- // none. Removing them would hand the dev agent the backlog the
124
- // 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.
125
157
  //
126
158
  // `TodoWrite` is gone from both — it is absent from an SDK session's
127
159
  // tool list on either model, so in-turn planning no longer has a tool
@@ -151,10 +183,11 @@ const BASE_DISALLOWED_TOOLS = [
151
183
  // not asked to fund a research fleet. Ordinary fan-out is unaffected:
152
184
  // `Task` stays, so the scaffold's subagents still run.
153
185
  "Workflow",
154
- // Cross-session messaging. A Runtime holds exactly one session, so
155
- // there is never a peer to reach; every call is a dead end that costs a
156
- // round trip to discover.
157
- "SendMessage",
186
+ // NOT listed: `SendMessage`. It is how the model follows up with a
187
+ // subagent it already ran, and the CLI tells it to on every Agent result.
188
+ // The continued subagent runs in the background, but it announces itself
189
+ // with `task_started` / `background_tasks_changed`, so the turn stays open
190
+ // until it finishes (`createTurnEndController`).
158
191
  // A Session review may not change anything. Removing the definitions is
159
192
  // the second of three layers — the key it carries is read-scoped and its
160
193
  // Bash calls go through `reviewBashPolicy` — because each fails
@@ -189,6 +222,16 @@ export function reviewQueryOptions(job) {
189
222
  ? { outputFormat: { type: "json_schema", schema: job.review.outputSchema }, hooks: reviewOutputHooks() }
190
223
  : {};
191
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
+ }
192
235
  /**
193
236
  * The review runs on Baker's own OpenRouter credential and its own read-only
194
237
  * key, both replacing whatever the Runtime was started with — the client never
@@ -215,6 +258,12 @@ export const REVIEW_DEBUG_LOG = "/tmp/baker-review-cli.jsonl";
215
258
  // briefly; model activity cancels it (the CLI clearly isn't idle). Turns that
216
259
  // never ran a task skip the delay entirely.
217
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;
218
267
  // After input ends, a healthy idle CLI exits within seconds. If the stream is
219
268
  // still open past this window with no messages arriving, the CLI is wedged —
220
269
  // close the query so the turn can finish instead of hanging to the watchdog.
@@ -242,10 +291,10 @@ const UNATTENDED_PROMPT_MESSAGE = "This run is unattended — it was started by
242
291
  * asked for in a text prompt renders a question nobody can answer. In each case the user
243
292
  * is left waiting for something that was never going to come.
244
293
  */
245
- function refuseMalformedCall(toolName, input, userText, priorUserText) {
294
+ async function refuseMalformedCall(toolName, input, judge, userText, priorUserText) {
246
295
  if (toolName === "Bash") {
247
296
  const command = typeof input?.command === "string" ? input.command : undefined;
248
- return toolRunAsCommand(command) ?? accentContradictsUser(command, userText, priorUserText);
297
+ return toolRunAsCommand(command) ?? (await accentContradictsUser(command, userText, priorUserText, judge));
249
298
  }
250
299
  if (toolName === "Agent" || toolName === "Task") {
251
300
  return blockingToolDelegated(typeof input?.prompt === "string" ? input.prompt : undefined);
@@ -253,11 +302,18 @@ function refuseMalformedCall(toolName, input, userText, priorUserText) {
253
302
  if (toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
254
303
  return writesInventedLogo(typeof input?.file_path === "string" ? input.file_path : undefined);
255
304
  }
256
- if (toolName === "AskUserQuestion" && asksForAvatarFace(input)) {
305
+ if (toolName === "AskUserQuestion" && (await asksForAvatarFace(input, judge))) {
257
306
  return FACE_QUESTION_MESSAGE;
258
307
  }
259
308
  return null;
260
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
+ }
261
317
  /** Blocking prompts: every tool that would leave a card the backend parks on. */
262
318
  function deniedUnattendedPrompt(toolName) {
263
319
  return (toolName === "AskUserQuestion" ||
@@ -265,6 +321,7 @@ function deniedUnattendedPrompt(toolName) {
265
321
  toolName === TAG_INPUT_TOOL_NAME ||
266
322
  toolName === FLOW_INPUT_TOOL_NAME ||
267
323
  toolName === CONNECTION_INPUT_TOOL_NAME ||
324
+ toolName === CREDENTIAL_INPUT_TOOL_NAME ||
268
325
  toolName === AVATAR_CASTING_TOOL_NAME);
269
326
  }
270
327
  export class StreamStallError extends Error {
@@ -341,8 +398,7 @@ function enrichImageError(message) {
341
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>\`.`;
342
399
  }
343
400
  /**
344
- * Which hooks a job runs. The client's agent runs the workspace's own and is
345
- * 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
346
402
  * written for the client's agent, and in the reviewer they speak about "this
347
403
  * Session" with the review's read-scoped key. The SessionStart probes are
348
404
  * refused on full-scope routes, and the reviewer filed its own "status unknown"
@@ -359,12 +415,11 @@ function hookSettingsForJob(job) {
359
415
  if (job.review) {
360
416
  return { disableAllHooks: true, permissions: { ask: ["Bash"] } };
361
417
  }
362
- const hooks = latitudeTelemetryFor(process.env, installedLatitudeHook());
363
- return hooks ? { hooks } : {};
418
+ return {};
364
419
  }
365
420
  // Carry a Runtime-wide model override down to the fan-out workers.
366
421
  //
367
- // 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
368
423
  // `DEFAULT_MODEL`). Since CLI 2.1.251 `CLAUDE_CODE_SUBAGENT_MODEL` is only the
369
424
  // default beneath that pin; `CLAUDE_CODE_SUBAGENT_MODEL_FORCE` (2.1.257) makes it
370
425
  // win over the frontmatter and any per-spawn model again. Absent (production),
@@ -498,9 +553,9 @@ function sdkResultError(msg) {
498
553
  // the whole turn; the deny THRESHOLD below still guards a one-off end-of-turn race.
499
554
  // The trailing colon matters: the CLI always emits the envelope followed by the
500
555
  // error that caused it ("…failed: Error: Stream closed", "…failed: AbortError: …").
501
- // Prose that merely NAMES the failure quotes it without one, which is how the
502
- // scaffold's guard-background-commands hook describes what it prevents — and its
503
- // block is an is_error tool_result, so a colon-less match counted a healthy
556
+ // Prose that merely NAMES the failure quotes it without one, which is how a
557
+ // hook explaining a block quotes it (the scaffold's former background-subagent
558
+ // block did) — and a hook block is an is_error tool_result, so a colon-less match counted a healthy
504
559
  // blocked spawn as a deny and could trip the breaker on a healthy turn.
505
560
  const CONTROL_CHANNEL_DENY_MARKER = "Tool permission request failed:";
506
561
  // Hook failures are reported through the same is_error tool_result channel but
@@ -658,14 +713,37 @@ export function createStallDetector(input) {
658
713
  timer.unref?.();
659
714
  return {
660
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
+ }
661
721
  lastMessageAt = input.now();
662
722
  trackPendingToolUses(pendingToolUses, msg);
663
723
  awaitingModel = (msg.type === "user" || msg.type === "stream_event") && pendingToolUses.size === 0;
664
724
  },
665
725
  stalled: () => stalled,
726
+ hasPendingToolUses: () => pendingToolUses.size > 0,
666
727
  dispose: () => clearInterval(timer),
667
728
  };
668
729
  }
730
+ // A query's cost and `modelUsage` are cumulative — on every result message and
731
+ // on the CLI's usage reply alike — so the latest reading replaces the last one
732
+ // rather than adding to it. It is folded into the turn totals when the query
733
+ // settles (see `runQueryOnce`). A spuriously empty later reading never wipes a
734
+ // real earlier one.
735
+ function recordCumulativeUsage(state, reading) {
736
+ const own = ownQueryUsage(reading, state.queryUsageBaseline);
737
+ state.observedCostUsd = own.costUsd;
738
+ const snapshot = queryModelUsage(own.modelUsage);
739
+ if (snapshot.size > 0) {
740
+ state.queryModelUsage = snapshot;
741
+ }
742
+ return own.costUsd;
743
+ }
744
+ // How long ending a turn waits on the CLI's usage reply before going ahead
745
+ // without it. Billing a turn late is recoverable; hanging one is not.
746
+ const USAGE_READING_TIMEOUT_MS = 5_000;
669
747
  // Decides when the turn is really over and the CLI may be told to exit.
670
748
  //
671
749
  // The prompt rides a held-open input stream (see `createHeldOpenQuery` in
@@ -687,7 +765,10 @@ export function createStallDetector(input) {
687
765
  // events must not, or the end would never re-arm. After input ends, an exit
688
766
  // guard closes the query if the CLI neither speaks nor exits, so a wedged
689
767
  // process cannot hang the turn until the session watchdog.
690
- function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
768
+ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent,
769
+ // Runs once, before input ends on a turn that ran tasks: a task can settle
770
+ // without waking the model, so the last result may not include what it spent.
771
+ beforeEndInput) {
691
772
  const inFlightTasks = new Set();
692
773
  // The background_tasks_changed LEVEL signal, tracked separately from the
693
774
  // edge-paired set above: a foreground command the CLI auto-backgrounds emits
@@ -700,15 +781,52 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
700
781
  let cleanResultSeen = false;
701
782
  let lastWasTaskLifecycle = false;
702
783
  let inputEnded = false;
784
+ // Between deciding to end and ending, while the usage reading is in flight.
785
+ // Anything that would have cancelled a pending end — model activity, a task
786
+ // starting — is recorded here and abandons the end instead.
787
+ let ending;
788
+ // The query stopped being drained (a Stop, a crash): a reading answering
789
+ // after that has no turn left to end.
790
+ let disposed = false;
703
791
  let settleTimer;
704
792
  let exitGuard;
705
793
  const endInput = () => {
706
794
  settleTimer = undefined;
707
795
  // Fire-time guard: a task that started during the settle window keeps the
708
796
  // channel open; a later settle re-arms the end.
709
- if (inputEnded || hasInFlightWork()) {
797
+ if (inputEnded || ending || hasInFlightWork()) {
798
+ return;
799
+ }
800
+ if (sawAnyTask && beforeEndInput) {
801
+ const attempt = { interrupted: false };
802
+ ending = attempt;
803
+ let timeout;
804
+ const timedOut = new Promise((resolveTimeout) => {
805
+ timeout = setTimeout(resolveTimeout, USAGE_READING_TIMEOUT_MS);
806
+ timeout.unref?.();
807
+ });
808
+ void Promise.race([beforeEndInput().catch(() => undefined), timedOut]).finally(() => {
809
+ clearTimeout(timeout);
810
+ ending = undefined;
811
+ if (disposed) {
812
+ return;
813
+ }
814
+ // Re-check at the moment input would end, as the fire-time guard above
815
+ // does: a wake or a task that landed during the reading keeps the
816
+ // channel open, and the woken turn's own result or settle ends it.
817
+ if (attempt.interrupted || hasInFlightWork()) {
818
+ if (cleanResultSeen && !hasInFlightWork()) {
819
+ scheduleEnd();
820
+ }
821
+ return;
822
+ }
823
+ finishEndInput();
824
+ });
710
825
  return;
711
826
  }
827
+ finishEndInput();
828
+ };
829
+ const finishEndInput = () => {
712
830
  inputEnded = true;
713
831
  query.endInput?.();
714
832
  onTurnEvent({
@@ -728,7 +846,7 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
728
846
  }, timings.exitGuardMs);
729
847
  exitGuard.unref?.();
730
848
  };
731
- const scheduleEnd = () => {
849
+ const scheduleEnd = (delayMs = timings.settleMs) => {
732
850
  if (settleTimer) {
733
851
  return;
734
852
  }
@@ -740,7 +858,7 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
740
858
  endInput();
741
859
  return;
742
860
  }
743
- settleTimer = setTimeout(endInput, timings.settleMs);
861
+ settleTimer = setTimeout(endInput, delayMs);
744
862
  settleTimer.unref?.();
745
863
  };
746
864
  return {
@@ -778,15 +896,21 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
778
896
  clearTimeout(settleTimer);
779
897
  settleTimer = undefined;
780
898
  }
899
+ if (ending && (isModelActivity || hasInFlightWork())) {
900
+ ending.interrupted = true;
901
+ }
781
902
  if (exitGuard) {
782
903
  exitGuard.refresh();
783
904
  }
784
905
  },
785
- maybeEndAfter: (msg) => {
906
+ maybeEndAfter: (msg, refused) => {
786
907
  if (inputEnded) {
787
908
  return;
788
909
  }
789
- if (msg.type === "result" && !msg.is_error) {
910
+ // A refusal's error result is not thrown (the refusal already decided how
911
+ // the turn ends), so it has to end input like a clean one or the CLI idles
912
+ // on stdin until the session watchdog.
913
+ if (msg.type === "result" && (!msg.is_error || refused)) {
790
914
  cleanResultSeen = true;
791
915
  if (hasInFlightWork()) {
792
916
  console.error(`[BridgeJob] result received with ${inFlightTasks.size} task(s) and ${backgroundTaskCount} background command(s) in flight — holding the turn open (thread ${job.threadId})`);
@@ -800,10 +924,13 @@ function createTurnEndController(job, query, closeQuery, timings, onTurnEvent) {
800
924
  // second result) would strand the turn until the watchdog. An emptied
801
925
  // background_tasks_changed level counts as a settle the same way.
802
926
  if (lastWasTaskLifecycle && cleanResultSeen && !hasInFlightWork()) {
803
- scheduleEnd();
927
+ scheduleEnd(timings.wakeMs);
804
928
  }
805
929
  },
930
+ hasInFlightWork,
931
+ ranTasks: () => sawAnyTask,
806
932
  dispose: () => {
933
+ disposed = true;
807
934
  if (settleTimer) {
808
935
  clearTimeout(settleTimer);
809
936
  settleTimer = undefined;
@@ -890,12 +1017,14 @@ export class BridgeJobExecutor {
890
1017
  stallCheckIntervalMs;
891
1018
  recordTurnEvent;
892
1019
  turnEndSettleMs;
1020
+ turnEndWakeMs;
893
1021
  turnEndExitGuardMs;
894
1022
  now;
895
1023
  readUsageBaseline;
896
1024
  stalledCliExitMs;
897
1025
  readFlowTree;
898
- mainSync;
1026
+ settleWorkspace;
1027
+ judge;
899
1028
  constructor(dependencies) {
900
1029
  this.sdk = dependencies.sdk;
901
1030
  this.relay = dependencies.relay;
@@ -908,15 +1037,17 @@ export class BridgeJobExecutor {
908
1037
  this.stallCheckIntervalMs = dependencies.stallCheckIntervalMs ?? DEFAULT_STALL_CHECK_INTERVAL_MS;
909
1038
  this.recordTurnEvent = dependencies.recordTurnEvent ?? recordTurnEvent;
910
1039
  this.turnEndSettleMs = dependencies.turnEndSettleMs ?? DEFAULT_TURN_END_SETTLE_MS;
1040
+ this.turnEndWakeMs = dependencies.turnEndWakeMs ?? DEFAULT_TURN_END_WAKE_MS;
911
1041
  this.turnEndExitGuardMs = dependencies.turnEndExitGuardMs ?? DEFAULT_TURN_END_EXIT_GUARD_MS;
912
1042
  this.now = dependencies.now ?? Date.now;
913
1043
  this.readUsageBaseline =
914
1044
  dependencies.readUsageBaseline ??
915
1045
  ((sessionId) => readUsageBaseline(sessionId, process.env.CLAUDE_CONFIG_DIR || resolve(process.env.HOME || homedir(), ".claude")));
916
1046
  this.stalledCliExitMs = dependencies.stalledCliExitMs ?? STALLED_CLI_EXIT_MS;
1047
+ this.settleWorkspace = dependencies.settleWorkspace ?? (() => settleWorkspaceAfterTasks());
917
1048
  this.readFlowTree =
918
1049
  dependencies.readFlowTree ?? ((flowSlug) => readRawFlowTree(bridgeRuntimeFiles.flowsDir(), flowSlug));
919
- this.mainSync = dependencies.mainSync;
1050
+ this.judge = dependencies.judge ?? jevJudge(process.env.OPENROUTER_API_KEY);
920
1051
  }
921
1052
  async execute(job, controls) {
922
1053
  const state = createBridgeJobState();
@@ -929,10 +1060,12 @@ export class BridgeJobExecutor {
929
1060
  return { kind: "cancelled", costUsd: 0, model: state.model };
930
1061
  }
931
1062
  const loaded = await this.loadTrustedBoundary(job, controls);
1063
+ if (loaded?.kind !== "clean") {
1064
+ request = await this.withContinuedAttachments(job, request);
1065
+ }
932
1066
  attempt = prepareSessionAttempt({
933
1067
  requestPrompt: request.prompt,
934
1068
  reminder: request.reminder,
935
- recoveryReminder: request.recoveryReminder,
936
1069
  history: job.history,
937
1070
  loaded,
938
1071
  leadingCommand: request.leadingCommand,
@@ -963,12 +1096,6 @@ export class BridgeJobExecutor {
963
1096
  await this.clearIfDirty(job, controls, state, classifyDirtyReason(error, controls));
964
1097
  return this.errorOutcome(job, controls, state, this.resultWithObservedCost(result, state), error);
965
1098
  }
966
- finally {
967
- // A review only reads the finished Session's workspace.
968
- if (!job.review) {
969
- await this.mainSync?.afterTurn();
970
- }
971
- }
972
1099
  }
973
1100
  async loadTrustedBoundary(job, controls) {
974
1101
  const loaded = await this.sessionStore.load(job.threadId);
@@ -990,34 +1117,21 @@ export class BridgeJobExecutor {
990
1117
  // skill) must stay the leading token, so they skip speaker attribution and
991
1118
  // reference context — otherwise the prepended lines push the command off the
992
1119
  // front and it lands as ordinary message text the model just echoes.
993
- const bridgeCommand = verbatimSlashCommand(job.prompt);
994
- const verbatim = bridgeCommand ??
1120
+ const verbatim = verbatimSlashCommand(job.prompt) ??
995
1121
  leadingSkillCommand(job.prompt, (await readSessionSkills(bridgeRuntimeFiles.projectRoot())) ?? []);
996
1122
  let requestPrompt = verbatim ?? applyCurrentSenderAttribution(job.prompt, job.currentSender);
997
- // Publish and compact keep the workspace as it is (Publish rebases onto main
998
- // itself). A skill reads and edits the workspace, so it is brought level
999
- // first — but a note beside the command would push it off the front, so
1000
- // the thread hears about it on its next turn.
1001
- const notes = bridgeCommand === null
1002
- ? ((await this.mainSync?.beforeTurn(job.threadId, job.publishedElsewhere ?? [], {
1003
- withNote: verbatim === null,
1004
- })) ?? {})
1005
- : {};
1006
- const viewing = verbatim === null ? currentReferenceReminder(job.currentReference) : undefined;
1007
- const reminder = joinReminders(notes.resumed, viewing);
1008
- const recoveryReminder = joinReminders(notes.recovered, viewing);
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;
1009
1128
  if (job.attachments && job.attachments.length > 0) {
1010
- requestPrompt += await this.prepareAttachments(job.attachments);
1129
+ requestPrompt += await this.prepareAttachments(job.attachments, "message");
1011
1130
  }
1012
1131
  if (!controls.isActive()) {
1013
1132
  return null;
1014
1133
  }
1015
- return {
1016
- prompt: requestPrompt,
1017
- ...(reminder ? { reminder } : {}),
1018
- ...(recoveryReminder ? { recoveryReminder } : {}),
1019
- leadingCommand: verbatim !== null,
1020
- };
1134
+ return { prompt: requestPrompt, ...(reminder ? { reminder } : {}), leadingCommand: verbatim !== null };
1021
1135
  }
1022
1136
  /**
1023
1137
  * The reviewer's prompt: the brief Convex wrote, with the evidence block this
@@ -1216,6 +1330,18 @@ export class BridgeJobExecutor {
1216
1330
  });
1217
1331
  }
1218
1332
  }
1333
+ /**
1334
+ * The files of the request a late reply continues, for a turn that does not
1335
+ * resume the Session that received them with the request. Its history names
1336
+ * them but cannot open them.
1337
+ */
1338
+ async withContinuedAttachments(job, request) {
1339
+ if (!job.continuedAttachments?.length || request.leadingCommand) {
1340
+ return request;
1341
+ }
1342
+ const suffix = await this.prepareAttachments(job.continuedAttachments, "continued_request");
1343
+ return { ...request, prompt: request.prompt + suffix };
1344
+ }
1219
1345
  async invalidResumeFallbackAttempt(job, controls, request, attempt, state, error) {
1220
1346
  if (!attempt.usedBoundary ||
1221
1347
  state.sawUserVisibleSdkMessage ||
@@ -1224,6 +1350,7 @@ export class BridgeJobExecutor {
1224
1350
  return null;
1225
1351
  }
1226
1352
  await this.sessionStore.clear(job.threadId, { runToken: controls.runToken, reason: "sdk_error" });
1353
+ const rebuilt = await this.withContinuedAttachments(job, request);
1227
1354
  void this.relay.contextSource({ threadId: job.threadId, turnId: job.turnId }, "rehydrated_invalid_resume", {
1228
1355
  historyEntryCount: job.history?.entries.length ?? 0,
1229
1356
  omittedEntryCount: job.history?.omittedEntryCount ?? 0,
@@ -1232,12 +1359,12 @@ export class BridgeJobExecutor {
1232
1359
  });
1233
1360
  // A leading command stays bare here too: a history preamble in front of it
1234
1361
  // would turn it into plain text (see `prepareSessionAttempt`).
1235
- if (request.leadingCommand) {
1236
- return { prompt: request.prompt, attemptedRehydration: false, resumeOptions: {} };
1362
+ if (rebuilt.leadingCommand) {
1363
+ return { prompt: rebuilt.prompt, attemptedRehydration: false, resumeOptions: {} };
1237
1364
  }
1238
1365
  return {
1239
- prompt: buildRehydratedPrompt(request.prompt, job.history),
1240
- ...(request.recoveryReminder ? { reminder: request.recoveryReminder } : {}),
1366
+ prompt: buildRehydratedPrompt(rebuilt.prompt, job.history),
1367
+ ...(rebuilt.reminder ? { reminder: rebuilt.reminder } : {}),
1241
1368
  attemptedRehydration: true,
1242
1369
  resumeOptions: {},
1243
1370
  };
@@ -1254,6 +1381,39 @@ export class BridgeJobExecutor {
1254
1381
  return undefined;
1255
1382
  }
1256
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
+ }
1257
1417
  reportMcpFailures(job, msg) {
1258
1418
  for (const scope of composioScopesToInvalidate(msg)) {
1259
1419
  void reportMcpInvalid(job.threadId, { scope }, job.currentSender?.userId);
@@ -1273,7 +1433,7 @@ export class BridgeJobExecutor {
1273
1433
  * handlers — except on an unattended turn, where there is nobody to answer and
1274
1434
  * every one of them is refused up front (see `UNATTENDED_PROMPT_MESSAGE`).
1275
1435
  */
1276
- routeToolUse(job, controls, toolName, input, opts) {
1436
+ async routeToolUse(job, controls, toolName, input, opts) {
1277
1437
  const refusedShell = reviewShellRefusal(job, toolName, input);
1278
1438
  if (refusedShell) {
1279
1439
  return Promise.resolve({ behavior: "deny", message: refusedShell });
@@ -1289,14 +1449,9 @@ export class BridgeJobExecutor {
1289
1449
  });
1290
1450
  return Promise.resolve({ behavior: "deny", message: UNATTENDED_PROMPT_MESSAGE });
1291
1451
  }
1292
- const malformed = refuseMalformedCall(toolName, input, job.prompt, userTurnsBefore(job));
1452
+ const malformed = await refuseMalformedCall(toolName, input, this.judge, job.prompt, userTurnsBefore(job));
1293
1453
  if (malformed)
1294
- return Promise.resolve({ behavior: "deny", message: malformed });
1295
- const clash = toolName === "Write" && typeof input?.file_path === "string"
1296
- ? (this.mainSync?.refuseWrite(job.threadId, input.file_path) ?? null)
1297
- : null;
1298
- if (clash)
1299
- return Promise.resolve({ behavior: "deny", message: clash });
1454
+ return { behavior: "deny", message: malformed };
1300
1455
  // Cast what is owed FIRST, whatever comes next — including another create. Checked
1301
1456
  // the other way round, a second create (the agent retrying after a rejected flag,
1302
1457
  // which is what happens in practice) would overwrite the handle and the first
@@ -1349,6 +1504,15 @@ export class BridgeJobExecutor {
1349
1504
  if (toolName === CONNECTION_INPUT_TOOL_NAME) {
1350
1505
  return this.handleConnectionInputRequest(job, controls, input, opts.toolUseID, opts.signal);
1351
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
+ }
1352
1516
  if (toolName === AVATAR_CASTING_TOOL_NAME) {
1353
1517
  return this.handleAvatarCastingRequest(job, controls, input, opts.toolUseID, opts.signal);
1354
1518
  }
@@ -1387,6 +1551,10 @@ export class BridgeJobExecutor {
1387
1551
  // warning to the user. Pinning it takes that threshold out of remote control.
1388
1552
  // See `autoCompactWindowForModel` for the full chain and its ceiling.
1389
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",
1390
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.
1391
1559
  ...(process.env.BAKER_CHAT_ID
1392
1560
  ? {
@@ -1411,19 +1579,16 @@ export class BridgeJobExecutor {
1411
1579
  // MUST stay a single constant. The resolved effort is rendered into the
1412
1580
  // prompt, so changing it between requests invalidates every cache
1413
1581
  // breakpoint above it — on a session carrying hundreds of thousands of
1414
- // tokens that re-write costs far more than any per-turn tuning saves. To
1415
- // vary reasoning per task, steer with prompt text on the newest user
1416
- // message instead; that leaves earlier breakpoints intact.
1582
+ // tokens that re-write costs far more than any per-turn tuning saves.
1583
+ // Vary it per message only once the route accepts per-message effort
1584
+ // (see `BEDROCK_SERVABLE_CAPABILITIES`); prose is no substitute.
1417
1585
  effort: "medium",
1418
1586
  cwd: process.cwd(),
1419
1587
  settingSources: ["project"],
1420
1588
  // `baker_ui` hosts in-process interactive dashboard tools (secure tag
1421
1589
  // form). Always present; HTTP (Composio) servers merge alongside it.
1422
- mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer() },
1423
- // Only send the key when there is something in it: the SDK forwards
1424
- // `settings` to the CLI whenever it is present, so an empty object still
1425
- // becomes a `--settings {}` flag tier for the CLI to parse.
1426
- ...(Object.keys(settings).length > 0 ? { settings } : {}),
1590
+ mcpServers: { ...(mcpServers ?? {}), baker_ui: createBakerUiServer({ credentials: credentialsEnabled() }) },
1591
+ settings,
1427
1592
  includePartialMessages: true,
1428
1593
  // Only the workspace's own skills. Omitting the option is NOT "skills
1429
1594
  // off" — the CLI's defaults still load its built-in developer skills
@@ -1432,8 +1597,11 @@ export class BridgeJobExecutor {
1432
1597
  // the workspace could not be read; omit rather than allowlist nothing.
1433
1598
  ...(skills ? { skills } : {}),
1434
1599
  disallowedTools: disallowedToolsFor(job),
1435
- ...reviewQueryOptions(job),
1436
- ...(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",
1437
1605
  canUseTool: (toolName, input, opts) => this.routeToolUse(job, controls, toolName, input, opts),
1438
1606
  // No `fallbackModel`, and no server-side refusal `fallbacks` in the extra
1439
1607
  // body: the CLI's refusal fallback is gated on a first-party base URL (see
@@ -1457,6 +1625,13 @@ export class BridgeJobExecutor {
1457
1625
  // system-reminder repeatedly pushed the agent toward that ephemeral
1458
1626
  // backlog instead of the product's own Tasks (`baker actions`).
1459
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),
1460
1635
  // `citty` writes ANSI escapes into `baker --help` unconditionally —
1461
1636
  // they are never TTY-gated, so every help read the agent does inside
1462
1637
  // the sandbox pays ~5-10% of its tokens on colour codes it can't see.
@@ -1478,6 +1653,7 @@ export class BridgeJobExecutor {
1478
1653
  ...attempt.resumeOptions,
1479
1654
  };
1480
1655
  const query = this.sdk.query({ prompt: attempt.prompt, reminder: attempt.reminder, options });
1656
+ controls.reloadMcpServers = () => this.reloadMcpServers(job, query);
1481
1657
  let queryClosed = false;
1482
1658
  const closeQuery = () => {
1483
1659
  if (queryClosed) {
@@ -1499,6 +1675,7 @@ export class BridgeJobExecutor {
1499
1675
  throw error;
1500
1676
  }
1501
1677
  finally {
1678
+ controls.reloadMcpServers = undefined;
1502
1679
  controls.abortSignal.removeEventListener("abort", closeQuery);
1503
1680
  // Release the held-open input if the turn-end policy never fired (the
1504
1681
  // stream ended on its own, or an error path closed the query). Idempotent.
@@ -1522,7 +1699,19 @@ export class BridgeJobExecutor {
1522
1699
  // in-progress session.
1523
1700
  async drainQuery(job, controls, state, query, closeQuery) {
1524
1701
  const stallDetector = this.createStallDetector(job, controls, closeQuery);
1525
- const turnEnd = createTurnEndController(job, query, closeQuery, { settleMs: this.turnEndSettleMs, exitGuardMs: this.turnEndExitGuardMs }, this.recordTurnEvent);
1702
+ // What the query had spent when its turn ended, read from the CLI — set
1703
+ // only when a task ran, and higher than the last result's figure when a
1704
+ // task kept working after it.
1705
+ let settledCostUsd = 0;
1706
+ // A reading that answers after the query drained (past its timeout) belongs
1707
+ // to a query already folded into the turn — and a retry reuses `state`.
1708
+ let drained = false;
1709
+ const turnEnd = createTurnEndController(job, query, closeQuery, { settleMs: this.turnEndSettleMs, wakeMs: this.turnEndWakeMs, exitGuardMs: this.turnEndExitGuardMs }, this.recordTurnEvent, async () => {
1710
+ const reading = await query.usage?.();
1711
+ if (reading && !drained) {
1712
+ settledCostUsd = recordCumulativeUsage(state, reading);
1713
+ }
1714
+ });
1526
1715
  let costUsd = 0;
1527
1716
  // Errors thrown while HANDLING a message (control-channel breaker, error
1528
1717
  // results) are real verdicts and must surface as-is; only errors from the
@@ -1532,6 +1721,7 @@ export class BridgeJobExecutor {
1532
1721
  for await (const msg of query) {
1533
1722
  stallDetector.observeMessage(msg);
1534
1723
  turnEnd.observeMessage(msg);
1724
+ controls.setWorkInFlight?.(stallDetector.hasPendingToolUses() || turnEnd.hasInFlightWork());
1535
1725
  try {
1536
1726
  costUsd = await this.consumeSdkMessage(job, controls, state, msg, costUsd);
1537
1727
  }
@@ -1539,7 +1729,7 @@ export class BridgeJobExecutor {
1539
1729
  consumeError = true;
1540
1730
  throw error;
1541
1731
  }
1542
- turnEnd.maybeEndAfter(msg);
1732
+ turnEnd.maybeEndAfter(msg, state.refusedWithoutFallback === true);
1543
1733
  if (!controls.isActive()) {
1544
1734
  closeQuery();
1545
1735
  break;
@@ -1554,6 +1744,8 @@ export class BridgeJobExecutor {
1554
1744
  throw error;
1555
1745
  }
1556
1746
  finally {
1747
+ drained = true;
1748
+ controls.setWorkInFlight?.(false);
1557
1749
  stallDetector.dispose();
1558
1750
  turnEnd.dispose();
1559
1751
  await this.openPendingCastingAtTurnEnd(job, controls);
@@ -1561,7 +1753,11 @@ export class BridgeJobExecutor {
1561
1753
  if (stallDetector.stalled() && controls.isActive()) {
1562
1754
  throw new StreamStallError();
1563
1755
  }
1564
- return costUsd;
1756
+ // Not for a Session review: it runs with every hook off and may not change anything.
1757
+ if (turnEnd.ranTasks() && controls.isActive() && !job.review) {
1758
+ await this.settleWorkspace();
1759
+ }
1760
+ return Math.max(costUsd, settledCostUsd);
1565
1761
  }
1566
1762
  /**
1567
1763
  * Open a casting the turn recorded but never reached.
@@ -1618,6 +1814,7 @@ export class BridgeJobExecutor {
1618
1814
  controls.pendingTagInputs.size > 0 ||
1619
1815
  controls.pendingFlowInputs.size > 0 ||
1620
1816
  controls.pendingConnectionInputs.size > 0 ||
1817
+ controls.pendingCredentialInputs.size > 0 ||
1621
1818
  controls.pendingAvatarCastings.size > 0,
1622
1819
  onStall: () => {
1623
1820
  console.error(`[BridgeJob] no stream data for ${this.stallTimeoutMs}ms while awaiting the model — closing query for retry (thread ${job.threadId})`);
@@ -1654,17 +1851,7 @@ export class BridgeJobExecutor {
1654
1851
  }
1655
1852
  trackMainRequestUsage(state, msg);
1656
1853
  if (msg.type === "result") {
1657
- const own = ownQueryUsage({ totalCostUsd: msg.total_cost_usd, modelUsage: msg.modelUsage }, state.queryUsageBaseline);
1658
- state.observedCostUsd = own.costUsd;
1659
- // A query's result `modelUsage` is cumulative (like `total_cost_usd`, which
1660
- // we overwrite above), so keep the latest snapshot rather than summing —
1661
- // it is folded into the turn totals when the query settles (see
1662
- // `runQueryOnce`). Guard against a spuriously empty later result wiping a
1663
- // real earlier one.
1664
- const snapshot = queryModelUsage(own.modelUsage);
1665
- if (snapshot.size > 0) {
1666
- state.queryModelUsage = snapshot;
1667
- }
1854
+ const ownCostUsd = recordCumulativeUsage(state, { totalCostUsd: msg.total_cost_usd, modelUsage: msg.modelUsage });
1668
1855
  await this.publishContextUsage(job, state, msg.modelUsage);
1669
1856
  // What a Session review returns. The SDK enforces the JSON schema, so this
1670
1857
  // either parses against the findings contract or the review failed to
@@ -1681,7 +1868,7 @@ export class BridgeJobExecutor {
1681
1868
  await this.clearIfDirty(job, controls, state, "result_error");
1682
1869
  throw resultError;
1683
1870
  }
1684
- return own.costUsd;
1871
+ return ownCostUsd;
1685
1872
  }
1686
1873
  if (isUserVisibleSdkMessage(msg)) {
1687
1874
  state.sawUserVisibleSdkMessage = true;
@@ -2056,6 +2243,16 @@ export class BridgeJobExecutor {
2056
2243
  // Strip any secret values the model tried to prefill; their names still reach
2057
2244
  // the user as requested fields. The backend strips again.
2058
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
+ }
2059
2256
  const posted = await this.relay.flowInputRequest(target, { toolUseId, requests: sanitizedRequests });
2060
2257
  if (!posted) {
2061
2258
  console.error(`[request_flow_input] failed to post flow-input-request for ${toolUseId} — denying to avoid a hang`);
@@ -2106,7 +2303,7 @@ export class BridgeJobExecutor {
2106
2303
  if (connections.length === 0) {
2107
2304
  return {
2108
2305
  behavior: "deny",
2109
- 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.`,
2110
2307
  };
2111
2308
  }
2112
2309
  const posted = await this.relay.connectionInputRequest(target, { toolUseId, connections });
@@ -2128,7 +2325,43 @@ export class BridgeJobExecutor {
2128
2325
  return { behavior: "deny", message: PROMPT_UNANSWERED_MESSAGE };
2129
2326
  }
2130
2327
  void this.relay.connectionInputResolved(target);
2131
- 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 } };
2132
2365
  }
2133
2366
  async handlePlanReview(job, controls, input, toolUseId, signal) {
2134
2367
  const target = { threadId: job.threadId, turnId: job.turnId };
@@ -2294,7 +2527,4 @@ function userTurnsBefore(job) {
2294
2527
  .trim();
2295
2528
  return said || undefined;
2296
2529
  }
2297
- function joinReminders(...blocks) {
2298
- return blocks.filter((block) => block !== undefined).join("\n\n") || undefined;
2299
- }
2300
2530
  //# sourceMappingURL=bridge-job.js.map