@synap-core/cli 1.12.2 → 1.13.1-next.20261102.ec3d166

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 (354) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -11
  3. package/dist/build-info.json +2 -3
  4. package/dist/commands/agents.d.ts +0 -1
  5. package/dist/commands/agents.js +13 -3
  6. package/dist/commands/agents.js.map +1 -1
  7. package/dist/commands/app.js +2 -1
  8. package/dist/commands/app.js.map +1 -1
  9. package/dist/commands/automation.js +46 -41
  10. package/dist/commands/automation.js.map +1 -1
  11. package/dist/commands/box.d.ts +42 -0
  12. package/dist/commands/box.js +89 -0
  13. package/dist/commands/box.js.map +1 -0
  14. package/dist/commands/bridge-setup.js +3 -1
  15. package/dist/commands/bridge-setup.js.map +1 -1
  16. package/dist/commands/capability.d.ts +87 -1
  17. package/dist/commands/capability.js +174 -64
  18. package/dist/commands/capability.js.map +1 -1
  19. package/dist/commands/close-questions.d.ts +27 -0
  20. package/dist/commands/close-questions.js +49 -0
  21. package/dist/commands/close-questions.js.map +1 -0
  22. package/dist/commands/close-questions.test.d.ts +1 -0
  23. package/dist/commands/close-questions.test.js +52 -0
  24. package/dist/commands/close-questions.test.js.map +1 -0
  25. package/dist/commands/connect.d.ts +1 -3
  26. package/dist/commands/connect.js +2 -2
  27. package/dist/commands/connect.js.map +1 -1
  28. package/dist/commands/context.js +72 -43
  29. package/dist/commands/context.js.map +1 -1
  30. package/dist/commands/data-extra.d.ts +13 -0
  31. package/dist/commands/data-extra.js +35 -6
  32. package/dist/commands/data-extra.js.map +1 -1
  33. package/dist/commands/data.js +2 -1
  34. package/dist/commands/data.js.map +1 -1
  35. package/dist/commands/doctor.d.ts +39 -0
  36. package/dist/commands/doctor.js +136 -14
  37. package/dist/commands/doctor.js.map +1 -1
  38. package/dist/commands/hooks.d.ts +19 -0
  39. package/dist/commands/hooks.js +70 -0
  40. package/dist/commands/hooks.js.map +1 -0
  41. package/dist/commands/import.d.ts +25 -0
  42. package/dist/commands/import.js +98 -12
  43. package/dist/commands/import.js.map +1 -1
  44. package/dist/commands/init.d.ts +14 -0
  45. package/dist/commands/init.js +63 -21
  46. package/dist/commands/init.js.map +1 -1
  47. package/dist/commands/keys-request.d.ts +48 -0
  48. package/dist/commands/keys-request.js +236 -0
  49. package/dist/commands/keys-request.js.map +1 -0
  50. package/dist/commands/keys-request.test.d.ts +1 -0
  51. package/dist/commands/keys-request.test.js +176 -0
  52. package/dist/commands/keys-request.test.js.map +1 -0
  53. package/dist/commands/knowledge.d.ts +6 -0
  54. package/dist/commands/knowledge.js +13 -3
  55. package/dist/commands/knowledge.js.map +1 -1
  56. package/dist/commands/lens.js +2 -1
  57. package/dist/commands/lens.js.map +1 -1
  58. package/dist/commands/market-authoring.js +1 -1
  59. package/dist/commands/market-authoring.js.map +1 -1
  60. package/dist/commands/market.d.ts +61 -4
  61. package/dist/commands/market.js +106 -48
  62. package/dist/commands/market.js.map +1 -1
  63. package/dist/commands/mcp.d.ts +1 -1
  64. package/dist/commands/mcp.js +14 -9
  65. package/dist/commands/mcp.js.map +1 -1
  66. package/dist/commands/observe.category.test.d.ts +1 -0
  67. package/dist/commands/observe.category.test.js +59 -0
  68. package/dist/commands/observe.category.test.js.map +1 -0
  69. package/dist/commands/observe.d.ts +3 -0
  70. package/dist/commands/observe.js +7 -5
  71. package/dist/commands/observe.js.map +1 -1
  72. package/dist/commands/observe.title.test.d.ts +1 -0
  73. package/dist/commands/observe.title.test.js +37 -0
  74. package/dist/commands/observe.title.test.js.map +1 -0
  75. package/dist/commands/playbook.js +9 -0
  76. package/dist/commands/playbook.js.map +1 -1
  77. package/dist/commands/pods.d.ts +6 -1
  78. package/dist/commands/pods.js +38 -17
  79. package/dist/commands/pods.js.map +1 -1
  80. package/dist/commands/proposals-actions.js +7 -1
  81. package/dist/commands/proposals-actions.js.map +1 -1
  82. package/dist/commands/proton-setup.js +3 -1
  83. package/dist/commands/proton-setup.js.map +1 -1
  84. package/dist/commands/providers.d.ts +22 -6
  85. package/dist/commands/providers.js +39 -32
  86. package/dist/commands/providers.js.map +1 -1
  87. package/dist/commands/raycast.d.ts +15 -0
  88. package/dist/commands/raycast.js +36 -10
  89. package/dist/commands/raycast.js.map +1 -1
  90. package/dist/commands/session-feed-wait.d.ts +48 -0
  91. package/dist/commands/session-feed-wait.js +261 -0
  92. package/dist/commands/session-feed-wait.js.map +1 -0
  93. package/dist/commands/session-post.d.ts +40 -0
  94. package/dist/commands/session-post.js +96 -0
  95. package/dist/commands/session-post.js.map +1 -0
  96. package/dist/commands/session-wait.d.ts +9 -4
  97. package/dist/commands/session-wait.js +20 -5
  98. package/dist/commands/session-wait.js.map +1 -1
  99. package/dist/commands/sessions.d.ts +2 -0
  100. package/dist/commands/sessions.js +23 -0
  101. package/dist/commands/sessions.js.map +1 -1
  102. package/dist/commands/skills-commands.d.ts +2 -0
  103. package/dist/commands/skills-commands.js +9 -1
  104. package/dist/commands/skills-commands.js.map +1 -1
  105. package/dist/commands/skills-commands.test.d.ts +1 -0
  106. package/dist/commands/skills-commands.test.js +57 -0
  107. package/dist/commands/skills-commands.test.js.map +1 -0
  108. package/dist/commands/status.js +13 -7
  109. package/dist/commands/status.js.map +1 -1
  110. package/dist/commands/statusline.backoff.test.js +13 -3
  111. package/dist/commands/statusline.backoff.test.js.map +1 -1
  112. package/dist/commands/statusline.d.ts +104 -9
  113. package/dist/commands/statusline.js +392 -148
  114. package/dist/commands/statusline.js.map +1 -1
  115. package/dist/commands/statusline.line2.test.d.ts +15 -0
  116. package/dist/commands/statusline.line2.test.js +226 -0
  117. package/dist/commands/statusline.line2.test.js.map +1 -0
  118. package/dist/commands/statusline.needs-you.test.d.ts +1 -0
  119. package/dist/commands/statusline.needs-you.test.js +17 -0
  120. package/dist/commands/statusline.needs-you.test.js.map +1 -0
  121. package/dist/commands/statusline.now.test.d.ts +10 -0
  122. package/dist/commands/statusline.now.test.js +250 -0
  123. package/dist/commands/statusline.now.test.js.map +1 -0
  124. package/dist/commands/tracks.d.ts +68 -6
  125. package/dist/commands/tracks.js +272 -16
  126. package/dist/commands/tracks.js.map +1 -1
  127. package/dist/commands/upload.js +12 -0
  128. package/dist/commands/upload.js.map +1 -1
  129. package/dist/commands/wait.d.ts +124 -0
  130. package/dist/commands/wait.js +384 -0
  131. package/dist/commands/wait.js.map +1 -0
  132. package/dist/commands/wait.test.d.ts +8 -0
  133. package/dist/commands/wait.test.js +308 -0
  134. package/dist/commands/wait.test.js.map +1 -0
  135. package/dist/commands/whoami.d.ts +7 -0
  136. package/dist/commands/whoami.js +17 -0
  137. package/dist/commands/whoami.js.map +1 -1
  138. package/dist/commands/worker-accounts.d.ts +149 -0
  139. package/dist/commands/worker-accounts.js +339 -0
  140. package/dist/commands/worker-accounts.js.map +1 -0
  141. package/dist/commands/worker-accounts.test.d.ts +1 -0
  142. package/dist/commands/worker-accounts.test.js +224 -0
  143. package/dist/commands/worker-accounts.test.js.map +1 -0
  144. package/dist/commands/worker-github-app.d.ts +63 -0
  145. package/dist/commands/worker-github-app.js +161 -0
  146. package/dist/commands/worker-github-app.js.map +1 -0
  147. package/dist/commands/worker-github-app.test.d.ts +1 -0
  148. package/dist/commands/worker-github-app.test.js +102 -0
  149. package/dist/commands/worker-github-app.test.js.map +1 -0
  150. package/dist/commands/worker-github-broker.d.ts +39 -0
  151. package/dist/commands/worker-github-broker.js +106 -0
  152. package/dist/commands/worker-github-broker.js.map +1 -0
  153. package/dist/commands/worker-github-broker.test.d.ts +1 -0
  154. package/dist/commands/worker-github-broker.test.js +90 -0
  155. package/dist/commands/worker-github-broker.test.js.map +1 -0
  156. package/dist/commands/worker-runs.test.d.ts +1 -0
  157. package/dist/commands/worker-runs.test.js +150 -0
  158. package/dist/commands/worker-runs.test.js.map +1 -0
  159. package/dist/commands/worker-worktrees.d.ts +34 -0
  160. package/dist/commands/worker-worktrees.js +87 -0
  161. package/dist/commands/worker-worktrees.js.map +1 -0
  162. package/dist/commands/worker.d.ts +406 -10
  163. package/dist/commands/worker.js +1180 -126
  164. package/dist/commands/worker.js.map +1 -1
  165. package/dist/commands/worker.test.js +588 -11
  166. package/dist/commands/worker.test.js.map +1 -1
  167. package/dist/index.js +220 -28
  168. package/dist/index.js.map +1 -1
  169. package/dist/lib/agent-door.d.ts +1 -3
  170. package/dist/lib/agent-door.js +10 -7
  171. package/dist/lib/agent-door.js.map +1 -1
  172. package/dist/lib/agents-config.d.ts +9 -0
  173. package/dist/lib/agents-config.js +20 -0
  174. package/dist/lib/agents-config.js.map +1 -1
  175. package/dist/lib/atomic-file.d.ts +20 -1
  176. package/dist/lib/atomic-file.js +100 -2
  177. package/dist/lib/atomic-file.js.map +1 -1
  178. package/dist/lib/auth.d.ts +28 -2
  179. package/dist/lib/auth.js +52 -12
  180. package/dist/lib/auth.js.map +1 -1
  181. package/dist/lib/box/egress.d.ts +82 -0
  182. package/dist/lib/box/egress.js +264 -0
  183. package/dist/lib/box/egress.js.map +1 -0
  184. package/dist/lib/box/evidence.d.ts +86 -0
  185. package/dist/lib/box/evidence.js +121 -0
  186. package/dist/lib/box/evidence.js.map +1 -0
  187. package/dist/lib/box/job-server.d.ts +72 -0
  188. package/dist/lib/box/job-server.js +250 -0
  189. package/dist/lib/box/job-server.js.map +1 -0
  190. package/dist/lib/box/provisioner.d.ts +83 -0
  191. package/dist/lib/box/provisioner.js +312 -0
  192. package/dist/lib/box/provisioner.js.map +1 -0
  193. package/dist/lib/box/proxmox.d.ts +112 -0
  194. package/dist/lib/box/proxmox.js +211 -0
  195. package/dist/lib/box/proxmox.js.map +1 -0
  196. package/dist/lib/box/wire.d.ts +98 -0
  197. package/dist/lib/box/wire.js +70 -0
  198. package/dist/lib/box/wire.js.map +1 -0
  199. package/dist/lib/bridge-invocation.test.d.ts +1 -0
  200. package/dist/lib/bridge-invocation.test.js +75 -0
  201. package/dist/lib/bridge-invocation.test.js.map +1 -0
  202. package/dist/lib/bridge-status.d.ts +144 -0
  203. package/dist/lib/bridge-status.js +227 -0
  204. package/dist/lib/bridge-status.js.map +1 -0
  205. package/dist/lib/bridge-status.test.d.ts +6 -0
  206. package/dist/lib/bridge-status.test.js +197 -0
  207. package/dist/lib/bridge-status.test.js.map +1 -0
  208. package/dist/lib/client-header.test.d.ts +1 -0
  209. package/dist/lib/client-header.test.js +103 -0
  210. package/dist/lib/client-header.test.js.map +1 -0
  211. package/dist/lib/conversation-headers.d.ts +89 -9
  212. package/dist/lib/conversation-headers.js +156 -3
  213. package/dist/lib/conversation-headers.js.map +1 -1
  214. package/dist/lib/conversation-headers.test.d.ts +1 -0
  215. package/dist/lib/conversation-headers.test.js +78 -0
  216. package/dist/lib/conversation-headers.test.js.map +1 -0
  217. package/dist/lib/credential-class.d.ts +40 -2
  218. package/dist/lib/credential-class.js +48 -6
  219. package/dist/lib/credential-class.js.map +1 -1
  220. package/dist/lib/device-connect.d.ts +45 -0
  221. package/dist/lib/device-connect.js +60 -0
  222. package/dist/lib/device-connect.js.map +1 -1
  223. package/dist/lib/exporter-coverage.d.ts +1 -1
  224. package/dist/lib/exporter-coverage.js +1 -1
  225. package/dist/lib/goal-words.d.ts +38 -0
  226. package/dist/lib/goal-words.js +115 -0
  227. package/dist/lib/goal-words.js.map +1 -0
  228. package/dist/lib/hub-client.d.ts +87 -8
  229. package/dist/lib/hub-client.js +205 -70
  230. package/dist/lib/hub-client.js.map +1 -1
  231. package/dist/lib/init-handoff.d.ts +8 -0
  232. package/dist/lib/init-handoff.js +9 -2
  233. package/dist/lib/init-handoff.js.map +1 -1
  234. package/dist/lib/key-locations.d.ts +3 -3
  235. package/dist/lib/key-locations.js +3 -39
  236. package/dist/lib/key-locations.js.map +1 -1
  237. package/dist/lib/key-source.d.ts +3 -1
  238. package/dist/lib/key-source.js +7 -4
  239. package/dist/lib/key-source.js.map +1 -1
  240. package/dist/lib/knowledge-title.d.ts +3 -2
  241. package/dist/lib/knowledge-title.js +3 -2
  242. package/dist/lib/knowledge-title.js.map +1 -1
  243. package/dist/lib/machine-instance.d.ts +7 -5
  244. package/dist/lib/machine-instance.js +7 -5
  245. package/dist/lib/machine-instance.js.map +1 -1
  246. package/dist/lib/mcp-bridge.d.ts +50 -0
  247. package/dist/lib/mcp-bridge.js +232 -11
  248. package/dist/lib/mcp-bridge.js.map +1 -1
  249. package/dist/lib/mcp-bridge.test.js +224 -4
  250. package/dist/lib/mcp-bridge.test.js.map +1 -1
  251. package/dist/lib/mcp-call.d.ts +5 -1
  252. package/dist/lib/mcp-call.js +7 -6
  253. package/dist/lib/mcp-call.js.map +1 -1
  254. package/dist/lib/mcp-connector-url.test.d.ts +1 -0
  255. package/dist/lib/mcp-connector-url.test.js +13 -0
  256. package/dist/lib/mcp-connector-url.test.js.map +1 -0
  257. package/dist/lib/mcp-snippets.d.ts +16 -0
  258. package/dist/lib/mcp-snippets.js +20 -2
  259. package/dist/lib/mcp-snippets.js.map +1 -1
  260. package/dist/lib/now-line.d.ts +66 -0
  261. package/dist/lib/now-line.js +193 -0
  262. package/dist/lib/now-line.js.map +1 -0
  263. package/dist/lib/now-line.test.d.ts +5 -0
  264. package/dist/lib/now-line.test.js +177 -0
  265. package/dist/lib/now-line.test.js.map +1 -0
  266. package/dist/lib/package-updates.d.ts +66 -0
  267. package/dist/lib/package-updates.js +76 -0
  268. package/dist/lib/package-updates.js.map +1 -0
  269. package/dist/lib/pod-moved.d.ts +70 -0
  270. package/dist/lib/pod-moved.js +146 -0
  271. package/dist/lib/pod-moved.js.map +1 -0
  272. package/dist/lib/pod-reach.d.ts +39 -0
  273. package/dist/lib/pod-reach.js +117 -0
  274. package/dist/lib/pod-reach.js.map +1 -0
  275. package/dist/lib/pod-reach.test.d.ts +1 -0
  276. package/dist/lib/pod-reach.test.js +67 -0
  277. package/dist/lib/pod-reach.test.js.map +1 -0
  278. package/dist/lib/pod.d.ts +8 -4
  279. package/dist/lib/pod.js +12 -10
  280. package/dist/lib/pod.js.map +1 -1
  281. package/dist/lib/register-claude-code-mcp.test.d.ts +1 -0
  282. package/dist/lib/register-claude-code-mcp.test.js +97 -0
  283. package/dist/lib/register-claude-code-mcp.test.js.map +1 -0
  284. package/dist/lib/review-credential.js +3 -1
  285. package/dist/lib/review-credential.js.map +1 -1
  286. package/dist/lib/safe-open.d.ts +5 -3
  287. package/dist/lib/safe-open.js +9 -8
  288. package/dist/lib/safe-open.js.map +1 -1
  289. package/dist/lib/self-host.d.ts +17 -0
  290. package/dist/lib/self-host.js +24 -0
  291. package/dist/lib/self-host.js.map +1 -0
  292. package/dist/lib/session-feed.d.ts +185 -0
  293. package/dist/lib/session-feed.js +257 -0
  294. package/dist/lib/session-feed.js.map +1 -0
  295. package/dist/lib/targets.d.ts +52 -3
  296. package/dist/lib/targets.js +150 -209
  297. package/dist/lib/targets.js.map +1 -1
  298. package/dist/lib/term.d.ts +24 -0
  299. package/dist/lib/term.js +52 -0
  300. package/dist/lib/term.js.map +1 -0
  301. package/dist/lib/term.test.d.ts +1 -0
  302. package/dist/lib/term.test.js +29 -0
  303. package/dist/lib/term.test.js.map +1 -0
  304. package/dist/lib/transcript-index.d.ts +94 -0
  305. package/dist/lib/transcript-index.js +321 -0
  306. package/dist/lib/transcript-index.js.map +1 -0
  307. package/dist/lib/transcript-index.test.d.ts +10 -0
  308. package/dist/lib/transcript-index.test.js +200 -0
  309. package/dist/lib/transcript-index.test.js.map +1 -0
  310. package/dist/lib/wait-help.d.ts +11 -0
  311. package/dist/lib/wait-help.js +25 -0
  312. package/dist/lib/wait-help.js.map +1 -0
  313. package/dist/lib/wait-state.d.ts +57 -0
  314. package/dist/lib/wait-state.js +114 -0
  315. package/dist/lib/wait-state.js.map +1 -0
  316. package/dist/lib/wake-hook-settings.d.ts +57 -0
  317. package/dist/lib/wake-hook-settings.js +153 -0
  318. package/dist/lib/wake-hook-settings.js.map +1 -0
  319. package/dist/lib/wake-state.d.ts +65 -0
  320. package/dist/lib/wake-state.js +122 -0
  321. package/dist/lib/wake-state.js.map +1 -0
  322. package/package.json +5 -2
  323. package/skills/README.md +1 -1
  324. package/skills/agent-os/SKILL.md +8 -8
  325. package/skills/synap/SKILL.md +178 -88
  326. package/skills/synap/code-skills.md +84 -0
  327. package/skills/synap/concepts.md +38 -20
  328. package/skills/synap/creative-loop.md +1 -1
  329. package/skills/synap/document-embeds.md +17 -1
  330. package/skills/synap/focus-sessions.md +17 -8
  331. package/skills/synap/from-intent.md +33 -17
  332. package/skills/synap/governance-rules.md +3 -1
  333. package/skills/synap/lenses.md +4 -4
  334. package/skills/synap/linking.md +2 -2
  335. package/skills/synap/mental-model.md +1 -1
  336. package/skills/synap/operating-mode-rules.md +1 -1
  337. package/skills/synap/quick-reference.md +2 -1
  338. package/skills/synap/reflexes.md +8 -8
  339. package/skills/synap/viewframe-cells.md +1 -1
  340. package/skills/synap/work-model.md +24 -10
  341. package/skills/synap/workspace-design.md +5 -5
  342. package/skills/synap/writes.md +16 -9
  343. package/skills/synap/writing-titles.md +7 -0
  344. package/skills/synap-app/SKILL.md +148 -66
  345. package/skills/synap-app/creating-the-base-app.md +12 -7
  346. package/skills/synap-app/governance.md +27 -16
  347. package/skills/synap-app/install-commands.md +18 -9
  348. package/skills/synap-app/overview.md +16 -9
  349. package/skills/synap-app/using-the-sdk.md +75 -25
  350. package/skills/synap-market/SKILL.md +1 -1
  351. package/skills/synap-market/governance-and-catalog.md +1 -1
  352. package/skills/synap-schema/SKILL.md +24 -21
  353. package/skills/synap-schema/intent-vocabulary-ssot.md +24 -21
  354. package/templates/box/runner.py +206 -0
@@ -16,11 +16,112 @@
16
16
  * reply hangs the client). The pod is stateless, so there is no server→client
17
17
  * GET stream to hold open.
18
18
  *
19
+ * ONE method is not transparent: `tools/list`. Claude Code asks for it once per
20
+ * spawn and never retries a failure, so a bridge restarted in a herd (an account
21
+ * switch moves every session at once) that meets a slow pod leaves the synap
22
+ * tools absent for the process's whole life. The bridge keeps the last good list
23
+ * on disk per (pod URL, key) and, when the pod fails or has not answered within
24
+ * `toolsListWaitMs`, answers from it; the real call carries on in the background
25
+ * and a changed list is announced with `notifications/tools/list_changed` (the
26
+ * pod declares `tools.listChanged`). No cache yet = today's behaviour exactly.
27
+ *
19
28
  * Known edge: the id is fixed at spawn. After `/clear` Claude Code starts a new
20
29
  * session id, but MCP writes keep the pre-clear one (its transcript up to the
21
30
  * clear stays resumable). CLI calls send the live id (`hub-client.ts`).
31
+ *
32
+ * THE MODEL: Claude Code gives a stdio server no model, and a plugin can set no
33
+ * header on another MCP server's requests. The synap-bridge plugin (0.14.0)
34
+ * writes what the conversation runs on to `~/.synap/run/claude-code-<id>.json`
35
+ * (`{"model", "provider", "at"}`) whenever it changes; the bridge reads that
36
+ * file for its spawn-time id and adds `X-Synap-Model` / `X-Synap-Model-
37
+ * Provider` (re-read only when the file changed). Missing, unreadable or
38
+ * invalid = no model headers, never an error, never a guess. Same edge as the
39
+ * conversation: after `/clear` the plugin writes under the new id, which this
40
+ * bridge does not read, so those calls carry no model.
22
41
  */
42
+ import { createHash } from "node:crypto";
43
+ import fs from "node:fs";
44
+ import os from "node:os";
45
+ import path from "node:path";
23
46
  import readline from "node:readline";
47
+ import { writeFileAtomic } from "./atomic-file.js";
48
+ import { CONVERSATION_CWD_HEADER, CONVERSATION_CWD_META_KEY, CONVERSATION_HEADER, CONVERSATION_META_KEY, modelHeaders, } from "./conversation-headers.js";
49
+ /** A Claude Code session id as a file name, as synap-bridge names it: nothing that walks a path. */
50
+ const SESSION_FILE_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
51
+ /**
52
+ * `~/.synap/run/claude-code-<session id>.json`, the file the synap-bridge
53
+ * plugin writes the conversation's model and provider to (its
54
+ * `modelHandoffPath`). Undefined without an id, or with one that could name
55
+ * another file.
56
+ */
57
+ export function modelHandoffPath(sessionId, home = os.homedir()) {
58
+ const id = sessionId?.trim();
59
+ if (!id || !SESSION_FILE_NAME.test(id) || !path.isAbsolute(home))
60
+ return undefined;
61
+ return path.join(home, ".synap", "run", `claude-code-${id}.json`);
62
+ }
63
+ /**
64
+ * The model headers from the hand-off file, read again only when the file
65
+ * changed (stat: mtime, size, inode). A missing, unreadable or invalid file is
66
+ * no headers; the values go through `modelHeaders` (a model the pod would
67
+ * drop, or a provider that is not a slug, is left out).
68
+ */
69
+ export function modelHandoffReader(file) {
70
+ let seen;
71
+ let headers = {};
72
+ return () => {
73
+ if (!file)
74
+ return {};
75
+ let stamp;
76
+ try {
77
+ const st = fs.statSync(file);
78
+ stamp = `${st.mtimeMs}:${st.size}:${st.ino}`;
79
+ }
80
+ catch {
81
+ seen = undefined;
82
+ headers = {};
83
+ return headers; // no file: this conversation's model is unknown
84
+ }
85
+ if (stamp === seen)
86
+ return headers;
87
+ seen = stamp;
88
+ try {
89
+ const said = JSON.parse(fs.readFileSync(file, "utf8"));
90
+ headers = modelHeaders(typeof said?.model === "string" ? said.model : undefined, typeof said?.provider === "string" ? said.provider : undefined);
91
+ }
92
+ catch {
93
+ headers = {}; // unreadable or not JSON: unknown, never a guess
94
+ }
95
+ return headers;
96
+ };
97
+ }
98
+ const TOOLS_LIST_WAIT_MS = 5_000;
99
+ const TOOLS_LIST_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
100
+ /** The background refresh after a cache answer must not hold the process open for ever. */
101
+ const TOOLS_LIST_REFRESH_LIMIT_MS = 60_000;
102
+ /** One file per (MCP URL, key): the list depends on the key's profile and the URL's lens. */
103
+ export function toolsListCachePath(dir, url, apiKey) {
104
+ const h = createHash("sha256").update(`${url}\n${apiKey}`).digest("hex").slice(0, 24);
105
+ return path.join(dir, `mcp-tools-${h}.json`);
106
+ }
107
+ /** A plain first-page tools/list REQUEST: the only shape the cache can stand in for. */
108
+ function isPlainToolsList(msg) {
109
+ if (!msg || Array.isArray(msg) || typeof msg !== "object")
110
+ return false;
111
+ const m = msg;
112
+ return (m.method === "tools/list" &&
113
+ (typeof m.id === "string" || typeof m.id === "number") &&
114
+ (m.params?.cursor === undefined || m.params.cursor === null));
115
+ }
116
+ /** The reply's `result.tools` when it is a complete (single-page) list, else undefined. */
117
+ function completeToolsResult(payload) {
118
+ if (!payload || Array.isArray(payload) || typeof payload !== "object")
119
+ return undefined;
120
+ const r = payload.result;
121
+ if (!r || !Array.isArray(r.tools) || r.nextCursor)
122
+ return undefined;
123
+ return r;
124
+ }
24
125
  /** Ids of the REQUESTS in a message (a batch may carry several). */
25
126
  function requestIds(msg) {
26
127
  const items = Array.isArray(msg) ? msg : [msg];
@@ -29,6 +130,38 @@ function requestIds(msg) {
29
130
  .map((m) => m.id)
30
131
  .filter((id) => typeof id === "string" || typeof id === "number");
31
132
  }
133
+ /**
134
+ * The line with `params._meta["live.synap/conversation"]` (and the cwd) set on
135
+ * every request, from the same conversation the headers carry, for a path that
136
+ * cannot set headers. The header still wins at the pod. Responses, and a line
137
+ * that is not an object, pass through untouched.
138
+ */
139
+ export function withConversationMeta(msg, headers) {
140
+ const conversation = headers?.[CONVERSATION_HEADER];
141
+ if (!conversation)
142
+ return msg;
143
+ const cwd = headers?.[CONVERSATION_CWD_HEADER];
144
+ const stamp = (m) => {
145
+ if (!m || typeof m !== "object" || typeof m.method !== "string")
146
+ return m;
147
+ const params = (m.params ?? {});
148
+ if (typeof params !== "object" || Array.isArray(params))
149
+ return m;
150
+ const meta = (params._meta ?? {});
151
+ return {
152
+ ...m,
153
+ params: {
154
+ ...params,
155
+ _meta: {
156
+ ...meta,
157
+ [CONVERSATION_META_KEY]: conversation,
158
+ ...(cwd ? { [CONVERSATION_CWD_META_KEY]: cwd } : {}),
159
+ },
160
+ },
161
+ };
162
+ };
163
+ return Array.isArray(msg) ? msg.map(stamp) : stamp(msg);
164
+ }
32
165
  /** Parse SSE text into the `data:` payload of each complete event. */
33
166
  export function sseEvents(buffer) {
34
167
  const events = [];
@@ -57,6 +190,7 @@ export async function runMcpBridge(opts) {
57
190
  let sessionId;
58
191
  let protocolVersion;
59
192
  const inFlight = new Set();
193
+ const ranOn = modelHandoffReader(opts.modelFile);
60
194
  const write = (msg) => {
61
195
  if (msg && typeof msg === "object") {
62
196
  const m = msg;
@@ -72,18 +206,32 @@ export async function runMcpBridge(opts) {
72
206
  for (const id of ids)
73
207
  write({ jsonrpc: "2.0", id, error: { code: -32000, message } });
74
208
  };
75
- const forward = async (line) => {
76
- let msg;
209
+ const cacheDir = opts.cacheDir ?? path.join(os.homedir(), ".synap", "cache");
210
+ const cacheFile = toolsListCachePath(cacheDir, opts.url, opts.apiKey);
211
+ const readCache = () => {
77
212
  try {
78
- msg = JSON.parse(line);
213
+ const c = JSON.parse(fs.readFileSync(cacheFile, "utf8"));
214
+ const fresh = Date.now() - c.savedAt <= (opts.toolsListMaxAgeMs ?? TOOLS_LIST_MAX_AGE_MS);
215
+ return fresh && completeToolsResult({ result: c.result }) ? c : undefined;
79
216
  }
80
217
  catch {
81
- log("dropped a line that is not JSON");
82
- return;
218
+ return undefined; // absent or unreadable: no cache, the pod answers alone
219
+ }
220
+ };
221
+ const writeCache = (result) => {
222
+ try {
223
+ writeFileAtomic(cacheFile, JSON.stringify({ savedAt: Date.now(), result }));
224
+ }
225
+ catch (err) {
226
+ log(`could not write the tools cache: ${err instanceof Error ? err.message : String(err)}`);
83
227
  }
84
- const ids = requestIds(msg);
228
+ };
229
+ /** POST one message to the pod; what comes back goes to `emit`, a failure to `onFail`. */
230
+ const send = async (msg, ids, emit, onFail, signal) => {
231
+ const emitAll = (payload) => (Array.isArray(payload) ? payload.forEach(emit) : emit(payload));
85
232
  try {
86
233
  const res = await doFetch(opts.url, {
234
+ ...(signal ? { signal } : {}),
87
235
  method: "POST",
88
236
  headers: {
89
237
  Authorization: `Bearer ${opts.apiKey}`,
@@ -92,15 +240,16 @@ export async function runMcpBridge(opts) {
92
240
  ...(sessionId ? { "Mcp-Session-Id": sessionId } : {}),
93
241
  ...(protocolVersion ? { "Mcp-Protocol-Version": protocolVersion } : {}),
94
242
  ...opts.headers,
243
+ ...ranOn(),
95
244
  },
96
- body: line,
245
+ body: JSON.stringify(withConversationMeta(msg, opts.headers)),
97
246
  });
98
247
  const minted = res.headers.get("mcp-session-id");
99
248
  if (minted)
100
249
  sessionId = minted;
101
250
  if (!res.ok) {
102
251
  const text = await res.text().catch(() => "");
103
- fail(ids, `Synap pod answered HTTP ${res.status}${text ? `: ${text.slice(0, 300)}` : ""}`);
252
+ onFail(ids, `Synap pod answered HTTP ${res.status}${text ? `: ${text.slice(0, 300)}` : ""}`);
104
253
  return;
105
254
  }
106
255
  const type = res.headers.get("content-type") ?? "";
@@ -115,7 +264,7 @@ export async function runMcpBridge(opts) {
115
264
  buffer = rest;
116
265
  for (const data of events) {
117
266
  try {
118
- writeAll(JSON.parse(data));
267
+ emitAll(JSON.parse(data));
119
268
  }
120
269
  catch {
121
270
  log("dropped an event that is not JSON");
@@ -128,10 +277,82 @@ export async function runMcpBridge(opts) {
128
277
  }
129
278
  const text = await res.text();
130
279
  if (text.trim())
131
- writeAll(JSON.parse(text));
280
+ emitAll(JSON.parse(text));
132
281
  }
133
282
  catch (err) {
134
- fail(ids, `Synap pod unreachable at ${opts.url}: ${err instanceof Error ? err.message : String(err)}`);
283
+ onFail(ids, `Synap pod unreachable at ${opts.url}: ${err instanceof Error ? err.message : String(err)}`);
284
+ }
285
+ };
286
+ const forward = async (line) => {
287
+ let msg;
288
+ try {
289
+ msg = JSON.parse(line);
290
+ }
291
+ catch {
292
+ log("dropped a line that is not JSON");
293
+ return;
294
+ }
295
+ if (isPlainToolsList(msg))
296
+ return forwardToolsList(msg);
297
+ await send(msg, requestIds(msg), write, fail);
298
+ };
299
+ const sleep = (ms) => new Promise((r) => setTimeout(() => r("slow"), ms).unref?.());
300
+ /**
301
+ * tools/list through the cache. The pod's reply is collected rather than
302
+ * written, so that exactly ONE reply per id reaches Claude Code: the pod's
303
+ * when it is good and in time, else the cache's (and the pod's late reply
304
+ * then only refreshes the cache).
305
+ */
306
+ const forwardToolsList = async (msg) => {
307
+ const collected = [];
308
+ let failure;
309
+ const settled = send(msg, [msg.id], (p) => collected.push(p), (ids, message) => {
310
+ failure = { ids, message };
311
+ }, AbortSignal.timeout(TOOLS_LIST_REFRESH_LIMIT_MS)).then(() => "done");
312
+ const first = await Promise.race([settled, sleep(opts.toolsListWaitMs ?? TOOLS_LIST_WAIT_MS)]);
313
+ const cached = readCache();
314
+ const goodResult = () => failure ? undefined : completeToolsResult(collected.find((c) => c?.id === msg.id));
315
+ const serveCache = (why) => {
316
+ log(`${why}: answering tools/list from the cache saved ${Math.round((Date.now() - cached.savedAt) / 1000)}s ago`);
317
+ write({ jsonrpc: "2.0", id: msg.id, result: cached.result });
318
+ };
319
+ if (first === "done") {
320
+ const fresh = goodResult();
321
+ if (fresh) {
322
+ writeCache(fresh);
323
+ collected.forEach(write);
324
+ }
325
+ else if (cached) {
326
+ serveCache(failure ? failure.message : "the pod's tools/list was not a list");
327
+ }
328
+ else if (failure) {
329
+ fail(failure.ids, failure.message);
330
+ }
331
+ else {
332
+ collected.forEach(write);
333
+ }
334
+ return;
335
+ }
336
+ // The pod is slow. No cache: wait for it, exactly as without one.
337
+ if (!cached) {
338
+ await settled;
339
+ const fresh = goodResult();
340
+ if (fresh)
341
+ writeCache(fresh);
342
+ if (failure)
343
+ fail(failure.ids, failure.message);
344
+ else
345
+ collected.forEach(write);
346
+ return;
347
+ }
348
+ serveCache(`the pod has not answered tools/list in ${opts.toolsListWaitMs ?? TOOLS_LIST_WAIT_MS}ms`);
349
+ await settled;
350
+ const fresh = goodResult();
351
+ if (!fresh)
352
+ return; // still failing: the cached list stays what Claude Code has
353
+ writeCache(fresh);
354
+ if (JSON.stringify(fresh.tools) !== JSON.stringify(cached.result.tools)) {
355
+ write({ jsonrpc: "2.0", method: "notifications/tools/list_changed" });
135
356
  }
136
357
  };
137
358
  const rl = readline.createInterface({ input, crlfDelay: Infinity });
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-bridge.js","sourceRoot":"","sources":["../../src/lib/mcp-bridge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-bridge.js","sourceRoot":"","sources":["../../src/lib/mcp-bridge.ts"],"names":[],"mappings":"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"}
@@ -6,10 +6,13 @@
6
6
  * silent drop hangs Claude Code); SSE and JSON bodies both come through; a
7
7
  * notification (no id) gets no invented reply.
8
8
  */
9
+ import fs from "node:fs";
10
+ import os from "node:os";
11
+ import path from "node:path";
9
12
  import { PassThrough } from "node:stream";
10
- import { describe, expect, it } from "vitest";
11
- import { runMcpBridge, sseEvents } from "./mcp-bridge.js";
12
- import { conversationHeaders, claudeCodeConversation } from "./conversation-headers.js";
13
+ import { describe, expect, it, vi } from "vitest";
14
+ import { modelHandoffPath, modelHandoffReader, runMcpBridge, sseEvents, toolsListCachePath, withConversationMeta, } from "./mcp-bridge.js";
15
+ import { conversationHeaders, claudeCodeConversation, cliConversation } from "./conversation-headers.js";
13
16
  const SID = "8c34a72b-b1c1-4c37-9c1f-02920b69ccb2";
14
17
  async function drive(lines, respond, headers = {}) {
15
18
  const input = new PassThrough();
@@ -78,7 +81,14 @@ describe("conversation headers", () => {
78
81
  it("only a real Claude Code UUID names a conversation", () => {
79
82
  expect(claudeCodeConversation(SID.toUpperCase())).toBe(`claude-code:${SID}`);
80
83
  expect(claudeCodeConversation("not-a-uuid")).toBeUndefined();
81
- expect(conversationHeaders(undefined, "/x")).toEqual({});
84
+ });
85
+ it("outside Claude Code the run names itself: one id per process, SYNAP_RUN_ID wins", () => {
86
+ const a = conversationHeaders(undefined, "/x")["X-Synap-Conversation"];
87
+ expect(a).toMatch(/^synap-cli:[0-9a-f-]{36}$/);
88
+ expect(conversationHeaders(undefined, "/y")["X-Synap-Conversation"]).toBe(a);
89
+ expect(conversationHeaders(undefined, "/x")).not.toHaveProperty("X-Synap-Conversation-Cwd");
90
+ expect(cliConversation({ SYNAP_RUN_ID: "nightly-42" })).toBe("synap-cli:nightly-42");
91
+ expect(cliConversation({ SYNAP_RUN_ID: "bad id" })).toBe(a);
82
92
  });
83
93
  it("a non-ASCII cwd is dropped, the conversation still sent", () => {
84
94
  expect(conversationHeaders(SID, "/Users/é")).toEqual({ "X-Synap-Conversation": `claude-code:${SID}` });
@@ -87,4 +97,214 @@ describe("conversation headers", () => {
87
97
  expect(sseEvents("data: a\n\ndata: b")).toEqual({ events: ["a"], rest: "data: b" });
88
98
  });
89
99
  });
100
+ describe("conversation in _meta", () => {
101
+ const conv = conversationHeaders(SID, "/Users/a/code");
102
+ it("every forwarded request carries live.synap/conversation (and the cwd) in params._meta", async () => {
103
+ const { seen } = await drive([
104
+ { jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "t", _meta: { keep: 1 } } },
105
+ { jsonrpc: "2.0", id: 2, method: "tools/list" },
106
+ ], (b) => json({ jsonrpc: "2.0", id: b.id, result: {} }), conv);
107
+ const bodies = seen.map((s) => JSON.parse(s.body));
108
+ for (const b of bodies) {
109
+ expect(b.params._meta["live.synap/conversation"]).toBe(`claude-code:${SID}`);
110
+ expect(b.params._meta["live.synap/conversationCwd"]).toBe("/Users/a/code");
111
+ }
112
+ expect(bodies.find((b) => b.id === 1).params).toMatchObject({ name: "t", _meta: { keep: 1 } });
113
+ });
114
+ it("leaves responses and header-less bridges untouched", () => {
115
+ const resp = { jsonrpc: "2.0", id: 1, result: {} };
116
+ expect(withConversationMeta(resp, conv)).toEqual(resp);
117
+ const req = { jsonrpc: "2.0", id: 1, method: "ping" };
118
+ expect(withConversationMeta(req, {})).toBe(req);
119
+ });
120
+ });
121
+ describe("tools/list cache (an account switch must not drop the tools)", () => {
122
+ const list = (names, id = 1) => ({
123
+ jsonrpc: "2.0",
124
+ id,
125
+ result: { tools: names.map((name) => ({ name })) },
126
+ });
127
+ const tmp = () => fs.mkdtempSync(path.join(os.tmpdir(), "synap-bridge-cache-"));
128
+ /** One bridge process: sends `tools/list`, the pod answers with `pod`. */
129
+ async function run(cacheDir, pod, extra = {}) {
130
+ const input = new PassThrough();
131
+ const output = new PassThrough();
132
+ const chunks = [];
133
+ output.on("data", (c) => chunks.push(String(c)));
134
+ const fetchImpl = ((_u, init) => pod(init));
135
+ const done = runMcpBridge({
136
+ url: "https://pod/mcp",
137
+ apiKey: extra.apiKey ?? "k",
138
+ input,
139
+ output,
140
+ fetchImpl,
141
+ log: () => { },
142
+ cacheDir,
143
+ toolsListWaitMs: extra.waitMs ?? 30,
144
+ toolsListMaxAgeMs: extra.maxAgeMs,
145
+ });
146
+ input.write(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: extra.params }) + "\n");
147
+ input.end();
148
+ await done;
149
+ return chunks.join("").split("\n").filter(Boolean).map((l) => JSON.parse(l));
150
+ }
151
+ const ok = (names) => async () => json(list(names));
152
+ const down = async () => new Response("bad gateway", { status: 502 });
153
+ it("a good list is cached, then answers when the pod fails", async () => {
154
+ const dir = tmp();
155
+ expect(await run(dir, ok(["a", "b"]))).toEqual([list(["a", "b"])]);
156
+ expect(await run(dir, down)).toEqual([list(["a", "b"])]);
157
+ });
158
+ it("a pod that does not answer in time is answered from the cache, once; a changed list is announced", async () => {
159
+ const dir = tmp();
160
+ await run(dir, ok(["a"]));
161
+ const late = async () => {
162
+ await new Promise((r) => setTimeout(r, 120));
163
+ return json(list(["a", "c"]));
164
+ };
165
+ const out = await run(dir, late);
166
+ expect(out).toEqual([list(["a"]), { jsonrpc: "2.0", method: "notifications/tools/list_changed" }]);
167
+ // the refresh was kept: the next outage serves the NEW list
168
+ expect(await run(dir, down)).toEqual([list(["a", "c"])]);
169
+ });
170
+ it("a late, identical list sends no notification", async () => {
171
+ const dir = tmp();
172
+ await run(dir, ok(["a"]));
173
+ const late = async () => {
174
+ await new Promise((r) => setTimeout(r, 120));
175
+ return json(list(["a"]));
176
+ };
177
+ expect(await run(dir, late)).toEqual([list(["a"])]);
178
+ });
179
+ it("no cache yet: the failure and the slow pod behave as before", async () => {
180
+ const out = await run(tmp(), down);
181
+ expect(out).toHaveLength(1);
182
+ expect(out[0]).toMatchObject({ id: 1, error: { code: -32000 } });
183
+ const slow = async () => {
184
+ await new Promise((r) => setTimeout(r, 120));
185
+ return json(list(["a"]));
186
+ };
187
+ expect(await run(tmp(), slow)).toEqual([list(["a"])]);
188
+ });
189
+ it("a JSON-RPC error from the pod is not cached and the cache answers it", async () => {
190
+ const dir = tmp();
191
+ const err = async () => json({ jsonrpc: "2.0", id: 1, error: { code: -32603, message: "boom" } });
192
+ expect((await run(dir, err))[0]).toMatchObject({ error: { message: "boom" } });
193
+ await run(dir, ok(["a"]));
194
+ expect(await run(dir, err)).toEqual([list(["a"])]);
195
+ });
196
+ it("a paged list is neither cached nor intercepted", async () => {
197
+ const dir = tmp();
198
+ const paged = async () => json({ jsonrpc: "2.0", id: 1, result: { tools: [{ name: "a" }], nextCursor: "p2" } });
199
+ await run(dir, paged);
200
+ expect(fs.readdirSync(dir)).toEqual([]);
201
+ await run(dir, ok(["a"]));
202
+ // a request carrying a cursor goes straight to the pod, even when it fails
203
+ const out = await run(dir, down, { params: { cursor: "p2" } });
204
+ expect(out[0]).toMatchObject({ id: 1, error: { code: -32000 } });
205
+ });
206
+ it("the cache belongs to a key: another key never gets this key's tools", async () => {
207
+ const dir = tmp();
208
+ await run(dir, ok(["a"]), { apiKey: "key-1" });
209
+ expect(toolsListCachePath(dir, "https://pod/mcp", "key-1")).not.toBe(toolsListCachePath(dir, "https://pod/mcp", "key-2"));
210
+ expect((await run(dir, down, { apiKey: "key-2" }))[0]).toMatchObject({ error: { code: -32000 } });
211
+ expect(fs.readFileSync(toolsListCachePath(dir, "https://pod/mcp", "key-1"), "utf8")).not.toContain("key-1");
212
+ });
213
+ it("a cache older than the max age is not served", async () => {
214
+ const dir = tmp();
215
+ await run(dir, ok(["a"]));
216
+ await new Promise((r) => setTimeout(r, 20));
217
+ expect((await run(dir, down, { maxAgeMs: 5 }))[0]).toMatchObject({ error: { code: -32000 } });
218
+ });
219
+ });
220
+ describe("model hand-off (synap-bridge writes what the conversation runs on)", () => {
221
+ const tmp = () => fs.mkdtempSync(path.join(os.tmpdir(), "synap-bridge-model-"));
222
+ let tick = 1_700_000_000;
223
+ /** Writes the file as the plugin does, with a fresh mtime so a rewrite is always seen. */
224
+ const handOff = (file, text) => {
225
+ fs.mkdirSync(path.dirname(file), { recursive: true });
226
+ fs.writeFileSync(file, text);
227
+ tick += 10;
228
+ fs.utimesSync(file, tick, tick);
229
+ };
230
+ it("the path is the plugin's: ~/.synap/run/claude-code-<id>.json; an id that walks a path names none", () => {
231
+ expect(modelHandoffPath(SID, "/home/a")).toBe(`/home/a/.synap/run/claude-code-${SID}.json`);
232
+ for (const bad of [undefined, "", "../x", "a/b", "..", ".hidden", "a b", "a".repeat(129)]) {
233
+ expect(modelHandoffPath(bad, "/home/a")).toBeUndefined();
234
+ }
235
+ expect(modelHandoffPath(SID, "relative/home")).toBeUndefined();
236
+ });
237
+ it("present, changed, missing, invalid: the headers follow the file, never a guess", () => {
238
+ const file = modelHandoffPath(SID, tmp());
239
+ const read = modelHandoffReader(file);
240
+ // Missing: no headers, no error.
241
+ expect(read()).toEqual({});
242
+ handOff(file, JSON.stringify({ model: "claude-opus-5-5", provider: "anthropic", at: 1 }));
243
+ expect(read()).toEqual({ "X-Synap-Model": "claude-opus-5-5", "X-Synap-Model-Provider": "anthropic" });
244
+ // A /model switch rewrites the file: the next request names the new model.
245
+ handOff(file, JSON.stringify({ model: "claude-sonnet-5-5", provider: "zai", at: 2 }));
246
+ expect(read()).toEqual({ "X-Synap-Model": "claude-sonnet-5-5", "X-Synap-Model-Provider": "zai" });
247
+ // Unknown model (null) or one the pod would drop: the provider alone.
248
+ handOff(file, JSON.stringify({ model: null, provider: "anthropic", at: 3 }));
249
+ expect(read()).toEqual({ "X-Synap-Model-Provider": "anthropic" });
250
+ handOff(file, JSON.stringify({ model: "https://api.z.ai/v1", provider: "https://api.z.ai", at: 4 }));
251
+ expect(read()).toEqual({});
252
+ // Not JSON, or not an object: nothing.
253
+ handOff(file, "{\"model\": \"claude-op");
254
+ expect(read()).toEqual({});
255
+ handOff(file, "null");
256
+ expect(read()).toEqual({});
257
+ // The file goes (the session ended): nothing, not the last value.
258
+ handOff(file, JSON.stringify({ model: "claude-opus-5-5", provider: "anthropic", at: 5 }));
259
+ expect(read()["X-Synap-Model"]).toBe("claude-opus-5-5");
260
+ fs.rmSync(file);
261
+ expect(read()).toEqual({});
262
+ // No file named (no Claude Code id): nothing.
263
+ expect(modelHandoffReader(undefined)()).toEqual({});
264
+ });
265
+ it("an unchanged file is not read again", () => {
266
+ const file = modelHandoffPath(SID, tmp());
267
+ handOff(file, JSON.stringify({ model: "claude-opus-5-5", provider: "anthropic", at: 1 }));
268
+ const read = modelHandoffReader(file);
269
+ const spy = vi.spyOn(fs, "readFileSync");
270
+ try {
271
+ read();
272
+ read();
273
+ read();
274
+ expect(spy.mock.calls.filter((c) => c[0] === file).length).toBe(1);
275
+ }
276
+ finally {
277
+ spy.mockRestore();
278
+ }
279
+ });
280
+ it("every forwarded request carries the model the file names at that moment", async () => {
281
+ const file = modelHandoffPath(SID, tmp());
282
+ handOff(file, JSON.stringify({ model: "claude-opus-5-5", provider: "anthropic", at: 1 }));
283
+ const input = new PassThrough();
284
+ const output = new PassThrough();
285
+ const sent = [];
286
+ const fetchImpl = (async (_u, init) => {
287
+ sent.push(init.headers);
288
+ const id = JSON.parse(String(init.body)).id;
289
+ return json({ jsonrpc: "2.0", id, result: {} });
290
+ });
291
+ const conv = conversationHeaders(SID, "/Users/a/code");
292
+ const done = runMcpBridge({ url: "u", apiKey: "k", headers: conv, input, output, fetchImpl, log: () => { }, modelFile: file });
293
+ const call = async (id) => {
294
+ input.write(JSON.stringify({ jsonrpc: "2.0", id, method: "tools/call", params: { name: "synap_ask" } }) + "\n");
295
+ await new Promise((r) => setTimeout(r, 20));
296
+ };
297
+ await call(1);
298
+ handOff(file, JSON.stringify({ model: "claude-sonnet-5-5", provider: "anthropic", at: 2 }));
299
+ await call(2);
300
+ fs.rmSync(file);
301
+ await call(3);
302
+ input.end();
303
+ await done;
304
+ expect(sent.map((h) => h["X-Synap-Model"])).toEqual(["claude-opus-5-5", "claude-sonnet-5-5", undefined]);
305
+ expect(sent.map((h) => h["X-Synap-Model-Provider"])).toEqual(["anthropic", "anthropic", undefined]);
306
+ // The conversation headers are untouched.
307
+ expect(sent.every((h) => h["X-Synap-Conversation"] === `claude-code:${SID}`)).toBe(true);
308
+ });
309
+ });
90
310
  //# sourceMappingURL=mcp-bridge.test.js.map