@intentic/sandbox-contract 1.311.0 → 1.313.0

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 (444) hide show
  1. package/README.md +32 -4
  2. package/dist/chores/chores.js +6 -6
  3. package/dist/chores/chores.js.map +1 -1
  4. package/dist/chores/index.d.ts +0 -1
  5. package/dist/chores/index.d.ts.map +1 -1
  6. package/dist/chores/index.js +0 -1
  7. package/dist/chores/index.js.map +1 -1
  8. package/dist/chores/prompt.d.ts.map +1 -1
  9. package/dist/chores/prompt.js +3 -2
  10. package/dist/chores/prompt.js.map +1 -1
  11. package/dist/contracts/accounts.contract.d.ts +69 -0
  12. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  13. package/dist/contracts/agent.contract.d.ts +436 -0
  14. package/dist/contracts/agent.contract.d.ts.map +1 -1
  15. package/dist/contracts/agent.contract.js +11 -1
  16. package/dist/contracts/agent.contract.js.map +1 -1
  17. package/dist/contracts/agents.contract.d.ts +718 -90
  18. package/dist/contracts/agents.contract.d.ts.map +1 -1
  19. package/dist/contracts/agents.contract.js +30 -2
  20. package/dist/contracts/agents.contract.js.map +1 -1
  21. package/dist/contracts/capabilities.contract.js +1 -1
  22. package/dist/contracts/capabilities.contract.js.map +1 -1
  23. package/dist/contracts/device.contract.d.ts +20 -4
  24. package/dist/contracts/device.contract.d.ts.map +1 -1
  25. package/dist/contracts/device.contract.js +1 -1
  26. package/dist/contracts/device.contract.js.map +1 -1
  27. package/dist/contracts/extensions.contract.d.ts +13 -0
  28. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  29. package/dist/contracts/git.contract.d.ts +2 -0
  30. package/dist/contracts/git.contract.d.ts.map +1 -1
  31. package/dist/contracts/offload.contract.d.ts +107 -0
  32. package/dist/contracts/offload.contract.d.ts.map +1 -0
  33. package/dist/contracts/offload.contract.js +53 -0
  34. package/dist/contracts/offload.contract.js.map +1 -0
  35. package/dist/contracts/personas.contract.d.ts +2 -0
  36. package/dist/contracts/personas.contract.d.ts.map +1 -1
  37. package/dist/contracts/ports.contract.d.ts +5 -0
  38. package/dist/contracts/ports.contract.d.ts.map +1 -1
  39. package/dist/contracts/runner.contract.d.ts +204 -0
  40. package/dist/contracts/runner.contract.d.ts.map +1 -1
  41. package/dist/contracts/runner.contract.js +3 -1
  42. package/dist/contracts/runner.contract.js.map +1 -1
  43. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  44. package/dist/contracts/secrets.contract.js +1 -0
  45. package/dist/contracts/secrets.contract.js.map +1 -1
  46. package/dist/contracts/sessions.contract.d.ts +59 -0
  47. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  48. package/dist/contracts/settings.contract.d.ts +20 -8
  49. package/dist/contracts/settings.contract.d.ts.map +1 -1
  50. package/dist/contracts/settings.contract.js +2 -2
  51. package/dist/contracts/settings.contract.js.map +1 -1
  52. package/dist/contracts/system.contract.d.ts +230 -15
  53. package/dist/contracts/system.contract.d.ts.map +1 -1
  54. package/dist/contracts/translator.contract.d.ts +88 -0
  55. package/dist/contracts/translator.contract.d.ts.map +1 -1
  56. package/dist/contracts/workspace.contract.d.ts +217 -0
  57. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  58. package/dist/contracts/workspace.contract.js +40 -3
  59. package/dist/contracts/workspace.contract.js.map +1 -1
  60. package/dist/documents/conversions.d.ts +81 -0
  61. package/dist/documents/conversions.d.ts.map +1 -0
  62. package/dist/documents/conversions.js +213 -0
  63. package/dist/documents/conversions.js.map +1 -0
  64. package/dist/documents/index.d.ts +4 -0
  65. package/dist/documents/index.d.ts.map +1 -0
  66. package/dist/documents/index.js +4 -0
  67. package/dist/documents/index.js.map +1 -0
  68. package/dist/documents/passthrough.d.ts +13 -0
  69. package/dist/documents/passthrough.d.ts.map +1 -0
  70. package/dist/documents/passthrough.js +101 -0
  71. package/dist/documents/passthrough.js.map +1 -0
  72. package/dist/documents/read.d.ts +24 -0
  73. package/dist/documents/read.d.ts.map +1 -0
  74. package/dist/documents/read.js +81 -0
  75. package/dist/documents/read.js.map +1 -0
  76. package/dist/events/agent-events.d.ts +235 -0
  77. package/dist/events/agent-events.d.ts.map +1 -1
  78. package/dist/events/agent-events.js +3 -0
  79. package/dist/events/agent-events.js.map +1 -1
  80. package/dist/events/child-run.d.ts +9 -0
  81. package/dist/events/child-run.d.ts.map +1 -0
  82. package/dist/events/child-run.js +41 -0
  83. package/dist/events/child-run.js.map +1 -0
  84. package/dist/events/errands.d.ts +5 -0
  85. package/dist/events/errands.d.ts.map +1 -0
  86. package/dist/events/errands.js +19 -0
  87. package/dist/events/errands.js.map +1 -0
  88. package/dist/events/land-breakage.d.ts +3 -0
  89. package/dist/events/land-breakage.d.ts.map +1 -1
  90. package/dist/events/land-breakage.js +3 -0
  91. package/dist/events/land-breakage.js.map +1 -1
  92. package/dist/events/land-conflict.d.ts +3 -0
  93. package/dist/events/land-conflict.d.ts.map +1 -0
  94. package/dist/events/land-conflict.js +4 -0
  95. package/dist/events/land-conflict.js.map +1 -0
  96. package/dist/events/requests.d.ts +152 -0
  97. package/dist/events/requests.d.ts.map +1 -1
  98. package/dist/events/requests.js +26 -1
  99. package/dist/events/requests.js.map +1 -1
  100. package/dist/events/system-events.d.ts +74 -12
  101. package/dist/events/system-events.d.ts.map +1 -1
  102. package/dist/events/system-events.js +3 -1
  103. package/dist/events/system-events.js.map +1 -1
  104. package/dist/events/transcript.d.ts +387 -0
  105. package/dist/events/transcript.d.ts.map +1 -1
  106. package/dist/events/transcript.js +3 -0
  107. package/dist/events/transcript.js.map +1 -1
  108. package/dist/events/verify-nudge.d.ts.map +1 -1
  109. package/dist/events/verify-nudge.js.map +1 -1
  110. package/dist/front/browser-wire.d.ts +6 -0
  111. package/dist/front/browser-wire.d.ts.map +1 -0
  112. package/dist/front/browser-wire.js +4 -0
  113. package/dist/front/browser-wire.js.map +1 -0
  114. package/dist/front/front-wire.d.ts +6 -0
  115. package/dist/front/front-wire.d.ts.map +1 -0
  116. package/dist/front/front-wire.js +5 -0
  117. package/dist/front/front-wire.js.map +1 -0
  118. package/dist/front/generated/browser-wire.d.ts +19 -0
  119. package/dist/front/generated/browser-wire.d.ts.map +1 -0
  120. package/dist/front/generated/browser-wire.js +2 -0
  121. package/dist/front/generated/browser-wire.js.map +1 -0
  122. package/dist/front/generated/wire.d.ts +70 -1
  123. package/dist/front/generated/wire.d.ts.map +1 -1
  124. package/dist/ids/conversation-ids.d.ts +3 -0
  125. package/dist/ids/conversation-ids.d.ts.map +1 -1
  126. package/dist/ids/conversation-ids.js +4 -2
  127. package/dist/ids/conversation-ids.js.map +1 -1
  128. package/dist/ids/session-names.d.ts +3 -0
  129. package/dist/ids/session-names.d.ts.map +1 -1
  130. package/dist/ids/session-names.js +3 -0
  131. package/dist/ids/session-names.js.map +1 -1
  132. package/dist/index.d.ts +2052 -190
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +15 -1
  135. package/dist/index.js.map +1 -1
  136. package/dist/models/agent-catalog.d.ts.map +1 -1
  137. package/dist/models/agent-catalog.js +4 -6
  138. package/dist/models/agent-catalog.js.map +1 -1
  139. package/dist/models/agent-runtimes.d.ts +2 -1
  140. package/dist/models/agent-runtimes.d.ts.map +1 -1
  141. package/dist/models/agent-runtimes.js +7 -1
  142. package/dist/models/agent-runtimes.js.map +1 -1
  143. package/dist/models/plan-pools.d.ts +22 -1
  144. package/dist/models/plan-pools.d.ts.map +1 -1
  145. package/dist/models/plan-pools.js +86 -0
  146. package/dist/models/plan-pools.js.map +1 -1
  147. package/dist/policy/request-status.d.ts +1 -1
  148. package/dist/policy/request-status.d.ts.map +1 -1
  149. package/dist/policy/request-status.js +4 -11
  150. package/dist/policy/request-status.js.map +1 -1
  151. package/dist/policy/reserved-servers.d.ts.map +1 -1
  152. package/dist/policy/reserved-servers.js +2 -1
  153. package/dist/policy/reserved-servers.js.map +1 -1
  154. package/dist/policy/turned-away.d.ts.map +1 -1
  155. package/dist/policy/turned-away.js +1 -0
  156. package/dist/policy/turned-away.js.map +1 -1
  157. package/dist/protocol/container-requirements.d.ts +0 -1
  158. package/dist/protocol/container-requirements.d.ts.map +1 -1
  159. package/dist/protocol/container-requirements.js +1 -2
  160. package/dist/protocol/container-requirements.js.map +1 -1
  161. package/dist/protocol/edge-verdict.d.ts +2 -1
  162. package/dist/protocol/edge-verdict.d.ts.map +1 -1
  163. package/dist/protocol/edge-verdict.js +1 -1
  164. package/dist/protocol/edge-verdict.js.map +1 -1
  165. package/dist/protocol/ingress-contract.d.ts +1 -12
  166. package/dist/protocol/ingress-contract.d.ts.map +1 -1
  167. package/dist/protocol/ingress-contract.js +1 -1
  168. package/dist/protocol/ingress-contract.js.map +1 -1
  169. package/dist/protocol/raw-routes.d.ts +36 -26
  170. package/dist/protocol/raw-routes.d.ts.map +1 -1
  171. package/dist/protocol/raw-routes.js +17 -23
  172. package/dist/protocol/raw-routes.js.map +1 -1
  173. package/dist/protocol/route-meta.d.ts +22 -0
  174. package/dist/protocol/route-meta.d.ts.map +1 -1
  175. package/dist/protocol/route-meta.js +16 -0
  176. package/dist/protocol/route-meta.js.map +1 -1
  177. package/dist/protocol/runner-protocol.d.ts +34 -0
  178. package/dist/protocol/runner-protocol.d.ts.map +1 -1
  179. package/dist/protocol/runner-protocol.js +33 -0
  180. package/dist/protocol/runner-protocol.js.map +1 -1
  181. package/dist/protocol/tunnel-bulk.d.ts +2 -0
  182. package/dist/protocol/tunnel-bulk.d.ts.map +1 -0
  183. package/dist/protocol/tunnel-bulk.js +3 -0
  184. package/dist/protocol/tunnel-bulk.js.map +1 -0
  185. package/dist/protocol/x402.d.ts +17 -0
  186. package/dist/protocol/x402.d.ts.map +1 -0
  187. package/dist/protocol/x402.js +37 -0
  188. package/dist/protocol/x402.js.map +1 -0
  189. package/dist/schemas/activity.d.ts.map +1 -1
  190. package/dist/schemas/activity.js.map +1 -1
  191. package/dist/schemas/agent.d.ts +25 -0
  192. package/dist/schemas/agent.d.ts.map +1 -1
  193. package/dist/schemas/agent.js +14 -2
  194. package/dist/schemas/agent.js.map +1 -1
  195. package/dist/schemas/agents.d.ts +90 -18
  196. package/dist/schemas/agents.d.ts.map +1 -1
  197. package/dist/schemas/agents.js +49 -5
  198. package/dist/schemas/agents.js.map +1 -1
  199. package/dist/schemas/automations.d.ts +23 -6
  200. package/dist/schemas/automations.d.ts.map +1 -1
  201. package/dist/schemas/automations.js.map +1 -1
  202. package/dist/schemas/devices.d.ts +134 -7
  203. package/dist/schemas/devices.d.ts.map +1 -1
  204. package/dist/schemas/devices.js +43 -18
  205. package/dist/schemas/devices.js.map +1 -1
  206. package/dist/schemas/extension-updates.d.ts +26 -0
  207. package/dist/schemas/extension-updates.d.ts.map +1 -1
  208. package/dist/schemas/extension-updates.js +4 -0
  209. package/dist/schemas/extension-updates.js.map +1 -1
  210. package/dist/schemas/git/git.d.ts +19 -0
  211. package/dist/schemas/git/git.d.ts.map +1 -1
  212. package/dist/schemas/git/git.js +1 -0
  213. package/dist/schemas/git/git.js.map +1 -1
  214. package/dist/schemas/hosts.d.ts +35 -3
  215. package/dist/schemas/hosts.d.ts.map +1 -1
  216. package/dist/schemas/hosts.js +22 -6
  217. package/dist/schemas/hosts.js.map +1 -1
  218. package/dist/schemas/keep-warm.d.ts +2 -12
  219. package/dist/schemas/keep-warm.d.ts.map +1 -1
  220. package/dist/schemas/keep-warm.js +8 -10
  221. package/dist/schemas/keep-warm.js.map +1 -1
  222. package/dist/schemas/metrics.d.ts +29 -0
  223. package/dist/schemas/metrics.d.ts.map +1 -1
  224. package/dist/schemas/metrics.js +19 -3
  225. package/dist/schemas/metrics.js.map +1 -1
  226. package/dist/schemas/offload.d.ts +62 -0
  227. package/dist/schemas/offload.d.ts.map +1 -0
  228. package/dist/schemas/offload.js +23 -0
  229. package/dist/schemas/offload.js.map +1 -0
  230. package/dist/schemas/personas.d.ts +3 -0
  231. package/dist/schemas/personas.d.ts.map +1 -1
  232. package/dist/schemas/personas.js +12 -3
  233. package/dist/schemas/personas.js.map +1 -1
  234. package/dist/schemas/ports.d.ts +10 -0
  235. package/dist/schemas/ports.d.ts.map +1 -1
  236. package/dist/schemas/ports.js +8 -0
  237. package/dist/schemas/ports.js.map +1 -1
  238. package/dist/schemas/providers/plan-limits.d.ts +169 -0
  239. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  240. package/dist/schemas/providers/plan-limits.js +54 -1
  241. package/dist/schemas/providers/plan-limits.js.map +1 -1
  242. package/dist/schemas/providers/provider-oauth.d.ts +69 -0
  243. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  244. package/dist/schemas/providers/provider-oauth.js +6 -1
  245. package/dist/schemas/providers/provider-oauth.js.map +1 -1
  246. package/dist/schemas/settings-history.d.ts +55 -0
  247. package/dist/schemas/settings-history.d.ts.map +1 -0
  248. package/dist/schemas/settings-history.js +73 -0
  249. package/dist/schemas/settings-history.js.map +1 -0
  250. package/dist/schemas/settings.d.ts +188 -4
  251. package/dist/schemas/settings.d.ts.map +1 -1
  252. package/dist/schemas/settings.js +41 -26
  253. package/dist/schemas/settings.js.map +1 -1
  254. package/dist/schemas/speaker.d.ts +36 -0
  255. package/dist/schemas/speaker.d.ts.map +1 -0
  256. package/dist/schemas/speaker.js +62 -0
  257. package/dist/schemas/speaker.js.map +1 -0
  258. package/dist/schemas/state-plan.d.ts +46 -0
  259. package/dist/schemas/state-plan.d.ts.map +1 -0
  260. package/dist/schemas/state-plan.js +31 -0
  261. package/dist/schemas/state-plan.js.map +1 -0
  262. package/dist/schemas/system.d.ts +64 -0
  263. package/dist/schemas/system.d.ts.map +1 -1
  264. package/dist/schemas/system.js +12 -1
  265. package/dist/schemas/system.js.map +1 -1
  266. package/dist/schemas/workspace/mainline.d.ts +513 -0
  267. package/dist/schemas/workspace/mainline.d.ts.map +1 -0
  268. package/dist/schemas/workspace/mainline.js +182 -0
  269. package/dist/schemas/workspace/mainline.js.map +1 -0
  270. package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
  271. package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
  272. package/dist/schemas/workspace/workspace-tree.js +34 -0
  273. package/dist/schemas/workspace/workspace-tree.js.map +1 -1
  274. package/dist/state/contract-lock.d.ts +1 -0
  275. package/dist/state/contract-lock.d.ts.map +1 -1
  276. package/dist/state/contract-lock.js +47 -1
  277. package/dist/state/contract-lock.js.map +1 -1
  278. package/dist/state/definition.d.ts +44 -20
  279. package/dist/state/definition.d.ts.map +1 -1
  280. package/dist/state/definition.js.map +1 -1
  281. package/dist/state/fix-stance.d.ts +1 -1
  282. package/dist/state/fix-stance.d.ts.map +1 -1
  283. package/dist/state/fix-stance.js +2 -1
  284. package/dist/state/fix-stance.js.map +1 -1
  285. package/dist/state/history-state.d.ts.map +1 -1
  286. package/dist/state/history-state.js +7 -0
  287. package/dist/state/history-state.js.map +1 -1
  288. package/dist/state/landed-commit.d.ts +1 -0
  289. package/dist/state/landed-commit.d.ts.map +1 -1
  290. package/dist/state/landed-commit.js +2 -0
  291. package/dist/state/landed-commit.js.map +1 -1
  292. package/dist/state/red-streak.d.ts +7 -0
  293. package/dist/state/red-streak.d.ts.map +1 -0
  294. package/dist/state/red-streak.js +11 -0
  295. package/dist/state/red-streak.js.map +1 -0
  296. package/dist/state/runtime-state.d.ts +3 -0
  297. package/dist/state/runtime-state.d.ts.map +1 -1
  298. package/dist/state/runtime-state.js +1 -0
  299. package/dist/state/runtime-state.js.map +1 -1
  300. package/dist/state/workspace-state.d.ts +23 -1
  301. package/dist/state/workspace-state.d.ts.map +1 -1
  302. package/dist/state/workspace-state.js +33 -3
  303. package/dist/state/workspace-state.js.map +1 -1
  304. package/dist/text/shell-regions.d.ts +1 -0
  305. package/dist/text/shell-regions.d.ts.map +1 -1
  306. package/dist/text/shell-regions.js +1 -1
  307. package/dist/text/shell-regions.js.map +1 -1
  308. package/dist/text/transcript-fold.d.ts +1 -0
  309. package/dist/text/transcript-fold.d.ts.map +1 -1
  310. package/dist/text/transcript-fold.js +24 -8
  311. package/dist/text/transcript-fold.js.map +1 -1
  312. package/package.json +42 -42
  313. package/src/chores/chores.ts +6 -6
  314. package/src/chores/index.ts +0 -1
  315. package/src/chores/prompt.ts +3 -2
  316. package/src/contracts/agent.contract.ts +14 -0
  317. package/src/contracts/agents.contract.ts +36 -1
  318. package/src/contracts/capabilities.contract.ts +2 -2
  319. package/src/contracts/device.contract.ts +5 -3
  320. package/src/contracts/offload.contract.ts +60 -0
  321. package/src/contracts/runner.contract.ts +7 -1
  322. package/src/contracts/secrets.contract.ts +2 -0
  323. package/src/contracts/settings.contract.ts +2 -1
  324. package/src/contracts/workspace.contract.ts +54 -2
  325. package/src/documents/conversions.ts +402 -0
  326. package/src/documents/index.ts +7 -0
  327. package/src/documents/passthrough.ts +139 -0
  328. package/src/documents/read.ts +114 -0
  329. package/src/events/agent-events.ts +5 -0
  330. package/src/events/child-run.test.ts +81 -0
  331. package/src/events/child-run.ts +73 -0
  332. package/src/events/errands.test.ts +22 -0
  333. package/src/events/errands.ts +27 -0
  334. package/src/events/land-breakage.ts +12 -0
  335. package/src/events/land-conflict.ts +12 -0
  336. package/src/events/requests.ts +40 -1
  337. package/src/events/system-events.ts +9 -3
  338. package/src/events/transcript.ts +8 -0
  339. package/src/events/verify-nudge.ts +4 -3
  340. package/src/front/browser-wire.ts +20 -0
  341. package/src/front/front-wire.ts +15 -0
  342. package/src/front/generated/browser-wire.json +138 -0
  343. package/src/front/generated/browser-wire.ts +16 -0
  344. package/src/front/generated/front-wire.json +793 -0
  345. package/src/front/generated/tunnel.json +38 -0
  346. package/src/front/generated/wire.ts +37 -9
  347. package/src/front/wire-manifests.test.ts +61 -0
  348. package/src/ids/conversation-ids.ts +13 -5
  349. package/src/ids/session-names.ts +7 -0
  350. package/src/index.ts +15 -2
  351. package/src/models/agent-catalog.test.ts +6 -4
  352. package/src/models/agent-catalog.ts +4 -6
  353. package/src/models/agent-runtimes.ts +22 -7
  354. package/src/models/capability-ledger.test.ts +2 -0
  355. package/src/models/plan-pools.test.ts +121 -2
  356. package/src/models/plan-pools.ts +152 -2
  357. package/src/policy/request-status.ts +4 -12
  358. package/src/policy/reserved-servers.test.ts +6 -0
  359. package/src/policy/reserved-servers.ts +13 -10
  360. package/src/policy/turned-away.ts +3 -0
  361. package/src/protocol/container-requirements.test.ts +2 -3
  362. package/src/protocol/container-requirements.ts +1 -7
  363. package/src/protocol/edge-verdict.ts +13 -11
  364. package/src/protocol/ingress-contract.fixture.json +90 -0
  365. package/src/protocol/ingress-contract.test.ts +18 -26
  366. package/src/protocol/ingress-contract.ts +10 -25
  367. package/src/protocol/raw-routes.ts +22 -26
  368. package/src/protocol/route-meta.ts +39 -0
  369. package/src/protocol/runner-protocol.ts +53 -0
  370. package/src/protocol/tunnel-bulk.test.ts +37 -0
  371. package/src/protocol/tunnel-bulk.ts +7 -0
  372. package/src/protocol/x402.test.ts +29 -0
  373. package/src/protocol/x402.ts +61 -0
  374. package/src/schemas/activity.ts +3 -1
  375. package/src/schemas/agent.ts +28 -3
  376. package/src/schemas/agents.ts +83 -8
  377. package/src/schemas/automations.ts +4 -2
  378. package/src/schemas/devices.ts +106 -39
  379. package/src/schemas/extension-updates.ts +6 -0
  380. package/src/schemas/git/git.ts +5 -0
  381. package/src/schemas/hosts.ts +64 -13
  382. package/src/schemas/ic-shape-args.test.ts +64 -0
  383. package/src/schemas/keep-warm.test.ts +6 -13
  384. package/src/schemas/keep-warm.ts +15 -20
  385. package/src/schemas/metrics.ts +28 -3
  386. package/src/schemas/offload.ts +44 -0
  387. package/src/schemas/personas.ts +18 -3
  388. package/src/schemas/ports.ts +10 -0
  389. package/src/schemas/providers/plan-limits.ts +84 -1
  390. package/src/schemas/providers/provider-oauth.ts +10 -1
  391. package/src/schemas/settings-history.ts +108 -0
  392. package/src/schemas/settings.test.ts +54 -1
  393. package/src/schemas/settings.ts +88 -49
  394. package/src/schemas/speaker.ts +89 -0
  395. package/src/schemas/state-plan.test.ts +69 -0
  396. package/src/schemas/state-plan.ts +47 -0
  397. package/src/schemas/system.ts +24 -3
  398. package/src/schemas/workspace/mainline.test.ts +28 -0
  399. package/src/schemas/workspace/mainline.ts +268 -0
  400. package/src/schemas/workspace/workspace-tree.ts +47 -0
  401. package/src/state/contract-lock.test.ts +65 -4
  402. package/src/state/contract-lock.ts +70 -2
  403. package/src/state/definition.ts +3 -2
  404. package/src/state/fix-stance.test.ts +8 -0
  405. package/src/state/fix-stance.ts +3 -3
  406. package/src/state/history-state.ts +19 -0
  407. package/src/state/landed-commit.test.ts +12 -1
  408. package/src/state/landed-commit.ts +5 -0
  409. package/src/state/red-streak.test.ts +28 -0
  410. package/src/state/red-streak.ts +28 -0
  411. package/src/state/runtime-state.ts +4 -0
  412. package/src/state/workspace-state.test.ts +7 -1
  413. package/src/state/workspace-state.ts +45 -3
  414. package/src/text/shell-regions.test.ts +38 -0
  415. package/src/text/shell-regions.ts +3 -3
  416. package/src/text/transcript-fold.test.ts +46 -0
  417. package/src/text/transcript-fold.ts +31 -9
  418. package/dist/chores/fix-deps.d.ts +0 -10
  419. package/dist/chores/fix-deps.d.ts.map +0 -1
  420. package/dist/chores/fix-deps.js +0 -18
  421. package/dist/chores/fix-deps.js.map +0 -1
  422. package/dist/protocol/ingress-protocol.d.ts +0 -30
  423. package/dist/protocol/ingress-protocol.d.ts.map +0 -1
  424. package/dist/protocol/ingress-protocol.js +0 -300
  425. package/dist/protocol/ingress-protocol.js.map +0 -1
  426. package/dist/protocol/terminal-protocol.d.ts +0 -18
  427. package/dist/protocol/terminal-protocol.d.ts.map +0 -1
  428. package/dist/protocol/terminal-protocol.js +0 -2
  429. package/dist/protocol/terminal-protocol.js.map +0 -1
  430. package/dist/protocol/tunnel-heartbeat.d.ts +0 -19
  431. package/dist/protocol/tunnel-heartbeat.d.ts.map +0 -1
  432. package/dist/protocol/tunnel-heartbeat.js +0 -41
  433. package/dist/protocol/tunnel-heartbeat.js.map +0 -1
  434. package/dist/protocol/upgrade-relay.d.ts +0 -4
  435. package/dist/protocol/upgrade-relay.d.ts.map +0 -1
  436. package/dist/protocol/upgrade-relay.js +0 -25
  437. package/dist/protocol/upgrade-relay.js.map +0 -1
  438. package/src/chores/fix-deps.ts +0 -22
  439. package/src/protocol/ingress-protocol.test.ts +0 -579
  440. package/src/protocol/ingress-protocol.ts +0 -469
  441. package/src/protocol/terminal-protocol.ts +0 -13
  442. package/src/protocol/tunnel-heartbeat.test.ts +0 -75
  443. package/src/protocol/tunnel-heartbeat.ts +0 -67
  444. package/src/protocol/upgrade-relay.ts +0 -28
@@ -1,5 +1,18 @@
1
- import { bindingWindow, gatesModel, gatingWindows, scopedWindow, windowLive, windowPeriod } from "./plan-pools.js";
2
- import type { AccountUsage, UsageWindow } from "../schemas/providers/plan-limits.js";
1
+ import {
2
+ bindingWindow,
3
+ gatesModel,
4
+ gatingWindows,
5
+ headroomState,
6
+ preferredAccount,
7
+ refusalVerdict,
8
+ roomiestAccount,
9
+ scopedWindow,
10
+ serviceState,
11
+ serviceStates,
12
+ windowLive,
13
+ windowPeriod,
14
+ } from "./plan-pools.js";
15
+ import type { AccountState, AccountUsage, ProviderRefusal, UsageWindow } from "../schemas/providers/plan-limits.js";
3
16
 
4
17
  // One rule for which pool blocks a given model, shared by the daemon and the browser: a plan can meter models
5
18
  // separately, so the account's fullest pool need not be the model's own.
@@ -102,3 +115,109 @@ test("a reading is live until its reset, or, with none published, for one window
102
115
  // And a pool whose length nothing names has only its reset instant; without one it stands.
103
116
  expect(windowLive(window({ kind: "claude:tangelo" }), NOW - 400 * HOUR, NOW)).toBe(true);
104
117
  });
118
+
119
+ // Serviceability: the one verdict on whether an account can serve a turn.
120
+
121
+ const at = (measuredAt: number, ...windows: UsageWindow[]): AccountUsage => ({ windows, measuredAt });
122
+ const refusal = (over: Partial<ProviderRefusal> & Pick<ProviderRefusal, "kind">): ProviderRefusal => ({ at: 1_000, message: "no.", ...over });
123
+
124
+ test("spent is the contract's one line: 99.5% still has room, 100% is spent until its last full pool reopens", () => {
125
+ expect(headroomState(usage(window({ kind: "five_hour", utilization: 99.5 })))).toEqual({ kind: "ready", room: 0.5 });
126
+ expect(
127
+ headroomState(usage(window({ kind: "five_hour", utilization: 100, resetsAt: 50 }), window({ kind: "seven_day", utilization: 100, resetsAt: 900 }))),
128
+ ).toEqual({ kind: "spent", reopensAt: 900 });
129
+ expect(headroomState(usage(window({ kind: "five_hour", utilization: 100 })))).toEqual({ kind: "spent" });
130
+ expect(headroomState(undefined)).toEqual({ kind: "unknown" });
131
+ expect(headroomState(CLAUDE, { id: "claude-haiku-4-5" })).toEqual({ kind: "ready", room: 70 });
132
+ expect(headroomState(CLAUDE, { id: "claude-opus-4-6" })).toEqual({ kind: "spent" });
133
+ });
134
+
135
+ test("with no model named, an account is spent only when no model can run on it", () => {
136
+ // A full Opus slice leaves every other model the room of the fullest pool still open.
137
+ expect(headroomState(CLAUDE)).toEqual({ kind: "ready", room: 70 });
138
+ expect(headroomState(GOOGLE)).toEqual({ kind: "ready", room: 73 });
139
+ // A pool gating every model, full: nothing runs, and it reopens with that pool.
140
+ expect(headroomState(usage(window({ kind: "seven_day", utilization: 100, resetsAt: 900 }), window({ kind: "model:Opus", utilization: 10, gates: { models: ["Opus"] } })))).toEqual({
141
+ kind: "spent",
142
+ reopensAt: 900,
143
+ });
144
+ // Every slice full: the first to reopen lets some model run again.
145
+ expect(
146
+ headroomState(
147
+ usage(
148
+ window({ kind: "g", utilization: 100, resetsAt: 500, gates: { models: ["gemini"] } }),
149
+ window({ kind: "c", utilization: 100, resetsAt: 800, gates: { models: ["claude"] } }),
150
+ ),
151
+ ),
152
+ ).toEqual({ kind: "spent", reopensAt: 500 });
153
+ });
154
+
155
+ test("a revoked sign-in outranks a lost seat, which outranks a bench, whatever the meters say", () => {
156
+ const facts = { account: "a", usage: usage(window({ kind: "five_hour", utilization: 0 })) };
157
+ expect(serviceState({ ...facts, needsReauth: true, seatRefusal: "no seat" })).toEqual({ kind: "blocked", fix: "reconnect", reason: "sign-in expired" });
158
+ expect(serviceState({ ...facts, seatRefusal: "Your org disabled Claude Code." })).toEqual({
159
+ kind: "blocked",
160
+ fix: "admin",
161
+ reason: "Your org disabled Claude Code.",
162
+ });
163
+ expect(serviceState({ ...facts, cooling: { reason: "no project" } })).toEqual({ kind: "blocked", fix: "reconnect", reason: "no project" });
164
+ expect(serviceState({ ...facts, cooling: { until: 2_000 } }, undefined, undefined, 1_000_000)).toEqual({
165
+ kind: "blocked",
166
+ fix: "wait",
167
+ reason: "cooling down",
168
+ until: 2_000,
169
+ });
170
+ // A bench whose instant has passed is over.
171
+ expect(serviceState({ ...facts, cooling: { until: 999 } }, undefined, undefined, 1_000_000)).toEqual({ kind: "ready", room: 100 });
172
+ });
173
+
174
+ test("a standing refusal blocks by its kind, and a limit refusal pins only the pool its model spends", () => {
175
+ const facts = { account: "a", usage: CLAUDE };
176
+ expect(serviceState(facts, refusal({ kind: "auth" }))).toEqual({ kind: "blocked", fix: "reconnect", reason: "no." });
177
+ expect(serviceState(facts, refusal({ kind: "entitlement" }))).toEqual({ kind: "blocked", fix: "admin", reason: "no." });
178
+ // Refused on Haiku: the 5-hour/weekly binding pool (seven_day, 30%) reads full, so Haiku is spent too.
179
+ expect(serviceState(facts, refusal({ kind: "limit", model: "claude-haiku-4-5" }), { id: "claude-haiku-4-5" })).toEqual({ kind: "spent" });
180
+ // Refused on Opus: its own slice was already full; Haiku keeps the room its pools show.
181
+ expect(serviceState(facts, refusal({ kind: "limit", model: "claude-opus-4-6" }), { id: "claude-haiku-4-5" })).toEqual({ kind: "ready", room: 70 });
182
+ // No reading at all: the refusal is the evidence, unless it was about another model.
183
+ expect(serviceState({ account: "a" }, refusal({ kind: "limit" }))).toEqual({ kind: "spent" });
184
+ expect(serviceState({ account: "a" }, refusal({ kind: "limit", model: "opus" }), { id: "haiku" })).toEqual({ kind: "unknown" });
185
+ });
186
+
187
+ test("a refusal stands until a later reading contradicts it, and never past its account's disconnection", () => {
188
+ const room = at(2_000, window({ kind: "five_hour", utilization: 40 }));
189
+ const full = at(2_000, window({ kind: "five_hour", utilization: 100 }));
190
+ const before = at(500, window({ kind: "five_hour", utilization: 40 }));
191
+ expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: room }])).toBe("answered");
192
+ expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: full }])).toBe("standing");
193
+ expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: before }])).toBe("standing");
194
+ // Another account's room proves nothing about the one refused.
195
+ expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [{ account: "a", usage: before }, { account: "b", usage: room }])).toBe("standing");
196
+ // A refusal naming no account is answered by any of them.
197
+ expect(refusalVerdict(refusal({ kind: "limit" }), [{ account: "a", usage: before }, { account: "b", usage: room }])).toBe("answered");
198
+ expect(refusalVerdict(refusal({ kind: "auth", account: "a" }), [{ account: "a", usage: full }])).toBe("answered");
199
+ expect(refusalVerdict(refusal({ kind: "auth", account: "a" }), [{ account: "a", usage: full, needsReauth: true }])).toBe("standing");
200
+ expect(refusalVerdict(refusal({ kind: "entitlement", account: "a" }), [{ account: "a", usage: room }])).toBe("standing");
201
+ expect(refusalVerdict(refusal({ kind: "limit", account: "gone" }), [{ account: "a", usage: room }])).toBe("gone");
202
+ expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [])).toBe("standing");
203
+ });
204
+
205
+ test("a provider's accounts are judged together: a standing refusal lands only on the account it names", () => {
206
+ const room = at(500, window({ kind: "five_hour", utilization: 40 }));
207
+ const states = serviceStates([{ account: "a", usage: room }, { account: "b", usage: room }], refusal({ kind: "entitlement", account: "a" }));
208
+ expect(Object.fromEntries(states)).toEqual({ a: { kind: "blocked", fix: "admin", reason: "no." }, b: { kind: "ready", room: 60 } });
209
+ const answered = serviceStates([{ account: "a", usage: at(2_000, window({ kind: "five_hour", utilization: 40 })) }], refusal({ kind: "limit", account: "a" }));
210
+ expect(answered.get("a")).toEqual({ kind: "ready", room: 60 });
211
+ });
212
+
213
+ test("an unnamed turn takes the most room, then an unmeasured account, then a spent one, and a blocked one only when that is all", () => {
214
+ const entry = (id: string, state: AccountState) => ({ id, state });
215
+ const blocked: AccountState = { kind: "blocked", fix: "admin", reason: "no seat" };
216
+ expect(preferredAccount([entry("seatless", blocked), entry("spent", { kind: "spent" }), entry("low", { kind: "ready", room: 5 }), entry("high", { kind: "ready", room: 60 })])?.id).toBe("high");
217
+ expect(preferredAccount([entry("seatless", blocked), entry("spent", { kind: "spent" }), entry("unread", { kind: "unknown" })])?.id).toBe("unread");
218
+ expect(preferredAccount([entry("seatless", blocked), entry("spent", { kind: "spent" })])?.id).toBe("spent");
219
+ expect(preferredAccount([entry("seatless", blocked), entry("other", blocked)])?.id).toBe("seatless");
220
+ expect(preferredAccount([entry("first", { kind: "ready", room: 60 }), entry("second", { kind: "ready", room: 60 })])?.id).toBe("first");
221
+ expect(roomiestAccount([entry("seatless", blocked), entry("unread", { kind: "unknown" })])).toBeUndefined();
222
+ expect(roomiestAccount([entry("low", { kind: "ready", room: 5 }), entry("high", { kind: "ready", room: 60 })])?.id).toBe("high");
223
+ });
@@ -1,10 +1,11 @@
1
- import type { AccountUsage, UsageWindow, WindowGates } from "../schemas/providers/plan-limits.js";
1
+ import type { AccountState, AccountUsage, ProviderRefusal, UsageWindow, WindowGates } from "../schemas/providers/plan-limits.js";
2
2
 
3
3
  // Single reading of which account pool gates a given model (or, with none named, the account's tightest pool), shared
4
4
  // by the daemon's picker/refusal logic and the browser's rings and rail. A pool gated `none` is excluded from the
5
5
  // no-model case: visible to the account, never a gate on a turn.
6
6
 
7
- // 100 = exhaustion (the call will be refused), not the browser's 90% warning threshold (usageStatus SPENT_PERCENT).
7
+ // 100 = exhaustion (the call will be refused). The one spent line: the daemon's pickers and every surface read it, and
8
+ // anything short of it still has room.
8
9
  export const SPENT_UTILIZATION = 100;
9
10
 
10
11
  export interface ModelRef {
@@ -132,3 +133,152 @@ export const windowLive = (window: UsageWindow, measuredAt: number, now: number)
132
133
  const period = windowPeriod(window);
133
134
  return period === undefined || measuredAt + period.seconds * 1000 > now;
134
135
  };
136
+
137
+ // Whether an account can serve a turn: the one rule every picker and surface reads (AccountState). The daemon runs it and
138
+ // publishes the verdict on each account row; the editor runs it only on a row from a daemon older than that field.
139
+
140
+ /** Everything the serviceability rule reads of one account, as the account rows already carry it. */
141
+ export interface ServiceFacts {
142
+ // The daemon's key: a native account's id, a routed credential's auth-file name.
143
+ readonly account: string;
144
+ readonly needsReauth?: boolean | undefined;
145
+ // Why the sign-in needs renewing, where the provider said.
146
+ readonly detail?: string | undefined;
147
+ readonly seatRefusal?: string | undefined;
148
+ readonly cooling?: { readonly until?: number | undefined; readonly reason?: string | undefined } | undefined;
149
+ readonly usage?: AccountUsage | undefined;
150
+ }
151
+
152
+ // A spent allowance is over once a reading with room lands after it; a refused credential once any reading does (the
153
+ // token worked). Nothing a reading says answers an entitlement refusal: a seatless account authenticates and reads fine.
154
+ const answers = (refusal: ProviderRefusal, facts: ServiceFacts): boolean => {
155
+ const measuredAt = facts.usage?.measuredAt;
156
+ if (refusal.kind === "entitlement" || measuredAt === undefined || measuredAt <= refusal.at) {
157
+ return false;
158
+ }
159
+ if (refusal.kind === "auth") {
160
+ return facts.needsReauth !== true;
161
+ }
162
+ const binding = bindingWindow(facts.usage);
163
+ return binding !== undefined && binding.utilization < SPENT_UTILIZATION;
164
+ };
165
+
166
+ /**
167
+ * How a provider's last refusal stands against everything read since, over every account the provider holds: `gone` once
168
+ * the account it named is disconnected, `answered` once that account (or, for a refusal naming none, any account) has a
169
+ * later reading that contradicts it, else `standing`. An empty list is one not loaded yet, so the refusal stands.
170
+ */
171
+ export const refusalVerdict = (refusal: ProviderRefusal, accounts: readonly ServiceFacts[]): "standing" | "answered" | "gone" => {
172
+ const named = accounts.filter((facts) => facts.account === refusal.account);
173
+ if (refusal.account !== undefined && named.length === 0) {
174
+ return accounts.length === 0 ? "standing" : "gone";
175
+ }
176
+ return (refusal.account === undefined ? accounts : named).some((facts) => answers(refusal, facts)) ? "answered" : "standing";
177
+ };
178
+
179
+ // The account reopens for this question when the pools holding it shut do: all of them for a model's own question (or
180
+ // the pools gating every model), the first to reopen when only per-model slices are full. Unknown if any is unpublished.
181
+ const reopening = (full: readonly UsageWindow[], first: boolean): number | undefined => {
182
+ const resets = full.flatMap((window) => (window.resetsAt === undefined ? [] : [window.resetsAt]));
183
+ return resets.length === full.length && resets.length > 0 ? (first ? Math.min(...resets) : Math.max(...resets)) : undefined;
184
+ };
185
+
186
+ const spent = (reopensAt: number | undefined): AccountState => (reopensAt === undefined ? { kind: "spent" } : { kind: "spent", reopensAt });
187
+
188
+ /**
189
+ * The plan-limit half of the verdict, at the contract's one spent line. With a model, the pools that model spends: spent
190
+ * once any is full, else the room the fullest leaves. With none, whether the account can serve SOME turn: spent once a
191
+ * pool gating every model is full, or every per-model slice is; a full slice alone (Opus at 100%) leaves the other
192
+ * models their room, which is then read off the fullest pool still open.
193
+ */
194
+ export const headroomState = (usage: AccountUsage | undefined, model?: ModelRef): AccountState => {
195
+ const windows = gatingWindows(usage, model);
196
+ if (windows.length === 0) {
197
+ return { kind: "unknown" };
198
+ }
199
+ const full = windows.filter((window) => window.utilization >= SPENT_UTILIZATION);
200
+ const open = windows.filter((window) => window.utilization < SPENT_UTILIZATION);
201
+ if (model !== undefined || full.some((window) => window.gates === "all") || open.length === 0) {
202
+ const holding = model !== undefined ? full : full.some((window) => window.gates === "all") ? full.filter((window) => window.gates === "all") : full;
203
+ if (holding.length > 0) {
204
+ return spent(reopening(holding, model === undefined && !holding.some((window) => window.gates === "all")));
205
+ }
206
+ }
207
+ return { kind: "ready", room: SPENT_UTILIZATION - Math.max(...open.map((window) => window.utilization)) };
208
+ };
209
+
210
+ // A standing limit refusal reads its pool as full, since a polled reading freezes the moment a pool actually empties: the
211
+ // pool the refused turn's model spends, else the one asked about, else the tightest. Never another model's allowance.
212
+ const refusedPool = (usage: AccountUsage | undefined, refusal: ProviderRefusal, model: ModelRef | undefined): AccountState => {
213
+ const refusedModel = refusal.model === undefined ? model : { id: refusal.model };
214
+ const binding = bindingWindow(usage, refusedModel);
215
+ if (usage === undefined || binding === undefined) {
216
+ // No pool to pin: the refusal itself is the evidence, unless it was about a different model than this question.
217
+ const sameModel = refusal.model === undefined || model === undefined || refusal.model === model.id;
218
+ return sameModel ? { kind: "spent" } : headroomState(usage, model);
219
+ }
220
+ const pinned = usage.windows.map((window) => (window === binding ? { ...window, utilization: Math.max(window.utilization, SPENT_UTILIZATION) } : window));
221
+ return headroomState({ ...usage, windows: pinned }, model);
222
+ };
223
+
224
+ /**
225
+ * One account's verdict. `refusal` is the provider's last refusal only when it still stands and covers this account
226
+ * (serviceStates decides that). Precedence: a revoked sign-in outranks a lost seat (reconnecting fixes one, nothing here
227
+ * fixes the other), both outrank a standing refusal, then a translator bench, then the plan limits.
228
+ */
229
+ export const serviceState = (facts: ServiceFacts, refusal?: ProviderRefusal, model?: ModelRef, now: number = Date.now()): AccountState => {
230
+ if (facts.needsReauth === true) {
231
+ return { kind: "blocked", fix: "reconnect", reason: facts.detail ?? "sign-in expired" };
232
+ }
233
+ if (facts.seatRefusal !== undefined) {
234
+ return { kind: "blocked", fix: "admin", reason: facts.seatRefusal };
235
+ }
236
+ if (refusal?.kind === "auth") {
237
+ return { kind: "blocked", fix: "reconnect", reason: refusal.message };
238
+ }
239
+ if (refusal?.kind === "entitlement") {
240
+ return { kind: "blocked", fix: "admin", reason: refusal.message };
241
+ }
242
+ const cooling = facts.cooling;
243
+ // A bench with no instant is one no wait lifts (a Google account with no project): somebody has to connect again.
244
+ if (cooling !== undefined && cooling.until === undefined) {
245
+ return { kind: "blocked", fix: "reconnect", reason: cooling.reason ?? "benched by the translator" };
246
+ }
247
+ if (cooling?.until !== undefined && cooling.until * 1000 > now) {
248
+ return { kind: "blocked", fix: "wait", reason: cooling.reason ?? "cooling down", until: cooling.until };
249
+ }
250
+ return refusal?.kind === "limit" ? refusedPool(facts.usage, refusal, model) : headroomState(facts.usage, model);
251
+ };
252
+
253
+ /** Every account of one provider, judged together, since a refusal naming no account is answered by any of them. */
254
+ export const serviceStates = (
255
+ accounts: readonly ServiceFacts[],
256
+ refusal: ProviderRefusal | undefined,
257
+ model?: ModelRef,
258
+ now: number = Date.now(),
259
+ ): ReadonlyMap<string, AccountState> => {
260
+ const standing = refusal !== undefined && refusalVerdict(refusal, accounts) === "standing" ? refusal : undefined;
261
+ return new Map(
262
+ accounts.map((facts) => [
263
+ facts.account,
264
+ serviceState(facts, standing !== undefined && (standing.account ?? facts.account) === facts.account ? standing : undefined, model, now),
265
+ ]),
266
+ );
267
+ };
268
+
269
+ // Worst last: proven room, no reading either way, known spent, and nothing a turn can run on.
270
+ const PREFERENCE: Record<AccountState["kind"], number> = { ready: 0, unknown: 1, spent: 2, blocked: 3 };
271
+
272
+ /** The account an unnamed turn runs on: ready by most room, then unmeasured, then spent, blocked only when that is all there is. Ties keep the caller's order. */
273
+ export const preferredAccount = <T extends { readonly state: AccountState }>(entries: readonly T[]): T | undefined =>
274
+ entries.reduce<T | undefined>((best, next) => {
275
+ if (best === undefined) {
276
+ return next;
277
+ }
278
+ const tier = PREFERENCE[next.state.kind] - PREFERENCE[best.state.kind];
279
+ return tier < 0 || (tier === 0 && next.state.kind === "ready" && best.state.kind === "ready" && next.state.room > best.state.room) ? next : best;
280
+ }, undefined);
281
+
282
+ /** The roomiest account with proven room, or none: what a move off a refused account may land on. Ties keep the caller's order. */
283
+ export const roomiestAccount = <T extends { readonly state: AccountState }>(entries: readonly T[]): T | undefined =>
284
+ preferredAccount(entries.filter((entry) => entry.state.kind === "ready"));
@@ -1,5 +1,6 @@
1
- import type { RequestField, TranscriptRequests, TranscriptRow } from "../events/transcript.js";
1
+ import { REQUEST_FIELDS, type RequestField, type TranscriptRequests, type TranscriptRow } from "../events/transcript.js";
2
2
  import type { AgentReply } from "../schemas/providers/plan-limits.js";
3
+ import { startedChildCard } from "../events/child-run.js";
3
4
 
4
5
  // How a reply settles the card it answers: one rule used both by the fold and by a chat freezing its own card
5
6
  // optimistically before the frame returns. No reply (turn stopped or died) reads as cancelled for every card; a reply
@@ -22,7 +23,7 @@ export const settledRequests = (cards: TranscriptRequests, reply: AgentReply | u
22
23
  }
23
24
  if (permission !== undefined) {
24
25
  out.permission = {
25
- ...permission,
26
+ ...startedChildCard(permission, reply),
26
27
  status:
27
28
  reply?.kind !== "permission"
28
29
  ? "cancelled"
@@ -65,16 +66,7 @@ export const settledRequests = (cards: TranscriptRequests, reply: AgentReply | u
65
66
  export const cancelledRequests = (cards: TranscriptRequests): TranscriptRequests => {
66
67
  const out: Cards = {};
67
68
  let changed = false;
68
- for (const field of [
69
- "plan",
70
- "question",
71
- "permission",
72
- "browserHelp",
73
- "terminalHelp",
74
- "capabilityOffer",
75
- "paymentOffer",
76
- "credentialOffer",
77
- ] as const) {
69
+ for (const field of REQUEST_FIELDS) {
78
70
  const card = cards[field];
79
71
  if (card === undefined) {
80
72
  continue;
@@ -24,6 +24,12 @@ describe("reserved MCP server names", () => {
24
24
  });
25
25
 
26
26
  describe("collidesWithReservedServer", () => {
27
+ // Derived from the manifest schema's meanings: a cli card mints a server when it serves tools, a device and a
28
+ // connected browser always do, and `mcp` is the core kind that is its own endpoint.
29
+ test("the minting kinds are exactly the ones whose id becomes a server name", () => {
30
+ expect([...MCP_SERVER_MINTING_KINDS].toSorted()).toEqual(["cli", "device", "mcp", "webext"]);
31
+ });
32
+
27
33
  test("a kind whose id becomes a server name may not reuse a reserved one", () => {
28
34
  for (const kind of MCP_SERVER_MINTING_KINDS) {
29
35
  for (const name of RESERVED_MCP_SERVER_NAMES) {
@@ -1,4 +1,5 @@
1
- // The MCP servers the daemon mounts in its OWN process, and whether each carries content from outside this container.
1
+ import { contributedServerMintingKinds } from "@intentic/extension-manifest";
2
+ // The MCP servers the daemon mounts under its own names, and whether each carries content from outside this container.
2
3
  // One source of truth: the outside-content guard's exemption list is the `control` subset of this, and a capability
3
4
  // whose id would become `mcp__<id>__…` is refused when the id is one of these, so nothing the owner configures can
4
5
  // shadow a daemon server. A daemon server's name is what reaches the model as the `mcp__<name>__` prefix.
@@ -8,9 +9,9 @@
8
9
  // - outside: the server carries content from beyond the container (a web page, a provider's verbatim sentence)
9
10
  export type ServerProvenance = "control" | "outside";
10
11
 
11
- // Every server agent/run/agent.ts and agent/run/harness/harness-servers.ts mount themselves; the browser router's two
12
- // names (web, browser) come in through `...turn.browser.servers`, so they are named here rather than discovered by the
13
- // text scan. A server added anywhere else without a row here fails the conformance test in the sandbox guard package.
12
+ // Every server agent/run/agent.ts and agent/run/harness/harness-servers.ts mount themselves, plus the browser router's two
13
+ // names (web, browser), which every turn mounts on its lease at the daemon's MCP door (browser-tools.ts) and which the
14
+ // text scan reads from their constants. A server added anywhere else without a row here fails the conformance test in the sandbox guard package.
14
15
  export const DAEMON_MCP_SERVERS: Readonly<Record<string, ServerProvenance>> = {
15
16
  ui: "control", // agent.ts: AskUserQuestion
16
17
  accounts: "control", // agent.ts: the account roster and the credential typists
@@ -22,8 +23,8 @@ export const DAEMON_MCP_SERVERS: Readonly<Record<string, ServerProvenance>> = {
22
23
  watch: "control", // harness: condition watches
23
24
  deps: "control", // harness: dependency readiness
24
25
  diagnostics: "outside", // harness: two tools relay a provider's own sentence verbatim
25
- web: "outside", // harness browser: the anonymous router; the page is the internet
26
- browser: "outside", // harness browser: the signed-in router; the page is the internet
26
+ web: "outside", // the turn's mounts: the anonymous browser router; the page is the internet
27
+ browser: "outside", // the turn's mounts: the signed-in browser router; the page is the internet
27
28
  };
28
29
 
29
30
  // Every daemon-mounted server name, the set a capability id may not collide with.
@@ -37,10 +38,12 @@ export const CONTROL_MCP_SERVERS: ReadonlySet<string> = new Set(
37
38
  .map(([name]) => name),
38
39
  );
39
40
 
40
- // Capability kinds whose id becomes an MCP server name for the turn (`mcp` → its own endpoint; `device`/`webext` → the
41
- // loopback peer bridge), and so whose id must not collide with a daemon server. Other kinds mint accounts, providers or
42
- // infrastructure, never a `mcp__<id>__` server keyed by the id.
43
- export const MCP_SERVER_MINTING_KINDS: ReadonlySet<string> = new Set(["mcp", "device", "webext"]);
41
+ // Capability kinds whose id becomes an MCP server name for the turn, and so whose id must not collide with a daemon
42
+ // server: `mcp`, a core kind that is its own endpoint, and every contributable kind the manifest schema says mints one
43
+ // (a device's and a connected browser's peer bridge, a cli card that serves tools), read off the schema's own meaning
44
+ // (`contributedServerMintingKinds`), so a kind that starts minting is refused here without a second list. Other kinds
45
+ // mint accounts, providers or infrastructure, never a `mcp__<id>__` server keyed by the id.
46
+ export const MCP_SERVER_MINTING_KINDS: ReadonlySet<string> = new Set(["mcp", ...contributedServerMintingKinds()]);
44
47
 
45
48
  // Whether adding or renaming a capability of this kind to this id would shadow a daemon server. The one predicate the
46
49
  // capability routes consult, so the refusal and this list can't drift.
@@ -5,6 +5,9 @@
5
5
  // A set rather than a run of case labels: what they share is a fact about the turn, not a shape.
6
6
  const TURNED_AWAY: ReadonlySet<string> = new Set([
7
7
  "claude-reauth",
8
+ // An organisation turned the account's Claude Code access off, or no account of the provider can serve: the next
9
+ // turn is routed off it (the daemon's blocked-account route), so the words wait for it rather than being spent.
10
+ "claude-not-entitled",
8
11
  "unknown-command",
9
12
  "context-window-too-small",
10
13
  "sandbox-memory-low",
@@ -38,9 +38,8 @@ describe(`containerDrift`, () => {
38
38
  expect(containerDrift({ SANDBOX_PUBLIC_URL: `` })).toEqual([]);
39
39
  });
40
40
 
41
- // A hosted Fly microVM has no grant by design, so this reports nothing on it.
42
- it(`stays quiet on a hosted machine, which serves its address without dialling out`, () => {
43
- expect(containerDrift({ SANDBOX_PUBLIC_URL: CURRENT.SANDBOX_PUBLIC_URL, SANDBOX_VM: `1` })).toEqual([]);
41
+ it(`holds a hosted machine to the same grant, since it dials the edge like any sandbox`, () => {
42
+ expect(containerDrift({ SANDBOX_PUBLIC_URL: CURRENT.SANDBOX_PUBLIC_URL, SANDBOX_VM: `1` }).map((gap) => gap.key)).toEqual([`reachability`]);
44
43
  });
45
44
  });
46
45
 
@@ -7,8 +7,6 @@ export interface ContainerRequirement {
7
7
  readonly requires: readonly string[];
8
8
  // Absent `given` evidence means this sandbox never had the capability: not a fault, just unconfigured.
9
9
  readonly given: readonly string[];
10
- // Row stays silent if any `unless` key is present: an alternate mechanism already satisfies the evidence.
11
- readonly unless?: readonly string[];
12
10
  readonly enables: string;
13
11
  readonly lost: string;
14
12
  readonly repair: string;
@@ -25,8 +23,6 @@ export const CONTAINER_REQUIREMENTS: readonly ContainerRequirement[] = [
25
23
  key: "reachability",
26
24
  requires: ["SANDBOX_GRANT", "INGRESS_URL"],
27
25
  given: ["SANDBOX_PUBLIC_URL"],
28
- // A Fly microVM's edge replays directly to it, so it holds no grant by design.
29
- unless: ["SANDBOX_VM"],
30
26
  enables: "reaching this sandbox at its public address, from anywhere",
31
27
  lost:
32
28
  "Its public address answers 502 to everyone, including this browser when it is not on the same machine as the sandbox. " +
@@ -45,9 +41,7 @@ export interface ContainerGap {
45
41
 
46
42
  /** Empty means every requirement is met or not applicable; non-empty always means a setup rerun is needed. */
47
43
  export const containerDrift = (env: ContainerEnv): readonly ContainerGap[] =>
48
- CONTAINER_REQUIREMENTS.filter(
49
- (requirement) => requirement.given.every((name) => present(env, name)) && !(requirement.unless ?? []).some((name) => present(env, name)),
50
- )
44
+ CONTAINER_REQUIREMENTS.filter((requirement) => requirement.given.every((name) => present(env, name)))
51
45
  .map((requirement) => ({
52
46
  key: requirement.key,
53
47
  missing: requirement.requires.filter((name) => !present(env, name)),
@@ -3,19 +3,21 @@
3
3
  // JavaScript unless the responder allows it — so the verdict rides an exposed header and the edge CORS-allows its own
4
4
  // errors. Ingress writes it, the editor's connection machine reads it; both sides import this file so they cannot drift.
5
5
 
6
+ import type { EdgeVerdict } from "../front/generated/browser-wire.js";
7
+
6
8
  export const EDGE_VERDICT_HEADER = "x-intentic-edge";
7
9
 
8
- export type EdgeVerdict =
9
- // No tunnel is registered for this sandbox: its container is not running, or the machine it runs on is off.
10
- // Retrying is right — a container that comes back redials within seconds.
11
- | "no-tunnel"
12
- // The platform has no such sandbox. The row is deleted, no tunnel for this id will ever be accepted again, and
13
- // nothing the reader waits for can change that.
14
- | "unknown-sandbox"
15
- // A tunnel was held and the forward failed mid-flight: the box went away between being routed to and answering.
16
- | "dropped";
17
-
18
- const VERDICTS: ReadonlySet<string> = new Set(["no-tunnel", "unknown-sandbox", "dropped"]);
10
+ // The verdicts themselves are the edge's (browser-wire's `EdgeVerdict`), each documented there:
11
+ // - no-tunnel: no tunnel is registered for this sandbox, and retrying is right, since a container that comes back redials
12
+ // within seconds;
13
+ // - unknown-sandbox: the platform has no such sandbox, and nothing the reader waits for can change that;
14
+ // - dropped: a tunnel was held and the forward failed mid-flight.
15
+ export type { EdgeVerdict };
16
+
17
+ // Every verdict, so a new one in the Rust enum fails this file's typecheck until it is read here too.
18
+ const VERDICTS: ReadonlySet<string> = new Set(
19
+ Object.keys({ "no-tunnel": true, "unknown-sandbox": true, dropped: true } satisfies Record<EdgeVerdict, true>),
20
+ );
19
21
 
20
22
  // A header value only counts when it is one of ours: a 502 from anything else on the path (a corporate proxy, a CDN)
21
23
  // carries no verdict and must not be read as one.
@@ -0,0 +1,90 @@
1
+ {
2
+ "publicKey": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAfiMlxPB/8tnjh6TYS3rVde63OXB/wY77KWWtJ39XjBg=\n-----END PUBLIC KEY-----\n",
3
+ "grants": [
4
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.wdpBpqmMl21mqAZvRSYi4WfDejreTKDxHfOQp_sBKOTQB0_N2trL6iFcRxFUDrGBnGHmYnv-x66FgU_j57ZECA", "claim": {"sandboxId": "abcdef012345", "issuedAt": 1700000000} },
5
+ { "token": "ig1.eyJzdWIiOiIwMTIzNDU2Nzg5YWIiLCJpYXQiOjE3NTcwMDAwMDAsImV4dHJhIjoiaWdub3JlZCJ9.NK2BR2L_ycCACdmnbZgkg7sovw-Nhlt5vIL6ldArAx_jN9XOh-k1wisviROAgU40YGBgUASIgA6Wdn5SyYHHCA", "claim": {"sandboxId": "0123456789ab", "issuedAt": 1757000000} },
6
+ { "token": "ig1.eyJpYXQiOjE3MDAwMDAwMDAsInN1YiI6ImFiY2RlZjAxMjM0NSJ9.AenzYQn3Opfc-Sg_vkBrMEIOnCFZ3a46oSzjABAWyoVe3GP0vXlIM8MFgddHDQc7yow4kdfn1kHcUcsOoKkLCg", "claim": {"sandboxId": "abcdef012345", "issuedAt": 1700000000} },
7
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.wNpBpqmMl21mqAZvRSYi4WfDejreTKDxHfOQp_sBKOTQB0_N2trL6iFcRxFUDrGBnGHmYnv-x66FgU_j57ZECA", "claim": null },
8
+ { "token": "ig1.eiJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.wdpBpqmMl21mqAZvRSYi4WfDejreTKDxHfOQp_sBKOTQB0_N2trL6iFcRxFUDrGBnGHmYnv-x66FgU_j57ZECA", "claim": null },
9
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.Ac2ofn7IxD3M51pchXEycOPg4TP_kiTzu-aTVowDLTirjVzYrvfJ-ntrzPbQg-3RpBhmc53yP7s7iWyOgGWuAw", "claim": null },
10
+ { "token": "ig2.eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.wdpBpqmMl21mqAZvRSYi4WfDejreTKDxHfOQp_sBKOTQB0_N2trL6iFcRxFUDrGBnGHmYnv-x66FgU_j57ZECA", "claim": null },
11
+ { "token": "eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.wdpBpqmMl21mqAZvRSYi4WfDejreTKDxHfOQp_sBKOTQB0_N2trL6iFcRxFUDrGBnGHmYnv-x66FgU_j57ZECA", "claim": null },
12
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.wdpBpqmMl21mqAZvRSYi4WfDejreTKDxHfOQp_sBKOTQB0_N2trL6iFcRxFUDrGBnGHmYnv-x66FgU_j57ZECA.wdpBpqmMl21mqAZvRSYi4WfDejreTKDxHfOQp_sBKOTQB0_N2trL6iFcRxFUDrGBnGHmYnv-x66FgU_j57ZECA", "claim": null },
13
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.", "claim": null },
14
+ { "token": "", "claim": null },
15
+ { "token": "ig1", "claim": null },
16
+ { "token": "ig1..", "claim": null },
17
+ { "token": "ig1.%%%.%%%", "claim": null },
18
+ { "token": "ig1.eyJzdWIiOiJBQkNERUYwMTIzNDUiLCJpYXQiOjE3MDAwMDAwMDB9.CbPEciFki-GXJnUcS5F9_9uXUOw5tWhv3iS69BjuQpSmsA72_xnvYUIjhAMiBPI98XBQ9-zKc9GSPJIFGQqaDA", "claim": null },
19
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNCIsImlhdCI6MTcwMDAwMDAwMH0.qfJbxpVtbcymaweaCu9fUounkldkMy4HSdllEDlHs5Yq8b11XxiHLguDyvqubXcMGCuVVygCeg2MjU3i0vEuDA", "claim": null },
20
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNDUiLCJpYXQiOiIxNzAwMDAwMDAwIn0.BI6UT1aTaN01bGZyI9SHc_qIf9S-JlpmNtiBd8by_giZCq8pQIJCBKIutYZW0mmqCF0YHf0NHc9icq1h9Wr7BA", "claim": null },
21
+ { "token": "ig1.eyJzdWIiOiJhYmNkZWYwMTIzNDUifQ.Aa5FeLKazhsr2IvYZ2p5BJmLL1qvQWReXeCKt5gxCq9PqSaAnn8Pa6PJp0jiaXY5FIbc_S9MFrUCeEfQ6vnkBQ", "claim": null },
22
+ { "token": "ig1.eyJzdWIiOjEyMzQ1Njc4OTAxMiwiaWF0IjoxNzAwMDAwMDAwfQ.kb_lMTBvFS4nzTmqLf-T7l9IF8PHaPPkP5HB1T0yd0IRWqYJnIZH2eoKexv7e6aA2PkqCpe-B1FL8Nbu4qPvDg", "claim": null },
23
+ { "token": "ig1.bm90IGpzb24.Ffec27cYSbvKvjlxtSUIFFX7sGo3Zp4BxotW02rFwcJHfJikM51Zfzn3_0Rg8coR4y43iOUA2u-ZOcBJg4mmDA", "claim": null },
24
+ { "token": "ig1.bnVsbA.lFa3FNSkMcf3WCyjdb87YRVdt8h3aSzt4cignx9Rkwq6drwl12m0rlrmzdPkLjsdrQdL1oWZ2E2MGuo2CMeSBw", "claim": null },
25
+ { "token": "ig1.WyJhYmNkZWYwMTIzNDUiLDE3MDAwMDAwMDBd.BWFTD0bHKDBAEWwhYq6F-cWo_HXEC8UvDWkrTmafOycvTZUHvVOjWy_97OYl_pF-svCO1vbeumhF89ybhqv4Dw", "claim": null }
26
+ ],
27
+ "owners": [
28
+ { "host": "sandbox-abcdef012345.sbx.example.test", "owner": "abcdef012345" },
29
+ { "host": "sandbox-abcdef012345.sbx.example.test:443", "owner": "abcdef012345" },
30
+ { "host": "preview-web-app-abcdef012345.sbx.example.test:443", "owner": "abcdef012345" },
31
+ { "host": "port-0f0f0f0f0f0f-abcdef012345.sbx.example.dev", "owner": "abcdef012345" },
32
+ { "host": "public-1a2b3c4d5e6f-abcdef012345.sbx.example.dev", "owner": "abcdef012345" },
33
+ { "host": "SANDBOX-abcdef012345.sbx.example.test", "owner": "abcdef012345" },
34
+ { "host": "-abcdef012345.example.com", "owner": "abcdef012345" },
35
+ { "host": "sandbox-abcdef012345", "owner": "abcdef012345" },
36
+ { "host": "ingress.sbx.example.test", "owner": null },
37
+ { "host": "sbx.example.test", "owner": null },
38
+ { "host": "abcdef012345.local.sbx.example.dev", "owner": null },
39
+ { "host": "sandbox-ABCDEF012345.sbx.example.test", "owner": null },
40
+ { "host": "sandbox-abcdef01234.sbx.example.test", "owner": null },
41
+ { "host": "sandbox-abcdef012345x.sbx.example.test", "owner": null },
42
+ { "host": "sandbox-abcdef012345-.sbx.example.test", "owner": null },
43
+ { "host": "[::1]:8788", "owner": null },
44
+ { "host": "127.0.0.1:8788", "owner": null },
45
+ { "host": "", "owner": null }
46
+ ],
47
+ "bulk": [
48
+ "GET /diff/raw",
49
+ "GET /workspace/raw",
50
+ "GET /workspace/media",
51
+ "POST /workspace/upload",
52
+ "POST /workspace/upload-archive",
53
+ "GET /system/sync/ssh",
54
+ "GET /bundles/download",
55
+ "GET /extensions/{id}/bundle",
56
+ "GET /system/runners/git/{repo}/info/refs",
57
+ "POST /system/runners/git/{repo}/git-upload-pack",
58
+ "POST /system/runners/git/{repo}/git-receive-pack"
59
+ ],
60
+ "lanes": [
61
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/workspace/media?path=a.mp4", "lane": "bulk" },
62
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "POST", "path": "/workspace/upload?path=a", "lane": "bulk" },
63
+ { "host": "sandbox-abcdef012345.sbx.example.test:443", "method": "GET", "path": "/system/runners/git/app/info/refs?service=git-upload-pack", "lane": "bulk" },
64
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "POST", "path": "/system/runners/git/app/git-receive-pack", "lane": "bulk" },
65
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/system/sync/ssh", "lane": "bulk" },
66
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/extensions/abc/bundle", "lane": "bulk" },
67
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/diff/raw?a=1?b=2", "lane": "bulk" },
68
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/bundles/download?ticket=x", "lane": "bulk" },
69
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "POST", "path": "/workspace/upload-archive", "lane": "bulk" },
70
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "HEAD", "path": "/workspace/raw?path=a", "lane": "bulk" },
71
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "OPTIONS", "path": "/workspace/raw", "lane": "bulk" },
72
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "OPTIONS", "path": "/system/runners/git/app/git-upload-pack", "lane": "bulk" },
73
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "get", "path": "/workspace/raw?path=a", "lane": "bulk" },
74
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/workspace/thumb?path=a.png", "lane": "interactive" },
75
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "POST", "path": "/workspace/upload-diff", "lane": "interactive" },
76
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/agents", "lane": "interactive" },
77
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/system/terminal?ticket=t", "lane": "interactive" },
78
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "POST", "path": "/workspace/raw", "lane": "interactive" },
79
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/workspace/raw/", "lane": "interactive" },
80
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/system/runners/git//info/refs", "lane": "interactive" },
81
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/x/anything/at/all", "lane": "interactive" },
82
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/", "lane": "interactive" },
83
+ { "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "", "lane": "interactive" },
84
+ { "host": "preview-web-abcdef012345.sbx.example.test", "method": "GET", "path": "/agents", "lane": "bulk" },
85
+ { "host": "port-1a2b3c4d5e6f-abcdef012345.sbx.example.test", "method": "GET", "path": "/", "lane": "bulk" },
86
+ { "host": "Sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/agents", "lane": "bulk" },
87
+ { "host": "sbx.example.test", "method": "GET", "path": "/agents", "lane": "bulk" },
88
+ { "host": "", "method": "GET", "path": "/agents", "lane": "bulk" }
89
+ ]
90
+ }