@intentic/sandbox-contract 1.344.0 → 1.346.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 (418) hide show
  1. package/README.md +27 -9
  2. package/dist/contracts/accounts.contract.d.ts +24 -80
  3. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  4. package/dist/contracts/agent.contract.d.ts +247 -8
  5. package/dist/contracts/agent.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.js +13 -2
  7. package/dist/contracts/agent.contract.js.map +1 -1
  8. package/dist/contracts/agents.contract.d.ts +1570 -33
  9. package/dist/contracts/agents.contract.d.ts.map +1 -1
  10. package/dist/contracts/agents.contract.js +47 -4
  11. package/dist/contracts/agents.contract.js.map +1 -1
  12. package/dist/contracts/device.contract.d.ts +244 -1
  13. package/dist/contracts/device.contract.d.ts.map +1 -1
  14. package/dist/contracts/device.contract.js +5 -1
  15. package/dist/contracts/device.contract.js.map +1 -1
  16. package/dist/contracts/endpoints.contract.d.ts +2 -1
  17. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  18. package/dist/contracts/endpoints.contract.js +1 -0
  19. package/dist/contracts/endpoints.contract.js.map +1 -1
  20. package/dist/contracts/providers.contract.d.ts +0 -1
  21. package/dist/contracts/providers.contract.d.ts.map +1 -1
  22. package/dist/contracts/push.contract.d.ts +11 -0
  23. package/dist/contracts/push.contract.d.ts.map +1 -1
  24. package/dist/contracts/push.contract.js +10 -1
  25. package/dist/contracts/push.contract.js.map +1 -1
  26. package/dist/contracts/runner.contract.d.ts +149 -123
  27. package/dist/contracts/runner.contract.d.ts.map +1 -1
  28. package/dist/contracts/secrets.contract.d.ts +169 -0
  29. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  30. package/dist/contracts/secrets.contract.js +35 -0
  31. package/dist/contracts/secrets.contract.js.map +1 -1
  32. package/dist/contracts/sessions.contract.d.ts +2 -0
  33. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  34. package/dist/contracts/settings.contract.d.ts +37 -0
  35. package/dist/contracts/settings.contract.d.ts.map +1 -1
  36. package/dist/contracts/system.contract.d.ts +235 -31
  37. package/dist/contracts/system.contract.d.ts.map +1 -1
  38. package/dist/contracts/translator.contract.d.ts +24 -80
  39. package/dist/contracts/translator.contract.d.ts.map +1 -1
  40. package/dist/contracts/workspace.contract.d.ts +11 -0
  41. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  42. package/dist/contracts/workspace.contract.js +11 -1
  43. package/dist/contracts/workspace.contract.js.map +1 -1
  44. package/dist/events/agent-events.d.ts +60 -0
  45. package/dist/events/agent-events.d.ts.map +1 -1
  46. package/dist/events/agent-events.js +44 -38
  47. package/dist/events/agent-events.js.map +1 -1
  48. package/dist/events/agent-transcript.d.ts +727 -0
  49. package/dist/events/agent-transcript.d.ts.map +1 -0
  50. package/dist/events/agent-transcript.js +34 -0
  51. package/dist/events/agent-transcript.js.map +1 -0
  52. package/dist/events/agent-words.d.ts +5 -0
  53. package/dist/events/agent-words.d.ts.map +1 -1
  54. package/dist/events/agent-words.js +9 -0
  55. package/dist/events/agent-words.js.map +1 -1
  56. package/dist/events/failure-rows.d.ts +14 -0
  57. package/dist/events/failure-rows.d.ts.map +1 -0
  58. package/dist/events/failure-rows.js +109 -0
  59. package/dist/events/failure-rows.js.map +1 -0
  60. package/dist/events/requests.d.ts +143 -0
  61. package/dist/events/requests.d.ts.map +1 -1
  62. package/dist/events/requests.js +35 -1
  63. package/dist/events/requests.js.map +1 -1
  64. package/dist/events/sandbox-notice.d.ts +23 -0
  65. package/dist/events/sandbox-notice.d.ts.map +1 -1
  66. package/dist/events/sandbox-notice.js +2 -0
  67. package/dist/events/sandbox-notice.js.map +1 -1
  68. package/dist/events/system-events.d.ts +156 -0
  69. package/dist/events/system-events.d.ts.map +1 -1
  70. package/dist/events/system-events.js +7 -1
  71. package/dist/events/system-events.js.map +1 -1
  72. package/dist/events/transcript.d.ts +20 -720
  73. package/dist/events/transcript.d.ts.map +1 -1
  74. package/dist/events/transcript.js +17 -19
  75. package/dist/events/transcript.js.map +1 -1
  76. package/dist/ids/conversation-ids.d.ts +2 -0
  77. package/dist/ids/conversation-ids.d.ts.map +1 -1
  78. package/dist/ids/conversation-ids.js +2 -0
  79. package/dist/ids/conversation-ids.js.map +1 -1
  80. package/dist/ids/row-ids.d.ts +3 -0
  81. package/dist/ids/row-ids.d.ts.map +1 -0
  82. package/dist/ids/row-ids.js +11 -0
  83. package/dist/ids/row-ids.js.map +1 -0
  84. package/dist/index.d.ts +2296 -199
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/models/agent-lanes.d.ts.map +1 -1
  87. package/dist/models/agent-lanes.js +6 -7
  88. package/dist/models/agent-lanes.js.map +1 -1
  89. package/dist/models/agent-standing.d.ts +6 -19
  90. package/dist/models/agent-standing.d.ts.map +1 -1
  91. package/dist/models/agent-standing.js +14 -38
  92. package/dist/models/agent-standing.js.map +1 -1
  93. package/dist/models/model-pins.d.ts +3 -0
  94. package/dist/models/model-pins.d.ts.map +1 -1
  95. package/dist/models/model-pins.js +2 -0
  96. package/dist/models/model-pins.js.map +1 -1
  97. package/dist/models/plan-pools.d.ts +26 -4
  98. package/dist/models/plan-pools.d.ts.map +1 -1
  99. package/dist/models/plan-pools.js +159 -40
  100. package/dist/models/plan-pools.js.map +1 -1
  101. package/dist/netd/generated/wire.d.ts +5 -0
  102. package/dist/netd/generated/wire.d.ts.map +1 -1
  103. package/dist/policy/command-classes.d.ts +1 -1
  104. package/dist/policy/command-classes.d.ts.map +1 -1
  105. package/dist/policy/command-classes.js +2 -2
  106. package/dist/policy/command-classes.js.map +1 -1
  107. package/dist/policy/keyring-origins.d.ts +27 -0
  108. package/dist/policy/keyring-origins.d.ts.map +1 -0
  109. package/dist/policy/keyring-origins.js +213 -0
  110. package/dist/policy/keyring-origins.js.map +1 -0
  111. package/dist/policy/keyring-seal.d.ts +9 -0
  112. package/dist/policy/keyring-seal.d.ts.map +1 -1
  113. package/dist/policy/keyring-seal.js +18 -6
  114. package/dist/policy/keyring-seal.js.map +1 -1
  115. package/dist/policy/safety-policy.d.ts +1 -1
  116. package/dist/policy/safety-policy.d.ts.map +1 -1
  117. package/dist/policy/safety-policy.js +1 -1
  118. package/dist/policy/safety-policy.js.map +1 -1
  119. package/dist/policy/turned-away.d.ts +1 -0
  120. package/dist/policy/turned-away.d.ts.map +1 -1
  121. package/dist/policy/turned-away.js +10 -0
  122. package/dist/policy/turned-away.js.map +1 -1
  123. package/dist/protocol/doors/host-protocol.d.ts +1 -0
  124. package/dist/protocol/doors/host-protocol.d.ts.map +1 -1
  125. package/dist/protocol/doors/host-protocol.js +1 -0
  126. package/dist/protocol/doors/host-protocol.js.map +1 -1
  127. package/dist/protocol/doors/runner-protocol.d.ts +1 -0
  128. package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
  129. package/dist/protocol/doors/runner-protocol.js +1 -0
  130. package/dist/protocol/doors/runner-protocol.js.map +1 -1
  131. package/dist/protocol/doors/webext-links.d.ts +1 -0
  132. package/dist/protocol/doors/webext-links.d.ts.map +1 -1
  133. package/dist/protocol/doors/webext-links.js +1 -0
  134. package/dist/protocol/doors/webext-links.js.map +1 -1
  135. package/dist/protocol/peer-dial.d.ts +2 -0
  136. package/dist/protocol/peer-dial.d.ts.map +1 -1
  137. package/dist/protocol/peer-dial.js +62 -29
  138. package/dist/protocol/peer-dial.js.map +1 -1
  139. package/dist/protocol/raw/raw-json-routes.d.ts +179 -1
  140. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  141. package/dist/protocol/raw/raw-json-routes.js +5 -0
  142. package/dist/protocol/raw/raw-json-routes.js.map +1 -1
  143. package/dist/protocol/raw/raw-routes.d.ts +35 -0
  144. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  145. package/dist/protocol/raw/raw-routes.js +9 -0
  146. package/dist/protocol/raw/raw-routes.js.map +1 -1
  147. package/dist/schemas/agent-next.d.ts +21 -0
  148. package/dist/schemas/agent-next.d.ts.map +1 -0
  149. package/dist/schemas/agent-next.js +21 -0
  150. package/dist/schemas/agent-next.js.map +1 -0
  151. package/dist/schemas/agent-place.d.ts +10 -0
  152. package/dist/schemas/agent-place.d.ts.map +1 -0
  153. package/dist/schemas/agent-place.js +15 -0
  154. package/dist/schemas/agent-place.js.map +1 -0
  155. package/dist/schemas/agent-search.d.ts +45 -0
  156. package/dist/schemas/agent-search.d.ts.map +1 -0
  157. package/dist/schemas/agent-search.js +28 -0
  158. package/dist/schemas/agent-search.js.map +1 -0
  159. package/dist/schemas/agent.d.ts +120 -78
  160. package/dist/schemas/agent.d.ts.map +1 -1
  161. package/dist/schemas/agent.js +40 -49
  162. package/dist/schemas/agent.js.map +1 -1
  163. package/dist/schemas/agents-list.d.ts +54 -0
  164. package/dist/schemas/agents-list.d.ts.map +1 -1
  165. package/dist/schemas/agents.d.ts +165 -59
  166. package/dist/schemas/agents.d.ts.map +1 -1
  167. package/dist/schemas/agents.js +25 -50
  168. package/dist/schemas/agents.js.map +1 -1
  169. package/dist/schemas/attach.d.ts +8 -0
  170. package/dist/schemas/attach.d.ts.map +1 -0
  171. package/dist/schemas/attach.js +14 -0
  172. package/dist/schemas/attach.js.map +1 -0
  173. package/dist/schemas/command-admission.d.ts +71 -0
  174. package/dist/schemas/command-admission.d.ts.map +1 -0
  175. package/dist/schemas/command-admission.js +27 -0
  176. package/dist/schemas/command-admission.js.map +1 -0
  177. package/dist/schemas/compose-overlay.d.ts +9 -0
  178. package/dist/schemas/compose-overlay.d.ts.map +1 -0
  179. package/dist/schemas/compose-overlay.js +11 -0
  180. package/dist/schemas/compose-overlay.js.map +1 -0
  181. package/dist/schemas/composer-drafts.d.ts +169 -0
  182. package/dist/schemas/composer-drafts.d.ts.map +1 -0
  183. package/dist/schemas/composer-drafts.js +60 -0
  184. package/dist/schemas/composer-drafts.js.map +1 -0
  185. package/dist/schemas/conversation-links.d.ts +44 -0
  186. package/dist/schemas/conversation-links.d.ts.map +1 -0
  187. package/dist/schemas/conversation-links.js +35 -0
  188. package/dist/schemas/conversation-links.js.map +1 -0
  189. package/dist/schemas/data-protection.d.ts +1 -1
  190. package/dist/schemas/data-protection.d.ts.map +1 -1
  191. package/dist/schemas/device-commands.d.ts +63 -0
  192. package/dist/schemas/device-commands.d.ts.map +1 -0
  193. package/dist/schemas/device-commands.js +42 -0
  194. package/dist/schemas/device-commands.js.map +1 -0
  195. package/dist/schemas/device-folder.d.ts +11 -0
  196. package/dist/schemas/device-folder.d.ts.map +1 -1
  197. package/dist/schemas/device-folder.js +1 -0
  198. package/dist/schemas/device-folder.js.map +1 -1
  199. package/dist/schemas/devices.d.ts +18 -68
  200. package/dist/schemas/devices.d.ts.map +1 -1
  201. package/dist/schemas/devices.js +8 -45
  202. package/dist/schemas/devices.js.map +1 -1
  203. package/dist/schemas/environment.d.ts.map +1 -1
  204. package/dist/schemas/environment.js.map +1 -1
  205. package/dist/schemas/hosts.d.ts +29 -0
  206. package/dist/schemas/hosts.d.ts.map +1 -1
  207. package/dist/schemas/hosts.js +23 -0
  208. package/dist/schemas/hosts.js.map +1 -1
  209. package/dist/schemas/keyring-native.d.ts +137 -0
  210. package/dist/schemas/keyring-native.d.ts.map +1 -0
  211. package/dist/schemas/keyring-native.js +69 -0
  212. package/dist/schemas/keyring-native.js.map +1 -0
  213. package/dist/schemas/keyring.d.ts +413 -0
  214. package/dist/schemas/keyring.d.ts.map +1 -1
  215. package/dist/schemas/keyring.js +139 -4
  216. package/dist/schemas/keyring.js.map +1 -1
  217. package/dist/schemas/phone-passwords.d.ts +186 -0
  218. package/dist/schemas/phone-passwords.d.ts.map +1 -0
  219. package/dist/schemas/phone-passwords.js +25 -0
  220. package/dist/schemas/phone-passwords.js.map +1 -0
  221. package/dist/schemas/privacy-egress.d.ts +41 -0
  222. package/dist/schemas/privacy-egress.d.ts.map +1 -0
  223. package/dist/schemas/privacy-egress.js +30 -0
  224. package/dist/schemas/privacy-egress.js.map +1 -0
  225. package/dist/schemas/privacy.d.ts +1 -0
  226. package/dist/schemas/privacy.d.ts.map +1 -1
  227. package/dist/schemas/privacy.js +1 -0
  228. package/dist/schemas/privacy.js.map +1 -1
  229. package/dist/schemas/providers/plan-limits.d.ts +76 -130
  230. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  231. package/dist/schemas/providers/plan-limits.js +36 -23
  232. package/dist/schemas/providers/plan-limits.js.map +1 -1
  233. package/dist/schemas/providers/provider-oauth.d.ts +24 -82
  234. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  235. package/dist/schemas/providers/provider-oauth.js +1 -6
  236. package/dist/schemas/providers/provider-oauth.js.map +1 -1
  237. package/dist/schemas/providers/refusals.d.ts +64 -0
  238. package/dist/schemas/providers/refusals.d.ts.map +1 -0
  239. package/dist/schemas/providers/refusals.js +33 -0
  240. package/dist/schemas/providers/refusals.js.map +1 -0
  241. package/dist/schemas/providers/usage.d.ts +1 -0
  242. package/dist/schemas/providers/usage.d.ts.map +1 -1
  243. package/dist/schemas/providers/usage.js +1 -0
  244. package/dist/schemas/providers/usage.js.map +1 -1
  245. package/dist/schemas/push.d.ts +13 -0
  246. package/dist/schemas/push.d.ts.map +1 -1
  247. package/dist/schemas/push.js +11 -1
  248. package/dist/schemas/push.js.map +1 -1
  249. package/dist/schemas/sandbox-passwords.d.ts +288 -0
  250. package/dist/schemas/sandbox-passwords.d.ts.map +1 -0
  251. package/dist/schemas/sandbox-passwords.js +66 -0
  252. package/dist/schemas/sandbox-passwords.js.map +1 -0
  253. package/dist/schemas/secrets.d.ts +2 -0
  254. package/dist/schemas/secrets.d.ts.map +1 -1
  255. package/dist/schemas/secrets.js +4 -0
  256. package/dist/schemas/secrets.js.map +1 -1
  257. package/dist/schemas/sessions.js +1 -1
  258. package/dist/schemas/sessions.js.map +1 -1
  259. package/dist/schemas/settings-savings.d.ts +709 -0
  260. package/dist/schemas/settings-savings.d.ts.map +1 -0
  261. package/dist/schemas/settings-savings.js +53 -0
  262. package/dist/schemas/settings-savings.js.map +1 -0
  263. package/dist/schemas/settings.d.ts +7 -671
  264. package/dist/schemas/settings.d.ts.map +1 -1
  265. package/dist/schemas/settings.js +5 -50
  266. package/dist/schemas/settings.js.map +1 -1
  267. package/dist/schemas/system.d.ts +16 -0
  268. package/dist/schemas/system.d.ts.map +1 -1
  269. package/dist/schemas/system.js +11 -0
  270. package/dist/schemas/system.js.map +1 -1
  271. package/dist/schemas/terminal.d.ts +5 -0
  272. package/dist/schemas/terminal.d.ts.map +1 -1
  273. package/dist/schemas/terminal.js +4 -0
  274. package/dist/schemas/terminal.js.map +1 -1
  275. package/dist/schemas/work-assurance.d.ts +24 -0
  276. package/dist/schemas/work-assurance.d.ts.map +1 -0
  277. package/dist/schemas/work-assurance.js +23 -0
  278. package/dist/schemas/work-assurance.js.map +1 -0
  279. package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
  280. package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
  281. package/dist/schemas/workspace/workspace-tree.js +20 -0
  282. package/dist/schemas/workspace/workspace-tree.js.map +1 -1
  283. package/dist/state/auth-state.d.ts +4 -0
  284. package/dist/state/auth-state.d.ts.map +1 -0
  285. package/dist/state/auth-state.js +37 -0
  286. package/dist/state/auth-state.js.map +1 -0
  287. package/dist/state/definition.d.ts +12 -0
  288. package/dist/state/definition.d.ts.map +1 -1
  289. package/dist/state/runtime-state.d.ts +3 -0
  290. package/dist/state/runtime-state.d.ts.map +1 -1
  291. package/dist/state/runtime-state.js +1 -0
  292. package/dist/state/runtime-state.js.map +1 -1
  293. package/dist/state/sandbox-build.d.ts +20 -0
  294. package/dist/state/sandbox-build.d.ts.map +1 -0
  295. package/dist/state/sandbox-build.js +43 -0
  296. package/dist/state/sandbox-build.js.map +1 -0
  297. package/dist/state/ways-back.d.ts +4 -0
  298. package/dist/state/ways-back.d.ts.map +1 -0
  299. package/dist/state/ways-back.js +8 -0
  300. package/dist/state/ways-back.js.map +1 -0
  301. package/dist/text/land-rows.d.ts +14 -0
  302. package/dist/text/land-rows.d.ts.map +1 -0
  303. package/dist/text/land-rows.js +86 -0
  304. package/dist/text/land-rows.js.map +1 -0
  305. package/dist/text/transcript-fold.d.ts +4 -2
  306. package/dist/text/transcript-fold.d.ts.map +1 -1
  307. package/dist/text/transcript-fold.js +21 -187
  308. package/dist/text/transcript-fold.js.map +1 -1
  309. package/dist/webext/index.d.ts +1 -0
  310. package/dist/webext/index.d.ts.map +1 -1
  311. package/dist/webext/index.js +1 -0
  312. package/dist/webext/index.js.map +1 -1
  313. package/package.json +7 -6
  314. package/src/contracts/agent.contract.ts +13 -1
  315. package/src/contracts/agents.contract.ts +53 -28
  316. package/src/contracts/device.contract.ts +16 -0
  317. package/src/contracts/endpoints.contract.ts +3 -0
  318. package/src/contracts/push.contract.ts +23 -1
  319. package/src/contracts/secrets.contract.ts +39 -0
  320. package/src/contracts/workspace.contract.ts +16 -0
  321. package/src/events/agent-events.ts +99 -79
  322. package/src/events/agent-transcript.ts +48 -0
  323. package/src/events/agent-words.ts +13 -0
  324. package/src/events/failure-rows.ts +148 -0
  325. package/src/events/requests.ts +41 -3
  326. package/src/events/sandbox-notice.ts +14 -2
  327. package/src/events/system-events.ts +14 -4
  328. package/src/events/transcript.ts +30 -28
  329. package/src/ids/conversation-ids.ts +7 -0
  330. package/src/ids/project-dir.test.ts +2 -2
  331. package/src/ids/project-dir.ts +4 -4
  332. package/src/ids/row-ids.ts +20 -0
  333. package/src/models/agent-lanes.ts +16 -24
  334. package/src/models/agent-runtimes.ts +2 -2
  335. package/src/models/agent-standing.ts +38 -95
  336. package/src/models/model-pins.test.ts +31 -1
  337. package/src/models/model-pins.ts +17 -0
  338. package/src/models/plan-pools.test.ts +274 -31
  339. package/src/models/plan-pools.ts +294 -79
  340. package/src/netd/generated/netd-wire.json +25 -0
  341. package/src/netd/generated/wire.ts +1 -1
  342. package/src/policy/command-classes.ts +2 -2
  343. package/src/policy/keyring-origins.test.ts +132 -0
  344. package/src/policy/keyring-origins.ts +287 -0
  345. package/src/policy/keyring-seal.test.ts +37 -2
  346. package/src/policy/keyring-seal.ts +33 -7
  347. package/src/policy/safety-policy.ts +1 -1
  348. package/src/policy/tests/safety-policy.test.ts +1 -1
  349. package/src/policy/turned-away.ts +15 -0
  350. package/src/protocol/doors/host-protocol.ts +3 -0
  351. package/src/protocol/doors/runner-protocol.ts +4 -1
  352. package/src/protocol/doors/webext-links.ts +3 -0
  353. package/src/protocol/peer-dial.test.ts +90 -4
  354. package/src/protocol/peer-dial.ts +83 -39
  355. package/src/protocol/raw/raw-json-routes.ts +13 -0
  356. package/src/protocol/raw/raw-routes.ts +13 -1
  357. package/src/schemas/agent-next.ts +34 -0
  358. package/src/schemas/agent-place.ts +22 -0
  359. package/src/schemas/agent-search.ts +51 -0
  360. package/src/schemas/agent.ts +63 -90
  361. package/src/schemas/agents.ts +55 -99
  362. package/src/schemas/attach.ts +23 -0
  363. package/src/schemas/command-admission.ts +57 -0
  364. package/src/schemas/compose-overlay.test.ts +35 -0
  365. package/src/schemas/compose-overlay.ts +19 -0
  366. package/src/schemas/composer-drafts.ts +86 -0
  367. package/src/schemas/conversation-links.ts +62 -0
  368. package/src/schemas/data-protection.ts +1 -1
  369. package/src/schemas/device-commands.ts +89 -0
  370. package/src/schemas/device-folder.ts +9 -0
  371. package/src/schemas/devices.ts +28 -110
  372. package/src/schemas/environment.ts +8 -9
  373. package/src/schemas/hosts.ts +60 -0
  374. package/src/schemas/keyring-native.test.ts +13 -0
  375. package/src/schemas/keyring-native.ts +101 -0
  376. package/src/schemas/keyring.ts +202 -4
  377. package/src/schemas/machine-presence.test.ts +33 -0
  378. package/src/schemas/phone-passwords.ts +51 -0
  379. package/src/schemas/privacy-egress.test.ts +83 -0
  380. package/src/schemas/privacy-egress.ts +86 -0
  381. package/src/schemas/privacy.ts +4 -0
  382. package/src/schemas/project-delivery.ts +1 -1
  383. package/src/schemas/providers/plan-limits.test.ts +16 -0
  384. package/src/schemas/providers/plan-limits.ts +55 -49
  385. package/src/schemas/providers/provider-oauth.ts +1 -13
  386. package/src/schemas/providers/refusals.ts +62 -0
  387. package/src/schemas/providers/usage.ts +2 -0
  388. package/src/schemas/push.ts +19 -3
  389. package/src/schemas/sandbox-passwords.ts +88 -0
  390. package/src/schemas/secrets.ts +4 -0
  391. package/src/schemas/sessions.ts +1 -1
  392. package/src/schemas/settings-savings.ts +125 -0
  393. package/src/schemas/settings.ts +10 -116
  394. package/src/schemas/system.ts +15 -0
  395. package/src/schemas/terminal.ts +8 -0
  396. package/src/schemas/work-assurance.ts +36 -0
  397. package/src/schemas/workspace/workspace-tree.ts +32 -0
  398. package/src/state/auth-state.ts +89 -0
  399. package/src/state/runtime-state.ts +10 -4
  400. package/src/state/sandbox-build.test.ts +95 -0
  401. package/src/state/sandbox-build.ts +96 -0
  402. package/src/state/ways-back.test.ts +36 -0
  403. package/src/state/ways-back.ts +19 -0
  404. package/src/state/workspace-state.ts +1 -1
  405. package/src/text/land-rows.ts +111 -0
  406. package/src/text/transcript-fold.test.ts +105 -9
  407. package/src/text/transcript-fold.ts +30 -232
  408. package/src/webext/index.ts +2 -0
  409. package/dist/events/undelivered.d.ts +0 -6
  410. package/dist/events/undelivered.d.ts.map +0 -1
  411. package/dist/events/undelivered.js +0 -9
  412. package/dist/events/undelivered.js.map +0 -1
  413. package/dist/state/history-state.d.ts +0 -3
  414. package/dist/state/history-state.d.ts.map +0 -1
  415. package/dist/state/history-state.js +0 -137
  416. package/dist/state/history-state.js.map +0 -1
  417. package/src/events/undelivered.ts +0 -15
  418. package/src/state/history-state.ts +0 -270
@@ -1,4 +1,5 @@
1
1
  import type { AccountState, AccountUsage, ProviderRefusal, UsageWindow, WindowGates } from "../schemas/providers/plan-limits.js";
2
+ import { humanizeModelId } from "./model-label.js";
2
3
 
3
4
  // Single reading of which account pool gates a given model (or, with none named, the account's tightest pool), shared
4
5
  // by the daemon's picker/refusal logic and the browser's rings and rail. A pool gated `none` is excluded from the
@@ -47,6 +48,11 @@ const nameMatches = (name: string, model: ModelRef): boolean => {
47
48
  export const gatesModel = (gates: WindowGates, model: ModelRef): boolean =>
48
49
  gates === "all" ? true : gates === "none" ? false : gates.models.some((name) => nameMatches(name, model));
49
50
 
51
+ // Served models no pool in `windows` gates: what a reading cannot say anything about. A pool gated `none` covers
52
+ // nothing, and one gated `all` covers every model.
53
+ export const uncoveredModels = (windows: readonly UsageWindow[], served: readonly ModelRef[]): string[] =>
54
+ served.filter((model) => !windows.some((window) => gatesModel(window.gates, model))).map((model) => model.id);
55
+
50
56
  // Pools that gate this model, or, with no model given, every pool that gates anything.
51
57
  export const gatingWindows = (usage: AccountUsage | undefined, model?: ModelRef): readonly UsageWindow[] =>
52
58
  (usage?.windows ?? []).filter((window) => (model === undefined ? window.gates !== "none" : gatesModel(window.gates, model)));
@@ -58,6 +64,17 @@ const fullest = (windows: readonly UsageWindow[]): UsageWindow | undefined =>
58
64
  // nothing was measured) — distinct from a measured 0%.
59
65
  export const bindingWindow = (usage: AccountUsage | undefined, model?: ModelRef): UsageWindow | undefined => fullest(gatingWindows(usage, model));
60
66
 
67
+ /**
68
+ * Whether a turn answered on `served` proves `other` had room on this account too: every pool `other` spends gates
69
+ * `served` as well, so the answer went through each of them. False where nothing is read, and where `other` spends a pool
70
+ * of its own (Claude's Opus slice, a model the plan has no entitlement to), which another model's answer says nothing
71
+ * about. Codex meters every model from one allowance, so a Luna turn going through proves Sol's allowance open as well.
72
+ */
73
+ export const answerCovers = (usage: AccountUsage | undefined, served: ModelRef, other: ModelRef): boolean => {
74
+ const pools = gatingWindows(usage, other);
75
+ return pools.length > 0 && pools.every((window) => gatesModel(window.gates, served));
76
+ };
77
+
61
78
  // The model's own metered pool, by most specific name match; distinct from bindingWindow, which may answer with an
62
79
  // all-models pool. Two matches tied in specificity return undefined rather than guess.
63
80
  export const scopedWindow = (usage: AccountUsage | undefined, model: ModelRef): UsageWindow | undefined => {
@@ -160,16 +177,24 @@ const lapsedWindow = (window: UsageWindow, measuredAt: number): UsageWindow => {
160
177
  // while the weekly rows stayed, a partial card that reads as complete. The floor of 0 is what dropping it already meant
161
178
  // to every verdict (an absent pool binds nothing). A reading with no window still current says nothing about now and
162
179
  // answers none, so the account reads as unmeasured. Idempotent: a pool already lapsed stays as it is.
180
+ // A pool the provider stopped reporting (`missingSince`) is dropped instead once its window closes: nothing will read it
181
+ // again, so its floor of 0 would be a pool that is not there.
163
182
  export const currentWindows = (usage: AccountUsage, now: number): UsageWindow[] => {
164
183
  const live = (window: UsageWindow): boolean => window.lapsedAt === undefined && windowLive(window, usage.measuredAt, now);
165
184
  if (!usage.windows.some(live)) {
166
185
  return [];
167
186
  }
168
- return usage.windows.map((window) => (window.lapsedAt !== undefined || live(window) ? window : lapsedWindow(window, usage.measuredAt)));
187
+ return usage.windows.flatMap((window) => {
188
+ if (window.lapsedAt !== undefined || live(window)) {
189
+ return [window];
190
+ }
191
+ return window.missingSince === undefined ? [lapsedWindow(window, usage.measuredAt)] : [];
192
+ });
169
193
  };
170
194
 
171
- // Whether an account can serve a turn: the one rule every picker and surface reads (AccountState). The daemon runs it and
172
- // publishes the verdict on each account row; the editor runs it only on a row from a daemon older than that field.
195
+ // Whether an account can serve a turn, and what one provider's accounts can serve together: the one rule every picker,
196
+ // gate, listing and surface reads. The daemon and the editor run it on the same facts (the account rows and every
197
+ // refusal on file); neither keeps a judged copy, so the two cannot disagree.
173
198
 
174
199
  /** Everything the serviceability rule reads of one account, as the account rows already carry it. */
175
200
  export interface ServiceFacts {
@@ -186,12 +211,17 @@ export interface ServiceFacts {
186
211
  readonly label?: string | undefined;
187
212
  }
188
213
 
214
+ /** Every refusal on file for one provider: what its accounts are judged against, each standing until something answers it. */
215
+ export type RefusalsOnFile = readonly ProviderRefusal[];
216
+
189
217
  // A spent allowance is over once a reading with room lands after it; a refused credential once any reading does (the
190
218
  // token worked). Nothing a reading says answers an entitlement refusal: a seatless account authenticates and reads fine.
191
- // Nor does any reading answer an allowance it does not measure (`unmetered`: an admin's credit).
192
- const answers = (refusal: ProviderRefusal, facts: ServiceFacts): boolean => {
219
+ // Nor does the refused account's own reading answer an allowance it does not measure (`unmetered`: an admin's credit).
220
+ // `measured` says whether this reading can speak to the allowance at all: false for that account's own, true for any
221
+ // account's where nothing says which one refused (refusalVerdict).
222
+ const answers = (refusal: ProviderRefusal, facts: ServiceFacts, measured: boolean = refusal.unmetered !== true): boolean => {
193
223
  const measuredAt = facts.usage?.measuredAt;
194
- if (refusal.kind === "entitlement" || refusal.unmetered === true || measuredAt === undefined || measuredAt <= refusal.at) {
224
+ if (refusal.kind === "entitlement" || !measured || measuredAt === undefined || measuredAt <= refusal.at) {
195
225
  return false;
196
226
  }
197
227
  if (refusal.kind === "auth") {
@@ -202,9 +232,39 @@ const answers = (refusal: ProviderRefusal, facts: ServiceFacts): boolean => {
202
232
  };
203
233
 
204
234
  /**
205
- * How a provider's last refusal stands against everything read since, over every account the provider holds: `gone` once
206
- * the account it named is disconnected, `answered` once that account (or, for a refusal naming none, any account) has a
207
- * later reading that contradicts it, else `standing`. An empty list is one not loaded yet, so the refusal stands.
235
+ * When the pool a spent allowance that named no instant was refused in reopens, in epoch seconds: the soonest reset of
236
+ * the binding pool for the model it named (the pool judgedReading pins full) on the accounts it may have refused, its own
237
+ * where it named one, any where it named none, as any account's room answers a nameless one. The daemon's ledger stamps
238
+ * it on the refusal as it files it (`poolResetsAt`), while the readings still show that pool open: once it reopens the
239
+ * reading lapses it to an empty pool and a different one binds, so it cannot be read back off the reading later.
240
+ * Undefined for a refusal that named its instant, one no reading measures (`unmetered`), a person's mark (lifted by a
241
+ * person or a turn, not a window), and where no reading publishes a reset still ahead of the refusal.
242
+ */
243
+ export const refusedPoolReset = (refusal: ProviderRefusal, accounts: readonly ServiceFacts[]): number | undefined => {
244
+ if (refusal.kind !== "limit" || refusal.resetsAt !== undefined || refusal.unmetered === true || refusal.byHand === true) {
245
+ return undefined;
246
+ }
247
+ const model = refusal.model === undefined ? undefined : { id: refusal.model };
248
+ const resets = accounts
249
+ .filter((facts) => refusal.account === undefined || facts.account === refusal.account)
250
+ .flatMap((facts) => {
251
+ const reset = bindingWindow(facts.usage, model)?.resetsAt;
252
+ return reset !== undefined && reset * 1000 > refusal.at ? [reset] : [];
253
+ });
254
+ return resets.length === 0 ? undefined : Math.min(...resets);
255
+ };
256
+
257
+ /**
258
+ * How a refusal on file stands against everything read since, over every account the provider holds: `gone` once the
259
+ * account it named is disconnected, `answered` once the instant it named has passed (or, where it named none, the
260
+ * reset of the pool it was refused in, stamped as it was filed: refusedPoolReset) or once that account (or, for a refusal
261
+ * naming none, any account) has a later reading that contradicts it, else `standing`. An empty list is one not loaded
262
+ * yet, so the refusal stands. Judged by the daemon and every open window at the same instant from what the refusal
263
+ * carries: nothing waits for a fresh reading or a ledger write to flip it. 2026-10-20: a refusal naming no instant stood
264
+ * past its pool's reset until the next reading landed, up to the 15-minute idle sweep, while the picker already said
265
+ * "back at" that reset. A stamp is this sandbox's guess, not the provider's word, so a reading taken after it outranks
266
+ * it: 2026-10-21, a weekly pool read at a rounded 100 once the five-hour one it was stamped with reopened, and the
267
+ * account read ready with no room, so every turn let through was refused again.
208
268
  */
209
269
  export const refusalVerdict = (
210
270
  refusal: ProviderRefusal,
@@ -215,14 +275,44 @@ export const refusalVerdict = (
215
275
  if (refusal.account !== undefined && named.length === 0) {
216
276
  return accounts.length === 0 ? "standing" : "gone";
217
277
  }
218
- // Where the provider named when to try again, and it is known which account it refused (it named one, or there is
219
- // only one), its own words outrank a usage endpoint that kept showing room while it refused: over at that instant,
220
- // and not before. A nameless refusal over several accounts is still answered by any of them reading room, since the
221
- // translator benches the one it refused and routes on.
222
- if (refusal.kind === "limit" && refusal.resetsAt !== undefined && (refusal.account !== undefined || accounts.length <= 1 || refusal.unmetered === true)) {
223
- return refusal.resetsAt * 1000 <= now ? "answered" : "standing";
278
+ const whose = refusal.account === undefined ? accounts : named;
279
+ // The provider's own "try again at" is over at that instant, whichever account it refused; so, where it named none,
280
+ // is the refused pool's reset the ledger stamped, unless a reading since says otherwise (read below).
281
+ const over = refusal.resetsAt ?? refusal.poolResetsAt;
282
+ const readSince = (instant: number): boolean => whose.some((facts) => (facts.usage?.measuredAt ?? 0) > instant * 1000);
283
+ if (refusal.kind === "limit" && over !== undefined && over * 1000 <= now && (refusal.resetsAt !== undefined || !readSince(over))) {
284
+ return "answered";
285
+ }
286
+ // Where it is known which account it refused (it named one, or there is only one), the provider's own instant
287
+ // outranks a usage endpoint that kept showing room while it refused: over at that instant, and not before.
288
+ const known = refusal.account !== undefined || accounts.length <= 1;
289
+ if (refusal.kind === "limit" && refusal.resetsAt !== undefined && known) {
290
+ return "standing";
291
+ }
292
+ if (known) {
293
+ return whose.some((facts) => answers(refusal, facts)) ? "answered" : "standing";
224
294
  }
225
- return (refusal.account === undefined ? accounts : named).some((facts) => answers(refusal, facts)) ? "answered" : "standing";
295
+ /* A nameless refusal over several accounts is one of them saying no, and nothing says which: the translator benches
296
+ the one it refused and routes on, so any of them reading room since answers it, whatever kind of allowance it was
297
+ and whatever instant it named. Held to its instant, or to an allowance no reading measures, one team account's
298
+ refusal held every Codex account spent for a day, the two free ones reading 0% used included (2026-10-19). */
299
+ return accounts.some((facts) => answers(refusal, facts, true)) ? "answered" : "standing";
300
+ };
301
+
302
+ // A stamp a reading has outlived (refusalVerdict) says nothing of when the pool now holding the refusal reopens: that
303
+ // pool's own reset does.
304
+ const unstampedPast = (refusal: ProviderRefusal, now: number): ProviderRefusal => {
305
+ if (refusal.poolResetsAt === undefined || refusal.poolResetsAt * 1000 > now) {
306
+ return refusal;
307
+ }
308
+ const { poolResetsAt: _outlived, ...standing } = refusal;
309
+ return standing;
310
+ };
311
+
312
+ /** The refusals still standing over each of a provider's accounts: those naming it, and those naming none. */
313
+ export const standingOver = (accounts: readonly ServiceFacts[], refusals: RefusalsOnFile, now: number = Date.now()): ((account: string) => RefusalsOnFile) => {
314
+ const standing = refusals.filter((refusal) => refusalVerdict(refusal, accounts, now) === "standing").map((refusal) => unstampedPast(refusal, now));
315
+ return (account) => standing.filter((refusal) => (refusal.account ?? account) === account);
226
316
  };
227
317
 
228
318
  // The account reopens for this question when the pools holding it shut do: all of them for a model's own question (or
@@ -232,105 +322,230 @@ const reopening = (full: readonly UsageWindow[], first: boolean): number | undef
232
322
  return resets.length === full.length && resets.length > 0 ? (first ? Math.min(...resets) : Math.max(...resets)) : undefined;
233
323
  };
234
324
 
235
- const spent = (reopensAt: number | undefined): AccountState => (reopensAt === undefined ? { kind: "spent" } : { kind: "spent", reopensAt });
325
+ // Spent until the pools holding it reopen, named by the one that decides when, where the plan meters it apart (an
326
+ // undivided allowance is just "the allowance").
327
+ const spentOn = (holding: readonly UsageWindow[], first: boolean): AccountState => {
328
+ const state: AccountState & { kind: "spent" } = { kind: "spent" };
329
+ const reopensAt = reopening(holding, first);
330
+ if (reopensAt !== undefined) {
331
+ state.reopensAt = reopensAt;
332
+ }
333
+ const deciding = holding.find((window) => window.resetsAt === reopensAt) ?? holding[0];
334
+ if (deciding !== undefined && deciding.gates !== "all" && deciding.label !== undefined) {
335
+ state.pool = deciding.label;
336
+ }
337
+ return state;
338
+ };
339
+
340
+ // A turn on the model this reading was contradicted for went through while the pool read full: the pool is at the line,
341
+ // not proven empty, exactly as a rounded one is (provenFull).
342
+ const answeredDespite = (usage: AccountUsage | undefined, window: UsageWindow): boolean =>
343
+ usage?.contradicted !== undefined && gatesModel(window.gates, { id: usage.contradicted.model });
344
+
345
+ // The pools holding the account shut: for a model, every full pool it spends; with none, the full pools gating every
346
+ // model, else every per-model slice once all of them are full; nothing while one is open.
347
+ const holdingPools = (full: readonly UsageWindow[], open: readonly UsageWindow[], model: ModelRef | undefined): readonly UsageWindow[] => {
348
+ if (model !== undefined) {
349
+ return full;
350
+ }
351
+ const wholePlan = full.filter((window) => window.gates === "all");
352
+ if (wholePlan.length > 0) {
353
+ return wholePlan;
354
+ }
355
+ return open.length === 0 ? full : [];
356
+ };
236
357
 
237
358
  /**
238
- * The plan-limit half of the verdict, at the contract's one spent line. With a model, the pools that model spends: spent
239
- * once any is full, else the room the fullest leaves. With none, whether the account can serve SOME turn: spent once a
240
- * pool gating every model is full, or every per-model slice is; a full slice alone (Opus at 100%) leaves the other
241
- * models their room, which is then read off the fullest pool still open.
359
+ * What a reading says, at the contract's one spent line, never whether the account can serve: that is `serviceStates`,
360
+ * which first lays every standing refusal on the reading (judgedReading). A provider that rounds its percentages (Claude
361
+ * does, every pool) is never proven spent here, so a caller reading a polled reading's verdict as the account's sees every
362
+ * spent account as open: a pick between two spent accounts re-booked a held turn for "now" that way (2026-10-19).
363
+ * Imported only by the few modules lint allows (.oxlintrc.json, no-restricted-imports).
364
+ *
365
+ * With a model, the pools that model spends: spent once any is full, else the room the fullest leaves. With none, whether
366
+ * the account can serve SOME turn: spent once a pool gating every model is full, or every per-model slice is; a full
367
+ * slice alone (Opus at 100%) leaves the other models their room, which is then read off the fullest pool still open.
242
368
  */
243
- export const headroomState = (usage: AccountUsage | undefined, model?: ModelRef): AccountState => {
244
- const windows = gatingWindows(usage, model);
369
+ export const readingHeadroom = (usage: AccountUsage | undefined, model?: ModelRef): AccountState => {
370
+ const windows = gatingWindows(usage, model).map((window) => (answeredDespite(usage, window) ? { ...window, rounded: true } : window));
245
371
  if (windows.length === 0) {
246
372
  return { kind: "unknown" };
247
373
  }
248
374
  const full = windows.filter(provenFull);
249
375
  const open = windows.filter((window) => !provenFull(window));
250
- if (model !== undefined || full.some((window) => window.gates === "all") || open.length === 0) {
251
- const holding = model !== undefined ? full : full.some((window) => window.gates === "all") ? full.filter((window) => window.gates === "all") : full;
252
- if (holding.length > 0) {
253
- return spent(reopening(holding, model === undefined && !holding.some((window) => window.gates === "all")));
254
- }
376
+ const holding = holdingPools(full, open, model);
377
+ if (holding.length === 0) {
378
+ return { kind: "ready", room: Math.max(0, SPENT_UTILIZATION - Math.max(...open.map((window) => window.utilization))) };
255
379
  }
256
- return { kind: "ready", room: Math.max(0, SPENT_UTILIZATION - Math.max(...open.map((window) => window.utilization))) };
380
+ const sliced = model === undefined && !holding.some((window) => window.gates === "all");
381
+ // Every per-model slice this reading names is full, but it names none for some models the account serves: whether
382
+ // those can run is unknown, not spent. Reading the account spent on this is how every Google account read out of
383
+ // allowance while its Gemini models, whose pool the provider had stopped reporting, still served.
384
+ if (sliced && (usage?.uncovered?.length ?? 0) > 0) {
385
+ return { kind: "unknown" };
386
+ }
387
+ return spentOn(holding, sliced);
388
+ };
389
+
390
+ // What a person calls a refused pool no reading names: the model it refused, or the allowance as a whole.
391
+ const REFUSED_ALLOWANCE = "Allowance";
392
+
393
+ // The pool a limit refusal proves full where no pool of the reading gates the model it refused: a plan that publishes
394
+ // nothing (Cursor's, Devin's) or names no pool for that model. Its own, gating only that model (every model, where it
395
+ // named none), reopening when the provider said.
396
+ const refusedPool = (refusal: ProviderRefusal): UsageWindow => {
397
+ const pool: UsageWindow = {
398
+ kind: `refused:${refusal.model ?? "all"}`,
399
+ label: refusal.model === undefined ? REFUSED_ALLOWANCE : humanizeModelId(refusal.model),
400
+ utilization: SPENT_UTILIZATION,
401
+ gates: refusal.model === undefined ? "all" : { models: [refusal.model] },
402
+ };
403
+ if (refusal.resetsAt !== undefined) {
404
+ pool.resetsAt = refusal.resetsAt;
405
+ }
406
+ return pool;
257
407
  };
258
408
 
259
- // A standing limit refusal reads its pool as full, since a polled reading freezes the moment a pool actually empties: the
260
- // pool the refused turn's model spends, else the one asked about, else the tightest. Never another model's allowance.
261
- const refusedPool = (usage: AccountUsage | undefined, refusal: ProviderRefusal, model: ModelRef | undefined): AccountState => {
262
- const refusedModel = refusal.model === undefined ? model : { id: refusal.model };
263
- const binding = bindingWindow(usage, refusedModel);
409
+ // One limit refusal laid on a reading: the pool the refused turn's model spends (else the one asked about, else the
410
+ // tightest) read full, since a polled reading freezes the moment a pool actually empties, and reopening when the provider
411
+ // said to try again, where it said, over whatever the polled pool last said. Never another model's allowance.
412
+ const pinRefusal = (usage: AccountUsage | undefined, refusal: ProviderRefusal, model: ModelRef | undefined): AccountUsage => {
413
+ const binding = bindingWindow(usage, refusal.model === undefined ? model : { id: refusal.model });
264
414
  if (usage === undefined || binding === undefined) {
265
- // No pool to pin: the refusal itself is the evidence, unless it was about a different model than this question.
266
- const sameModel = refusal.model === undefined || model === undefined || refusal.model === model.id;
267
- return sameModel ? spent(refusal.resetsAt) : headroomState(usage, model);
268
- }
269
- const pinned = usage.windows.map((window) => (window === binding ? pinnedFull(window) : window));
270
- const state = headroomState({ ...usage, windows: pinned }, model);
271
- // The provider's own "try again at" is when it reopens, over whatever the polled pool last said.
272
- return state.kind === "spent" && refusal.resetsAt !== undefined ? spent(refusal.resetsAt) : state;
415
+ return { ...(usage ?? { measuredAt: refusal.at }), windows: [...(usage?.windows ?? []), refusedPool(refusal)] };
416
+ }
417
+ const pinned = pinnedFull(binding);
418
+ // Reopening when the refusal ends (refusalVerdict), so the "back at" every surface draws is when it flips.
419
+ const over = refusal.resetsAt ?? refusal.poolResetsAt;
420
+ if (over !== undefined) {
421
+ pinned.resetsAt = over;
422
+ }
423
+ return { ...usage, windows: usage.windows.map((window) => (window === binding ? pinned : window)) };
273
424
  };
274
425
 
275
426
  /**
276
- * One account's verdict. `refusal` is the provider's last refusal only when it still stands and covers this account
277
- * (serviceStates decides that). Precedence: a revoked sign-in outranks a lost seat (reconnecting fixes one, nothing here
278
- * fixes the other), both outrank a standing refusal, then a translator bench, then the plan limits.
427
+ * An account's reading as the refusals standing over it leave it: what every surface draws and every verdict judges,
428
+ * never the polled reading alone. Each standing limit refusal is laid on it, oldest first so the newest says when its
429
+ * pool reopens. `standing` is what standingOver answers for the account; a refusal of another kind pins nothing.
279
430
  */
280
- export const serviceState = (facts: ServiceFacts, refusal?: ProviderRefusal, model?: ModelRef, now: number = Date.now()): AccountState => {
431
+ export const judgedReading = (usage: AccountUsage | undefined, standing: RefusalsOnFile, model?: ModelRef): AccountUsage | undefined =>
432
+ standing
433
+ .filter((refusal) => refusal.kind === "limit")
434
+ .toSorted((left, right) => left.at - right.at)
435
+ .reduce<AccountUsage | undefined>((reading, refusal) => pinRefusal(reading, refusal, model), usage);
436
+
437
+ /** The newest of some refusals: the latest word, where a surface says one line about a provider or an account. */
438
+ export const newestRefusal = (refusals: RefusalsOnFile): ProviderRefusal | undefined =>
439
+ refusals.reduce<ProviderRefusal | undefined>((newest, refusal) => (newest === undefined || refusal.at > newest.at ? refusal : newest), undefined);
440
+
441
+ /**
442
+ * One account's verdict on everything on file over it. `standing` is the refusals still standing over it (standingOver).
443
+ * Precedence: a revoked sign-in outranks a lost seat (reconnecting fixes one, nothing here fixes the other), both outrank
444
+ * the newest standing refusal where it refused the account itself, then a translator bench, then the plan limits on the
445
+ * reading the refusals leave (judgedReading).
446
+ */
447
+ export const serviceState = (facts: ServiceFacts, standing: RefusalsOnFile = [], model?: ModelRef, now: number = Date.now()): AccountState => {
281
448
  if (facts.needsReauth === true) {
282
449
  return { kind: "blocked", fix: "reconnect", reason: facts.detail ?? "sign-in expired" };
283
450
  }
284
451
  if (facts.seatRefusal !== undefined) {
285
452
  return { kind: "blocked", fix: "admin", reason: facts.seatRefusal };
286
453
  }
287
- if (refusal?.kind === "auth") {
288
- return { kind: "blocked", fix: "reconnect", reason: refusal.message };
454
+ // The newest word on the account: a sign-in or an entitlement refused after any spent allowance blocks it, and a
455
+ // spent allowance refused after either says the account authenticated and was served, so that one is over.
456
+ const newest = newestRefusal(standing);
457
+ if (newest?.kind === "auth") {
458
+ return { kind: "blocked", fix: "reconnect", reason: newest.message };
289
459
  }
290
- if (refusal?.kind === "entitlement") {
291
- return { kind: "blocked", fix: "admin", reason: refusal.message };
460
+ if (newest?.kind === "entitlement") {
461
+ return { kind: "blocked", fix: "admin", reason: newest.message };
292
462
  }
293
- const cooling = facts.cooling;
294
- // A bench the provider wants a person for: the account's owner confirms it on the provider's page, and no wait or
295
- // reconnect here lifts it. Outranks the retry instant, which is only when the proxy will ask again.
296
- if (cooling?.verify !== undefined) {
463
+ const plan = readingHeadroom(judgedReading(facts.usage, standing, model), model);
464
+ return facts.cooling === undefined ? plan : benched(facts.cooling, plan, now);
465
+ };
466
+
467
+ // A translator bench over the plan verdict. One the provider wants a person for: the account's owner confirms it on the
468
+ // provider's page, and no wait or reconnect here lifts it, whatever the retry instant says. One with no instant is one no
469
+ // wait lifts either (a Google account with no project): somebody has to connect again. One with an instant is the
470
+ // translator's own retry, a floor rather than the reopening: a Kimi credential benched for thirty minutes with its
471
+ // five-hour window two hours from reopening was re-run at thirty minutes and refused again (2026-10-17), so it lifts once
472
+ // its pools have reopened too. Past its instant it is over, and the plan speaks.
473
+ const benched = (cooling: NonNullable<ServiceFacts["cooling"]>, plan: AccountState, now: number): AccountState => {
474
+ if (cooling.verify !== undefined) {
297
475
  return { kind: "blocked", fix: "verify", reason: cooling.reason ?? "the provider wants this account verified", url: cooling.verify };
298
476
  }
299
- // A bench with no instant is one no wait lifts (a Google account with no project): somebody has to connect again.
300
- if (cooling !== undefined && cooling.until === undefined) {
477
+ if (cooling.until === undefined) {
301
478
  return { kind: "blocked", fix: "reconnect", reason: cooling.reason ?? "benched by the translator" };
302
479
  }
303
- if (cooling?.until !== undefined && cooling.until * 1000 > now) {
304
- return { kind: "blocked", fix: "wait", reason: cooling.reason ?? "cooling down", until: cooling.until };
480
+ if (cooling.until * 1000 <= now) {
481
+ return plan;
305
482
  }
306
- return refusal?.kind === "limit" ? refusedPool(facts.usage, refusal, model) : headroomState(facts.usage, model);
483
+ const until = plan.kind === "spent" && plan.reopensAt !== undefined ? Math.max(cooling.until, plan.reopensAt) : cooling.until;
484
+ return { kind: "blocked", fix: "wait", reason: cooling.reason ?? "cooling down", until };
307
485
  };
308
486
 
309
- /** The refusals a provider's accounts are judged against: its last one, or every one on file (each account's own beside it). */
310
- export type RefusalsOnFile = ProviderRefusal | readonly ProviderRefusal[] | undefined;
311
-
312
- const refusalList = (refusals: RefusalsOnFile): readonly ProviderRefusal[] =>
313
- refusals === undefined ? [] : "kind" in refusals ? [refusals] : refusals;
314
-
315
- /**
316
- * Every account of one provider, judged together, since a refusal naming no account is answered by any of them. Each
317
- * account answers to the newest refusal that still stands over it: the provider's last one moves on to whichever account
318
- * said no next, and an account's own refusal on file stays true of it until something answers it.
319
- */
487
+ /** Every account of one provider, judged together, since a refusal naming no account is answered by any of them. */
320
488
  export const serviceStates = (
321
489
  accounts: readonly ServiceFacts[],
322
- refusals: RefusalsOnFile,
490
+ refusals: RefusalsOnFile = [],
323
491
  model?: ModelRef,
324
492
  now: number = Date.now(),
325
493
  ): ReadonlyMap<string, AccountState> => {
326
- const standing = refusalList(refusals)
327
- .filter((refusal) => refusalVerdict(refusal, accounts, now) === "standing")
328
- .toSorted((left, right) => right.at - left.at);
329
- return new Map(
330
- accounts.map((facts) => [facts.account, serviceState(facts, standing.find((refusal) => (refusal.account ?? facts.account) === facts.account), model, now)]),
331
- );
494
+ const standing = standingOver(accounts, refusals, now);
495
+ return new Map(accounts.map((facts) => [facts.account, serviceState(facts, standing(facts.account), model, now)]));
496
+ };
497
+
498
+ /** When an account that cannot serve says it can again, in epoch seconds; undefined where its verdict names no instant. */
499
+ export const reopensAtOf = (state: AccountState): number | undefined => {
500
+ if (state.kind === "spent") {
501
+ return state.reopensAt;
502
+ }
503
+ return state.kind === "blocked" && state.fix === "wait" ? state.until : undefined;
504
+ };
505
+
506
+ /**
507
+ * What one provider's accounts can do for one model, together: the answer every gate, listing, picker and offer reads,
508
+ * so none can call a model spent that another lets through. An unmeasured account may still answer, so it keeps the
509
+ * model servable; only every account spent or blocked makes it spent.
510
+ */
511
+ export type FleetCapacity =
512
+ // No account to judge: nothing on file says either way, so the model is asked.
513
+ | { readonly kind: "unknown" }
514
+ // `serving` of the `accounts` are ready or unmeasured; `roomMeasuredAt` is the newest reading with room among them.
515
+ | { readonly kind: "serving"; readonly serving: number; readonly accounts: number; readonly roomMeasuredAt?: number | undefined }
516
+ // Every account is spent or blocked: `reopensAt` is the soonest any is back (any one unblocks the turn), where one
517
+ // says when, and `pool` what holds that one.
518
+ | { readonly kind: "spent"; readonly accounts: number; readonly reopensAt?: number | undefined; readonly pool?: string | undefined };
519
+
520
+ /** A provider's capacity for one model from its accounts as already judged on that model (serviceStates). */
521
+ export const fleetOf = (accounts: readonly ServiceFacts[], states: ReadonlyMap<string, AccountState>): FleetCapacity => {
522
+ if (accounts.length === 0) {
523
+ return { kind: "unknown" };
524
+ }
525
+ const judged = accounts.map((facts) => ({ facts, state: states.get(facts.account) ?? ({ kind: "unknown" } as const) }));
526
+ const serving = judged.filter(({ state }) => state.kind === "ready" || state.kind === "unknown");
527
+ if (serving.length > 0) {
528
+ const room = serving.flatMap(({ facts, state }) => (state.kind === "ready" && facts.usage !== undefined ? [facts.usage.measuredAt] : []));
529
+ return { kind: "serving", serving: serving.length, accounts: accounts.length, roomMeasuredAt: room.length === 0 ? undefined : Math.max(...room) };
530
+ }
531
+ // The soonest instant any account names; one with none only decides the pool where nothing names an instant.
532
+ const soonest = judged.reduce<AccountState | undefined>((best, { state }) => {
533
+ const at = reopensAtOf(state);
534
+ const bestAt = best === undefined ? undefined : reopensAtOf(best);
535
+ return best === undefined || (at !== undefined && (bestAt === undefined || at < bestAt)) ? state : best;
536
+ }, undefined);
537
+ return {
538
+ kind: "spent",
539
+ accounts: accounts.length,
540
+ reopensAt: soonest === undefined ? undefined : reopensAtOf(soonest),
541
+ pool: soonest?.kind === "spent" ? soonest.pool : undefined,
542
+ };
332
543
  };
333
544
 
545
+ /** A provider's capacity for one model, judged on its accounts and every refusal on file. */
546
+ export const fleetCapacity = (accounts: readonly ServiceFacts[], refusals: RefusalsOnFile = [], model?: ModelRef, now: number = Date.now()): FleetCapacity =>
547
+ fleetOf(accounts, serviceStates(accounts, refusals, model, now));
548
+
334
549
  // Worst last: proven room, no reading either way, a pool at the line (it may hold a sliver, or nothing), known spent,
335
550
  // and nothing a turn can run on.
336
551
  const preference = (state: AccountState): number => {
@@ -358,8 +573,8 @@ export const preferredAccount = <T extends { readonly state: AccountState }>(ent
358
573
 
359
574
  /**
360
575
  * The roomiest account with proven room, or none: what a move off a refused account may land on. Ties keep the caller's
361
- * order. An account at the line is left out: a move there could be refused in turn, and with one refusal remembered per
362
- * provider, two such accounts would hand the turn back and forth.
576
+ * order. An account at the line is left out: a move there could be refused in turn, and two such accounts would hand the
577
+ * turn back and forth.
363
578
  */
364
579
  export const roomiestAccount = <T extends { readonly state: AccountState }>(entries: readonly T[]): T | undefined =>
365
580
  preferredAccount(entries.filter((entry) => entry.state.kind === "ready" && entry.state.room > 0));
@@ -874,6 +874,31 @@
874
874
  "connected"
875
875
  ],
876
876
  "type": "object"
877
+ },
878
+ {
879
+ "description": "A line the person typed with echo off in one of the editor's own terminal sessions (`web-…`), after a\npassword-ish question: told once, on Enter, for the daemon to hand Passwords if the owner turned remembering on\nfor sandbox terminals (2026-10-19). Never logged on either side; netd keeps nothing of it after sending.",
880
+ "properties": {
881
+ "kind": {
882
+ "const": "hiddenLine",
883
+ "type": "string"
884
+ },
885
+ "prompt": {
886
+ "type": "string"
887
+ },
888
+ "session": {
889
+ "type": "string"
890
+ },
891
+ "value": {
892
+ "type": "string"
893
+ }
894
+ },
895
+ "required": [
896
+ "kind",
897
+ "session",
898
+ "prompt",
899
+ "value"
900
+ ],
901
+ "type": "object"
877
902
  }
878
903
  ],
879
904
  "title": "ToNode"
@@ -73,7 +73,7 @@ export type TerminalPlan = { "plan": "session", name: string, createIn?: string,
73
73
  /**
74
74
  * Everything netd sends Node.
75
75
  */
76
- export type ToNode = { "kind": "ask", id: number, question: Question, } | { "kind": "answer", id: number, answer: NetdAnswer, } | { "kind": "refused", id: number, message: string, } | { "kind": "tunnel", connected: boolean, reason?: string, refused?: TunnelRefusal, };
76
+ export type ToNode = { "kind": "ask", id: number, question: Question, } | { "kind": "answer", id: number, answer: NetdAnswer, } | { "kind": "refused", id: number, message: string, } | { "kind": "tunnel", connected: boolean, reason?: string, refused?: TunnelRefusal, } | { "kind": "hiddenLine", session: string, prompt: string, value: string, };
77
77
 
78
78
  /**
79
79
  * The ingress tunnel's door, the reachability grant it presents there, and the daemon's transfer routes it announces
@@ -1,5 +1,5 @@
1
1
  import { HISTORY_ROOT, WORKSPACE_ROOT } from "@intentic/constants";
2
- import { type CommandClass, CommandClassSchema, type CommandLocus } from "../schemas/agent.js";
2
+ import { type CommandClass, CommandClassSchema, type CommandLocus } from "../schemas/command-admission.js";
3
3
  import { SECRET_REFERENCE } from "../schemas/needs.js";
4
4
  import { inertRegions, isLive } from "../text/shell-regions.js";
5
5
 
@@ -413,7 +413,7 @@ const findAndXargsDeletes = (command: string): CommandSpan[] => [
413
413
  const POWERSHELL_RECURSE = String.raw`\s-r(?:e(?:c(?:u(?:r(?:s(?:e)?)?)?)?)?)?(?::\$true)?(?![\w-])`;
414
414
  const WINDOWS_RECURSIVE_DELETES = [
415
415
  new RegExp(String.raw`(?<![\w-])(?:Remove-Item|ri|rd|rmdir|del|erase)\b[^|;&\n]*?${POWERSHELL_RECURSE}`, "i"),
416
- new RegExp(String.raw`(?<![\w-])rm\b[^|;&\n]*?\s-recurse(?![\w-])`, "i"),
416
+ /(?<![\w-])rm\b[^|;&\n]*?\s-recurse(?![\w-])/i,
417
417
  /(?<![\w-])(?:rd|rmdir|del|erase)\b[^|;&\n]*?\s\/s\b/i,
418
418
  /\[(?:System\.)?IO\.Directory\]::Delete\s*\([^)]*,\s*\$true\s*\)/i,
419
419
  ];