@synap-core/cli 1.12.2 → 1.13.1-next.20261102.246dc73

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 (358) 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 +74 -44
  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 +42 -5
  54. package/dist/commands/knowledge.js +94 -29
  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 +89 -31
  61. package/dist/commands/market.js +215 -222
  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/template-lifecycle.d.ts +0 -31
  299. package/dist/lib/template-lifecycle.js +0 -74
  300. package/dist/lib/template-lifecycle.js.map +1 -1
  301. package/dist/lib/term.d.ts +24 -0
  302. package/dist/lib/term.js +52 -0
  303. package/dist/lib/term.js.map +1 -0
  304. package/dist/lib/term.test.d.ts +1 -0
  305. package/dist/lib/term.test.js +29 -0
  306. package/dist/lib/term.test.js.map +1 -0
  307. package/dist/lib/transcript-index.d.ts +94 -0
  308. package/dist/lib/transcript-index.js +321 -0
  309. package/dist/lib/transcript-index.js.map +1 -0
  310. package/dist/lib/transcript-index.test.d.ts +10 -0
  311. package/dist/lib/transcript-index.test.js +200 -0
  312. package/dist/lib/transcript-index.test.js.map +1 -0
  313. package/dist/lib/wait-help.d.ts +11 -0
  314. package/dist/lib/wait-help.js +25 -0
  315. package/dist/lib/wait-help.js.map +1 -0
  316. package/dist/lib/wait-state.d.ts +57 -0
  317. package/dist/lib/wait-state.js +114 -0
  318. package/dist/lib/wait-state.js.map +1 -0
  319. package/dist/lib/wake-hook-settings.d.ts +57 -0
  320. package/dist/lib/wake-hook-settings.js +153 -0
  321. package/dist/lib/wake-hook-settings.js.map +1 -0
  322. package/dist/lib/wake-state.d.ts +65 -0
  323. package/dist/lib/wake-state.js +122 -0
  324. package/dist/lib/wake-state.js.map +1 -0
  325. package/package.json +5 -2
  326. package/skills/README.md +1 -1
  327. package/skills/agent-os/SKILL.md +8 -8
  328. package/skills/synap/SKILL.md +188 -94
  329. package/skills/synap/code-skills.md +84 -0
  330. package/skills/synap/concepts.md +38 -20
  331. package/skills/synap/creative-loop.md +1 -1
  332. package/skills/synap/document-embeds.md +17 -1
  333. package/skills/synap/focus-sessions.md +17 -8
  334. package/skills/synap/from-intent.md +33 -17
  335. package/skills/synap/governance-rules.md +3 -1
  336. package/skills/synap/hand-off.md +10 -6
  337. package/skills/synap/lenses.md +4 -4
  338. package/skills/synap/linking.md +2 -2
  339. package/skills/synap/mental-model.md +1 -1
  340. package/skills/synap/operating-mode-rules.md +1 -1
  341. package/skills/synap/quick-reference.md +2 -1
  342. package/skills/synap/reflexes.md +8 -8
  343. package/skills/synap/viewframe-cells.md +1 -1
  344. package/skills/synap/work-model.md +24 -10
  345. package/skills/synap/workspace-design.md +5 -5
  346. package/skills/synap/writes.md +16 -9
  347. package/skills/synap/writing-titles.md +7 -0
  348. package/skills/synap-app/SKILL.md +148 -66
  349. package/skills/synap-app/creating-the-base-app.md +12 -7
  350. package/skills/synap-app/governance.md +27 -16
  351. package/skills/synap-app/install-commands.md +18 -9
  352. package/skills/synap-app/overview.md +16 -9
  353. package/skills/synap-app/using-the-sdk.md +75 -25
  354. package/skills/synap-market/SKILL.md +1 -1
  355. package/skills/synap-market/governance-and-catalog.md +1 -1
  356. package/skills/synap-schema/SKILL.md +24 -21
  357. package/skills/synap-schema/intent-vocabulary-ssot.md +24 -21
  358. package/templates/box/runner.py +206 -0
@@ -3,10 +3,11 @@ import { join } from "node:path";
3
3
  import { getActivePodConfig, getActiveWorkspaceId, getActiveWorkspaceIdForPod, findPodNameByUrl, getActiveProjectBinding, getActiveProjectId, listPodProfiles, getPodOverride, getSurfaceAgentKey, } from "./pod.js";
4
4
  import { resolveAgentOverride } from "./agents-config.js";
5
5
  import { resolveActiveLensForPod, getClaudeSessionId, writeLens, clearLensField, } from "./session-lens.js";
6
- import { conversationHeaders } from "./conversation-headers.js";
6
+ import { clientHeader, conversationHeaders } from "./conversation-headers.js";
7
7
  import { effectiveDirectoryLens, resolveDirectoryLensForPod, writeDirectoryLens, clearDirectoryLensField, } from "./directory-lens.js";
8
8
  import { preferClaudeCodeSurfaceKey } from "./key-source.js";
9
9
  import { samePodOrigin } from "./project-ref.js";
10
+ import { PodMovedError, podFetch } from "./pod-moved.js";
10
11
  import { HubRestClient } from "@synap-core/hub-rest-client";
11
12
  import { log } from "../utils/logger.js";
12
13
  import chalk from "chalk";
@@ -46,6 +47,47 @@ function resolveProjectIdForPod(podUrl, opts) {
46
47
  }
47
48
  return undefined;
48
49
  }
50
+ /**
51
+ * The pod's `code` on a 403 that refused a LIMITED key at the door (a key
52
+ * whose grant is not unbounded full access), whatever the door: a read no
53
+ * grant bounds yet, or a door that acts on everything the person owns.
54
+ * synap-backend `routers/hub-protocol/_middleware/auth.ts`.
55
+ */
56
+ export const GRANT_DOOR_UNSUPPORTED_CODE = "GRANT_DOOR_UNSUPPORTED";
57
+ /**
58
+ * The code a pod puts on a 403 that refused a write the calling key's GRANT
59
+ * does not cover (synap-backend `utils/grant-write-check.ts` `GRANT_DENIED`,
60
+ * added to every Hub REST denial by `_middleware/grant-denial-code.ts`), as
61
+ * `code` (or `reasonCode`, when the door set its own `code`), with the refused
62
+ * `action`.
63
+ */
64
+ export const GRANT_DENIED_CODE = "GRANT_DENIED";
65
+ /**
66
+ * How an OLDER pod words the same refusal, with no code (`{ error }` only):
67
+ * read only when the body carries no `GRANT_DENIED` code.
68
+ */
69
+ export const GRANT_DENIAL_PREFIX = "This key's grant does not allow";
70
+ /**
71
+ * A 403 the pod sent because the calling key's grant leaves the action out:
72
+ * a limited key (a computer's or an agent's) refused by design, never a
73
+ * missing workspace. `action` is the refused action key when the pod named it
74
+ * (`workspace.update`). The code first; the words only for an older pod.
75
+ */
76
+ export function grantDenialOf(err) {
77
+ if (!(err instanceof HubError) || err.status !== 403)
78
+ return null;
79
+ const body = err.body && typeof err.body === "object" && !Array.isArray(err.body)
80
+ ? err.body
81
+ : undefined;
82
+ if (err.code === GRANT_DENIED_CODE || body?.reasonCode === GRANT_DENIED_CODE) {
83
+ return typeof body?.action === "string" && body.action ? { action: body.action } : {};
84
+ }
85
+ const said = err.detail ?? "";
86
+ if (!said.startsWith(GRANT_DENIAL_PREFIX))
87
+ return null;
88
+ const named = /^This key's grant does not allow "([^"]+)"/.exec(said);
89
+ return named ? { action: named[1] } : {};
90
+ }
49
91
  /**
50
92
  * A failed Hub call, carrying the structured facts callers need to branch on.
51
93
  *
@@ -59,6 +101,13 @@ export class HubError extends Error {
59
101
  status;
60
102
  code;
61
103
  reason;
104
+ /**
105
+ * What the pod itself said, without the `Hub <METHOD> <path> failed` prefix
106
+ * `message` carries. Read a hint's words from here, never from `message`:
107
+ * the path is in `message`, so `/workspace/i` over it matched every refusal
108
+ * on a `/workspaces` route (finding de33a075).
109
+ */
110
+ detail;
62
111
  body;
63
112
  rawBody;
64
113
  path;
@@ -73,6 +122,7 @@ export class HubError extends Error {
73
122
  this.status = init.status;
74
123
  this.code = init.code;
75
124
  this.reason = init.reason;
125
+ this.detail = init.detail;
76
126
  this.body = init.body;
77
127
  this.rawBody = init.rawBody;
78
128
  this.path = init.path;
@@ -269,6 +319,7 @@ function hubError(method, path, status, rawBody, suffix, ctx) {
269
319
  status,
270
320
  code,
271
321
  reason,
322
+ detail,
272
323
  body: parsed,
273
324
  rawBody,
274
325
  path,
@@ -277,46 +328,50 @@ function hubError(method, path, status, rawBody, suffix, ctx) {
277
328
  workspaceId: ctx?.workspaceId,
278
329
  });
279
330
  }
280
- /** Where to send users who hit a wall. */
331
+ /**
332
+ * Where to send users who hit a wall. The support door is the ONE customer
333
+ * support entry point shared by every Synap surface (`from=cli` tells support
334
+ * which surface the person came from); Discord stays as the community option.
335
+ */
336
+ export const SUPPORT_URL = "https://synap.live/support?from=cli";
281
337
  const DISCORD_INVITE_URL = "https://discord.gg/xhRdQ7hG5h";
282
338
  /** Wrap a URL in an OSC-8 terminal hyperlink so `label` is clickable. */
283
339
  function osc8Link(url, label) {
284
340
  return `\x1b]8;;${url}\x1b\\${label}\x1b]8;;\x1b\\`;
285
341
  }
286
342
  /**
287
- * Once-per-process guard for the Discord help footer. A single CLI invocation
343
+ * Once-per-process guard for the help footer. A single CLI invocation
288
344
  * runs one command; printing the footer at most once keeps it a footer, not a
289
345
  * per-error/per-line spam.
290
346
  */
291
- let _discordHelpShown = false;
347
+ let _helpShown = false;
292
348
  /**
293
- * Append a one-line "need help?" footer with a clickable Discord invite. Prints
294
- * to stderr (same stream as the error it follows, so `2>/dev/null` hides both)
295
- * and only once per process. The invite is an OSC-8 hyperlink on an interactive
296
- * terminal, a plain URL when the stream is redirected/piped.
349
+ * Append a one-line "need help?" footer naming the support door (open a ticket)
350
+ * and the Discord invite. Prints to stderr (same stream as the error it
351
+ * follows, so `2>/dev/null` hides both) and only once per process. Each URL is
352
+ * an OSC-8 hyperlink on an interactive terminal, a plain URL when the stream is
353
+ * redirected/piped.
297
354
  */
298
- export function discordHelpLine() {
299
- if (_discordHelpShown)
355
+ export function helpFooterLine() {
356
+ if (_helpShown)
300
357
  return;
301
- _discordHelpShown = true;
302
- const link = process.stderr.isTTY
303
- ? osc8Link(DISCORD_INVITE_URL, DISCORD_INVITE_URL)
304
- : DISCORD_INVITE_URL;
305
- console.error(chalk.dim(` Need help? Join our Discord: ${link}`));
358
+ _helpShown = true;
359
+ const link = (url) => (process.stderr.isTTY ? osc8Link(url, url) : url);
360
+ console.error(chalk.dim(` Need help? Open a ticket: ${link(SUPPORT_URL)} (or join our Discord: ${link(DISCORD_INVITE_URL)})`));
306
361
  }
307
362
  /**
308
363
  * Print a failed Hub call: its humanized message plus a hint for the classes
309
364
  * where the cause is not obvious from the message alone. Lives here rather than
310
365
  * in utils/logger so the logger stays domain-agnostic and the taxonomy and its
311
- * presentation sit in one file. Always closes with the one-time Discord footer.
366
+ * presentation sit in one file. Always closes with the one-time help footer.
312
367
  */
313
- export function renderHubError(err) {
314
- renderHubErrorBody(err);
368
+ export function renderHubError(err, opts = {}) {
369
+ renderHubErrorBody(err, opts);
315
370
  // ONE central place every failed Hub command flows through — append the help
316
371
  // footer here so it prints once per failed command (guarded), not per line.
317
- discordHelpLine();
372
+ helpFooterLine();
318
373
  }
319
- function renderHubErrorBody(err) {
374
+ function renderHubErrorBody(err, opts) {
320
375
  // Hints use log.hint (stderr), not log.dim (stdout): a hint belongs to the
321
376
  // error it explains, and splitting one message across two streams means
322
377
  // `2>/dev/null` shows a bare hint with no error above it.
@@ -325,6 +380,11 @@ function renderHubErrorBody(err) {
325
380
  log.hint(err.hint);
326
381
  return;
327
382
  }
383
+ if (err instanceof PodMovedError) {
384
+ // One line: what happened and what to run.
385
+ log.error(err.message);
386
+ return;
387
+ }
328
388
  if (!(err instanceof HubError)) {
329
389
  log.error(err instanceof Error ? err.message : String(err));
330
390
  return;
@@ -390,16 +450,37 @@ function renderHubErrorBody(err) {
390
450
  // template or data problem. Only add the generic pod-fault hint when the body
391
451
  // gave nothing better than the status phrase.
392
452
  const hasSpecificCause = !!err.rawBody && !err.message.endsWith("the pod hit an internal error");
393
- // A 403/message naming a workspace: the real cause is almost always "that
394
- // workspace doesn't exist or isn't accessible ON THIS POD" (a stale saved
395
- // workspace id after a pod switch), not a scopes problem — name it + the
396
- // exact next command instead of the generic "credentials" hint.
397
- const namesWorkspace = /workspace/i.test(err.message);
453
+ // A limited key refused at the door: the pod says so in a structured `code`
454
+ // (synap-backend access/grant-door-fence.ts, the Hub auth middleware), for a
455
+ // read door no grant bounds yet and for a door that acts on everything the
456
+ // person owns. It says nothing about any workspace.
457
+ const limitedKeyRefusal = err.status === 403 && err.code === GRANT_DOOR_UNSUPPORTED_CODE;
458
+ // A limited key whose grant leaves this action out (no code: the pod's
459
+ // words, `grantDenialOf`). Refused by design; its words often name a
460
+ // workspace ("workspace.update", "in workspace <id>"), which is no reason to
461
+ // say the workspace is missing.
462
+ const grantDenial = grantDenialOf(err);
463
+ // A 403 whose OWN words name a workspace: the real cause is almost always
464
+ // "that workspace doesn't exist or isn't accessible ON THIS POD" (a stale
465
+ // saved workspace id after a pod switch), not a scopes problem — name it +
466
+ // the exact next command instead of the generic "credentials" hint. The
467
+ // pod's words (`detail`), never `message`: that one carries the path.
468
+ const namesWorkspace = !limitedKeyRefusal && !grantDenial && /workspace/i.test(err.detail ?? "");
398
469
  if (err.status >= 500) {
399
470
  if (!hasSpecificCause) {
400
471
  log.hint("The pod hit an internal error — check the pod logs for the stack trace.");
401
472
  }
402
473
  }
474
+ else if (limitedKeyRefusal) {
475
+ log.hint(`This key is limited (a computer's or an agent's key), and the pod does not let a limited key use ${err.method} ${err.path}. Nothing is wrong with your workspace.`);
476
+ log.hint("Until your pod opens this door to limited keys, do it with full access: in the Synap app, or with a full-access key in SYNAP_HUB_API_KEY.");
477
+ }
478
+ else if (grantDenial) {
479
+ const what = grantDenial.action ? `"${grantDenial.action}"` : "this action";
480
+ log.hint(`This key may not do ${what}: it is a limited key (a computer's or an agent's), and its grant leaves that out on purpose. Nothing is wrong with your workspace.`);
481
+ log.hint(opts.personDoor ??
482
+ "Do it yourself with full access: in the Synap app or pod admin.");
483
+ }
403
484
  else if (err.status === 403 && namesWorkspace) {
404
485
  // Name the exact workspace + pod this call used: the usual cause is a global
405
486
  // active workspace left over from ANOTHER pod (a stale `synap use` after a
@@ -475,8 +556,9 @@ export async function resolveHubConfig(opts) {
475
556
  return {
476
557
  podUrl: podOverride.podUrl,
477
558
  apiKey: podOverride.hubApiKey,
478
- // A profile's agentUserId may be empty; "cli" makes resolveUserId fetch /users/me.
479
- userId: podOverride.agentUserId || "cli",
559
+ // "cli": resolveUserId asks the pod who this key acts as (see HubConfig.userId).
560
+ userId: "cli",
561
+ agentUserId: podOverride.agentUserId || undefined,
480
562
  workspaceId: safeWs,
481
563
  projectId: resolveProjectIdForPod(podOverride.podUrl, {}),
482
564
  };
@@ -499,10 +581,13 @@ export async function resolveHubConfig(opts) {
499
581
  return {
500
582
  podUrl,
501
583
  apiKey: agentOverride.apiKey,
502
- // The AGENT's own id, recorded with its key. Never the pod profile name
503
- // (it was, so `synap worker` never recognised its own room posts and
504
- // resumed itself on them). Unrecorded → "cli": resolveUserId asks the pod.
505
- userId: agentOverride.agentUserId || "cli",
584
+ // The acting user is asked of the pod ("cli" → /users/me): an agent key
585
+ // acts as its linked human, and the pod 403s the agent's own id.
586
+ userId: "cli",
587
+ // The AGENT's own id, recorded with its key — for `synap worker` to
588
+ // recognise its own room posts (resolveAgentUserId). Never the pod
589
+ // profile name (it once was, and the worker resumed itself on them).
590
+ agentUserId: agentOverride.agentUserId || undefined,
506
591
  scopes: envScopes ? envScopes.split(",") : undefined,
507
592
  };
508
593
  }
@@ -513,7 +598,13 @@ export async function resolveHubConfig(opts) {
513
598
  const envScopes = process.env.SYNAP_KEY_SCOPES;
514
599
  const envWorkspace = process.env.SYNAP_WORKSPACE_ID;
515
600
  const envProject = process.env.SYNAP_PROJECT_ID;
516
- if (envPod && envKey && envUser) {
601
+ // Pod + key are enough. SYNAP_USER_ID used to be required too, so a shell
602
+ // with just the pair (`SYNAP_POD_URL=… SYNAP_HUB_API_KEY=… synap app
603
+ // connect`, the documented way to try another key) fell through to "No pod
604
+ // configured" (2026-10-20). Unset, userId is "cli" and resolveUserId asks
605
+ // the pod (/users/me) — the same fallback --pod and SYNAP_AGENT use. The
606
+ // claude-code surface-key preference below still applies.
607
+ if (envPod && envKey) {
517
608
  // Surface-bound resolution guard: if this pod has a `claude-code` surface
518
609
  // key pinned to it (via `synap connect --target=claude-code`) and the
519
610
  // ambient SYNAP_HUB_API_KEY doesn't match it, the session inherited a
@@ -534,6 +625,7 @@ export async function resolveHubConfig(opts) {
534
625
  }
535
626
  const apiKey = preferred.apiKey;
536
627
  const userId = preferred.userId;
628
+ const agentUserId = preferred.agentUserId;
537
629
  // Per-Claude-session lens wins over the global env var, so concurrent
538
630
  // sessions can each be scoped to a different workspace — but ONLY when the
539
631
  // lens belongs to THIS pod. A lens stamped with another pod is ignored so
@@ -550,6 +642,7 @@ export async function resolveHubConfig(opts) {
550
642
  podUrl: envPod,
551
643
  apiKey,
552
644
  userId,
645
+ agentUserId,
553
646
  // The global-config fallback is resolved AGAINST THIS POD (envPod) —
554
647
  // never send a workspace that belongs to a different pod.
555
648
  workspaceId: lens?.workspaceId ||
@@ -580,7 +673,10 @@ export async function resolveHubConfig(opts) {
580
673
  return {
581
674
  podUrl: config.podUrl,
582
675
  apiKey: config.hubApiKey,
583
- userId: config.agentUserId,
676
+ // "cli": resolveUserId asks the pod. A profile's agentUserId is the key's
677
+ // principal (a computer or agent key), not who the pod acts as.
678
+ userId: "cli",
679
+ agentUserId: config.agentUserId || undefined,
584
680
  // session lens › directory lens › global config. (No env rung here: this
585
681
  // branch is only reached when SYNAP_POD_URL is unset.)
586
682
  workspaceId: activeLens?.workspaceId || activeDirLens?.workspaceId || getActiveWorkspaceId(),
@@ -696,13 +792,18 @@ export function detachActiveSessionId(podUrl) {
696
792
  return previous;
697
793
  }
698
794
  /**
699
- * Per-request headers: the focus session this terminal is attached to, and the
795
+ * Per-request headers: which client calls (`synap-cli/<version>`), the focus
796
+ * session this terminal is attached to, and the
700
797
  * Claude Code conversation the call comes from (`conversation-headers.ts`) — so
701
798
  * the work it lands can be traced back to, and reopened in, that conversation.
702
799
  */
703
800
  function sessionHeaders(cfg) {
704
801
  const id = resolveActiveSessionId(cfg?.podUrl);
705
- return { ...conversationHeaders(getClaudeSessionId()), ...(id ? { "X-Session-Id": id } : {}) };
802
+ return {
803
+ ...clientHeader("synap-cli"),
804
+ ...conversationHeaders(getClaudeSessionId()),
805
+ ...(id ? { "X-Session-Id": id } : {}),
806
+ };
706
807
  }
707
808
  /** Max 429 retries for bulk import (each waits up to the rate-limit window). */
708
809
  const HUB_429_MAX_RETRIES = 12;
@@ -710,6 +811,12 @@ const HUB_429_MAX_RETRIES = 12;
710
811
  const HUB_429_DEFAULT_WAIT_MS = 62_000;
711
812
  /** Never sleep more than this on a single 429 (proxies sometimes send 180+). */
712
813
  const HUB_429_MAX_WAIT_MS = 75_000;
814
+ /**
815
+ * Never wait more than this in total on one request. 12 waits of up to 75s
816
+ * was ~15 minutes of a terminal that looked hung; past this budget the call
817
+ * fails and says so.
818
+ */
819
+ export const HUB_429_MAX_TOTAL_WAIT_MS = 150_000;
713
820
  /**
714
821
  * Client-side request budget — stay UNDER the server limit so we rarely 429.
715
822
  * Default 80/min is safe for pods still on the old 100/min auth limit.
@@ -752,26 +859,59 @@ async function acquireHubSlot() {
752
859
  }
753
860
  }
754
861
  /**
755
- * Parse Retry-After from headers or a Hub 429 body. Caps at 75s so a bad
756
- * proxy header never parks the importer for 3 minutes.
862
+ * The wait (ms) a pod 429 asks for, capped at 75s so a bad proxy header never
863
+ * parks the importer. The pod sends the wait in three shapes, and this reads
864
+ * all of them: the `Retry-After` header (seconds), a JSON body's numeric
865
+ * `retryAfter` (hub commands door) or `cause.retryAfterSec`, and the text
866
+ * "Retry after 60s." the key limiter writes into its message.
757
867
  */
758
- function retryAfterMs(res, bodyText) {
868
+ export function retryAfterMs(res, bodyText) {
869
+ const cap = (sec) => Math.min(Math.max(sec * 1000, 1_000), HUB_429_MAX_WAIT_MS);
870
+ const positive = (n) => typeof n === "number" && Number.isFinite(n) && n > 0;
759
871
  const h = res.headers.get("retry-after");
760
872
  if (h) {
761
873
  const sec = parseInt(h, 10);
762
- if (Number.isFinite(sec) && sec > 0) {
763
- return Math.min(Math.max(sec * 1000, 1_000), HUB_429_MAX_WAIT_MS);
874
+ if (positive(sec))
875
+ return cap(sec);
876
+ }
877
+ try {
878
+ const body = JSON.parse(bodyText);
879
+ const cause = body?.cause;
880
+ for (const v of [body?.retryAfter, body?.retryAfterSec, cause?.retryAfterSec]) {
881
+ if (positive(v))
882
+ return cap(v);
764
883
  }
765
884
  }
885
+ catch {
886
+ // not JSON: fall through to the text shape
887
+ }
766
888
  const m = bodyText.match(/retry after\s+(\d+)\s*s/i);
767
889
  if (m) {
768
890
  const sec = parseInt(m[1], 10);
769
- if (Number.isFinite(sec) && sec > 0) {
770
- return Math.min(Math.max(sec * 1000, 1_000), HUB_429_MAX_WAIT_MS);
771
- }
891
+ if (positive(sec))
892
+ return cap(sec);
772
893
  }
773
894
  return HUB_429_DEFAULT_WAIT_MS;
774
895
  }
896
+ /**
897
+ * After a 429: say what is happening, sleep the pod's wait, or give up with a
898
+ * plain error once the retry count or the total wait budget is spent. Both
899
+ * the JSON and the multipart doors use it, so they cannot drift.
900
+ */
901
+ async function waitOutRateLimit(res, state, method, path, label) {
902
+ state.attempt++;
903
+ const bodyText = await res.text().catch(() => "");
904
+ if (state.attempt > HUB_429_MAX_RETRIES) {
905
+ throw hubError(method, path, 429, bodyText, `after ${HUB_429_MAX_RETRIES} retries`);
906
+ }
907
+ const wait = retryAfterMs(res, bodyText);
908
+ if (state.waitedMs + wait > HUB_429_MAX_TOTAL_WAIT_MS) {
909
+ throw hubError(method, path, 429, bodyText, `after waiting ${Math.round(state.waitedMs / 1000)}s (rate-limited; the pod is up, try again in a minute)`);
910
+ }
911
+ console.error(`[hub] rate-limited; the pod is up; retrying in ${Math.ceil(wait / 1000)}s (${label}, retry ${state.attempt}/${HUB_429_MAX_RETRIES})`);
912
+ state.waitedMs += wait;
913
+ await sleep(wait);
914
+ }
775
915
  /**
776
916
  * fetch with client pacing + automatic 429 backoff.
777
917
  *
@@ -781,30 +921,27 @@ function retryAfterMs(res, bodyText) {
781
921
  async function hubFetchWithRetry(url, init, label, timeoutMs) {
782
922
  const [labelMethod, ...labelRest] = label.split(" ");
783
923
  const labelPath = labelRest.join(" ");
784
- let attempt = 0;
924
+ const rl = { attempt: 0, waitedMs: 0 };
785
925
  for (;;) {
786
926
  await acquireHubSlot();
787
927
  let res;
788
928
  try {
789
- res = await fetch(url, init);
929
+ res = await podFetch(url, init);
790
930
  }
791
931
  catch (err) {
932
+ if (err instanceof PodMovedError)
933
+ throw err;
792
934
  throw networkError(err, url, labelMethod, labelPath, timeoutMs);
793
935
  }
794
936
  if (res.status !== 429)
795
937
  return res;
796
- attempt++;
797
- if (attempt > HUB_429_MAX_RETRIES) {
798
- const bodyText = await res.text().catch(() => "");
799
- throw hubError(labelMethod, labelPath, 429, bodyText, `after ${HUB_429_MAX_RETRIES} retries`);
800
- }
801
- const bodyText = await res.text().catch(() => "");
802
- const wait = retryAfterMs(res, bodyText);
803
- console.error(`[hub] 429 rate limit on ${label} — waiting ${Math.ceil(wait / 1000)}s (retry ${attempt}/${HUB_429_MAX_RETRIES})`);
804
- await sleep(wait);
938
+ await waitOutRateLimit(res, rl, labelMethod, labelPath, label);
805
939
  }
806
940
  }
807
- export async function hubGet(path, params, cfg) {
941
+ export async function hubGet(path, params, cfg,
942
+ // Default 15s suits reads. A long-poll door (`/answers/wait`) holds the
943
+ // request open for its own `timeoutSeconds`; its caller passes more.
944
+ timeoutMs = 15_000) {
808
945
  const url = new URL(`${cfg.podUrl}/api/hub${path}`);
809
946
  for (const [k, v] of Object.entries(params)) {
810
947
  if (v !== undefined)
@@ -812,8 +949,8 @@ export async function hubGet(path, params, cfg) {
812
949
  }
813
950
  const res = await hubFetchWithRetry(url.toString(), {
814
951
  headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
815
- signal: AbortSignal.timeout(15_000),
816
- }, `GET ${path}`, 15_000);
952
+ signal: AbortSignal.timeout(timeoutMs),
953
+ }, `GET ${path}`, timeoutMs);
817
954
  if (!res.ok) {
818
955
  const body = await res.text().catch(() => "");
819
956
  throw hubError("GET", path, res.status, body, undefined, cfg);
@@ -880,14 +1017,14 @@ export async function hubPostMultipart(path, form, cfg, timeoutMs = 120_000) {
880
1017
  * can only be read once).
881
1018
  */
882
1019
  export async function hubPostMultipartRetryable(path, buildForm, cfg, timeoutMs = 120_000) {
883
- let attempt = 0;
1020
+ const rl = { attempt: 0, waitedMs: 0 };
884
1021
  for (;;) {
885
1022
  await acquireHubSlot();
886
1023
  const form = buildForm();
887
1024
  const url = `${cfg.podUrl}/api/hub${path}`;
888
1025
  let res;
889
1026
  try {
890
- res = await fetch(url, {
1027
+ res = await podFetch(url, {
891
1028
  method: "POST",
892
1029
  headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
893
1030
  body: form,
@@ -895,6 +1032,8 @@ export async function hubPostMultipartRetryable(path, buildForm, cfg, timeoutMs
895
1032
  });
896
1033
  }
897
1034
  catch (err) {
1035
+ if (err instanceof PodMovedError)
1036
+ throw err;
898
1037
  throw networkError(err, url, "POST multipart", path, timeoutMs);
899
1038
  }
900
1039
  if (res.status !== 429) {
@@ -904,22 +1043,14 @@ export async function hubPostMultipartRetryable(path, buildForm, cfg, timeoutMs
904
1043
  }
905
1044
  return res.json();
906
1045
  }
907
- attempt++;
908
- if (attempt > HUB_429_MAX_RETRIES) {
909
- const bodyText = await res.text().catch(() => "");
910
- throw hubError("POST multipart", path, 429, bodyText, `after ${HUB_429_MAX_RETRIES} retries`);
911
- }
912
- const bodyText = await res.text().catch(() => "");
913
- const wait = retryAfterMs(res, bodyText);
914
- console.error(`[hub] 429 rate limit on POST multipart ${path} — waiting ${Math.ceil(wait / 1000)}s (retry ${attempt}/${HUB_429_MAX_RETRIES})`);
915
- await sleep(wait);
1046
+ await waitOutRateLimit(res, rl, "POST multipart", path, `POST multipart ${path}`);
916
1047
  }
917
1048
  }
918
1049
  export async function hubPatch(path, body, cfg) {
919
1050
  const url = `${cfg.podUrl}/api/hub${path}`;
920
1051
  let res;
921
1052
  try {
922
- res = await fetch(url, {
1053
+ res = await podFetch(url, {
923
1054
  method: "PATCH",
924
1055
  headers: { Authorization: `Bearer ${cfg.apiKey}`, "Content-Type": "application/json", ...sessionHeaders(cfg) },
925
1056
  body: JSON.stringify(body),
@@ -927,6 +1058,8 @@ export async function hubPatch(path, body, cfg) {
927
1058
  });
928
1059
  }
929
1060
  catch (err) {
1061
+ if (err instanceof PodMovedError)
1062
+ throw err;
930
1063
  throw networkError(err, url, "PATCH", path, 15_000);
931
1064
  }
932
1065
  if (!res.ok) {
@@ -942,13 +1075,15 @@ export async function hubDelete(path, cfg) {
942
1075
  const url = `${cfg.podUrl}/api/hub${path}`;
943
1076
  let res;
944
1077
  try {
945
- res = await fetch(url, {
1078
+ res = await podFetch(url, {
946
1079
  method: "DELETE",
947
1080
  headers: { Authorization: `Bearer ${cfg.apiKey}`, ...sessionHeaders(cfg) },
948
1081
  signal: AbortSignal.timeout(15_000),
949
1082
  });
950
1083
  }
951
1084
  catch (err) {
1085
+ if (err instanceof PodMovedError)
1086
+ throw err;
952
1087
  throw networkError(err, url, "DELETE", path, 15_000);
953
1088
  }
954
1089
  if (!res.ok) {