@intentic/sandbox-contract 1.344.0 → 1.346.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/README.md +27 -9
  2. package/dist/contracts/accounts.contract.d.ts +24 -80
  3. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  4. package/dist/contracts/agent.contract.d.ts +247 -8
  5. package/dist/contracts/agent.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.js +13 -2
  7. package/dist/contracts/agent.contract.js.map +1 -1
  8. package/dist/contracts/agents.contract.d.ts +1570 -33
  9. package/dist/contracts/agents.contract.d.ts.map +1 -1
  10. package/dist/contracts/agents.contract.js +47 -4
  11. package/dist/contracts/agents.contract.js.map +1 -1
  12. package/dist/contracts/device.contract.d.ts +244 -1
  13. package/dist/contracts/device.contract.d.ts.map +1 -1
  14. package/dist/contracts/device.contract.js +5 -1
  15. package/dist/contracts/device.contract.js.map +1 -1
  16. package/dist/contracts/endpoints.contract.d.ts +2 -1
  17. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  18. package/dist/contracts/endpoints.contract.js +1 -0
  19. package/dist/contracts/endpoints.contract.js.map +1 -1
  20. package/dist/contracts/providers.contract.d.ts +0 -1
  21. package/dist/contracts/providers.contract.d.ts.map +1 -1
  22. package/dist/contracts/push.contract.d.ts +11 -0
  23. package/dist/contracts/push.contract.d.ts.map +1 -1
  24. package/dist/contracts/push.contract.js +10 -1
  25. package/dist/contracts/push.contract.js.map +1 -1
  26. package/dist/contracts/runner.contract.d.ts +149 -123
  27. package/dist/contracts/runner.contract.d.ts.map +1 -1
  28. package/dist/contracts/secrets.contract.d.ts +169 -0
  29. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  30. package/dist/contracts/secrets.contract.js +35 -0
  31. package/dist/contracts/secrets.contract.js.map +1 -1
  32. package/dist/contracts/sessions.contract.d.ts +2 -0
  33. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  34. package/dist/contracts/settings.contract.d.ts +37 -0
  35. package/dist/contracts/settings.contract.d.ts.map +1 -1
  36. package/dist/contracts/system.contract.d.ts +235 -31
  37. package/dist/contracts/system.contract.d.ts.map +1 -1
  38. package/dist/contracts/translator.contract.d.ts +24 -80
  39. package/dist/contracts/translator.contract.d.ts.map +1 -1
  40. package/dist/contracts/workspace.contract.d.ts +11 -0
  41. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  42. package/dist/contracts/workspace.contract.js +11 -1
  43. package/dist/contracts/workspace.contract.js.map +1 -1
  44. package/dist/events/agent-events.d.ts +60 -0
  45. package/dist/events/agent-events.d.ts.map +1 -1
  46. package/dist/events/agent-events.js +44 -38
  47. package/dist/events/agent-events.js.map +1 -1
  48. package/dist/events/agent-transcript.d.ts +727 -0
  49. package/dist/events/agent-transcript.d.ts.map +1 -0
  50. package/dist/events/agent-transcript.js +34 -0
  51. package/dist/events/agent-transcript.js.map +1 -0
  52. package/dist/events/agent-words.d.ts +5 -0
  53. package/dist/events/agent-words.d.ts.map +1 -1
  54. package/dist/events/agent-words.js +9 -0
  55. package/dist/events/agent-words.js.map +1 -1
  56. package/dist/events/failure-rows.d.ts +14 -0
  57. package/dist/events/failure-rows.d.ts.map +1 -0
  58. package/dist/events/failure-rows.js +109 -0
  59. package/dist/events/failure-rows.js.map +1 -0
  60. package/dist/events/requests.d.ts +143 -0
  61. package/dist/events/requests.d.ts.map +1 -1
  62. package/dist/events/requests.js +35 -1
  63. package/dist/events/requests.js.map +1 -1
  64. package/dist/events/sandbox-notice.d.ts +23 -0
  65. package/dist/events/sandbox-notice.d.ts.map +1 -1
  66. package/dist/events/sandbox-notice.js +2 -0
  67. package/dist/events/sandbox-notice.js.map +1 -1
  68. package/dist/events/system-events.d.ts +156 -0
  69. package/dist/events/system-events.d.ts.map +1 -1
  70. package/dist/events/system-events.js +7 -1
  71. package/dist/events/system-events.js.map +1 -1
  72. package/dist/events/transcript.d.ts +20 -720
  73. package/dist/events/transcript.d.ts.map +1 -1
  74. package/dist/events/transcript.js +17 -19
  75. package/dist/events/transcript.js.map +1 -1
  76. package/dist/ids/conversation-ids.d.ts +2 -0
  77. package/dist/ids/conversation-ids.d.ts.map +1 -1
  78. package/dist/ids/conversation-ids.js +2 -0
  79. package/dist/ids/conversation-ids.js.map +1 -1
  80. package/dist/ids/row-ids.d.ts +3 -0
  81. package/dist/ids/row-ids.d.ts.map +1 -0
  82. package/dist/ids/row-ids.js +11 -0
  83. package/dist/ids/row-ids.js.map +1 -0
  84. package/dist/index.d.ts +2296 -199
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/models/agent-lanes.d.ts.map +1 -1
  87. package/dist/models/agent-lanes.js +6 -7
  88. package/dist/models/agent-lanes.js.map +1 -1
  89. package/dist/models/agent-standing.d.ts +6 -19
  90. package/dist/models/agent-standing.d.ts.map +1 -1
  91. package/dist/models/agent-standing.js +14 -38
  92. package/dist/models/agent-standing.js.map +1 -1
  93. package/dist/models/model-pins.d.ts +3 -0
  94. package/dist/models/model-pins.d.ts.map +1 -1
  95. package/dist/models/model-pins.js +2 -0
  96. package/dist/models/model-pins.js.map +1 -1
  97. package/dist/models/plan-pools.d.ts +26 -4
  98. package/dist/models/plan-pools.d.ts.map +1 -1
  99. package/dist/models/plan-pools.js +159 -40
  100. package/dist/models/plan-pools.js.map +1 -1
  101. package/dist/netd/generated/wire.d.ts +5 -0
  102. package/dist/netd/generated/wire.d.ts.map +1 -1
  103. package/dist/policy/command-classes.d.ts +1 -1
  104. package/dist/policy/command-classes.d.ts.map +1 -1
  105. package/dist/policy/command-classes.js +2 -2
  106. package/dist/policy/command-classes.js.map +1 -1
  107. package/dist/policy/keyring-origins.d.ts +27 -0
  108. package/dist/policy/keyring-origins.d.ts.map +1 -0
  109. package/dist/policy/keyring-origins.js +213 -0
  110. package/dist/policy/keyring-origins.js.map +1 -0
  111. package/dist/policy/keyring-seal.d.ts +9 -0
  112. package/dist/policy/keyring-seal.d.ts.map +1 -1
  113. package/dist/policy/keyring-seal.js +18 -6
  114. package/dist/policy/keyring-seal.js.map +1 -1
  115. package/dist/policy/safety-policy.d.ts +1 -1
  116. package/dist/policy/safety-policy.d.ts.map +1 -1
  117. package/dist/policy/safety-policy.js +1 -1
  118. package/dist/policy/safety-policy.js.map +1 -1
  119. package/dist/policy/turned-away.d.ts +1 -0
  120. package/dist/policy/turned-away.d.ts.map +1 -1
  121. package/dist/policy/turned-away.js +10 -0
  122. package/dist/policy/turned-away.js.map +1 -1
  123. package/dist/protocol/doors/host-protocol.d.ts +1 -0
  124. package/dist/protocol/doors/host-protocol.d.ts.map +1 -1
  125. package/dist/protocol/doors/host-protocol.js +1 -0
  126. package/dist/protocol/doors/host-protocol.js.map +1 -1
  127. package/dist/protocol/doors/runner-protocol.d.ts +1 -0
  128. package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
  129. package/dist/protocol/doors/runner-protocol.js +1 -0
  130. package/dist/protocol/doors/runner-protocol.js.map +1 -1
  131. package/dist/protocol/doors/webext-links.d.ts +1 -0
  132. package/dist/protocol/doors/webext-links.d.ts.map +1 -1
  133. package/dist/protocol/doors/webext-links.js +1 -0
  134. package/dist/protocol/doors/webext-links.js.map +1 -1
  135. package/dist/protocol/peer-dial.d.ts +2 -0
  136. package/dist/protocol/peer-dial.d.ts.map +1 -1
  137. package/dist/protocol/peer-dial.js +62 -29
  138. package/dist/protocol/peer-dial.js.map +1 -1
  139. package/dist/protocol/raw/raw-json-routes.d.ts +179 -1
  140. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  141. package/dist/protocol/raw/raw-json-routes.js +5 -0
  142. package/dist/protocol/raw/raw-json-routes.js.map +1 -1
  143. package/dist/protocol/raw/raw-routes.d.ts +35 -0
  144. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  145. package/dist/protocol/raw/raw-routes.js +9 -0
  146. package/dist/protocol/raw/raw-routes.js.map +1 -1
  147. package/dist/schemas/agent-next.d.ts +21 -0
  148. package/dist/schemas/agent-next.d.ts.map +1 -0
  149. package/dist/schemas/agent-next.js +21 -0
  150. package/dist/schemas/agent-next.js.map +1 -0
  151. package/dist/schemas/agent-place.d.ts +10 -0
  152. package/dist/schemas/agent-place.d.ts.map +1 -0
  153. package/dist/schemas/agent-place.js +15 -0
  154. package/dist/schemas/agent-place.js.map +1 -0
  155. package/dist/schemas/agent-search.d.ts +45 -0
  156. package/dist/schemas/agent-search.d.ts.map +1 -0
  157. package/dist/schemas/agent-search.js +28 -0
  158. package/dist/schemas/agent-search.js.map +1 -0
  159. package/dist/schemas/agent.d.ts +120 -78
  160. package/dist/schemas/agent.d.ts.map +1 -1
  161. package/dist/schemas/agent.js +40 -49
  162. package/dist/schemas/agent.js.map +1 -1
  163. package/dist/schemas/agents-list.d.ts +54 -0
  164. package/dist/schemas/agents-list.d.ts.map +1 -1
  165. package/dist/schemas/agents.d.ts +165 -59
  166. package/dist/schemas/agents.d.ts.map +1 -1
  167. package/dist/schemas/agents.js +25 -50
  168. package/dist/schemas/agents.js.map +1 -1
  169. package/dist/schemas/attach.d.ts +8 -0
  170. package/dist/schemas/attach.d.ts.map +1 -0
  171. package/dist/schemas/attach.js +14 -0
  172. package/dist/schemas/attach.js.map +1 -0
  173. package/dist/schemas/command-admission.d.ts +71 -0
  174. package/dist/schemas/command-admission.d.ts.map +1 -0
  175. package/dist/schemas/command-admission.js +27 -0
  176. package/dist/schemas/command-admission.js.map +1 -0
  177. package/dist/schemas/compose-overlay.d.ts +9 -0
  178. package/dist/schemas/compose-overlay.d.ts.map +1 -0
  179. package/dist/schemas/compose-overlay.js +11 -0
  180. package/dist/schemas/compose-overlay.js.map +1 -0
  181. package/dist/schemas/composer-drafts.d.ts +169 -0
  182. package/dist/schemas/composer-drafts.d.ts.map +1 -0
  183. package/dist/schemas/composer-drafts.js +60 -0
  184. package/dist/schemas/composer-drafts.js.map +1 -0
  185. package/dist/schemas/conversation-links.d.ts +44 -0
  186. package/dist/schemas/conversation-links.d.ts.map +1 -0
  187. package/dist/schemas/conversation-links.js +35 -0
  188. package/dist/schemas/conversation-links.js.map +1 -0
  189. package/dist/schemas/data-protection.d.ts +1 -1
  190. package/dist/schemas/data-protection.d.ts.map +1 -1
  191. package/dist/schemas/device-commands.d.ts +63 -0
  192. package/dist/schemas/device-commands.d.ts.map +1 -0
  193. package/dist/schemas/device-commands.js +42 -0
  194. package/dist/schemas/device-commands.js.map +1 -0
  195. package/dist/schemas/device-folder.d.ts +11 -0
  196. package/dist/schemas/device-folder.d.ts.map +1 -1
  197. package/dist/schemas/device-folder.js +1 -0
  198. package/dist/schemas/device-folder.js.map +1 -1
  199. package/dist/schemas/devices.d.ts +18 -68
  200. package/dist/schemas/devices.d.ts.map +1 -1
  201. package/dist/schemas/devices.js +8 -45
  202. package/dist/schemas/devices.js.map +1 -1
  203. package/dist/schemas/environment.d.ts.map +1 -1
  204. package/dist/schemas/environment.js.map +1 -1
  205. package/dist/schemas/hosts.d.ts +29 -0
  206. package/dist/schemas/hosts.d.ts.map +1 -1
  207. package/dist/schemas/hosts.js +23 -0
  208. package/dist/schemas/hosts.js.map +1 -1
  209. package/dist/schemas/keyring-native.d.ts +137 -0
  210. package/dist/schemas/keyring-native.d.ts.map +1 -0
  211. package/dist/schemas/keyring-native.js +69 -0
  212. package/dist/schemas/keyring-native.js.map +1 -0
  213. package/dist/schemas/keyring.d.ts +413 -0
  214. package/dist/schemas/keyring.d.ts.map +1 -1
  215. package/dist/schemas/keyring.js +139 -4
  216. package/dist/schemas/keyring.js.map +1 -1
  217. package/dist/schemas/phone-passwords.d.ts +186 -0
  218. package/dist/schemas/phone-passwords.d.ts.map +1 -0
  219. package/dist/schemas/phone-passwords.js +25 -0
  220. package/dist/schemas/phone-passwords.js.map +1 -0
  221. package/dist/schemas/privacy-egress.d.ts +41 -0
  222. package/dist/schemas/privacy-egress.d.ts.map +1 -0
  223. package/dist/schemas/privacy-egress.js +30 -0
  224. package/dist/schemas/privacy-egress.js.map +1 -0
  225. package/dist/schemas/privacy.d.ts +1 -0
  226. package/dist/schemas/privacy.d.ts.map +1 -1
  227. package/dist/schemas/privacy.js +1 -0
  228. package/dist/schemas/privacy.js.map +1 -1
  229. package/dist/schemas/providers/plan-limits.d.ts +76 -130
  230. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  231. package/dist/schemas/providers/plan-limits.js +36 -23
  232. package/dist/schemas/providers/plan-limits.js.map +1 -1
  233. package/dist/schemas/providers/provider-oauth.d.ts +24 -82
  234. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  235. package/dist/schemas/providers/provider-oauth.js +1 -6
  236. package/dist/schemas/providers/provider-oauth.js.map +1 -1
  237. package/dist/schemas/providers/refusals.d.ts +64 -0
  238. package/dist/schemas/providers/refusals.d.ts.map +1 -0
  239. package/dist/schemas/providers/refusals.js +33 -0
  240. package/dist/schemas/providers/refusals.js.map +1 -0
  241. package/dist/schemas/providers/usage.d.ts +1 -0
  242. package/dist/schemas/providers/usage.d.ts.map +1 -1
  243. package/dist/schemas/providers/usage.js +1 -0
  244. package/dist/schemas/providers/usage.js.map +1 -1
  245. package/dist/schemas/push.d.ts +13 -0
  246. package/dist/schemas/push.d.ts.map +1 -1
  247. package/dist/schemas/push.js +11 -1
  248. package/dist/schemas/push.js.map +1 -1
  249. package/dist/schemas/sandbox-passwords.d.ts +288 -0
  250. package/dist/schemas/sandbox-passwords.d.ts.map +1 -0
  251. package/dist/schemas/sandbox-passwords.js +66 -0
  252. package/dist/schemas/sandbox-passwords.js.map +1 -0
  253. package/dist/schemas/secrets.d.ts +2 -0
  254. package/dist/schemas/secrets.d.ts.map +1 -1
  255. package/dist/schemas/secrets.js +4 -0
  256. package/dist/schemas/secrets.js.map +1 -1
  257. package/dist/schemas/sessions.js +1 -1
  258. package/dist/schemas/sessions.js.map +1 -1
  259. package/dist/schemas/settings-savings.d.ts +709 -0
  260. package/dist/schemas/settings-savings.d.ts.map +1 -0
  261. package/dist/schemas/settings-savings.js +53 -0
  262. package/dist/schemas/settings-savings.js.map +1 -0
  263. package/dist/schemas/settings.d.ts +7 -671
  264. package/dist/schemas/settings.d.ts.map +1 -1
  265. package/dist/schemas/settings.js +5 -50
  266. package/dist/schemas/settings.js.map +1 -1
  267. package/dist/schemas/system.d.ts +16 -0
  268. package/dist/schemas/system.d.ts.map +1 -1
  269. package/dist/schemas/system.js +11 -0
  270. package/dist/schemas/system.js.map +1 -1
  271. package/dist/schemas/terminal.d.ts +5 -0
  272. package/dist/schemas/terminal.d.ts.map +1 -1
  273. package/dist/schemas/terminal.js +4 -0
  274. package/dist/schemas/terminal.js.map +1 -1
  275. package/dist/schemas/work-assurance.d.ts +24 -0
  276. package/dist/schemas/work-assurance.d.ts.map +1 -0
  277. package/dist/schemas/work-assurance.js +23 -0
  278. package/dist/schemas/work-assurance.js.map +1 -0
  279. package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
  280. package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
  281. package/dist/schemas/workspace/workspace-tree.js +20 -0
  282. package/dist/schemas/workspace/workspace-tree.js.map +1 -1
  283. package/dist/state/auth-state.d.ts +4 -0
  284. package/dist/state/auth-state.d.ts.map +1 -0
  285. package/dist/state/auth-state.js +37 -0
  286. package/dist/state/auth-state.js.map +1 -0
  287. package/dist/state/definition.d.ts +12 -0
  288. package/dist/state/definition.d.ts.map +1 -1
  289. package/dist/state/runtime-state.d.ts +3 -0
  290. package/dist/state/runtime-state.d.ts.map +1 -1
  291. package/dist/state/runtime-state.js +1 -0
  292. package/dist/state/runtime-state.js.map +1 -1
  293. package/dist/state/sandbox-build.d.ts +20 -0
  294. package/dist/state/sandbox-build.d.ts.map +1 -0
  295. package/dist/state/sandbox-build.js +43 -0
  296. package/dist/state/sandbox-build.js.map +1 -0
  297. package/dist/state/ways-back.d.ts +4 -0
  298. package/dist/state/ways-back.d.ts.map +1 -0
  299. package/dist/state/ways-back.js +8 -0
  300. package/dist/state/ways-back.js.map +1 -0
  301. package/dist/text/land-rows.d.ts +14 -0
  302. package/dist/text/land-rows.d.ts.map +1 -0
  303. package/dist/text/land-rows.js +86 -0
  304. package/dist/text/land-rows.js.map +1 -0
  305. package/dist/text/transcript-fold.d.ts +4 -2
  306. package/dist/text/transcript-fold.d.ts.map +1 -1
  307. package/dist/text/transcript-fold.js +21 -187
  308. package/dist/text/transcript-fold.js.map +1 -1
  309. package/dist/webext/index.d.ts +1 -0
  310. package/dist/webext/index.d.ts.map +1 -1
  311. package/dist/webext/index.js +1 -0
  312. package/dist/webext/index.js.map +1 -1
  313. package/package.json +7 -6
  314. package/src/contracts/agent.contract.ts +13 -1
  315. package/src/contracts/agents.contract.ts +53 -28
  316. package/src/contracts/device.contract.ts +16 -0
  317. package/src/contracts/endpoints.contract.ts +3 -0
  318. package/src/contracts/push.contract.ts +23 -1
  319. package/src/contracts/secrets.contract.ts +39 -0
  320. package/src/contracts/workspace.contract.ts +16 -0
  321. package/src/events/agent-events.ts +99 -79
  322. package/src/events/agent-transcript.ts +48 -0
  323. package/src/events/agent-words.ts +13 -0
  324. package/src/events/failure-rows.ts +148 -0
  325. package/src/events/requests.ts +41 -3
  326. package/src/events/sandbox-notice.ts +14 -2
  327. package/src/events/system-events.ts +14 -4
  328. package/src/events/transcript.ts +30 -28
  329. package/src/ids/conversation-ids.ts +7 -0
  330. package/src/ids/project-dir.test.ts +2 -2
  331. package/src/ids/project-dir.ts +4 -4
  332. package/src/ids/row-ids.ts +20 -0
  333. package/src/models/agent-lanes.ts +16 -24
  334. package/src/models/agent-runtimes.ts +2 -2
  335. package/src/models/agent-standing.ts +38 -95
  336. package/src/models/model-pins.test.ts +31 -1
  337. package/src/models/model-pins.ts +17 -0
  338. package/src/models/plan-pools.test.ts +274 -31
  339. package/src/models/plan-pools.ts +294 -79
  340. package/src/netd/generated/netd-wire.json +25 -0
  341. package/src/netd/generated/wire.ts +1 -1
  342. package/src/policy/command-classes.ts +2 -2
  343. package/src/policy/keyring-origins.test.ts +132 -0
  344. package/src/policy/keyring-origins.ts +287 -0
  345. package/src/policy/keyring-seal.test.ts +37 -2
  346. package/src/policy/keyring-seal.ts +33 -7
  347. package/src/policy/safety-policy.ts +1 -1
  348. package/src/policy/tests/safety-policy.test.ts +1 -1
  349. package/src/policy/turned-away.ts +15 -0
  350. package/src/protocol/doors/host-protocol.ts +3 -0
  351. package/src/protocol/doors/runner-protocol.ts +4 -1
  352. package/src/protocol/doors/webext-links.ts +3 -0
  353. package/src/protocol/peer-dial.test.ts +90 -4
  354. package/src/protocol/peer-dial.ts +83 -39
  355. package/src/protocol/raw/raw-json-routes.ts +13 -0
  356. package/src/protocol/raw/raw-routes.ts +13 -1
  357. package/src/schemas/agent-next.ts +34 -0
  358. package/src/schemas/agent-place.ts +22 -0
  359. package/src/schemas/agent-search.ts +51 -0
  360. package/src/schemas/agent.ts +63 -90
  361. package/src/schemas/agents.ts +55 -99
  362. package/src/schemas/attach.ts +23 -0
  363. package/src/schemas/command-admission.ts +57 -0
  364. package/src/schemas/compose-overlay.test.ts +35 -0
  365. package/src/schemas/compose-overlay.ts +19 -0
  366. package/src/schemas/composer-drafts.ts +86 -0
  367. package/src/schemas/conversation-links.ts +62 -0
  368. package/src/schemas/data-protection.ts +1 -1
  369. package/src/schemas/device-commands.ts +89 -0
  370. package/src/schemas/device-folder.ts +9 -0
  371. package/src/schemas/devices.ts +28 -110
  372. package/src/schemas/environment.ts +8 -9
  373. package/src/schemas/hosts.ts +60 -0
  374. package/src/schemas/keyring-native.test.ts +13 -0
  375. package/src/schemas/keyring-native.ts +101 -0
  376. package/src/schemas/keyring.ts +202 -4
  377. package/src/schemas/machine-presence.test.ts +33 -0
  378. package/src/schemas/phone-passwords.ts +51 -0
  379. package/src/schemas/privacy-egress.test.ts +83 -0
  380. package/src/schemas/privacy-egress.ts +86 -0
  381. package/src/schemas/privacy.ts +4 -0
  382. package/src/schemas/project-delivery.ts +1 -1
  383. package/src/schemas/providers/plan-limits.test.ts +16 -0
  384. package/src/schemas/providers/plan-limits.ts +55 -49
  385. package/src/schemas/providers/provider-oauth.ts +1 -13
  386. package/src/schemas/providers/refusals.ts +62 -0
  387. package/src/schemas/providers/usage.ts +2 -0
  388. package/src/schemas/push.ts +19 -3
  389. package/src/schemas/sandbox-passwords.ts +88 -0
  390. package/src/schemas/secrets.ts +4 -0
  391. package/src/schemas/sessions.ts +1 -1
  392. package/src/schemas/settings-savings.ts +125 -0
  393. package/src/schemas/settings.ts +10 -116
  394. package/src/schemas/system.ts +15 -0
  395. package/src/schemas/terminal.ts +8 -0
  396. package/src/schemas/work-assurance.ts +36 -0
  397. package/src/schemas/workspace/workspace-tree.ts +32 -0
  398. package/src/state/auth-state.ts +89 -0
  399. package/src/state/runtime-state.ts +10 -4
  400. package/src/state/sandbox-build.test.ts +95 -0
  401. package/src/state/sandbox-build.ts +96 -0
  402. package/src/state/ways-back.test.ts +36 -0
  403. package/src/state/ways-back.ts +19 -0
  404. package/src/state/workspace-state.ts +1 -1
  405. package/src/text/land-rows.ts +111 -0
  406. package/src/text/transcript-fold.test.ts +105 -9
  407. package/src/text/transcript-fold.ts +30 -232
  408. package/src/webext/index.ts +2 -0
  409. package/dist/events/undelivered.d.ts +0 -6
  410. package/dist/events/undelivered.d.ts.map +0 -1
  411. package/dist/events/undelivered.js +0 -9
  412. package/dist/events/undelivered.js.map +0 -1
  413. package/dist/state/history-state.d.ts +0 -3
  414. package/dist/state/history-state.d.ts.map +0 -1
  415. package/dist/state/history-state.js +0 -137
  416. package/dist/state/history-state.js.map +0 -1
  417. package/src/events/undelivered.ts +0 -15
  418. package/src/state/history-state.ts +0 -270
@@ -0,0 +1,96 @@
1
+ import { DEV_SANDBOX_IMAGE } from "../policy/overlay-lint.js";
2
+
3
+ // WHICH BUILD A SANDBOX IS, as one record, and every comparison made between two of them. A build is the base image it
4
+ // sits on and the environment overlay built on that base, each by content: the base by its registry digest when the
5
+ // runner read one, the overlay by the hash of the recipe built. A sandbox holds two at any moment, the build it runs
6
+ // (stamped on its container) and the overlay it wants (composed from its approvals now); a parent and its runner hold
7
+ // one each. "Rebuild pending" and runner parity are asked here and nowhere else. Each fix that once picked its own field
8
+ // (an image tag for parity, a cached file for pending) stayed local while the next surface kept its own (2026-10).
9
+
10
+ export interface SandboxBuild {
11
+ // The base image as the runner named it (SANDBOX_BASE_IMAGE, else the image itself); empty when no runner said.
12
+ readonly base: string;
13
+ // The base's registry digest (`sha256:…`) when the runner could read one: what makes `base` one exact image.
14
+ readonly baseDigest?: string | undefined;
15
+ // sha256 of the environment overlay built on the base; absent on a stock image.
16
+ readonly overlay?: string | undefined;
17
+ }
18
+
19
+ // Built on a base compiled from a checkout, which names whatever that machine's checkout held when it was built: it is
20
+ // compared with nothing, and a newer build of it comes from the checkout rather than the registry.
21
+ export const builtFromCheckout = (build: Pick<SandboxBuild, "base">): boolean => build.base === DEV_SANDBOX_IMAGE;
22
+
23
+ // The overlay a sandbox wants is not the one it runs. Wanting none is nothing to rebuild; a container built with an
24
+ // overlay whose every input has since gone still wants the bare one composed for it, which has a hash of its own.
25
+ export const rebuildPending = (desired: string | undefined, running: string | undefined): boolean => desired !== undefined && desired !== (running ?? "");
26
+
27
+ // Whether two sandboxes run the same code and environment: the base by digest when both sides read one (one tag pulled
28
+ // on two machines at two moments is two builds), else by its reference, and the overlay by hash, none on both sides
29
+ // agreeing. Unknown when either side named no base or was built from a checkout: nothing an update could bring level.
30
+ export const buildParity = (parent: SandboxBuild, other: SandboxBuild | undefined): "current" | "outdated" | "unknown" => {
31
+ if (other === undefined || parent.base === "" || other.base === "" || builtFromCheckout(parent) || builtFromCheckout(other)) {
32
+ return "unknown";
33
+ }
34
+ const sameBase =
35
+ parent.baseDigest !== undefined && other.baseDigest !== undefined ? parent.baseDigest === other.baseDigest : parent.base === other.base;
36
+ return sameBase && (parent.overlay ?? "") === (other.overlay ?? "") ? "current" : "outdated";
37
+ };
38
+
39
+ // WHAT ANSWERS: the build a daemon runs and the moment it started, as /info reports it (`running`). A swap (a rebuild, an
40
+ // update, a rollback) replaces the container, so the sandbox answering afterwards is a different boot; whether it is the
41
+ // build the swap aimed at is the build's to say. A daemon's version cannot: every build from a checkout is 0.0.0, so a
42
+ // swap told by "the version moved" never landed for one (2026-10).
43
+ export interface RunningSandbox {
44
+ readonly build: SandboxBuild;
45
+ // Epoch ms this daemon started: a container swapped in is a new boot even where its build reads the same.
46
+ readonly bootedAt: number;
47
+ }
48
+
49
+ // Whether two builds are the same image and overlay: false when either differs, true when both bases carry a digest and
50
+ // they and the overlays agree, undefined when nothing can tell (a checkout build, or one tag with no digest read on
51
+ // either side, which two pulls a release apart both are).
52
+ export const sameBuild = (a: SandboxBuild, b: SandboxBuild): boolean | undefined => {
53
+ if ((a.overlay ?? "") !== (b.overlay ?? "")) {
54
+ return false;
55
+ }
56
+ if (builtFromCheckout(a) || builtFromCheckout(b)) {
57
+ return undefined;
58
+ }
59
+ if (a.baseDigest !== undefined && b.baseDigest !== undefined) {
60
+ return a.baseDigest === b.baseDigest;
61
+ }
62
+ return a.base === b.base ? undefined : false;
63
+ };
64
+
65
+ // What a swap is aiming at, read when it was pressed: the sandbox answering then, and for a rebuild the overlay it builds.
66
+ export interface SwapAim {
67
+ readonly from: RunningSandbox | undefined;
68
+ readonly overlay?: string | undefined;
69
+ }
70
+
71
+ // Where a swap stands by what answers now. `landed`: the sandbox runs what it aimed at (the overlay a rebuild built; for
72
+ // an update or a rollback, a build other than the one pressed on, or a new boot where the builds cannot tell). `unmoved`:
73
+ // a new boot answers on the very build it was pressed on, so the swap came back without taking. `waiting`: the sandbox
74
+ // pressed on still answers, or nothing here can tell yet.
75
+ export type SwapVerdict = "landed" | "unmoved" | "waiting";
76
+
77
+ export const swapVerdict = (aim: SwapAim, now: RunningSandbox): SwapVerdict => {
78
+ const rebooted = aim.from !== undefined && now.bootedAt !== aim.from.bootedAt;
79
+ if (aim.overlay !== undefined) {
80
+ if (now.build.overlay === aim.overlay) {
81
+ return "landed";
82
+ }
83
+ return rebooted ? "unmoved" : "waiting";
84
+ }
85
+ if (aim.from === undefined) {
86
+ return "waiting";
87
+ }
88
+ const same = sameBuild(aim.from.build, now.build);
89
+ if (same === false) {
90
+ return "landed";
91
+ }
92
+ if (!rebooted) {
93
+ return "waiting";
94
+ }
95
+ return same === true ? "unmoved" : "landed";
96
+ };
@@ -0,0 +1,36 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { packageRoot } from "@intentic/constants/node";
4
+ import { DeviceFeatureSchema } from "../schemas/hosts.js";
5
+ import { waysBack } from "./ways-back.js";
6
+
7
+ // What `ic features --json` answers (_sandbox/ic/src/features.rs holds the same list to this golden).
8
+ // SAFETY: the golden is this package's own file; the assertions below fail on any field it lacks.
9
+ const IC_FEATURES = JSON.parse(readFileSync(join(packageRoot(import.meta.url), "golden", "ic-features.json"), "utf8")) as { readonly features: readonly string[] };
10
+
11
+ const pruned = (image: string, version: string) => ({ image, version, download: true });
12
+ const kept = (image: string, version: string) => ({ image, version });
13
+
14
+ test("every feature ic says it has is one the contract knows, the reachable listing among them", () => {
15
+ for (const feature of IC_FEATURES.features) {
16
+ expect(DeviceFeatureSchema.safeParse(feature).success).toBe(true);
17
+ }
18
+ expect(IC_FEATURES.features).toContain("reachable-ways-back");
19
+ });
20
+
21
+ test("a current ic's ways back are offered as it lists them, each image once", () => {
22
+ const current = { features: [...IC_FEATURES.features] };
23
+ const pin = kept("pin:a", "1.2.0");
24
+ const release = pruned("ghcr.io/intentic/sandbox:1.1.0", "1.1.0");
25
+ expect(waysBack(current, [pin, pin, release])).toEqual([pin, release]);
26
+ });
27
+
28
+ // 2026-10-19: an ic from before then listed pruned dev builds, rows of 0.0.0 no download can bring back.
29
+ test("an older ic's rows it could never reach are left out, and only those", () => {
30
+ const older = { features: ["rollback-to", "sleep"] };
31
+ const dev = kept("pin:dev", "0.0.0");
32
+ const release = pruned("pin:gone", "1.1.0");
33
+ const rows = [dev, pruned("pin:gone-dev", "0.0.0"), release, { image: "pin:nameless", download: true }];
34
+ expect(waysBack(older, rows)).toEqual([dev, release]);
35
+ expect(waysBack(undefined, rows)).toEqual([dev, release]);
36
+ });
@@ -0,0 +1,19 @@
1
+ import { DEVICE_FEATURE_REACHABLE_WAYS_BACK, type DeviceFacts, deviceSupports } from "../schemas/hosts.js";
2
+ import type { RollbackTarget } from "../schemas/updates.js";
3
+ import { DEV_VERSION } from "./versions.js";
4
+
5
+ // THE WAYS BACK A SANDBOX ROW OFFERS, as the device's ic means them (2026-10-20). An ic from 2026-10-19 on lists only the
6
+ // ways back a rollback can reach (versions.rs `ways_back`) and says so in the device's features (`reachable-ways-back`,
7
+ // ic's own `ic features`): its rows are offered as they are. An older ic also listed every pin pruned outside it, each as
8
+ // a download of its version, and a dev build's version is 0.0.0, which no registry publishes: three rows of "0.0.0", none
9
+ // of which could be had. Only for such a host are the rows an older ic could not reach left out, by that ic's own rule
10
+ // (a download needs a published version), rather than every host's rows being filtered on a guess. Either way each image
11
+ // is offered once.
12
+
13
+ // A release the registry publishes an image under: a dotted version, and not the one every dev build says.
14
+ const published = (version: string | undefined): boolean => version !== undefined && /^v?\d+\.\d+\.\d+$/u.test(version) && version.replace(/^v/u, "") !== DEV_VERSION;
15
+
16
+ export const waysBack = <T extends Pick<RollbackTarget, "image" | "version" | "download">>(facts: Pick<DeviceFacts, "features"> | undefined, targets: readonly T[]): T[] => {
17
+ const once = [...new Map(targets.map((target) => [target.image, target])).values()];
18
+ return deviceSupports(facts, DEVICE_FEATURE_REACHABLE_WAYS_BACK) ? once : once.filter((target) => target.download !== true || published(target.version));
19
+ };
@@ -210,7 +210,7 @@ const STATE_FILES = [
210
210
  portability: "carry",
211
211
  },
212
212
  // Where releases before 2026-10-12 kept the wakes automations held for a person. They now sit on the history volume
213
- // (`automation-holds/`, history-state.ts), off the workspace every turn writes, and nothing reads this directory; a
213
+ // (`automation-holds/`, the daemon's held-wakes-store.ts), off the workspace every turn writes, and nothing reads this directory; a
214
214
  // build that read it is only ever rolled back to. Declared still, since the extension that listed them binds it.
215
215
  {
216
216
  path: ".intentic/records/approvals/",
@@ -0,0 +1,111 @@
1
+ import type { AgentEvent } from "../events/agent-events.js";
2
+ import type { TranscriptRow } from "../events/transcript.js";
3
+ import { noticeCode } from "../events/sandbox-notice.js";
4
+
5
+ // The notices a turn's work leaves as it moves: an install starting, the branch rebased onto the workspace while it
6
+ // waited, and where the finished work went (landed, held, conflicted, or into a parent's checkout).
7
+
8
+ // Clause appended to the landed notice for a workspace dependency change, or empty when there is none. Reports the
9
+ // install as already started, or as on its way: the daemon starts it within seconds, in the install lane, behind any
10
+ // install already running; never as a request.
11
+ const dependencyLine = (deps: { missing: number; started: string[]; deferred: boolean } | undefined): string => {
12
+ if (deps === undefined || deps.missing === 0) {
13
+ return ``;
14
+ }
15
+ // `missing` counts versions the turn bumped as well as names it added, so the sentence names both.
16
+ const what = `${deps.missing} ${deps.missing === 1 ? `dependency` : `dependencies`} it added or changed`;
17
+ return deps.deferred
18
+ ? ` ${what} ${deps.missing === 1 ? `is` : `are`} being installed in your tree: the install waits for any other install to finish, appears in Work terminals, and its outcome lands in Activity.`
19
+ : ` Installing ${what}; the project's checks run when that finishes, and the outcome lands in Activity.`;
20
+ };
21
+
22
+ // Said as an agent's own install starts: where it writes and what outlasts it. Names up to three projects, the workspace
23
+ // root first; an install the gate could not place in a project says only where it writes. Coded like every sandbox
24
+ // notice (sandbox-notice.ts `installing`), so the chat can say it in the reader's language.
25
+ const INSTALL_NAMED = 3;
26
+ export const installRow = (event: { readonly reach: "own-copy" | "main-tree"; readonly projects: readonly string[] }): TranscriptRow => {
27
+ const root = event.projects.includes(``);
28
+ const dirs = event.projects.filter((dir) => dir !== ``).map((dir) => `${dir}/`);
29
+ const shownDirs = dirs.slice(0, INSTALL_NAMED - (root ? 1 : 0));
30
+ const more = dirs.length - shownDirs.length;
31
+ const named = [...(root ? [`the workspace root`] : []), ...shownDirs].join(`, `);
32
+ const list = more > 0 ? `${named} and ${more} more` : named;
33
+ const where = list === `` ? `` : ` in ${list}`;
34
+ const ownCopy = event.reach === `own-copy`;
35
+ return {
36
+ role: "notice",
37
+ text: ownCopy
38
+ ? `Installing packages${where} into this conversation's own copy. The manifest and lockfile land with the work, and the review lists what it adds.`
39
+ : `Installing packages${where} in the main tree, one install at a time.`,
40
+ noticeCode: noticeCode({
41
+ code: "installing",
42
+ params: {
43
+ ownCopy,
44
+ root: root || undefined,
45
+ projects: shownDirs.length === 0 ? undefined : shownDirs.join(`, `),
46
+ more: more > 0 ? more : undefined,
47
+ },
48
+ }),
49
+ };
50
+ };
51
+
52
+ // One line reporting the daemon's rebase of this agent's branch onto the current workspace; `blocked` names repos it
53
+ // couldn't rebase, where the turn ran from an older base.
54
+ export const syncLine = (sync: { commits: number; blocked: readonly string[] }): string => {
55
+ const moved =
56
+ sync.commits > 0
57
+ ? `Your workspace moved on while this agent waited, its branch was rebased onto your latest ${sync.commits} commit${sync.commits === 1 ? `` : `s`}.`
58
+ : undefined;
59
+ const blocked =
60
+ sync.blocked.length > 0
61
+ ? `Couldn't rebase onto your workspace in ${sync.blocked.join(`, `)}: the turn is running from the older base, so its land may need a resolve.`
62
+ : undefined;
63
+ return [moved, blocked].filter((line) => line !== undefined).join(` `);
64
+ };
65
+
66
+ // Row for a spawned child's work gone into its parent's checkout, or held off it by a clash with the parent's edits.
67
+ const intoParentRow = (event: Extract<AgentEvent, { kind: "landed" }>): TranscriptRow => {
68
+ if (event.landed) {
69
+ return {
70
+ role: "notice",
71
+ text: `Changes went into the parent agent's checkout, as its in-process subagents' edits do: they reach your workspace with its land.`,
72
+ noticeCode: noticeCode({ code: "intoParent" }),
73
+ };
74
+ }
75
+ const files = (event.conflicts ?? []).flatMap((conflict) => conflict.paths).length;
76
+ return {
77
+ role: "notice",
78
+ text: `${files} file(s) clash with the parent agent's own edits, so nothing was written into its checkout. The parent was told, and can bring the changes in to resolve.`,
79
+ noticeCode: noticeCode({ code: "intoParentClash", params: { files } }),
80
+ };
81
+ };
82
+
83
+ // Row for a finished turn: held on its branch, landed automatically, or conflicted. A landed turn that changed
84
+ // dependencies appends dependencyLine, since the Changes diff can't show that.
85
+ export const landedRow = (event: Extract<AgentEvent, { kind: "landed" }>): TranscriptRow => {
86
+ if (event.into !== undefined) {
87
+ return intoParentRow(event);
88
+ }
89
+ if (event.held === true) {
90
+ return { role: "notice", text: `Finished: the work is on this agent's branch, ready to land from its review.`, noticeCode: noticeCode({ code: "landHeld" }) };
91
+ }
92
+ if (!event.landed) {
93
+ // Per-file cause (your edits, a moved main line, a binary) is spelled out in the review, not named here.
94
+ const conflicts = event.conflicts ?? [];
95
+ const files = conflicts.flatMap((conflict) => conflict.paths).length;
96
+ const repos = conflicts.map((conflict) => conflict.repo).join(`, `);
97
+ return {
98
+ role: "notice",
99
+ text: `${files} file(s) couldn't land automatically in ${repos}. Open the agent's review to see what blocked them and land from there.`,
100
+ noticeCode: noticeCode({ code: "landConflict", params: { files, repos } }),
101
+ };
102
+ }
103
+ // noticeAction fires only here, right as the auto-behavior ran; an active install takes the slot instead.
104
+ const deps = event.deps === undefined || event.deps.missing === 0 ? undefined : event.deps;
105
+ return {
106
+ role: "notice",
107
+ text: `Changes landed in your workspace: review them in the Changes panel.${dependencyLine(event.deps)}`,
108
+ noticeAction: (event.deps?.started.length ?? 0) > 0 ? "depsInstall" : "landHold",
109
+ noticeCode: noticeCode({ code: "landed", params: deps === undefined ? undefined : { deps: deps.missing, queued: deps.deferred || undefined } }),
110
+ };
111
+ };
@@ -1,5 +1,5 @@
1
1
  import { STATE_DIR } from "@intentic/constants";
2
- import type { AgentEvent } from "../events/agent-events.js";
2
+ import { type AgentErrorCode, AgentErrorCodeSchema, type AgentEvent } from "../events/agent-events.js";
3
3
  import { type TranscriptPatch, type TranscriptRow, TranscriptRowSchema } from "../events/transcript.js";
4
4
  import { sandboxNoticeOf } from "../events/sandbox-notice.js";
5
5
  import { watchWakePrompt } from "../events/watch-wake.js";
@@ -113,6 +113,22 @@ describe("foldTurn", () => {
113
113
  ]);
114
114
  });
115
115
 
116
+ // What a reader needs once nothing in memory remembers the subagent: the card the turn settles into keeps it.
117
+ it("keeps where the runtime holds an in-process subagent's own record on its card", () => {
118
+ const events: AgentEvent[] = [
119
+ { kind: "tool_call", id: "task-1", name: "Agent", category: "other", status: "in_progress" },
120
+ { kind: "subagent", id: "task-1", subagentKind: "subagent", agentType: "Explore" },
121
+ { kind: "subagent_update", id: "task-1", sdk: { sessionId: "ses-1", agentId: "a1b2" } },
122
+ { kind: "subagent_update", id: "task-1", status: "completed" },
123
+ ];
124
+ expect(foldOf("delegate", events).at(-1)?.tools?.[0]?.subagent).toEqual({
125
+ kind: "subagent",
126
+ agentType: "Explore",
127
+ status: "completed",
128
+ sdk: { sessionId: "ses-1", agentId: "a1b2" },
129
+ });
130
+ });
131
+
116
132
  // A spawned subagent is named by its own conversation, not the call that started it; the spawn answers with that
117
133
  // name, which is how its card is found, so it reads on its card exactly as an in-process one does.
118
134
  it("puts a spawned subagent on the card of the call whose result names it", () => {
@@ -290,7 +306,8 @@ describe("foldTurn", () => {
290
306
  expect(foldOf("hi", events)).toEqual([
291
307
  { role: "user", text: "hi", sentAt: SENT_AT },
292
308
  { role: "assistant", text: "I'll take a look" },
293
- { role: "notice", text: refusal },
309
+ // A failure nobody named is still a failure: coded, the unclassified kind, never bare words.
310
+ { role: "notice", text: refusal, noticeCode: { code: "failed", params: { message: refusal } } },
294
311
  ]);
295
312
  const outage: AgentEvent[] = [
296
313
  {
@@ -333,11 +350,16 @@ describe("foldTurn", () => {
333
350
  noticeCode: { code: "renewalWithdrawn", params: { message: "Token refused.", error: "claude-token-refused" } },
334
351
  };
335
352
  // The live window hears the rewrite as a patch on the row it already drew, then the stop's own row.
353
+ const stop: TranscriptRow = {
354
+ role: "notice",
355
+ text: "Claude Code returned an error result: 401.",
356
+ noticeCode: { code: "failed", params: { message: "Claude Code returned an error result: 401." } },
357
+ };
336
358
  expect(patches).toEqual([
337
359
  { op: "replace", index: 1, row: withdrawn },
338
- { op: "append", row: { role: "notice", text: "Claude Code returned an error result: 401." } },
360
+ { op: "append", row: stop },
339
361
  ]);
340
- expect(fold.rows.slice(1)).toEqual([withdrawn, { role: "notice", text: "Claude Code returned an error result: 401." }]);
362
+ expect(fold.rows.slice(1)).toEqual([withdrawn, stop]);
341
363
  // A renewal armed again keeps its promise.
342
364
  const again = new TranscriptFold(openingOf("hi"));
343
365
  again.apply(refused);
@@ -931,6 +953,57 @@ describe(`a refusal that ran nothing`, () => {
931
953
  expect(fold.ranNothing).toBe(false);
932
954
  });
933
955
 
956
+ // The reported row (onboarding review 2026-10-18, F07): the trial ran out on a later step of a turn that had already
957
+ // edited a file, and the row said the message was not delivered and held to send again. Neither was true: the turn
958
+ // ran, nothing holds its words (turn-runs keeps only a turn turned away at the door), and a resend repeats its edits.
959
+ describe(`arriving after the turn already ran`, () => {
960
+ const SPENT = {
961
+ kind: `error`,
962
+ code: `trial-exhausted`,
963
+ message: `Free trial used up for today: every free agent step is spent. Connect a model to keep going.`,
964
+ } as const satisfies AgentEvent;
965
+ const EDITED: readonly AgentEvent[] = [
966
+ { kind: `tool_call`, id: `t1`, name: `Edit`, category: `edit`, status: `completed`, target: `src/page.tsx` },
967
+ ];
968
+
969
+ it(`says the run stopped partway with its changes kept, and offers another model rather than a resend`, () => {
970
+ const fold = new TranscriptFold(openingOf(`change the heading`));
971
+ for (const event of EDITED) {
972
+ fold.apply(event);
973
+ }
974
+ fold.apply(SPENT);
975
+
976
+ expect(fold.rows.map((row) => row.role)).toEqual([`user`, `assistant`, `notice`]);
977
+ expect(fold.rows.at(-1)).toEqual({
978
+ role: `notice`,
979
+ text: `${SPENT.message} The run stopped partway. The changes it made so far are kept: continue with another model to pick up from here.`,
980
+ noticeAction: `switchModel`,
981
+ noticeCode: { code: `stoppedPartway`, params: { message: SPENT.message, error: `trial-exhausted`, switchModel: true } },
982
+ });
983
+ expect(fold.ranNothing).toBe(false);
984
+ });
985
+
986
+ // Another model does not clear a full disk; the row still never claims the words are held.
987
+ it(`offers no model switch for a refusal another model would not get past`, () => {
988
+ const disk = { kind: `error`, code: `sandbox-disk-low`, message: `The sandbox's disk is full.` } as const satisfies AgentEvent;
989
+ expect(foldOf(`change the heading`, [...EDITED, disk]).at(-1)).toEqual({
990
+ role: `notice`,
991
+ text: `${disk.message} The run stopped partway. The changes it made so far are kept: send a message to pick up from here.`,
992
+ noticeCode: { code: `stoppedPartway`, params: { message: disk.message, error: `sandbox-disk-low` } },
993
+ });
994
+ });
995
+
996
+ it(`keeps the not-delivered row for the same refusal when nothing had run`, () => {
997
+ expect(foldOf(`change the heading`, [SPENT])).toEqual([
998
+ {
999
+ role: `notice`,
1000
+ text: `${SPENT.message} Your message was not delivered: it is held for you to send again.`,
1001
+ noticeCode: { code: `undelivered`, params: { message: SPENT.message, error: `trial-exhausted` } },
1002
+ },
1003
+ ]);
1004
+ });
1005
+ });
1006
+
934
1007
  // The sandbox, not a composer, keeps a turn it started itself (a fix press, a peer's message): the frame says so
935
1008
  // with `held`. The bug this exists for: such a refusal took the fix prompt out, and no queue anywhere held a copy.
936
1009
  describe(`of a turn the sandbox keeps`, () => {
@@ -1029,14 +1102,23 @@ describe(`the code beside a notice's words`, () => {
1029
1102
  });
1030
1103
  });
1031
1104
 
1032
- it(`codes a failure the sandbox names, and leaves a provider's own words to themselves`, () => {
1105
+ it(`codes every failure, says who worded it, and names an unnamed one as unclassified`, () => {
1033
1106
  const busy = `This agent is already running a turn, wait for it to finish.`;
1034
- expect(lastOf([{ kind: `error`, code: `agent-busy`, message: busy }])).toEqual({
1107
+ expect(lastOf([{ kind: `error`, code: `agent-busy`, message: busy, own: true }])).toEqual({
1035
1108
  role: `notice`,
1036
1109
  text: busy,
1037
- noticeCode: { code: `failed`, params: { message: busy, error: `agent-busy` } },
1110
+ noticeCode: { code: `failed`, params: { message: busy, error: `agent-busy`, own: true } },
1111
+ });
1112
+ expect(lastOf([{ kind: `error`, message: `Overloaded.` }])).toEqual({
1113
+ role: `notice`,
1114
+ text: `Overloaded.`,
1115
+ noticeCode: { code: `failed`, params: { message: `Overloaded.` } },
1116
+ });
1117
+ // A spent allowance is a failure like any other: it was the one code left as bare words (2026-10-20: 41 rows a week).
1118
+ expect(lastOf([{ kind: `error`, code: `rate_limit`, message: `You've hit your usage limit.` }])?.noticeCode).toEqual({
1119
+ code: `failed`,
1120
+ params: { message: `You've hit your usage limit.`, error: `rate_limit` },
1038
1121
  });
1039
- expect(lastOf([{ kind: `error`, message: `Overloaded.` }])).toEqual({ role: `notice`, text: `Overloaded.` });
1040
1122
  expect(lastOf([{ kind: `error`, code: `claude-token-refused`, message: `Token refused.` }])).toEqual({
1041
1123
  role: `notice`,
1042
1124
  text: `Token refused. Reconnect the account to pick this conversation back up.`,
@@ -1060,10 +1142,24 @@ describe(`the code beside a notice's words`, () => {
1060
1142
  const rows = events.flatMap((turn) => foldTurn(openingOf(`go`), turn, `stopped`)).filter((row) => row.noticeCode !== undefined);
1061
1143
  expect(rows.filter((row) => sandboxNoticeOf(row) === undefined)).toEqual([]);
1062
1144
  expect(new Set(rows.map((row) => row.noticeCode?.code))).toEqual(
1063
- new Set([`synced`, `compacted`, `intoParent`, `intoParentClash`, `landHeld`, `outageWaiting`, `memoryHeld`, `kept`, `renewalWithdrawn`, `stopped`]),
1145
+ new Set([`synced`, `compacted`, `intoParent`, `intoParentClash`, `landHeld`, `outageWaiting`, `memoryHeld`, `kept`, `renewalWithdrawn`, `failed`, `stopped`]),
1064
1146
  );
1065
1147
  });
1066
1148
 
1149
+ // Every failure code the wire names, and none at all, folds to a failure row this build reads back: the class of row
1150
+ // a reader had to recognise by its words cannot be written. A code added to the wire is held to it here.
1151
+ it(`folds every failure, attended or not, ran or not, to a coded failure row`, () => {
1152
+ const codes: readonly (AgentErrorCode | undefined)[] = [...AgentErrorCodeSchema.options, undefined];
1153
+ const ran: AgentEvent = { kind: `tool_call`, id: `t1`, name: `Edit`, category: `edit`, status: `completed` };
1154
+ for (const code of codes) {
1155
+ const frame: AgentEvent = { kind: `error`, message: `Words.`, code };
1156
+ for (const events of [[frame], [{ ...frame, unattended: true }], [{ ...frame, held: { ran: false } }], [ran, frame]]) {
1157
+ const failure = foldTurn(openingOf(`go`), events, `settled`).at(-1);
1158
+ expect({ code, coded: failure?.role === `notice` && sandboxNoticeOf(failure)?.code !== undefined }).toEqual({ code, coded: true });
1159
+ }
1160
+ }
1161
+ });
1162
+
1067
1163
  // A newer sandbox's code must not cost an older app the whole page: the row parses, and the reader draws its words.
1068
1164
  it(`reads a row whose code this build does not know, and one written before rows carried codes`, () => {
1069
1165
  const newer: TranscriptRow = { role: `notice`, text: `Something new happened.`, noticeCode: { code: `somethingNew`, params: { count: 2 } } };