@intentic/sandbox-contract 1.311.0 → 1.313.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +32 -4
  2. package/dist/chores/chores.js +6 -6
  3. package/dist/chores/chores.js.map +1 -1
  4. package/dist/chores/index.d.ts +0 -1
  5. package/dist/chores/index.d.ts.map +1 -1
  6. package/dist/chores/index.js +0 -1
  7. package/dist/chores/index.js.map +1 -1
  8. package/dist/chores/prompt.d.ts.map +1 -1
  9. package/dist/chores/prompt.js +3 -2
  10. package/dist/chores/prompt.js.map +1 -1
  11. package/dist/contracts/accounts.contract.d.ts +69 -0
  12. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  13. package/dist/contracts/agent.contract.d.ts +436 -0
  14. package/dist/contracts/agent.contract.d.ts.map +1 -1
  15. package/dist/contracts/agent.contract.js +11 -1
  16. package/dist/contracts/agent.contract.js.map +1 -1
  17. package/dist/contracts/agents.contract.d.ts +718 -90
  18. package/dist/contracts/agents.contract.d.ts.map +1 -1
  19. package/dist/contracts/agents.contract.js +30 -2
  20. package/dist/contracts/agents.contract.js.map +1 -1
  21. package/dist/contracts/capabilities.contract.js +1 -1
  22. package/dist/contracts/capabilities.contract.js.map +1 -1
  23. package/dist/contracts/device.contract.d.ts +20 -4
  24. package/dist/contracts/device.contract.d.ts.map +1 -1
  25. package/dist/contracts/device.contract.js +1 -1
  26. package/dist/contracts/device.contract.js.map +1 -1
  27. package/dist/contracts/extensions.contract.d.ts +13 -0
  28. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  29. package/dist/contracts/git.contract.d.ts +2 -0
  30. package/dist/contracts/git.contract.d.ts.map +1 -1
  31. package/dist/contracts/offload.contract.d.ts +107 -0
  32. package/dist/contracts/offload.contract.d.ts.map +1 -0
  33. package/dist/contracts/offload.contract.js +53 -0
  34. package/dist/contracts/offload.contract.js.map +1 -0
  35. package/dist/contracts/personas.contract.d.ts +2 -0
  36. package/dist/contracts/personas.contract.d.ts.map +1 -1
  37. package/dist/contracts/ports.contract.d.ts +5 -0
  38. package/dist/contracts/ports.contract.d.ts.map +1 -1
  39. package/dist/contracts/runner.contract.d.ts +204 -0
  40. package/dist/contracts/runner.contract.d.ts.map +1 -1
  41. package/dist/contracts/runner.contract.js +3 -1
  42. package/dist/contracts/runner.contract.js.map +1 -1
  43. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  44. package/dist/contracts/secrets.contract.js +1 -0
  45. package/dist/contracts/secrets.contract.js.map +1 -1
  46. package/dist/contracts/sessions.contract.d.ts +59 -0
  47. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  48. package/dist/contracts/settings.contract.d.ts +20 -8
  49. package/dist/contracts/settings.contract.d.ts.map +1 -1
  50. package/dist/contracts/settings.contract.js +2 -2
  51. package/dist/contracts/settings.contract.js.map +1 -1
  52. package/dist/contracts/system.contract.d.ts +230 -15
  53. package/dist/contracts/system.contract.d.ts.map +1 -1
  54. package/dist/contracts/translator.contract.d.ts +88 -0
  55. package/dist/contracts/translator.contract.d.ts.map +1 -1
  56. package/dist/contracts/workspace.contract.d.ts +217 -0
  57. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  58. package/dist/contracts/workspace.contract.js +40 -3
  59. package/dist/contracts/workspace.contract.js.map +1 -1
  60. package/dist/documents/conversions.d.ts +81 -0
  61. package/dist/documents/conversions.d.ts.map +1 -0
  62. package/dist/documents/conversions.js +213 -0
  63. package/dist/documents/conversions.js.map +1 -0
  64. package/dist/documents/index.d.ts +4 -0
  65. package/dist/documents/index.d.ts.map +1 -0
  66. package/dist/documents/index.js +4 -0
  67. package/dist/documents/index.js.map +1 -0
  68. package/dist/documents/passthrough.d.ts +13 -0
  69. package/dist/documents/passthrough.d.ts.map +1 -0
  70. package/dist/documents/passthrough.js +101 -0
  71. package/dist/documents/passthrough.js.map +1 -0
  72. package/dist/documents/read.d.ts +24 -0
  73. package/dist/documents/read.d.ts.map +1 -0
  74. package/dist/documents/read.js +81 -0
  75. package/dist/documents/read.js.map +1 -0
  76. package/dist/events/agent-events.d.ts +235 -0
  77. package/dist/events/agent-events.d.ts.map +1 -1
  78. package/dist/events/agent-events.js +3 -0
  79. package/dist/events/agent-events.js.map +1 -1
  80. package/dist/events/child-run.d.ts +9 -0
  81. package/dist/events/child-run.d.ts.map +1 -0
  82. package/dist/events/child-run.js +41 -0
  83. package/dist/events/child-run.js.map +1 -0
  84. package/dist/events/errands.d.ts +5 -0
  85. package/dist/events/errands.d.ts.map +1 -0
  86. package/dist/events/errands.js +19 -0
  87. package/dist/events/errands.js.map +1 -0
  88. package/dist/events/land-breakage.d.ts +3 -0
  89. package/dist/events/land-breakage.d.ts.map +1 -1
  90. package/dist/events/land-breakage.js +3 -0
  91. package/dist/events/land-breakage.js.map +1 -1
  92. package/dist/events/land-conflict.d.ts +3 -0
  93. package/dist/events/land-conflict.d.ts.map +1 -0
  94. package/dist/events/land-conflict.js +4 -0
  95. package/dist/events/land-conflict.js.map +1 -0
  96. package/dist/events/requests.d.ts +152 -0
  97. package/dist/events/requests.d.ts.map +1 -1
  98. package/dist/events/requests.js +26 -1
  99. package/dist/events/requests.js.map +1 -1
  100. package/dist/events/system-events.d.ts +74 -12
  101. package/dist/events/system-events.d.ts.map +1 -1
  102. package/dist/events/system-events.js +3 -1
  103. package/dist/events/system-events.js.map +1 -1
  104. package/dist/events/transcript.d.ts +387 -0
  105. package/dist/events/transcript.d.ts.map +1 -1
  106. package/dist/events/transcript.js +3 -0
  107. package/dist/events/transcript.js.map +1 -1
  108. package/dist/events/verify-nudge.d.ts.map +1 -1
  109. package/dist/events/verify-nudge.js.map +1 -1
  110. package/dist/front/browser-wire.d.ts +6 -0
  111. package/dist/front/browser-wire.d.ts.map +1 -0
  112. package/dist/front/browser-wire.js +4 -0
  113. package/dist/front/browser-wire.js.map +1 -0
  114. package/dist/front/front-wire.d.ts +6 -0
  115. package/dist/front/front-wire.d.ts.map +1 -0
  116. package/dist/front/front-wire.js +5 -0
  117. package/dist/front/front-wire.js.map +1 -0
  118. package/dist/front/generated/browser-wire.d.ts +19 -0
  119. package/dist/front/generated/browser-wire.d.ts.map +1 -0
  120. package/dist/front/generated/browser-wire.js +2 -0
  121. package/dist/front/generated/browser-wire.js.map +1 -0
  122. package/dist/front/generated/wire.d.ts +70 -1
  123. package/dist/front/generated/wire.d.ts.map +1 -1
  124. package/dist/ids/conversation-ids.d.ts +3 -0
  125. package/dist/ids/conversation-ids.d.ts.map +1 -1
  126. package/dist/ids/conversation-ids.js +4 -2
  127. package/dist/ids/conversation-ids.js.map +1 -1
  128. package/dist/ids/session-names.d.ts +3 -0
  129. package/dist/ids/session-names.d.ts.map +1 -1
  130. package/dist/ids/session-names.js +3 -0
  131. package/dist/ids/session-names.js.map +1 -1
  132. package/dist/index.d.ts +2052 -190
  133. package/dist/index.d.ts.map +1 -1
  134. package/dist/index.js +15 -1
  135. package/dist/index.js.map +1 -1
  136. package/dist/models/agent-catalog.d.ts.map +1 -1
  137. package/dist/models/agent-catalog.js +4 -6
  138. package/dist/models/agent-catalog.js.map +1 -1
  139. package/dist/models/agent-runtimes.d.ts +2 -1
  140. package/dist/models/agent-runtimes.d.ts.map +1 -1
  141. package/dist/models/agent-runtimes.js +7 -1
  142. package/dist/models/agent-runtimes.js.map +1 -1
  143. package/dist/models/plan-pools.d.ts +22 -1
  144. package/dist/models/plan-pools.d.ts.map +1 -1
  145. package/dist/models/plan-pools.js +86 -0
  146. package/dist/models/plan-pools.js.map +1 -1
  147. package/dist/policy/request-status.d.ts +1 -1
  148. package/dist/policy/request-status.d.ts.map +1 -1
  149. package/dist/policy/request-status.js +4 -11
  150. package/dist/policy/request-status.js.map +1 -1
  151. package/dist/policy/reserved-servers.d.ts.map +1 -1
  152. package/dist/policy/reserved-servers.js +2 -1
  153. package/dist/policy/reserved-servers.js.map +1 -1
  154. package/dist/policy/turned-away.d.ts.map +1 -1
  155. package/dist/policy/turned-away.js +1 -0
  156. package/dist/policy/turned-away.js.map +1 -1
  157. package/dist/protocol/container-requirements.d.ts +0 -1
  158. package/dist/protocol/container-requirements.d.ts.map +1 -1
  159. package/dist/protocol/container-requirements.js +1 -2
  160. package/dist/protocol/container-requirements.js.map +1 -1
  161. package/dist/protocol/edge-verdict.d.ts +2 -1
  162. package/dist/protocol/edge-verdict.d.ts.map +1 -1
  163. package/dist/protocol/edge-verdict.js +1 -1
  164. package/dist/protocol/edge-verdict.js.map +1 -1
  165. package/dist/protocol/ingress-contract.d.ts +1 -12
  166. package/dist/protocol/ingress-contract.d.ts.map +1 -1
  167. package/dist/protocol/ingress-contract.js +1 -1
  168. package/dist/protocol/ingress-contract.js.map +1 -1
  169. package/dist/protocol/raw-routes.d.ts +36 -26
  170. package/dist/protocol/raw-routes.d.ts.map +1 -1
  171. package/dist/protocol/raw-routes.js +17 -23
  172. package/dist/protocol/raw-routes.js.map +1 -1
  173. package/dist/protocol/route-meta.d.ts +22 -0
  174. package/dist/protocol/route-meta.d.ts.map +1 -1
  175. package/dist/protocol/route-meta.js +16 -0
  176. package/dist/protocol/route-meta.js.map +1 -1
  177. package/dist/protocol/runner-protocol.d.ts +34 -0
  178. package/dist/protocol/runner-protocol.d.ts.map +1 -1
  179. package/dist/protocol/runner-protocol.js +33 -0
  180. package/dist/protocol/runner-protocol.js.map +1 -1
  181. package/dist/protocol/tunnel-bulk.d.ts +2 -0
  182. package/dist/protocol/tunnel-bulk.d.ts.map +1 -0
  183. package/dist/protocol/tunnel-bulk.js +3 -0
  184. package/dist/protocol/tunnel-bulk.js.map +1 -0
  185. package/dist/protocol/x402.d.ts +17 -0
  186. package/dist/protocol/x402.d.ts.map +1 -0
  187. package/dist/protocol/x402.js +37 -0
  188. package/dist/protocol/x402.js.map +1 -0
  189. package/dist/schemas/activity.d.ts.map +1 -1
  190. package/dist/schemas/activity.js.map +1 -1
  191. package/dist/schemas/agent.d.ts +25 -0
  192. package/dist/schemas/agent.d.ts.map +1 -1
  193. package/dist/schemas/agent.js +14 -2
  194. package/dist/schemas/agent.js.map +1 -1
  195. package/dist/schemas/agents.d.ts +90 -18
  196. package/dist/schemas/agents.d.ts.map +1 -1
  197. package/dist/schemas/agents.js +49 -5
  198. package/dist/schemas/agents.js.map +1 -1
  199. package/dist/schemas/automations.d.ts +23 -6
  200. package/dist/schemas/automations.d.ts.map +1 -1
  201. package/dist/schemas/automations.js.map +1 -1
  202. package/dist/schemas/devices.d.ts +134 -7
  203. package/dist/schemas/devices.d.ts.map +1 -1
  204. package/dist/schemas/devices.js +43 -18
  205. package/dist/schemas/devices.js.map +1 -1
  206. package/dist/schemas/extension-updates.d.ts +26 -0
  207. package/dist/schemas/extension-updates.d.ts.map +1 -1
  208. package/dist/schemas/extension-updates.js +4 -0
  209. package/dist/schemas/extension-updates.js.map +1 -1
  210. package/dist/schemas/git/git.d.ts +19 -0
  211. package/dist/schemas/git/git.d.ts.map +1 -1
  212. package/dist/schemas/git/git.js +1 -0
  213. package/dist/schemas/git/git.js.map +1 -1
  214. package/dist/schemas/hosts.d.ts +35 -3
  215. package/dist/schemas/hosts.d.ts.map +1 -1
  216. package/dist/schemas/hosts.js +22 -6
  217. package/dist/schemas/hosts.js.map +1 -1
  218. package/dist/schemas/keep-warm.d.ts +2 -12
  219. package/dist/schemas/keep-warm.d.ts.map +1 -1
  220. package/dist/schemas/keep-warm.js +8 -10
  221. package/dist/schemas/keep-warm.js.map +1 -1
  222. package/dist/schemas/metrics.d.ts +29 -0
  223. package/dist/schemas/metrics.d.ts.map +1 -1
  224. package/dist/schemas/metrics.js +19 -3
  225. package/dist/schemas/metrics.js.map +1 -1
  226. package/dist/schemas/offload.d.ts +62 -0
  227. package/dist/schemas/offload.d.ts.map +1 -0
  228. package/dist/schemas/offload.js +23 -0
  229. package/dist/schemas/offload.js.map +1 -0
  230. package/dist/schemas/personas.d.ts +3 -0
  231. package/dist/schemas/personas.d.ts.map +1 -1
  232. package/dist/schemas/personas.js +12 -3
  233. package/dist/schemas/personas.js.map +1 -1
  234. package/dist/schemas/ports.d.ts +10 -0
  235. package/dist/schemas/ports.d.ts.map +1 -1
  236. package/dist/schemas/ports.js +8 -0
  237. package/dist/schemas/ports.js.map +1 -1
  238. package/dist/schemas/providers/plan-limits.d.ts +169 -0
  239. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  240. package/dist/schemas/providers/plan-limits.js +54 -1
  241. package/dist/schemas/providers/plan-limits.js.map +1 -1
  242. package/dist/schemas/providers/provider-oauth.d.ts +69 -0
  243. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  244. package/dist/schemas/providers/provider-oauth.js +6 -1
  245. package/dist/schemas/providers/provider-oauth.js.map +1 -1
  246. package/dist/schemas/settings-history.d.ts +55 -0
  247. package/dist/schemas/settings-history.d.ts.map +1 -0
  248. package/dist/schemas/settings-history.js +73 -0
  249. package/dist/schemas/settings-history.js.map +1 -0
  250. package/dist/schemas/settings.d.ts +188 -4
  251. package/dist/schemas/settings.d.ts.map +1 -1
  252. package/dist/schemas/settings.js +41 -26
  253. package/dist/schemas/settings.js.map +1 -1
  254. package/dist/schemas/speaker.d.ts +36 -0
  255. package/dist/schemas/speaker.d.ts.map +1 -0
  256. package/dist/schemas/speaker.js +62 -0
  257. package/dist/schemas/speaker.js.map +1 -0
  258. package/dist/schemas/state-plan.d.ts +46 -0
  259. package/dist/schemas/state-plan.d.ts.map +1 -0
  260. package/dist/schemas/state-plan.js +31 -0
  261. package/dist/schemas/state-plan.js.map +1 -0
  262. package/dist/schemas/system.d.ts +64 -0
  263. package/dist/schemas/system.d.ts.map +1 -1
  264. package/dist/schemas/system.js +12 -1
  265. package/dist/schemas/system.js.map +1 -1
  266. package/dist/schemas/workspace/mainline.d.ts +513 -0
  267. package/dist/schemas/workspace/mainline.d.ts.map +1 -0
  268. package/dist/schemas/workspace/mainline.js +182 -0
  269. package/dist/schemas/workspace/mainline.js.map +1 -0
  270. package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
  271. package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
  272. package/dist/schemas/workspace/workspace-tree.js +34 -0
  273. package/dist/schemas/workspace/workspace-tree.js.map +1 -1
  274. package/dist/state/contract-lock.d.ts +1 -0
  275. package/dist/state/contract-lock.d.ts.map +1 -1
  276. package/dist/state/contract-lock.js +47 -1
  277. package/dist/state/contract-lock.js.map +1 -1
  278. package/dist/state/definition.d.ts +44 -20
  279. package/dist/state/definition.d.ts.map +1 -1
  280. package/dist/state/definition.js.map +1 -1
  281. package/dist/state/fix-stance.d.ts +1 -1
  282. package/dist/state/fix-stance.d.ts.map +1 -1
  283. package/dist/state/fix-stance.js +2 -1
  284. package/dist/state/fix-stance.js.map +1 -1
  285. package/dist/state/history-state.d.ts.map +1 -1
  286. package/dist/state/history-state.js +7 -0
  287. package/dist/state/history-state.js.map +1 -1
  288. package/dist/state/landed-commit.d.ts +1 -0
  289. package/dist/state/landed-commit.d.ts.map +1 -1
  290. package/dist/state/landed-commit.js +2 -0
  291. package/dist/state/landed-commit.js.map +1 -1
  292. package/dist/state/red-streak.d.ts +7 -0
  293. package/dist/state/red-streak.d.ts.map +1 -0
  294. package/dist/state/red-streak.js +11 -0
  295. package/dist/state/red-streak.js.map +1 -0
  296. package/dist/state/runtime-state.d.ts +3 -0
  297. package/dist/state/runtime-state.d.ts.map +1 -1
  298. package/dist/state/runtime-state.js +1 -0
  299. package/dist/state/runtime-state.js.map +1 -1
  300. package/dist/state/workspace-state.d.ts +23 -1
  301. package/dist/state/workspace-state.d.ts.map +1 -1
  302. package/dist/state/workspace-state.js +33 -3
  303. package/dist/state/workspace-state.js.map +1 -1
  304. package/dist/text/shell-regions.d.ts +1 -0
  305. package/dist/text/shell-regions.d.ts.map +1 -1
  306. package/dist/text/shell-regions.js +1 -1
  307. package/dist/text/shell-regions.js.map +1 -1
  308. package/dist/text/transcript-fold.d.ts +1 -0
  309. package/dist/text/transcript-fold.d.ts.map +1 -1
  310. package/dist/text/transcript-fold.js +24 -8
  311. package/dist/text/transcript-fold.js.map +1 -1
  312. package/package.json +42 -42
  313. package/src/chores/chores.ts +6 -6
  314. package/src/chores/index.ts +0 -1
  315. package/src/chores/prompt.ts +3 -2
  316. package/src/contracts/agent.contract.ts +14 -0
  317. package/src/contracts/agents.contract.ts +36 -1
  318. package/src/contracts/capabilities.contract.ts +2 -2
  319. package/src/contracts/device.contract.ts +5 -3
  320. package/src/contracts/offload.contract.ts +60 -0
  321. package/src/contracts/runner.contract.ts +7 -1
  322. package/src/contracts/secrets.contract.ts +2 -0
  323. package/src/contracts/settings.contract.ts +2 -1
  324. package/src/contracts/workspace.contract.ts +54 -2
  325. package/src/documents/conversions.ts +402 -0
  326. package/src/documents/index.ts +7 -0
  327. package/src/documents/passthrough.ts +139 -0
  328. package/src/documents/read.ts +114 -0
  329. package/src/events/agent-events.ts +5 -0
  330. package/src/events/child-run.test.ts +81 -0
  331. package/src/events/child-run.ts +73 -0
  332. package/src/events/errands.test.ts +22 -0
  333. package/src/events/errands.ts +27 -0
  334. package/src/events/land-breakage.ts +12 -0
  335. package/src/events/land-conflict.ts +12 -0
  336. package/src/events/requests.ts +40 -1
  337. package/src/events/system-events.ts +9 -3
  338. package/src/events/transcript.ts +8 -0
  339. package/src/events/verify-nudge.ts +4 -3
  340. package/src/front/browser-wire.ts +20 -0
  341. package/src/front/front-wire.ts +15 -0
  342. package/src/front/generated/browser-wire.json +138 -0
  343. package/src/front/generated/browser-wire.ts +16 -0
  344. package/src/front/generated/front-wire.json +793 -0
  345. package/src/front/generated/tunnel.json +38 -0
  346. package/src/front/generated/wire.ts +37 -9
  347. package/src/front/wire-manifests.test.ts +61 -0
  348. package/src/ids/conversation-ids.ts +13 -5
  349. package/src/ids/session-names.ts +7 -0
  350. package/src/index.ts +15 -2
  351. package/src/models/agent-catalog.test.ts +6 -4
  352. package/src/models/agent-catalog.ts +4 -6
  353. package/src/models/agent-runtimes.ts +22 -7
  354. package/src/models/capability-ledger.test.ts +2 -0
  355. package/src/models/plan-pools.test.ts +121 -2
  356. package/src/models/plan-pools.ts +152 -2
  357. package/src/policy/request-status.ts +4 -12
  358. package/src/policy/reserved-servers.test.ts +6 -0
  359. package/src/policy/reserved-servers.ts +13 -10
  360. package/src/policy/turned-away.ts +3 -0
  361. package/src/protocol/container-requirements.test.ts +2 -3
  362. package/src/protocol/container-requirements.ts +1 -7
  363. package/src/protocol/edge-verdict.ts +13 -11
  364. package/src/protocol/ingress-contract.fixture.json +90 -0
  365. package/src/protocol/ingress-contract.test.ts +18 -26
  366. package/src/protocol/ingress-contract.ts +10 -25
  367. package/src/protocol/raw-routes.ts +22 -26
  368. package/src/protocol/route-meta.ts +39 -0
  369. package/src/protocol/runner-protocol.ts +53 -0
  370. package/src/protocol/tunnel-bulk.test.ts +37 -0
  371. package/src/protocol/tunnel-bulk.ts +7 -0
  372. package/src/protocol/x402.test.ts +29 -0
  373. package/src/protocol/x402.ts +61 -0
  374. package/src/schemas/activity.ts +3 -1
  375. package/src/schemas/agent.ts +28 -3
  376. package/src/schemas/agents.ts +83 -8
  377. package/src/schemas/automations.ts +4 -2
  378. package/src/schemas/devices.ts +106 -39
  379. package/src/schemas/extension-updates.ts +6 -0
  380. package/src/schemas/git/git.ts +5 -0
  381. package/src/schemas/hosts.ts +64 -13
  382. package/src/schemas/ic-shape-args.test.ts +64 -0
  383. package/src/schemas/keep-warm.test.ts +6 -13
  384. package/src/schemas/keep-warm.ts +15 -20
  385. package/src/schemas/metrics.ts +28 -3
  386. package/src/schemas/offload.ts +44 -0
  387. package/src/schemas/personas.ts +18 -3
  388. package/src/schemas/ports.ts +10 -0
  389. package/src/schemas/providers/plan-limits.ts +84 -1
  390. package/src/schemas/providers/provider-oauth.ts +10 -1
  391. package/src/schemas/settings-history.ts +108 -0
  392. package/src/schemas/settings.test.ts +54 -1
  393. package/src/schemas/settings.ts +88 -49
  394. package/src/schemas/speaker.ts +89 -0
  395. package/src/schemas/state-plan.test.ts +69 -0
  396. package/src/schemas/state-plan.ts +47 -0
  397. package/src/schemas/system.ts +24 -3
  398. package/src/schemas/workspace/mainline.test.ts +28 -0
  399. package/src/schemas/workspace/mainline.ts +268 -0
  400. package/src/schemas/workspace/workspace-tree.ts +47 -0
  401. package/src/state/contract-lock.test.ts +65 -4
  402. package/src/state/contract-lock.ts +70 -2
  403. package/src/state/definition.ts +3 -2
  404. package/src/state/fix-stance.test.ts +8 -0
  405. package/src/state/fix-stance.ts +3 -3
  406. package/src/state/history-state.ts +19 -0
  407. package/src/state/landed-commit.test.ts +12 -1
  408. package/src/state/landed-commit.ts +5 -0
  409. package/src/state/red-streak.test.ts +28 -0
  410. package/src/state/red-streak.ts +28 -0
  411. package/src/state/runtime-state.ts +4 -0
  412. package/src/state/workspace-state.test.ts +7 -1
  413. package/src/state/workspace-state.ts +45 -3
  414. package/src/text/shell-regions.test.ts +38 -0
  415. package/src/text/shell-regions.ts +3 -3
  416. package/src/text/transcript-fold.test.ts +46 -0
  417. package/src/text/transcript-fold.ts +31 -9
  418. package/dist/chores/fix-deps.d.ts +0 -10
  419. package/dist/chores/fix-deps.d.ts.map +0 -1
  420. package/dist/chores/fix-deps.js +0 -18
  421. package/dist/chores/fix-deps.js.map +0 -1
  422. package/dist/protocol/ingress-protocol.d.ts +0 -30
  423. package/dist/protocol/ingress-protocol.d.ts.map +0 -1
  424. package/dist/protocol/ingress-protocol.js +0 -300
  425. package/dist/protocol/ingress-protocol.js.map +0 -1
  426. package/dist/protocol/terminal-protocol.d.ts +0 -18
  427. package/dist/protocol/terminal-protocol.d.ts.map +0 -1
  428. package/dist/protocol/terminal-protocol.js +0 -2
  429. package/dist/protocol/terminal-protocol.js.map +0 -1
  430. package/dist/protocol/tunnel-heartbeat.d.ts +0 -19
  431. package/dist/protocol/tunnel-heartbeat.d.ts.map +0 -1
  432. package/dist/protocol/tunnel-heartbeat.js +0 -41
  433. package/dist/protocol/tunnel-heartbeat.js.map +0 -1
  434. package/dist/protocol/upgrade-relay.d.ts +0 -4
  435. package/dist/protocol/upgrade-relay.d.ts.map +0 -1
  436. package/dist/protocol/upgrade-relay.js +0 -25
  437. package/dist/protocol/upgrade-relay.js.map +0 -1
  438. package/src/chores/fix-deps.ts +0 -22
  439. package/src/protocol/ingress-protocol.test.ts +0 -579
  440. package/src/protocol/ingress-protocol.ts +0 -469
  441. package/src/protocol/terminal-protocol.ts +0 -13
  442. package/src/protocol/tunnel-heartbeat.test.ts +0 -75
  443. package/src/protocol/tunnel-heartbeat.ts +0 -67
  444. package/src/protocol/upgrade-relay.ts +0 -28
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { AccountUsageSchema } from "./plan-limits.js";
2
+ import { AccountStateSchema, AccountUsageSchema } from "./plan-limits.js";
3
3
  import { KeyedProviderSchema } from "./provider-subscriptions.js";
4
4
  // Claude uses PKCE paste-back (start → exchange); Codex uses OpenAI's device-code flow (start → poll). A sandbox can
5
5
  // hold several accounts per provider; `id` is the store key, `label` the display name. Tokens never ride this shape —
@@ -15,6 +15,12 @@ export const OauthAccountSchema = z.object({
15
15
  "Who it signs in as, in the provider's own words. Kept beside the label rather than folded into it, so a renamed account can still say whose it is. Absent when the provider says nothing, which is exactly when renaming is the only answer.",
16
16
  ),
17
17
  organization: z.string().optional().describe("Which organisation it belongs to, where the provider says."),
18
+ variant: z
19
+ .string()
20
+ .optional()
21
+ .describe(
22
+ "Which of the provider's estates it signs in to, for a provider selling more than one (Z.ai's international or mainland plan). With the email and organisation, it is who the account is: a sign-in matching all three lands on this account again, same id.",
23
+ ),
18
24
  scope: z.string().optional().describe("What the credential is permitted to do, in the provider's terms."),
19
25
  connectedAt: z.number().describe("When it was connected, in milliseconds."),
20
26
  needsReauth: z
@@ -31,6 +37,9 @@ export const OauthAccountSchema = z.object({
31
37
  usage: AccountUsageSchema.optional().describe(
32
38
  "How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.",
33
39
  ),
40
+ state: AccountStateSchema.optional().describe(
41
+ "Whether it can serve a turn now, judged once from the sign-in, the seat, the provider's last refusal and the plan limits: the verdict every picker in the sandbox uses. Read this rather than the fields it was judged from. Absent from a daemon older than this field.",
42
+ ),
34
43
  });
35
44
  export type OauthAccount = z.infer<typeof OauthAccountSchema>;
36
45
  export const OauthAccountListSchema = z.object({
@@ -0,0 +1,108 @@
1
+ import { z } from "zod";
2
+ import { dropAll, fold, isJsonObject, mapValue, retype } from "../documents/conversions.js";
3
+ import type { KeepWarmSettings, RuleSchema } from "./settings.js";
4
+
5
+ // The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
6
+ // SandboxSettingsSchema. Two documents carry this shape, the daemon's settings.json and a sandbox.toml's [settings]
7
+ // table, and both read through this one list; it sits beside the schema so that neither reader has to reach into the
8
+ // other for it. A name retired here stays retired: no later setting may take it to mean something else.
9
+
10
+ // Rules at a moment or with an action a later version withdrew (the `push.starting` moment, `instruct` actions, three
11
+ // built-in checks) can never run, and one of them made the whole file unreadable, every setting in it with it.
12
+ const RETIRED_MOMENTS: ReadonlySet<unknown> = new Set(["push.starting"]);
13
+ const RETIRED_ACTIONS: ReadonlySet<unknown> = new Set(["instruct"]);
14
+ const RETIRED_BUILTINS: ReadonlySet<unknown> = new Set(["verify-edits", "verify-removals", "verify-tests"]);
15
+
16
+ const isRetiredRule = (rule: unknown): boolean => {
17
+ if (!isJsonObject(rule)) {
18
+ return false;
19
+ }
20
+ const action = isJsonObject(rule["action"]) ? rule["action"] : {};
21
+ return RETIRED_MOMENTS.has(rule["moment"]) || RETIRED_ACTIONS.has(action["kind"]) || (action["kind"] === "builtin" && RETIRED_BUILTINS.has(action["name"]));
22
+ };
23
+
24
+ const holdsRetiredRule = (value: unknown): value is readonly unknown[] => Array.isArray(value) && value.some(isRetiredRule);
25
+
26
+ // A rule at the `turn.ending` moment reads and runs nothing since nothing checks inside a turn (its moment and the
27
+ // `verify-ui-edits` built-in stay in the read schema so a file written before reads); dropped, so it stops showing as a
28
+ // rule the owner has and stops travelling with the settings.
29
+ const isInertRule = (rule: unknown): boolean =>
30
+ isJsonObject(rule) &&
31
+ (rule["moment"] === "turn.ending" || (isJsonObject(rule["action"]) && rule["action"]["kind"] === "builtin" && rule["action"]["name"] === "verify-ui-edits"));
32
+
33
+ const holdsInertRule = (value: unknown): value is readonly unknown[] => Array.isArray(value) && value.some(isInertRule);
34
+
35
+ // keep-warm's four top-level settings (2026-09-26), folded into the one `keepWarm` object; a value of the wrong type is
36
+ // left out, for the schema's own default to fill.
37
+ const KEEP_WARM_FLAT = ["keepWarm", "keepWarmHours", "keepWarmMinTokens", "keepWarmReserve"] as const;
38
+ const FlatSwitch = z.boolean();
39
+ const FlatNumber = z.number();
40
+ const keepWarmObject = fold("folds keepWarm, keepWarmHours, keepWarmMinTokens and keepWarmReserve into one keepWarm object", {
41
+ from: KEEP_WARM_FLAT,
42
+ into: "keepWarm",
43
+ applies: (settings) => FlatSwitch.safeParse(settings["keepWarm"]).success || KEEP_WARM_FLAT.slice(1).some((key) => Object.hasOwn(settings, key)),
44
+ convert: ({ keepWarm, keepWarmHours, keepWarmMinTokens, keepWarmReserve }): Partial<KeepWarmSettings> => {
45
+ const folded: Partial<KeepWarmSettings> = {};
46
+ const auto = FlatSwitch.safeParse(keepWarm);
47
+ const hours = FlatNumber.safeParse(keepWarmHours);
48
+ const minTokens = FlatNumber.safeParse(keepWarmMinTokens);
49
+ const reserve = FlatNumber.safeParse(keepWarmReserve);
50
+ if (auto.success) {
51
+ folded.auto = auto.data;
52
+ }
53
+ if (hours.success) {
54
+ folded.hours = hours.data;
55
+ }
56
+ if (minTokens.success) {
57
+ folded.minTokens = minTokens.data;
58
+ }
59
+ if (reserve.success) {
60
+ folded.reserve = reserve.data;
61
+ }
62
+ return folded;
63
+ },
64
+ });
65
+
66
+ export const SETTINGS_HISTORY = [
67
+ // Every setting a release since 2026-08-10 had and this one does not (read off the contract lock's history):
68
+ // retired, so passthrough stops carrying them, a definition naming one still applies, and no later setting may
69
+ // take one of these names to mean something else.
70
+ ...dropAll([
71
+ "agentRunEffort",
72
+ "agentRunModel",
73
+ "agentRunModels",
74
+ "autoFastModels",
75
+ "autoTier",
76
+ "autoTierEagerness",
77
+ "commandJudgeModels",
78
+ "commandRules",
79
+ "contextShelf",
80
+ "dependencyFreshness",
81
+ "explainCommands",
82
+ "iqContext",
83
+ "iqContextHoldout",
84
+ "moveAfterLimit",
85
+ "quickModel",
86
+ "resumeAfterLimit",
87
+ "resumeAfterOutage",
88
+ "terseHoldout",
89
+ "terseOutput",
90
+ "testFaultDetection",
91
+ ]),
92
+ retype(
93
+ "rules",
94
+ holdsRetiredRule,
95
+ // What survives the filter is every rule this version can read; the parse after the chain says so.
96
+ (rules) => rules.filter((rule) => !isRetiredRule(rule)) as z.input<typeof RuleSchema>[],
97
+ "drops rules at a withdrawn moment or with a withdrawn action (push.starting, instruct, three built-in checks)",
98
+ ),
99
+ // suggest, the old default, becomes the new default: routing on.
100
+ mapValue("personaRouting", { off: false, suggest: true, auto: true }),
101
+ retype(
102
+ "rules",
103
+ holdsInertRule,
104
+ (rules) => rules.filter((rule) => !isInertRule(rule)) as z.input<typeof RuleSchema>[],
105
+ "drops rules at the retired turn.ending moment (and the verify-ui-edits built-in), which run nothing",
106
+ ),
107
+ keepWarmObject,
108
+ ] as const;
@@ -1,4 +1,6 @@
1
- import { RepoChecksFileSchema, RuleSchema, SandboxSettingsSchema } from "./settings.js";
1
+ import { convertDocument } from "../documents/conversions.js";
2
+ import { SETTINGS_HISTORY } from "./settings-history.js";
3
+ import { RepoChecksFileSchema, RuleSchema, SandboxSettingsSchema, SandboxSettingsWriteSchema } from "./settings.js";
2
4
 
3
5
  // A rule that saves cleanly and silently does nothing is the failure this schema exists to refuse: an action, and a
4
6
  // built-in's name, are checked against the moment they are written at.
@@ -57,3 +59,54 @@ describe(`where a command lives`, () => {
57
59
  expect(file([{ when: `land`, run: `pnpm verify`, paths: [`src/**`] }])).toBe(false);
58
60
  });
59
61
  });
62
+
63
+ // Nothing runs when a turn ends any more. A file written before still reads, its conversion drops the inert rule, and no
64
+ // save may stand a new one there.
65
+ describe(`the retired turn.ending moment`, () => {
66
+ const look = { id: `verify-ui-edits`, label: `Look at what changed`, moment: `turn.ending`, action: { kind: `builtin`, name: `verify-ui-edits` } };
67
+ const land = { id: `auto-land`, label: `land`, moment: `agent.finished`, action: { kind: `verdict`, verdict: `allow` } };
68
+
69
+ test(`still reads, so an older file parses`, () => {
70
+ expect(SandboxSettingsSchema.safeParse({ rules: [look, land] }).success).toBe(true);
71
+ });
72
+
73
+ test(`is refused on a save`, () => {
74
+ const saved = SandboxSettingsWriteSchema.safeParse(SandboxSettingsSchema.parse({ rules: [look, land] }));
75
+ expect(saved.error?.issues.map(({ message, path }) => ({ message, path }))).toEqual([
76
+ { message: `turn.ending is retired: nothing runs when a turn ends any more, so a rule cannot stand there`, path: [`rules`] },
77
+ ]);
78
+ expect(SandboxSettingsWriteSchema.safeParse(SandboxSettingsSchema.parse({ rules: [land] })).success).toBe(true);
79
+ });
80
+
81
+ test(`is dropped from a stored file by its conversion, which leaves every other rule and settles`, () => {
82
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, { rules: [look, land] }, true);
83
+ expect(converted.value).toEqual({ rules: [land] });
84
+ expect(converted.changes.map(({ conversion }) => conversion)).toEqual([
85
+ `drops rules at the retired turn.ending moment (and the verify-ui-edits built-in), which run nothing`,
86
+ ]);
87
+ });
88
+ });
89
+
90
+ // keep-warm's four top-level settings became one object on 2026-09-26; a stored file keeps what its owner picked.
91
+ describe(`the folded keep-warm settings`, () => {
92
+ test(`fold into one object, keeping each value and the key's place`, () => {
93
+ const stored = { timezone: `Europe/Warsaw`, keepWarm: true, keepWarmHours: 6, keepWarmMinTokens: 50_000, keepWarmReserve: 20, iqSearch: true };
94
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, stored, true);
95
+ expect(converted.value).toEqual({ timezone: `Europe/Warsaw`, keepWarm: { auto: true, hours: 6, minTokens: 50_000, reserve: 20 }, iqSearch: true });
96
+ expect(JSON.stringify(converted.value)).toBe(
97
+ `{"timezone":"Europe/Warsaw","keepWarm":{"auto":true,"hours":6,"minTokens":50000,"reserve":20},"iqSearch":true}`,
98
+ );
99
+ expect(SandboxSettingsSchema.parse(converted.value).keepWarm).toEqual({ auto: true, hours: 6, minTokens: 50_000, reserve: 20 });
100
+ });
101
+
102
+ test(`fold a file that set only some, leaving the rest to the defaults`, () => {
103
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, { keepWarmReserve: 30 }, true);
104
+ expect(converted.value).toEqual({ keepWarm: { reserve: 30 } });
105
+ expect(SandboxSettingsSchema.parse(converted.value).keepWarm).toEqual({ auto: false, hours: 4, minTokens: 100_000, reserve: 30 });
106
+ });
107
+
108
+ test(`leave a file already in the new shape alone`, () => {
109
+ const current = { keepWarm: { auto: true, hours: 2 } };
110
+ expect(convertDocument(SETTINGS_HISTORY, `object`, current, true).changes).toEqual([]);
111
+ });
112
+ });
@@ -13,13 +13,15 @@ export const SystemPromptModeSchema = z.enum(["intentic", "claude", "custom"]);
13
13
  export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
14
14
  // Excludes "custom": there is nothing to fetch, it's whatever the owner already typed into the settings field.
15
15
  export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
16
- // Rules: "at this moment, if this is true, do this". The owner's rules decide (land, hold, version) and switch the
17
- // daemon's built-in reviews; every command a moment runs is a repository's own check (`<repo>/.intentic/checks.json`),
18
- // compiled into this same table by the daemon, so no command lives in settings.
16
+ // Rules: "at this moment, if this is true, do this". The owner's rules decide (land, hold, version); every command a
17
+ // moment runs is a repository's own check (`<repo>/.intentic/checks.json`), compiled into this same table by the daemon,
18
+ // so no command lives in settings. Nothing verifies inside a turn: the whole-tree check runs after work lands and never
19
+ // holds anything (schemas/workspace/mainline.ts).
19
20
  export const RuleMomentSchema = z.enum([
20
21
  // A command here runs on the just-written file (`{file}` is its path); the cheapest moment to catch a defect.
21
22
  "file.edited",
22
- // The assistant is about to stop. A rule here can send it back to work, which is the only moment that can.
23
+ // Retired: the assistant stopping. Nothing runs here any more and nothing sends a turn back to work; the value stays
24
+ // so settings written before it went still read, and a rule standing here is inert.
23
25
  "turn.ending",
24
26
  // An agent's turn is over and its delta is sitting on its branch. A rule here decides whether it lands.
25
27
  "agent.finished",
@@ -30,7 +32,8 @@ export type RuleMoment = z.infer<typeof RuleMomentSchema>;
30
32
  // command: a repository's check; its exit code is the verdict.
31
33
  // verdict: allows or holds what is about to happen.
32
34
  // builtin: a daemon behaviour that reads a record only the daemon keeps.
33
- // verify-ui-edits: rendered surfaces a turn changed, against whether it ever looked at one.
35
+ // verify-ui-edits: retired with `turn.ending`, read only so older settings parse; whether a turn looked at the surfaces it
36
+ // changed is recorded on its card instead (AgentSummary.proof), never asked of the model.
34
37
  // version-landed: commits what a land brought, in the main tree, under the subject drafted for it, and the tree's own
35
38
  // remainder before the next isolated turn starts; the only way a person who never commits keeps every agent seeing
36
39
  // the latest tree, since worktrees are cut from HEAD.
@@ -47,8 +50,8 @@ export const RuleActionSchema = z.discriminatedUnion("kind", [
47
50
  z.object({ kind: z.literal("builtin"), name: RuleBuiltinSchema }),
48
51
  ]);
49
52
  export type RuleAction = z.infer<typeof RuleActionSchema>;
50
- // `checks-failed` is a clean turn whose `turn.ending` check went red on the tree about to land; unlike the others, it
51
- // defaults to HELD unless a rule explicitly allows it.
53
+ // `checks-failed` was a clean turn whose `turn.ending` check went red. Nothing produces it now that no check runs inside a
54
+ // turn, and a check never holds work; it stays so older rules naming it still read.
52
55
  export const RuleOutcomeSchema = z.enum(["clean", "error", "conflict", "checks-failed"]);
53
56
  export type RuleOutcome = z.infer<typeof RuleOutcomeSchema>;
54
57
  // Every key absent means the rule always matches.
@@ -57,9 +60,10 @@ export const RuleConditionSchema = z.object({
57
60
  repo: z.string().min(1).optional(),
58
61
  // Globs the change has to touch for the rule to fire. Absent/empty ⇒ any.
59
62
  paths: z.array(z.string().min(1)).max(20).optional(),
60
- // How the turn ended. Absent/empty ⇒ any, except that `checks-failed` never lands by omission.
63
+ // How the turn ended. Absent/empty ⇒ any.
61
64
  outcome: z.array(RuleOutcomeSchema).optional(),
62
- // The fraction of occasions a rule fires on, at moments that draw one (turn.ending); absent ⇒ every occasion.
65
+ // The fraction of occasions a rule fires on, at a moment that draws one; absent ⇒ every occasion. No moment draws
66
+ // now that `turn.ending` is retired, so a sampled rule fires every time.
63
67
  sample: z.number().gt(0).lt(1).optional(),
64
68
  });
65
69
  export type RuleCondition = z.infer<typeof RuleConditionSchema>;
@@ -94,6 +98,9 @@ export const RuleSchema = z
94
98
  path: ["action"],
95
99
  });
96
100
  export type Rule = z.infer<typeof RuleSchema>;
101
+ // A rule at a retired moment or with a retired built-in: read, so an older file parses, but never written again.
102
+ export const isRetiredMomentRule = (rule: Pick<Rule, "moment" | "action">): boolean =>
103
+ rule.moment === "turn.ending" || (rule.action.kind === "builtin" && rule.action.name === "verify-ui-edits");
97
104
  // Kept out of the settings object on purpose: a firing is not an edit, and writing config on every push would make
98
105
  // every run a settings save.
99
106
  export const RuleFiringsSchema = z.record(z.string(), z.number());
@@ -182,7 +189,42 @@ export type SkillSwitch = z.infer<typeof SkillSwitchSchema>;
182
189
  // Opt-in settings the /settings routes edit and streamAgent reads, defaulting off so each can be A/B tested (`skills`
183
190
  // defaults on for the baked tools worth having, since a skill file is the only thing that tells the agent a baked
184
191
  // binary exists). Every default lives in the schema, so an older settings file still parses and keeps the owner's other
185
- // picks rather than failing whole.
192
+ // picks rather than failing whole. A setting removed or reshaped here gets its conversion in settings-history.ts, which
193
+ // also names every retired setting, none of whose names may come back.
194
+
195
+ // keep-warm's knobs, one object since each only means anything beside the others (cache-keepwarm.ts).
196
+ export const KeepWarmSettingsSchema = z.object({
197
+ auto: z
198
+ .boolean()
199
+ .default(false)
200
+ .describe(
201
+ "Keep a Claude conversation's prompt cache warm after each turn a person asked for. Each refresh re-reads the cached context at the cache price and adds nothing to the conversation.",
202
+ ),
203
+ hours: z
204
+ .number()
205
+ .min(1)
206
+ .max(8)
207
+ .default(4)
208
+ .describe(
209
+ "How long an idle conversation is kept warm after its last turn, in hours, and what a press on one offers first. Shortened where refreshing would cost more than the cold resume it saves, and at midnight where the agent runs, when the date in its prompt changes.",
210
+ ),
211
+ minTokens: z
212
+ .number()
213
+ .int()
214
+ .min(0)
215
+ .default(100_000)
216
+ .describe("Only conversations at least this large, in tokens, are kept warm by `auto`: a small one is cheap to re-read anyway."),
217
+ reserve: z
218
+ .number()
219
+ .int()
220
+ .min(0)
221
+ .max(90)
222
+ .default(15)
223
+ .describe(
224
+ "How much of an account's usage limit, in percent, keeping conversations warm must leave untouched for real work. Refreshing stops once any limit that account's model spends is fuller than that.",
225
+ ),
226
+ });
227
+ export type KeepWarmSettings = z.infer<typeof KeepWarmSettingsSchema>;
186
228
 
187
229
  export const SandboxSettingsSchema = z.object({
188
230
  // The zone every WALL-CLOCK RULE in this sandbox is meant in — an automation's cron, and anything else that says
@@ -387,46 +429,37 @@ export const SandboxSettingsSchema = z.object({
387
429
  .describe(
388
430
  "When a spent usage limit moves a turn to another account, carry the provider session (the model keeps everything, and re-reads all of it once on the other account) while the conversation's context is under this many tokens; at or above it, start a fresh session with the sandbox's measured brief instead. Zero always starts fresh.",
389
431
  ),
390
- // Off by default: every refresh spends the account's own allowance on a conversation nobody is using yet.
391
- keepWarm: z
392
- .boolean()
393
- .default(false)
394
- .describe(
395
- "Keep a Claude conversation's prompt cache warm after each turn a person asked for, so coming back to it hours later costs a cache read instead of re-sending everything. Each refresh re-reads the cached context at the cache price and adds nothing to the conversation. Any one conversation can be kept warm or let cool by hand either way.",
396
- ),
397
- keepWarmHours: z
398
- .number()
399
- .min(1)
400
- .max(8)
401
- .default(4)
402
- .describe(
403
- "How long an idle conversation is kept warm after its last turn, in hours. Shortened where refreshing would cost more than the cold resume it saves, and at midnight where the agent runs, when the date in its prompt changes.",
404
- ),
405
- keepWarmMinTokens: z
406
- .number()
407
- .int()
408
- .min(0)
409
- .default(100_000)
410
- .describe("Only conversations at least this large, in tokens, are kept warm by themselves: a small one is cheap to re-read anyway."),
411
- keepWarmReserve: z
412
- .number()
413
- .int()
414
- .min(0)
415
- .max(90)
416
- .default(15)
417
- .describe(
418
- "How much of an account's usage limit, in percent, keeping conversations warm must leave untouched for real work. Refreshing stops once any limit that account's model spends is fuller than that.",
419
- ),
432
+ // Off by default: every refresh spends the account's own allowance on a conversation nobody is using yet. Before
433
+ // 2026-09-26 these were four top-level settings (settings-history.ts folds them).
434
+ keepWarm: KeepWarmSettingsSchema.prefault({}).describe(
435
+ "Keeping an idle conversation's prompt cache warm, so coming back to it hours later costs a cache read instead of re-sending everything. Any one conversation can be kept warm or let cool by hand whatever `auto` says.",
436
+ ),
420
437
  // Worth it since the container is recreated on every update or environment approval — otherwise approving a
421
438
  // Dockerfile change costs the run that asked for it.
422
- // What happens to breakage found after the work that caused it has left the turn: a land that turns the main tree's own
423
- // check red is sent back to the conversation that landed it, and main's CI staying red on one failure gets a fix agent
424
- // once pushes go quiet, with a fleet failure re-run instead. Off, both are only reported.
439
+ // What happens to breakage found after the work that caused it has left the turn. A land that turns the main tree's own
440
+ // check red waits for the lands queued behind it to be checked too, then goes back to the conversation that landed it
441
+ // while that one still has it in mind, or to a fresh conversation when nobody can be named or the one named has gone
442
+ // cold; main's CI staying red on one failure gets a fix agent once pushes go quiet, with a fleet failure re-run instead.
443
+ // Off, all of it is only reported.
425
444
  autoRepair: z
426
445
  .boolean()
427
446
  .default(true)
428
447
  .describe(
429
- "Whether breakage found after the work left its turn is repaired without asking: a land that turns the main tree's check red goes back to the conversation that landed it, and main's CI staying red on the same failure gets a fix agent once pushes go quiet. A failure on the CI fleet itself is re-run once instead. Off, both are only reported.",
448
+ "Whether breakage found after the work left its turn is repaired without asking. A land that turns the main tree's check red is repaired once the work queued behind it has been checked too: by the conversation that landed it while it still has the work in mind, otherwise by a fresh conversation handed the failures and the suspects' changes. Main's CI staying red on the same failure gets a fix agent once pushes go quiet, and a failure on the CI fleet itself is re-run once instead. Off, all of it is only reported.",
449
+ ),
450
+ // Where heavy work runs: a runner (the same image, on one of the owner's machines) instead of this sandbox. Keyed by
451
+ // the heavy-command rule an agent's command matched (system/resources/heavy-commands.ts), and one entry for the
452
+ // check after landing, each naming a runner id. A runner that is offline, outdated or full hands the work back here,
453
+ // which the command's output says. Never pushed to a runner itself (portability/definition.ts), which must not pass
454
+ // work on again.
455
+ offload: z
456
+ .object({
457
+ commands: z.record(z.string().min(1), z.string().min(1)).default({}),
458
+ landCheck: z.string().min(1).optional(),
459
+ })
460
+ .default({ commands: {} })
461
+ .describe(
462
+ "Which heavy work runs on a runner on one of your machines instead of this sandbox: agents' commands by the kind the heavy-command rules sort them into (tests, typechecks, verify…), and the check after landing. The code travels as it stands, uncommitted work included; the output streams back, and any file the command changed comes back with it. A machine that is offline, outdated or busy hands the work back to this sandbox, and the output says so.",
430
463
  ),
431
464
  autoResumeOnRestart: z
432
465
  .boolean()
@@ -454,7 +487,7 @@ export const SandboxSettingsSchema = z.object({
454
487
  message: "a command belongs in the repository's own .intentic/checks.json, not in settings",
455
488
  })
456
489
  .describe(
457
- "Standing decisions about the sandbox's own work: land or hold finished work, save a version of what landed, ask the assistant to look at an interface it changed. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment. A command to run is a repository's own check, declared in its .intentic/checks.json.",
490
+ "Standing decisions about the sandbox's own work: land or hold finished work, save a version of what landed. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment. A command to run is a repository's own check, declared in its .intentic/checks.json.",
458
491
  ),
459
492
  automationFailureLimit: z
460
493
  .number()
@@ -496,6 +529,12 @@ export const SandboxSettingsSchema = z.object({
496
529
  .describe("How many levels deep the delegation may go, since a subagent can start subagents of its own."),
497
530
  });
498
531
  export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
532
+ // What a save takes: the settings as read, less what is only read. `turn.ending` (and its `verify-ui-edits` built-in) is
533
+ // retired; a file written before still parses (and its conversion drops such rules), but no save may stand a new one.
534
+ export const SandboxSettingsWriteSchema = SandboxSettingsSchema.refine((settings) => !settings.rules.some(isRetiredMomentRule), {
535
+ message: "turn.ending is retired: nothing runs when a turn ends any more, so a rule cannot stand there",
536
+ path: ["rules"],
537
+ });
499
538
 
500
539
  // A browser telling the sandbox which clock IT is on. An offer, not an instruction: see `adoptTimezone`.
501
540
  export const TimezoneOfferSchema = z.object({
@@ -626,9 +665,9 @@ export type SavingsReport = z.infer<typeof SavingsReportSchema>;
626
665
  // spelling for the daemon that reads it, the screen that names it and the demo that mimics it.
627
666
  export const REPO_CHECKS_FILE = `${STATE_DIR}/checks.json`;
628
667
 
629
- // Named for the occasion as a repository would say it: `edit` is `file.edited`, `turn` is `turn.ending`, and `land` is
630
- // the daemon's run over the main tree after an install a land caused (rules/repo-checks.ts maps them). A push is gated by
631
- // the repository's own git pre-push hook, which every push runs, the app's included.
668
+ // Named for the occasion as a repository would say it: `edit` is `file.edited`, and `land` is the daemon's run over the
669
+ // main tree after every land (rules/repo-checks.ts maps them). `turn` is retired: nothing runs when a turn ends any more,
670
+ // and a declaration naming it still reads but runs nothing. No check refuses a land, a commit or a push.
632
671
  // `edit` runs on one file (`{file}`) and its output rides back in the edit's own response, so a command declared there is
633
672
  // paid per edit and has to take the file.
634
673
  export const RepoCheckMomentSchema = z.enum(["edit", "turn", "land"]);
@@ -636,7 +675,7 @@ export type RepoCheckMoment = z.infer<typeof RepoCheckMomentSchema>;
636
675
 
637
676
  export const RepoCheckSchema = z.object({
638
677
  when: RepoCheckMomentSchema.describe(
639
- "When to run it: `edit` on each file as it is written (`{file}` is its path), `turn` before the assistant finishes, `land` on the main tree after finished work lands (absent, the package's own verify or test script runs there).",
678
+ "When to run it: `edit` on each file as it is written (`{file}` is its path), `land` on the main tree after finished work lands (absent, the package's own verify or test script runs there). `turn` is retired and runs nothing: checks no longer run while a conversation works.",
640
679
  ),
641
680
  run: z.string().min(1).max(500).describe("The command, run in this repository's own directory, so it reads as it would in a terminal there."),
642
681
  label: z.string().min(1).max(80).optional().describe("What to call it on screen. Absent names it after the command."),
@@ -0,0 +1,89 @@
1
+ import { z } from "zod";
2
+ import type { MessageVoice } from "./agent.js";
3
+ import type { SessionOwner } from "./agents.js";
4
+
5
+ // WHO IS SPEAKING, as the sandbox verified it: one typed value every door builds once, from which the older one-line
6
+ // facts are derived (the message's `voice`, the turn's `actor` string, the conversation's `owner`), so no reader parses a
7
+ // prefix to learn whether a person is at the keyboard. Shared with transcript rows that want to say who spoke. Named apart
8
+ // from `Speaker` (agents.ts), which is only which side of a transcript a search snippet came from.
9
+
10
+ export const TurnSpeakerSchema = z.discriminatedUnion("kind", [
11
+ z.object({
12
+ kind: z.literal("person"),
13
+ email: z.string().describe("The signed-in member, as the sandbox verified them."),
14
+ name: z.string().optional().describe("Their display name, where the sign-in carries one."),
15
+ }),
16
+ z.object({
17
+ kind: z.literal("program"),
18
+ token: z.string().describe("The label of the control token a person minted and handed to this program."),
19
+ }),
20
+ z.object({
21
+ kind: z.literal("agent"),
22
+ conversationId: z.string().describe("The conversation whose agent is speaking: a child reporting back, a peer's message."),
23
+ }),
24
+ z.object({
25
+ kind: z.literal("sandbox"),
26
+ source: z
27
+ .string()
28
+ .optional()
29
+ .describe("What in the sandbox spoke: an automation, a watch that fired, a job that ended, a repair. Absent when it does not say."),
30
+ }),
31
+ ]);
32
+ export type TurnSpeaker = z.infer<typeof TurnSpeakerSchema>;
33
+
34
+ // Prefixed so a token labelled like an email, or a conversation id, can never read as a person.
35
+ const TOKEN_PREFIX = "token:";
36
+ const AGENT_PREFIX = "agent:";
37
+
38
+ /** The one-line attribution a turn and a queued message carry: a person's email, else the prefixed program or agent. */
39
+ export const speakerActor = (speaker: TurnSpeaker): string | undefined => {
40
+ switch (speaker.kind) {
41
+ case "person":
42
+ return speaker.email;
43
+ case "program":
44
+ return `${TOKEN_PREFIX}${speaker.token}`;
45
+ case "agent":
46
+ return `${AGENT_PREFIX}${speaker.conversationId}`;
47
+ case "sandbox":
48
+ return undefined;
49
+ }
50
+ };
51
+
52
+ /** Whose words a message is: a program writes through a person's door, so it speaks with a person's voice. */
53
+ export const speakerVoice = (speaker: TurnSpeaker): MessageVoice =>
54
+ speaker.kind === "program" ? "person" : speaker.kind === "agent" ? "agent" : speaker.kind === "sandbox" ? "sandbox" : "person";
55
+
56
+ /** The member who owns what this speaker opens; a program, an agent and the sandbox own nothing, so theirs stay claimable. */
57
+ export const speakerOwner = (speaker: TurnSpeaker): Pick<SessionOwner, "email" | "name"> | undefined => {
58
+ if (speaker.kind !== "person") {
59
+ return undefined;
60
+ }
61
+ const owner: Pick<SessionOwner, "email" | "name"> = { email: speaker.email };
62
+ if (speaker.name !== undefined) {
63
+ owner.name = speaker.name;
64
+ }
65
+ return owner;
66
+ };
67
+
68
+ /** A person, verified, at the keyboard: never a program holding a token a person minted. */
69
+ export const spokenByPerson = (speaker: TurnSpeaker | undefined): boolean => speaker?.kind === "person";
70
+
71
+ // WHAT A COMPOSED PROMPT IS FOR, when the sandbox or the app wrote it rather than whoever is named as speaking: a land's
72
+ // breakage sent back to it, a fresh fix-up on a red main line and its later nudges, a note to a conversation still
73
+ // working on what failed, the brief a person's press hands an agent (what a push left, main's red CI), a land conflict to
74
+ // resolve. Carried on the turn and the row it opens, so a reader shows it as the sandbox's words, not the owner's, and
75
+ // never has to recognise the prompt by its opening; the four openings older rows are recognised by stay what they are
76
+ // (events/errands.ts, errandOfPrompt).
77
+ export const TurnErrandSchema = z.enum([
78
+ "land-conflict",
79
+ "verify-nudge",
80
+ "land-breakage",
81
+ "land-fix",
82
+ "land-fix-nudge",
83
+ "land-held",
84
+ "push-fix",
85
+ "push-fix-nudge",
86
+ "ci-fix",
87
+ "ci-fix-nudge",
88
+ ]);
89
+ export type TurnErrand = z.infer<typeof TurnErrandSchema>;
@@ -0,0 +1,69 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { STATE_DIR } from "@intentic/constants";
4
+ import { packageRoot } from "@intentic/constants/node";
5
+ import { STATE_PLAN_FORMAT, type StatePlan, StatePlanSchema, type StateStatus, StateStatusSchema } from "./state-plan.js";
6
+
7
+ // The documents `ic` reads by field name, spelled once here: each golden file under golden/ is one of these examples
8
+ // as JSON, and the Rust tests (_sandbox/ic/src/sandbox/preflight.rs, health.rs) parse the same files. A change here
9
+ // that the committed file does not carry fails, naming the file; `INTENTIC_WRITE_GOLDEN=1` rewrites them, and the Rust
10
+ // tests then say whether ic still reads them.
11
+
12
+ const GOLDEN = join(packageRoot(import.meta.url), "golden");
13
+
14
+ const clear: StatePlan = {
15
+ plan: STATE_PLAN_FORMAT,
16
+ version: "1.400.0",
17
+ engine: 84,
18
+ digest: "c8cff51cab20a2b5",
19
+ ok: true,
20
+ downgrade: false,
21
+ failures: [],
22
+ steps: [
23
+ { document: "conversations-db-schema", change: "upgrades the conversations database schema" },
24
+ { document: `${STATE_DIR}/config/personas.json`, change: `moves it from ${STATE_DIR}/identities.json` },
25
+ ],
26
+ converts: [
27
+ { document: `${STATE_DIR}/config/settings.json`, change: "converts personaRouting from its old values", detail: '"suggest" became true' },
28
+ ],
29
+ files: [`${STATE_DIR}/config/personas.json`],
30
+ };
31
+
32
+ const refused: StatePlan = {
33
+ plan: STATE_PLAN_FORMAT,
34
+ version: "1.400.0",
35
+ engine: 84,
36
+ digest: "c8cff51cab20a2b5",
37
+ ok: false,
38
+ downgrade: true,
39
+ failures: [{ document: `${STATE_DIR}/config/automations.json`, detail: 'conversion "converts numbered kinds" failed: no such kind' }],
40
+ steps: [],
41
+ converts: [],
42
+ files: [],
43
+ };
44
+
45
+ // The whole `/health` answer is the daemon's; ic reads only `state` out of it, so the golden carries just enough around it.
46
+ const health: { readonly ok: true; readonly state: StateStatus } = { ok: true, state: { journal: "open", engine: 84 } };
47
+
48
+ const goldens = [
49
+ { file: "state-plan-clear.json", value: StatePlanSchema.parse(clear) },
50
+ { file: "state-plan-refused.json", value: StatePlanSchema.parse(refused) },
51
+ { file: "health-state.json", value: { ...health, state: StateStatusSchema.parse(health.state) } },
52
+ ];
53
+
54
+ test.each(goldens)("golden/$file is the contract's example, as the host reads it", ({ file, value }) => {
55
+ const path = join(GOLDEN, file);
56
+ const expected = `${JSON.stringify(value, undefined, 2)}\n`;
57
+ if (process.env["INTENTIC_WRITE_GOLDEN"] === "1") {
58
+ writeFileSync(path, expected);
59
+ }
60
+ expect(readFileSync(path, "utf8")).toBe(expected);
61
+ });
62
+
63
+ test("a marker plan an older ic wrote, with only what it kept, still reads", () => {
64
+ expect(StatePlanSchema.partial().extend({ ok: StatePlanSchema.shape.ok }).parse({ ok: true, downgrade: false, steps: [{ document: "a.json", change: "renames a to b" }] })).toEqual({
65
+ ok: true,
66
+ downgrade: false,
67
+ steps: [{ document: "a.json", change: "renames a to b" }],
68
+ });
69
+ });
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+
3
+ // The daemon's state engine as the host reads it, in two documents a Rust binary (`ic`) parses by these field names:
4
+ // the update pre-flight's one line (the target image's state-plan.ts, read by _sandbox/ic/src/sandbox/preflight.rs and
5
+ // embedded verbatim in the staged-update marker), and the `state` of `/health` (read by _sandbox/ic/src/health.rs). The
6
+ // golden files beside the contract (golden/) are this schema's examples, and the Rust tests assert against them.
7
+
8
+ export const PlanStepSchema = z.object({
9
+ document: z.string().describe("The stored file, workspace-relative, or `<volume>:<path>` for one on another volume; a structural step's id."),
10
+ change: z.string().describe("What is done to it, in one line."),
11
+ detail: z.string().optional().describe("What was particular about this one: a conflict's losing value, a retired entry, a mapped value."),
12
+ });
13
+ export type PlanStep = z.infer<typeof PlanStepSchema>;
14
+
15
+ export const PlanFailureSchema = z.object({
16
+ document: z.string().describe("The stored file whose conversion would fail, or the structural step that would."),
17
+ detail: z.string().describe("Why, in the conversion's own words."),
18
+ });
19
+ export type PlanFailure = z.infer<typeof PlanFailureSchema>;
20
+
21
+ // The one format `ic` reads; any other is "no plan", never a guess at what its fields mean.
22
+ export const STATE_PLAN_FORMAT = 1;
23
+
24
+ export const StatePlanSchema = z.object({
25
+ plan: z.literal(STATE_PLAN_FORMAT).describe("The format of this line. A reader refuses any other rather than guessing at its fields."),
26
+ version: z.string().describe("The release of the image that planned it; 0.0.0 for a development build."),
27
+ engine: z.number().describe("The conversion count builds before the digest compared. Reported, never decided by."),
28
+ digest: z.string().describe("What identifies the planning build's conversion set: its episodes resume only under the same one."),
29
+ ok: z.boolean().describe("False when a conversion would fail on this sandbox's files, which refuses the update before anything is touched."),
30
+ downgrade: z.boolean().describe("A newer release than the planning build ran here: it opens what it cannot read read-only."),
31
+ failures: z.array(PlanFailureSchema).describe("Each conversion or step that would fail, and why."),
32
+ steps: z.array(PlanStepSchema).describe("What the first boot changes on disk: documents moved, structural steps run."),
33
+ converts: z
34
+ .array(PlanStepSchema)
35
+ .optional()
36
+ .describe("What the build's conversions change as its stores read these files, written by each store's next save. Absent from a build before it."),
37
+ files: z.array(z.string()).describe("Every file the first boot writes, workspace-relative where it can be."),
38
+ });
39
+ export type StatePlan = z.infer<typeof StatePlanSchema>;
40
+
41
+ // The `state` of `/health`: whether an update's state changes are still uncommitted. The host rolls an update back when
42
+ // the journal stays open, so this rides the same probe as the boot's progress.
43
+ export const StateStatusSchema = z.object({
44
+ journal: z.enum(["open", "none"]).describe("Open from the start of a boot that changed stored files until that boot has converged."),
45
+ engine: z.number().describe("The running build's conversion count."),
46
+ });
47
+ export type StateStatus = z.infer<typeof StateStatusSchema>;