@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
@@ -9,9 +9,22 @@ import { z } from "zod";
9
9
  export const WslEnvironmentSchema = z.object({ distro: z.string() });
10
10
  export type WslEnvironment = z.infer<typeof WslEnvironmentSchema>;
11
11
 
12
+ // WHICH PHYSICAL COMPUTER A DOOR IS ON. The machine agent mints this once, at install, and keeps it in its own state
13
+ // (`~/.intentic/machine/machine-id`); a WSL distro's agent is handed its Windows side's, since a distro is an
14
+ // environment of the PC, not a second computer. It is what an enrollment, a sync enrollment and a device row are
15
+ // joined on — never a hostname, which WSL shares between a PC and its distros and a user can rename.
16
+ export const MachineIdSchema = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$/);
17
+ // An id the daemon stands in for a machine that has not said its own yet (an enrollment made before agents reported
18
+ // one): named after the card, so every environment of that card shares it until each reports the real id. Never
19
+ // sent by an agent, and never the join key of two different cards.
20
+ export const derivedMachineId = (card: string): string => `card:${card}`;
21
+ export const isDerivedMachineId = (machineId: string): boolean => machineId.startsWith("card:");
22
+
12
23
  // What a machine reports once at connect (`host.describe`), cached until it reconnects: the skill pack says how to
13
24
  // drive Windows, this says which Windows it is.
14
25
  export const DeviceFactsSchema = z.object({
26
+ // The computer this environment is on (MachineIdSchema). Absent from an agent older than the field.
27
+ machineId: MachineIdSchema.optional(),
15
28
  // The OS's own name for itself, e.g. "Windows 11 Pro 24H2".
16
29
  os: z.string(),
17
30
  arch: z.string(),
@@ -24,8 +37,7 @@ export const DeviceFactsSchema = z.object({
24
37
  // Docker engine's size (WSL guest on Windows, Desktop VM on macOS, host on Linux); the ceiling a sandbox's share is
25
38
  // bounded by.
26
39
  engine: z.object({ memoryBytes: z.number(), cpus: z.number() }).optional(),
27
- // OS hostname: the key that joins the doors of one physical machine. Reported by every agent that also reports
28
- // `wsl`, so a hostname with no `wsl` beside it is a native install; absent from an agent older than both.
40
+ // OS hostname, for the owner to read. Not a join key: `machineId` is.
29
41
  hostname: z.string().optional(),
30
42
  // Present only inside a WSL distro.
31
43
  wsl: WslEnvironmentSchema.optional(),
@@ -35,9 +47,33 @@ export const DeviceFactsSchema = z.object({
35
47
  // the rest are other sandboxes' addresses, and a sandbox has no business learning its siblings'. Absent from an
36
48
  // agent older than this field, which is why "none unreachable" is not the same value as "did not say".
37
49
  links: z.object({ total: z.number(), unreachable: z.number(), unreachableSince: z.number().optional() }).optional(),
50
+ // Which of the optional ops this agent AND the `ic` it drives implement (DeviceFeatureSchema), for a page to offer
51
+ // only what works and for the daemon to refuse an op before sending it. Strings on the wire, so a newer agent's
52
+ // feature never fails an older daemon's read; `deviceFeatures` keeps the ones this build knows. Nothing is
53
+ // detected by a field's presence any more: the device RPC inputs are strict, and an agent rejects a field it does
54
+ // not know rather than dropping it.
55
+ features: z.array(z.string()).optional(),
38
56
  });
39
57
  export type DeviceFacts = z.infer<typeof DeviceFactsSchema>;
40
58
 
59
+ // The optional device ops, by what enables them:
60
+ // - `reshape-later`: the old `reshape` op with `later`, saving the change for the next restart. An agent from before it
61
+ // strips `later` and reshapes NOW, which is why a later-reshape is never sent to one.
62
+ // - `set-shape`: the `set-shape`/`forget-shape` ops, and `start`/`restart` applying a saved shape, all through an `ic`
63
+ // whose `ic sandbox shape` takes the contract's own shape (`--set`); advertised only when the agent's `ic` does.
64
+ // Both features ride that verb, so an agent advertises both or neither.
65
+ export const DeviceFeatureSchema = z.enum(["reshape-later", "set-shape"]);
66
+ export type DeviceFeature = z.infer<typeof DeviceFeatureSchema>;
67
+ export const DEVICE_FEATURE_RESHAPE_LATER: DeviceFeature = "reshape-later";
68
+ export const DEVICE_FEATURE_SET_SHAPE: DeviceFeature = "set-shape";
69
+ // The features an agent advertised that this build knows; an unknown one is a newer agent's and means nothing here.
70
+ export const deviceFeatures = (facts: Pick<DeviceFacts, "features"> | undefined): DeviceFeature[] =>
71
+ (facts?.features ?? []).flatMap((feature) => {
72
+ const known = DeviceFeatureSchema.safeParse(feature);
73
+ return known.success ? [known.data] : [];
74
+ });
75
+ export const deviceSupports = (facts: Pick<DeviceFacts, "features"> | undefined, feature: DeviceFeature): boolean => deviceFeatures(facts).includes(feature);
76
+
41
77
  // Docker Desktop's own distros: `wsl -l -q` lists them like any other, and none ever runs an agent or holds a checkout.
42
78
  export const WSL_SYSTEM_DISTROS: ReadonlySet<string> = new Set(["docker-desktop", "docker-desktop-data"]);
43
79
 
@@ -56,11 +92,7 @@ export const windowsPathOf = (distro: string, linuxPath: string): string => `\\\
56
92
  // ONE CARD IS ONE COMPUTER, AND EACH OS INSTALL ON IT IS A CONNECTION OF THAT CARD. A PC's Windows side and every WSL
57
93
  // distro on it hold their own agent — mutagen has to watch the filesystem it syncs, and a distro's login shell is its
58
94
  // own — but the owner connected a computer, not a shell, so the grant, the row and the tools are the machine's.
59
- // The native environment's connection key IS the card id, by definition rather than as a fallback: a machine with one
60
- // OS install has one connection named after itself, and a sibling hangs off it as `<card>::wsl:<distro>`. `::` cannot
61
- // collide with the single colon in an environment key.
62
95
  export const HOST_NATIVE_ENVIRONMENT = "native";
63
- const ENVIRONMENT_SEPARATOR = "::";
64
96
 
65
97
  // Which environment a machine is describing when it connects: the distro by the name WSL registered (what `wsl -l -q`
66
98
  // prints and `in: "wsl:<name>"` takes), or the metal. Facts arrive at connect and no scope withholds them, so this is
@@ -68,14 +100,27 @@ const ENVIRONMENT_SEPARATOR = "::";
68
100
  export const environmentKeyOf = (facts: Pick<DeviceFacts, "wsl">): string =>
69
101
  facts.wsl === undefined ? HOST_NATIVE_ENVIRONMENT : `wsl:${facts.wsl.distro}`;
70
102
 
71
- export const hostConnectionKey = (entry: string, environment: string): string =>
72
- environment === HOST_NATIVE_ENVIRONMENT ? entry : `${entry}${ENVIRONMENT_SEPARATOR}${environment}`;
103
+ // A CONNECTION'S KEY, spelled and read in exactly one place. The key is what the hub, a device RPC and a page's row
104
+ // address a connection by: the native environment's key IS the card id, and a sibling hangs off it as
105
+ // `<card>::wsl:<distro>` (`::` cannot collide with the single colon in an environment key). What a connection IS —
106
+ // its card, its environment, its computer — is the enrollment's own record (a host enrollment says all three, and a
107
+ // device row carries `card`), so the parser is only for a key with nothing beside it: the key the owner asks to pair,
108
+ // an enrollment written before records said, a row from a daemon older than `card`. A rename relabels the record and
109
+ // the key follows from the label; the live connection is held under the new key without being cut (PeerHub.rekey).
110
+ export interface HostConnection {
111
+ readonly card: string;
112
+ readonly environment: string;
113
+ }
114
+ const ENVIRONMENT_SEPARATOR = "::";
73
115
 
74
- export const hostEntryOf = (connection: string): string => connection.split(ENVIRONMENT_SEPARATOR)[0] ?? connection;
116
+ export const hostConnectionKey = (card: string, environment: string): string =>
117
+ environment === HOST_NATIVE_ENVIRONMENT ? card : `${card}${ENVIRONMENT_SEPARATOR}${environment}`;
75
118
 
76
- export const hostEnvironmentOf = (connection: string): string => {
77
- const at = connection.indexOf(ENVIRONMENT_SEPARATOR);
78
- return at === -1 ? HOST_NATIVE_ENVIRONMENT : connection.slice(at + ENVIRONMENT_SEPARATOR.length);
119
+ export const parseHostConnection = (key: string): HostConnection => {
120
+ const at = key.indexOf(ENVIRONMENT_SEPARATOR);
121
+ return at === -1
122
+ ? { card: key, environment: HOST_NATIVE_ENVIRONMENT }
123
+ : { card: key.slice(0, at), environment: key.slice(at + ENVIRONMENT_SEPARATOR.length) };
79
124
  };
80
125
 
81
126
  // One OS install of a machine, as its card knows it. `key` is the environment (`native`, `wsl:archlinux`); everything
@@ -83,6 +128,9 @@ export const hostEnvironmentOf = (connection: string): string => {
83
128
  // version — one side can be asleep, or running a build behind.
84
129
  export const HostEnvironmentSchema = z.object({
85
130
  key: z.string().min(1),
131
+ // The computer this environment's enrollment is on, once its agent has said (never a derived id): what joins it to
132
+ // a sync enrollment and to the other environments of that computer, whether or not it is connected right now.
133
+ machineId: MachineIdSchema.optional(),
86
134
  online: z.boolean(),
87
135
  version: z.string().optional(),
88
136
  lastSeen: z.number().optional(),
@@ -91,8 +139,11 @@ export const HostEnvironmentSchema = z.object({
91
139
  export type HostEnvironment = z.infer<typeof HostEnvironmentSchema>;
92
140
 
93
141
  export const HostSummarySchema = z.object({
94
- // The capability id, the machine's name, and the prefix of its tools (mcp__<id>__run_command).
142
+ // The capability id, the machine's name, and the prefix of its tools (mcp__<id>__run_command). On a per-connection
143
+ // row (`hostConnections`) it is that connection's key instead, and `card` names the machine.
95
144
  id: z.string(),
145
+ // The card a per-connection row is a connection of: a label on the row, read rather than parsed out of `id`.
146
+ card: z.string().optional(),
96
147
  platform: z.string().min(1),
97
148
  // Every environment of this machine, native first: what the page draws a row per, and what a command picks from.
98
149
  // Never empty — a card that has never connected still has its native environment, offline.
@@ -0,0 +1,64 @@
1
+ import { DeviceSandboxFlowSchema, icForgetShapeArgs, icPowerArgs, icShapeArgs, SandboxShapeSchema } from "./devices.js";
2
+
3
+ // The one spelling of ic's shape and power verbs outside ic: the machine agent runs these and the Devices view prints them for a
4
+ // person to type, so a drift here is a command that fails on the machine, or one that does something else there.
5
+
6
+ test("a whole shape is one `--set` per field, in the contract's own names and values", () => {
7
+ expect(icShapeArgs("work", { memoryGib: 20, cpus: null, privileged: false, gpu: true }, "nextRestart")).toEqual([
8
+ "sandbox",
9
+ "shape",
10
+ "work",
11
+ "--set",
12
+ "memoryGib=20",
13
+ "--set",
14
+ "cpus=null",
15
+ "--set",
16
+ "privileged=false",
17
+ "--set",
18
+ "gpu=true",
19
+ "--when",
20
+ "nextRestart",
21
+ ]);
22
+ expect(icShapeArgs("work", { memoryGib: null, cpus: 4, privileged: true, gpu: false }, "now").slice(3)).toEqual([
23
+ "--set",
24
+ "memoryGib=null",
25
+ "--set",
26
+ "cpus=4",
27
+ "--set",
28
+ "privileged=true",
29
+ "--set",
30
+ "gpu=false",
31
+ "--when",
32
+ "now",
33
+ ]);
34
+ });
35
+
36
+ test("a field left out is left to ic, which keeps what it has for it", () => {
37
+ expect(icShapeArgs("work", { memoryGib: 12, gpu: undefined }, "now")).toEqual(["sandbox", "shape", "work", "--set", "memoryGib=12", "--when", "now"]);
38
+ });
39
+
40
+ test("forgetting and power name only the sandbox", () => {
41
+ expect(icForgetShapeArgs("work")).toEqual(["sandbox", "shape", "work", "--forget"]);
42
+ expect(icPowerArgs("restart", "work")).toEqual(["sandbox", "restart", "work"]);
43
+ });
44
+
45
+ // A shape is whole: a delta would put the merge back into every reader, which is what this schema exists to end.
46
+ test("a shape missing a field is not a shape", () => {
47
+ expect(SandboxShapeSchema.safeParse({ memoryGib: 20, cpus: null, privileged: false }).success).toBe(false);
48
+ expect(SandboxShapeSchema.safeParse({ memoryGib: 0, cpus: null, privileged: false, gpu: false }).success).toBe(false);
49
+ expect(SandboxShapeSchema.safeParse({ memoryGib: null, cpus: null, privileged: false, gpu: false }).success).toBe(true);
50
+ });
51
+
52
+ // Every device input is strict, the shape nested in one included: an agent that dropped a field it did not know would
53
+ // carry out a different order than the one sent.
54
+ test("a device order refuses a shape or delta field it does not know", () => {
55
+ const whole = { memoryGib: 20, cpus: null, privileged: false, gpu: false };
56
+ // As the order arrives off the wire, where the nested object is only ever JSON.
57
+ const accepted = (fields: object): boolean =>
58
+ DeviceSandboxFlowSchema.safeParse(JSON.parse(`{"op":"set-shape","slug":"work","when":"now","shape":${JSON.stringify(fields)}}`)).success;
59
+ expect(accepted(whole)).toBe(true);
60
+ expect(accepted({ ...whole, swapGib: 4 })).toBe(false);
61
+ expect(DeviceSandboxFlowSchema.safeParse({ op: "reshape", slug: "work", resources: { memoryGib: 20, swapGib: 4 } }).success).toBe(false);
62
+ // A report is read leniently: a newer ic's extra field must not fail an older reader.
63
+ expect(SandboxShapeSchema.safeParse({ ...whole, swapGib: 4 }).success).toBe(true);
64
+ });
@@ -5,7 +5,6 @@ import {
5
5
  keepWarmDueAt,
6
6
  keepWarmHorizon,
7
7
  keepWarmLeadMs,
8
- keepWarmMaxRefreshes,
9
8
  keepWarmRefreshes,
10
9
  keptWarmLine,
11
10
  } from "./keep-warm.js";
@@ -13,20 +12,13 @@ import {
13
12
  const MINUTE = 60_000;
14
13
  const HOUR = 60 * MINUTE;
15
14
 
16
- // The arithmetic both the daemon and every press read, so the price shown before a press is the price the daemon keeps.
15
+ // The clock both the daemon and every press read, so the refreshes shown before a press are the ones the daemon sends.
17
16
  describe("keep-warm economics", () => {
18
17
  test("refreshes a fifth of an entry's life early, never under a minute or over ten", () => {
19
18
  expect(keepWarmLeadMs(HOUR)).toBe(10 * MINUTE);
20
19
  expect(keepWarmLeadMs(5 * MINUTE)).toBe(MINUTE);
21
20
  });
22
21
 
23
- test("spends at most half of what a cold resume would rewrite", () => {
24
- // A 1h rewrite costs 2x base input and a read 0.1x: nineteen reads break even, so nine.
25
- expect(keepWarmMaxRefreshes(HOUR)).toBe(9);
26
- // A 5m rewrite costs 1.25x: eleven reads break even, so five.
27
- expect(keepWarmMaxRefreshes(5 * MINUTE)).toBe(5);
28
- });
29
-
30
22
  test("counts the refreshes that keep an entry alive through the deadline", () => {
31
23
  const cache = { at: 0, ttlMs: HOUR };
32
24
  expect(keepWarmRefreshes(cache, HOUR)).toBe(0);
@@ -34,11 +26,12 @@ describe("keep-warm economics", () => {
34
26
  expect(keepWarmDueAt(cache)).toBe(50 * MINUTE);
35
27
  });
36
28
 
37
- test("reaches no further than the horizon, or the date change when that comes first", () => {
29
+ test("reaches no further than the refreshes left carry it, or the date change when that comes first", () => {
38
30
  const cache = { at: 0, ttlMs: HOUR };
39
- expect(keepWarmHorizon(cache)).toBe(HOUR + 9 * 50 * MINUTE);
40
- expect(keepWarmCap(cache, 3 * HOUR)).toBe(3 * HOUR);
41
- expect(keepWarmCap({ at: 0, ttlMs: 5 * MINUTE }, undefined)).toBe(5 * MINUTE + 5 * 4 * MINUTE);
31
+ expect(keepWarmHorizon(cache, 9)).toBe(HOUR + 9 * 50 * MINUTE);
32
+ expect(keepWarmHorizon(cache, -2)).toBe(HOUR);
33
+ expect(keepWarmCap(cache, 3 * HOUR, 9)).toBe(3 * HOUR);
34
+ expect(keepWarmCap({ at: 0, ttlMs: 5 * MINUTE }, undefined, 5)).toBe(5 * MINUTE + 5 * 4 * MINUTE);
42
35
  });
43
36
 
44
37
  test("names the parts two fingerprints disagree on", () => {
@@ -1,13 +1,8 @@
1
- // keep-warm: holding an idle conversation's prompt cache open by re-reading it shortly before it expires.
1
+ // keep-warm: holding an idle conversation's prompt cache open by re-reading it shortly before it expires. Only the
2
+ // clock lives here; how many refreshes a hold may spend is priced by the provider that serves it (runtimes/claude).
2
3
 
3
4
  import { z } from "zod";
4
5
 
5
- const HOUR_MS = 3_600_000;
6
-
7
- // Anthropic's price multipliers on base input: a read costs 0.1x, a write 2x under the 1h TTL and 1.25x under 5m.
8
- const READ_COST = 0.1;
9
- const writeCost = (ttlMs: number): number => (ttlMs >= HOUR_MS ? 2 : 1.25);
10
-
11
6
  // A cache clock as the daemon publishes it: the last request that touched the entry, and the entry's lifetime.
12
7
  export interface CacheClock {
13
8
  readonly at: number;
@@ -17,28 +12,26 @@ export interface CacheClock {
17
12
  /** How long before expiry a refresh starts: a fifth of the entry's life, never under a minute or over ten. */
18
13
  export const keepWarmLeadMs = (ttlMs: number): number => Math.min(10 * 60_000, Math.max(60_000, Math.round(ttlMs / 5)));
19
14
 
20
- /** Refreshes one hold may spend: half of what a cold resume rewrites, the other half left for each refresh's own tail. */
21
- export const keepWarmMaxRefreshes = (ttlMs: number): number => Math.floor((writeCost(ttlMs) - READ_COST) / READ_COST / 2);
22
-
23
15
  /** How many refreshes keep an entry alive through `until`. */
24
16
  export const keepWarmRefreshes = (cache: CacheClock, until: number): number => {
25
17
  const beyond = until - (cache.at + cache.ttlMs);
26
18
  return beyond <= 0 ? 0 : Math.ceil(beyond / (cache.ttlMs - keepWarmLeadMs(cache.ttlMs)));
27
19
  };
28
20
 
29
- /** The latest instant a hold may reach, given the refreshes it already spent: past it, refreshing costs more than the cold resume it saves. */
30
- export const keepWarmHorizon = (cache: CacheClock, spent = 0): number =>
31
- cache.at + cache.ttlMs + Math.max(0, keepWarmMaxRefreshes(cache.ttlMs) - spent) * (cache.ttlMs - keepWarmLeadMs(cache.ttlMs));
21
+ /** The latest instant `left` more refreshes can carry an entry to: past it, refreshing costs more than the cold resume it saves. */
22
+ export const keepWarmHorizon = (cache: CacheClock, left: number): number =>
23
+ cache.at + cache.ttlMs + Math.max(0, left) * (cache.ttlMs - keepWarmLeadMs(cache.ttlMs));
32
24
 
33
25
  /** When the next refresh is due for an entry last touched at `cache.at`. */
34
26
  export const keepWarmDueAt = (cache: CacheClock): number => cache.at + cache.ttlMs - keepWarmLeadMs(cache.ttlMs);
35
27
 
36
28
  /** The furthest `until` a hold can honestly promise: the economic horizon, or the date change, whichever comes first. */
37
- export const keepWarmCap = (cache: CacheClock, rollsAt: number | undefined, spent = 0): number =>
38
- rollsAt === undefined ? keepWarmHorizon(cache, spent) : Math.min(keepWarmHorizon(cache, spent), rollsAt);
29
+ export const keepWarmCap = (cache: CacheClock, rollsAt: number | undefined, left: number): number =>
30
+ rollsAt === undefined ? keepWarmHorizon(cache, left) : Math.min(keepWarmHorizon(cache, left), rollsAt);
39
31
 
40
- // Why a hold ended before anyone picked the conversation up. `elapsed` is the one ending that is not a problem.
41
- export const KeepWarmEndSchema = z.enum(["elapsed", "allowance", "limited", "changed", "rewrote", "cold", "failed", "midnight", "moved"]);
32
+ // Why a hold ended before anyone picked the conversation up, one per thing a person would do about it; `detail` carries
33
+ // the specifics. `elapsed` is the one ending that is not a problem.
34
+ export const KeepWarmEndSchema = z.enum(["elapsed", "allowance", "changed", "cold", "failed"]);
42
35
  export type KeepWarmEnd = z.infer<typeof KeepWarmEndSchema>;
43
36
 
44
37
  export const KeepWarmSchema = z.object({
@@ -48,7 +41,6 @@ export const KeepWarmSchema = z.object({
48
41
  .describe(
49
42
  "When it stops by itself, in milliseconds: the time asked for, shortened to what the sandbox can honestly keep, which is never past the point where refreshing costs more than re-reading, nor past the date change that rewrites the prompt.",
50
43
  ),
51
- auto: z.boolean().optional().describe("Started by the sandbox-wide setting after a turn, rather than by a press on this conversation."),
52
44
  refreshes: z.number().int().min(0).describe("Refreshes sent so far."),
53
45
  readTokens: z
54
46
  .number()
@@ -58,9 +50,12 @@ export const KeepWarmSchema = z.object({
58
50
  .object({
59
51
  at: z.number().describe("When it stopped, in milliseconds."),
60
52
  reason: KeepWarmEndSchema.describe(
61
- "Why: `elapsed` the time asked for ran out; `allowance` the account reached the reserve kept for real work; `limited` the provider refused a refresh; `changed` what the next turn would send no longer matches the cache; `rewrote` a refresh found the cache already gone; `cold` it expired before a refresh could run; `failed` a refresh failed; `midnight` the date in the prompt changed; `moved` the conversation's session or account changed.",
53
+ "Why: `elapsed` the time asked for ran out; `allowance` the account reached the reserve kept for real work, or the provider refused a refresh for its limit; `changed` what the next turn would send no longer matches the cache (a part of the prompt, the date in it, or the conversation's session or account); `cold` the cache was gone, expired before a refresh could run or found missing by one; `failed` a refresh failed.",
62
54
  ),
63
- detail: z.string().optional().describe("The specifics, when there are any: which parts of the prompt changed, or the failure's own words."),
55
+ detail: z
56
+ .string()
57
+ .optional()
58
+ .describe("The specifics, when there are any: which parts of the prompt changed, how full the account was, or the failure's own words."),
64
59
  })
65
60
  .optional()
66
61
  .describe("Why keeping it warm stopped before anyone picked the conversation up. Absent while it is still being kept."),
@@ -53,15 +53,38 @@ export const PressureMetricsSchema = z.object({
53
53
  });
54
54
  export type PressureMetrics = z.infer<typeof PressureMetricsSchema>;
55
55
 
56
+ // The memory figures the daemon admits work by (its resource budget), so a gauge built on them warns exactly when a turn
57
+ // would be held.
58
+ export const MemoryRoomSchema = z.object({
59
+ freeBytes: z
60
+ .number()
61
+ .optional()
62
+ .describe("The limit less what is used and what work admitted in the last minute and a half still holds. Absent where nothing measures it."),
63
+ reservedBytes: z.number().describe("What work admitted in the last minute and a half holds before it shows in `memoryBytes`."),
64
+ personNeedBytes: z.number().describe("What a person's turn needs free to start without a warning: below it, the sandbox is short."),
65
+ stallPercent: z.number().describe("Percent of the last ten seconds in which everything in the sandbox waited on memory (pressure `full`)."),
66
+ stallLimitPercent: z.number().describe("The stall at or past which the sandbox counts as short of memory, whatever `freeBytes` says."),
67
+ });
68
+ export type MemoryRoom = z.infer<typeof MemoryRoomSchema>;
69
+
56
70
  export const SandboxUsageSchema = z.object({
57
71
  cpuPercent: z
58
72
  .number()
59
73
  .optional()
60
74
  .describe("CPU the whole sandbox used over the window, as a percentage of all it may use (`cores`). Absent on a first reading."),
61
75
  cores: z.number().describe("How many cores the sandbox may use: its CPU quota, or every core it is allowed to run on when it has none."),
62
- memoryBytes: z.number().describe("Memory in use, less the file cache the kernel takes back on demand: the figure that runs into the limit."),
63
- memoryLimitBytes: z.number().describe("The memory limit: the container's own, or the machine's memory when the container has none."),
64
- swapBytes: z.number().optional().describe("Memory pushed out to swap. Absent where the sandbox cannot see its own swap."),
76
+ memoryBytes: z
77
+ .number()
78
+ .describe(
79
+ "Memory in use as the daemon admits work by it: resident memory less the file cache the kernel takes back on demand, plus what was pushed to swap.",
80
+ ),
81
+ memoryLimitBytes: z
82
+ .number()
83
+ .describe(
84
+ "The memory limit work is admitted against: where the kernel starts throttling the container (memory.high), else its hard limit, else the machine's memory.",
85
+ ),
86
+ swapBytes: z.number().optional().describe("Of `memoryBytes`, what was pushed out to swap. Absent where the sandbox cannot see its own memory."),
87
+ memoryRoom: MemoryRoomSchema.optional().describe("What admission reads off the same reading. Absent from a daemon that predates it."),
65
88
  diskBytes: z.number().optional().describe("Space used on the volume the workspace lives on. Absent when the volume would not say."),
66
89
  diskTotalBytes: z.number().optional().describe("That volume's size. Absent when the volume would not say."),
67
90
  loadAverage: z
@@ -140,6 +163,8 @@ export const STORAGE_CLEANABILITY = {
140
163
  other: "none",
141
164
  // Git data and checkouts of repositories deleted from the workspace; nothing reads it again, but it may hold commits never pushed.
142
165
  trash: "confirm",
166
+ // Transcripts as they were before the sandbox converted them to its current format; nothing reads them again.
167
+ backups: "confirm",
143
168
  // Copies of the sandbox packed for download.
144
169
  exports: "confirm",
145
170
  // Files agents produced: generated images, reports, harness output.
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ import { RunnerCommandFrameSchema, RunnerCommandSchema } from "../protocol/runner-protocol.js";
3
+
4
+ // OFFLOADED WORK, as the in-sandbox `offload-run` command and the editor see it (settings `offload`): a heavy line this
5
+ // sandbox hands a runner on one of the owner's machines instead of running it itself.
6
+
7
+ // Whether a runner can take a line right now, asked before the tree is snapshotted, so a machine that is asleep costs
8
+ // nothing. `name` is the machine the runner sits on, the word the owner picked it by.
9
+ export const OffloadTargetSchema = z.object({
10
+ runner: z.string().min(1),
11
+ name: z.string().min(1),
12
+ ready: z.boolean(),
13
+ // Why not, in the words the command prints before it runs the line here instead.
14
+ why: z.string().optional(),
15
+ });
16
+ export type OffloadTarget = z.infer<typeof OffloadTargetSchema>;
17
+
18
+ export const OffloadRunSchema = RunnerCommandSchema.extend({ runner: z.string().min(1) });
19
+ export type OffloadRun = z.infer<typeof OffloadRunSchema>;
20
+
21
+ // What a run streams back: the runner's own frames, or one refusal when the runner could not take it after all (it went
22
+ // offline between the question and the run, or it predates offloading), which sends the line back here.
23
+ export const OffloadFrameSchema = z.union([...RunnerCommandFrameSchema.options, z.object({ kind: z.literal("refused"), why: z.string() })]);
24
+ export type OffloadFrame = z.infer<typeof OffloadFrameSchema>;
25
+
26
+ // One offloaded run as the runner card lists it: the recent ones, kept in memory, newest first.
27
+ export const OffloadRecordSchema = z.object({
28
+ runId: z.string(),
29
+ runner: z.string(),
30
+ name: z.string(),
31
+ label: z.string(),
32
+ // The line, cut to a readable length.
33
+ command: z.string(),
34
+ startedAt: z.number(),
35
+ endedAt: z.number().optional(),
36
+ code: z.number().int().optional(),
37
+ failure: z.string().optional(),
38
+ });
39
+ export type OffloadRecord = z.infer<typeof OffloadRecordSchema>;
40
+
41
+ // One kind of heavy work the owner can send elsewhere: a heavy-command rule this sandbox queues (its id, as settings
42
+ // `offload.commands` keys it) and the pattern that sorts a line into it.
43
+ export const OffloadKindSchema = z.object({ id: z.string().min(1), pattern: z.string() });
44
+ export type OffloadKind = z.infer<typeof OffloadKindSchema>;
@@ -42,6 +42,21 @@ export const PersonaPowersSchema = z.object({
42
42
  connectors: z.array(entryId).max(100).optional(),
43
43
  devices: z.array(entryId).max(50).optional(),
44
44
  mcp: z.array(entryId).max(50).optional(),
45
+ // By extension id (`publisher.name`, or a git install's card id), the same tri-state. A card an extension serves
46
+ // rides `connectors`; this is what the extension brings of its own.
47
+ extensions: z
48
+ .array(
49
+ z
50
+ .string()
51
+ .min(1)
52
+ .max(121)
53
+ .regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/),
54
+ )
55
+ .max(100)
56
+ .optional()
57
+ .describe(
58
+ "Which extensions' own agent tools and agent plugin (skills, commands, subagents) it gets, by extension id. Absent means every enabled one; empty means none. The tools an extension serves for a connected card follow the connectors list instead.",
59
+ ),
45
60
  });
46
61
  export type PersonaPowers = z.infer<typeof PersonaPowersSchema>;
47
62
  // `folders` only refuses file-tool calls outside it; it stops a misread instruction, not a shell. The container is the
@@ -125,9 +140,9 @@ export const TURN_BRIEFING_NOTES: readonly TurnBriefingNote[] = [
125
140
  },
126
141
  {
127
142
  id: "checks",
128
- label: "Automatic end-of-turn checks",
129
- when: "First message, and again after a compaction, when you have rules that run at the end of a turn.",
130
- cost: "Your checks still run; the agent just does not know they are coming, so a failure arrives as a surprise it has to re-read the turn to understand.",
143
+ label: "Checks after landing",
144
+ when: "First message and after a compaction, and again whenever the main tree's own check turns red or back to green: what runs after its work lands, and which failures the main tree already has.",
145
+ cost: "The agent does not know which failures are the main tree's own and not its doing, so it may spend a turn chasing one somebody else is already fixing.",
131
146
  },
132
147
  {
133
148
  id: "dependencies",
@@ -45,6 +45,16 @@ export const PortSummarySchema = z.object({
45
45
  .describe(
46
46
  'The terminal it came from, to watch it in or stop it from. Absent when nothing in its ancestry is one, which is the honest "you cannot reach this from here".',
47
47
  ),
48
+ // A job an agent's turn left running for the person (AgentSummary.jobs[].handed); ports listed anywhere else stay
49
+ // unclaimed by any conversation.
50
+ job: z
51
+ .object({
52
+ conversationId: z.string().describe("The conversation whose turn left it running."),
53
+ jobId: z.string().describe("The job, as that conversation's card names it; stopping it stops this port."),
54
+ label: z.string().describe("What the job is, in the agent's own words when it gave any."),
55
+ })
56
+ .optional()
57
+ .describe("The background job an agent left running for you on this port, when that is what answers here."),
48
58
  forwarded: z.boolean().describe("Whether it is currently reachable from outside."),
49
59
  // `https://port-<slot>-<sandboxId>.<zone>`.
50
60
  previewUrl: z.string().optional().describe("Where to open it. Present only while forwarded, and only on a sandbox that has an outside address."),
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { ChildRunSchema } from "../../events/requests.js";
2
3
  import { TRANSLATOR_PROVIDERS, type TranslatorProvider } from "../../models/provider-specs.js";
3
4
  import { AgentHarnessSchema, AgentProviderSchema, EditorContextSchema } from "../agent.js";
4
5
  import { MENTION_LIMIT } from "../../text/mentions.js";
@@ -24,13 +25,58 @@ export const UsageWindowSchema = z.object({
24
25
  gates: WindowGatesSchema,
25
26
  });
26
27
  export type UsageWindow = z.infer<typeof UsageWindowSchema>;
28
+ // A failed re-read, whatever the cause: a 429 park, a 403 asking for verification, a 5xx, a timeout. Recorded as the fact
29
+ // "the last attempt failed", never inferred from a list of known failure kinds, so a surface that dates readings by
30
+ // age can leave out every reading that cannot move, including one whose failure nobody has seen before.
31
+ export const UsageUnreadSchema = z.object({
32
+ since: z.number().describe("When re-reading this account first failed, in milliseconds. It has failed on every attempt since."),
33
+ reason: z
34
+ .string()
35
+ .describe("Why, in the provider's own words where it gave some (\"Verify your account to continue.\"). Short enough to print; never a pasted response body."),
36
+ });
37
+ export type UsageUnread = z.infer<typeof UsageUnreadSchema>;
27
38
  // Every window kept, not just the binding one, since which pool binds changes between turns. Utilization only climbs
28
39
  // within a window, so a stale reading is still a valid floor. `measuredAt` is epoch ms — windows are seconds.
29
40
  export const AccountUsageSchema = z.object({
30
41
  windows: z.array(UsageWindowSchema),
31
42
  measuredAt: z.number(),
43
+ unread: UsageUnreadSchema.optional().describe(
44
+ "Present while re-reading this account keeps failing: these windows are the last reading that succeeded, and `measuredAt` will not move until a read succeeds again.",
45
+ ),
32
46
  });
33
47
  export type AccountUsage = z.infer<typeof AccountUsageSchema>;
48
+ // Who has to act for a blocked account, which is the only thing a surface chooses its instruction by: the reason is the
49
+ // words for a person, never a key.
50
+ export const AccountFixSchema = z.enum(["reconnect", "admin", "wait"]);
51
+ export type AccountFix = z.infer<typeof AccountFixSchema>;
52
+ // Whether an account can serve a turn, decided once by the daemon (models/plan-pools.ts `serviceState`) from every fact
53
+ // it holds about it: a revoked sign-in, a lost seat, a translator bench, a refusal still standing, and its plan limits.
54
+ // Every picker (an unnamed turn, a limit move, a keep-warm refresh) and every surface reads this one verdict.
55
+ export const AccountStateSchema = z.discriminatedUnion("kind", [
56
+ z.object({
57
+ kind: z.literal("ready"),
58
+ room: z
59
+ .number()
60
+ .describe("How much of the fullest pool that gates the turn is left, in percent (above 0, up to 100). Pickers take the most room."),
61
+ }),
62
+ z.object({
63
+ kind: z.literal("spent"),
64
+ reopensAt: z
65
+ .number()
66
+ .optional()
67
+ .describe("When every full pool has reopened, in epoch seconds, where the plan publishes it. Absent means unknown, never now."),
68
+ }),
69
+ z.object({
70
+ kind: z.literal("blocked"),
71
+ fix: AccountFixSchema.describe(
72
+ "Who can make it serve again: `reconnect` (sign in again on this sandbox), `admin` (an organisation admin hands the seat back), or `wait` (it lifts by itself, at `until` where known).",
73
+ ),
74
+ reason: z.string().describe("Why, in words a person can act on: the provider's own sentence where it gave one."),
75
+ until: z.number().optional().describe("When waiting lifts it, in epoch seconds, for `wait` only."),
76
+ }),
77
+ z.object({ kind: z.literal("unknown").describe("Nothing blocks it and nothing has been measured: usable, never read as room.") }),
78
+ ]);
79
+ export type AccountState = z.infer<typeof AccountStateSchema>;
34
80
  // Anthropic's once-a-week reset of the session window only; the weekly allowance is untouched. The answer is entirely
35
81
  // the provider's — never infer `available` from a 100% window. `reason` is its own word, carried verbatim.
36
82
  export const LimitResetStatusSchema = z.object({
@@ -107,6 +153,9 @@ export const TranslatorAccountSchema = z.object({
107
153
  reason: z.string().optional(),
108
154
  })
109
155
  .optional(),
156
+ state: AccountStateSchema.optional().describe(
157
+ "Whether it can serve a turn now, judged from everything above plus the provider's last refusal. Absent from a daemon older than this field.",
158
+ ),
110
159
  });
111
160
  export type TranslatorAccount = z.infer<typeof TranslatorAccountSchema>;
112
161
  // A list per provider, not a flag: CLIProxyAPI holds several auth files per provider and balances across them. Built
@@ -154,6 +203,10 @@ export const AgentReplySchema = z.discriminatedUnion("kind", [
154
203
  .enum(["once", "always", "deny"])
155
204
  .describe("Once allows this call alone; always allows that whole tool for the rest of the conversation; no blocks it."),
156
205
  feedback: z.string().optional().describe("Why not, which goes back to the model as the reason."),
206
+ // Whole, not a patch: effort or an account named for one model means nothing on another.
207
+ child: ChildRunSchema.optional().describe(
208
+ "For a request to start a child agent: what to start it on instead of what the agent asked for. It replaces the whole run (model, account, effort and the rest), not only the fields it names. Ignored with a no, and on any other request.",
209
+ ),
157
210
  }),
158
211
  z.object({
159
212
  kind: z
@@ -208,6 +261,11 @@ export const AgentReplySchema = z.discriminatedUnion("kind", [
208
261
  }),
209
262
  ]);
210
263
  export type AgentReply = z.infer<typeof AgentReplySchema>;
264
+ // Every card a turn can park on its person with, one per reply kind, so a new card reaches every park reader at once.
265
+ export type ParkKind = AgentReply["kind"];
266
+ export const PARK_KINDS: readonly ParkKind[] = AgentReplySchema.options.map((option) => option.shape.kind.value);
267
+ const PARKING = new Set<string>(PARK_KINDS);
268
+ export const isParkKind = (kind: string): kind is ParkKind => PARKING.has(kind);
211
269
  // A message injected into a running turn between tool calls; NOT_FOUND when none is steerable, and the client queues it
212
270
  // as the next turn instead. Carries everything a fresh prompt can (files, editor context).
213
271
  export const SteerSchema = z
@@ -282,7 +340,12 @@ export type StopResult = z.infer<typeof StopResultSchema>;
282
340
  export const ResumeRoutingSchema = z.object({
283
341
  agent: AgentProviderSchema.describe("Which provider serves the re-run."),
284
342
  harness: AgentHarnessSchema.describe("Which agentic loop runs it."),
285
- account: z.string().optional().describe("Which of that provider's accounts pays for it. Leave it out for the first one."),
343
+ account: z
344
+ .string()
345
+ .optional()
346
+ .describe(
347
+ "Which of that provider's accounts pays for it. Leave it out to keep the account the conversation runs on, or, on another provider, to take whichever of its accounts can serve with the most room. Moving to another account of the same provider is `switchAccount`'s job; naming one here still works.",
348
+ ),
286
349
  // A client whose catalog hasn't loaded has nothing to send; the turn's own model is the better fallback.
287
350
  model: z.string().optional().describe("Which model. Leave it out to keep the one the refused turn named."),
288
351
  // A session is a file the daemon keeps; a credential is an env var. That's what lets a same-provider account switch
@@ -326,3 +389,23 @@ export const QueueResumeSchema = z.object({
326
389
  ),
327
390
  });
328
391
  export type QueueResume = z.infer<typeof QueueResumeSchema>;
392
+ // Moves a conversation to another account of the provider it runs on: the one command that changes who pays, so the
393
+ // account a conversation runs on is always the daemon's record and never a window's guess.
394
+ export const SwitchAccountSchema = z.object({
395
+ conversationId: z.string().min(1).describe("Which conversation to move."),
396
+ account: z.string().min(1).describe("Which of the conversation's provider's connected accounts pays for its turns from now on."),
397
+ carry: z
398
+ .boolean()
399
+ .optional()
400
+ .describe(
401
+ "Keep the provider session across the move (the model keeps everything, and re-reads all of it once on the other account) rather than opening a fresh one seeded from the record.",
402
+ ),
403
+ });
404
+ export type SwitchAccount = z.infer<typeof SwitchAccountSchema>;
405
+ export const AccountSwitchedSchema = z.object({
406
+ run: z
407
+ .string()
408
+ .optional()
409
+ .describe("The run that re-ran a held turn on the new account, when one was waiting: attach to it. Absent when nothing was held."),
410
+ });
411
+ export type AccountSwitched = z.infer<typeof AccountSwitchedSchema>;