@intentic/sandbox-contract 1.331.0 → 1.333.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 (416) hide show
  1. package/README.md +23 -1
  2. package/dist/chores/probes.d.ts.map +1 -1
  3. package/dist/chores/probes.js.map +1 -1
  4. package/dist/contracts/accounts.contract.d.ts +11 -0
  5. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.d.ts +38 -0
  7. package/dist/contracts/agent.contract.d.ts.map +1 -1
  8. package/dist/contracts/agent.contract.js +14 -3
  9. package/dist/contracts/agent.contract.js.map +1 -1
  10. package/dist/contracts/agents.contract.d.ts +29 -0
  11. package/dist/contracts/agents.contract.d.ts.map +1 -1
  12. package/dist/contracts/automations.contract.d.ts +3 -0
  13. package/dist/contracts/automations.contract.d.ts.map +1 -1
  14. package/dist/contracts/device.contract.d.ts +7 -0
  15. package/dist/contracts/device.contract.d.ts.map +1 -1
  16. package/dist/contracts/git.contract.d.ts +2 -0
  17. package/dist/contracts/git.contract.d.ts.map +1 -1
  18. package/dist/contracts/logs.contract.d.ts +10 -0
  19. package/dist/contracts/logs.contract.d.ts.map +1 -1
  20. package/dist/contracts/needs.contract.d.ts +36 -1
  21. package/dist/contracts/needs.contract.d.ts.map +1 -1
  22. package/dist/contracts/needs.contract.js +2 -2
  23. package/dist/contracts/needs.contract.js.map +1 -1
  24. package/dist/contracts/offload.contract.d.ts +70 -0
  25. package/dist/contracts/offload.contract.d.ts.map +1 -1
  26. package/dist/contracts/offload.contract.js +10 -1
  27. package/dist/contracts/offload.contract.js.map +1 -1
  28. package/dist/contracts/privacy.contract.d.ts +14 -0
  29. package/dist/contracts/privacy.contract.d.ts.map +1 -1
  30. package/dist/contracts/privacy.contract.js +11 -1
  31. package/dist/contracts/privacy.contract.js.map +1 -1
  32. package/dist/contracts/providers.contract.d.ts +3 -0
  33. package/dist/contracts/providers.contract.d.ts.map +1 -1
  34. package/dist/contracts/runner.contract.d.ts +103 -2
  35. package/dist/contracts/runner.contract.d.ts.map +1 -1
  36. package/dist/contracts/runner.contract.js +2 -2
  37. package/dist/contracts/runner.contract.js.map +1 -1
  38. package/dist/contracts/sessions.contract.d.ts +1 -0
  39. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  40. package/dist/contracts/settings.contract.d.ts +106 -94
  41. package/dist/contracts/settings.contract.d.ts.map +1 -1
  42. package/dist/contracts/settings.contract.js +19 -1
  43. package/dist/contracts/settings.contract.js.map +1 -1
  44. package/dist/contracts/system.contract.d.ts +17 -0
  45. package/dist/contracts/system.contract.d.ts.map +1 -1
  46. package/dist/contracts/translator.contract.d.ts +4 -0
  47. package/dist/contracts/translator.contract.d.ts.map +1 -1
  48. package/dist/events/agent-events.d.ts +46 -0
  49. package/dist/events/agent-events.d.ts.map +1 -1
  50. package/dist/events/agent-events.js +6 -0
  51. package/dist/events/agent-events.js.map +1 -1
  52. package/dist/events/agent-words.d.ts.map +1 -1
  53. package/dist/events/agent-words.js +1 -1
  54. package/dist/events/agent-words.js.map +1 -1
  55. package/dist/events/resume.d.ts +3 -0
  56. package/dist/events/resume.d.ts.map +1 -1
  57. package/dist/events/resume.js +17 -2
  58. package/dist/events/resume.js.map +1 -1
  59. package/dist/events/sandbox-notice.d.ts +18 -0
  60. package/dist/events/sandbox-notice.d.ts.map +1 -1
  61. package/dist/events/sandbox-notice.js +27 -1
  62. package/dist/events/sandbox-notice.js.map +1 -1
  63. package/dist/events/system-events.d.ts +2 -0
  64. package/dist/events/system-events.d.ts.map +1 -1
  65. package/dist/events/transcript.d.ts +64 -0
  66. package/dist/events/transcript.d.ts.map +1 -1
  67. package/dist/events/transcript.js +17 -1
  68. package/dist/events/transcript.js.map +1 -1
  69. package/dist/index.d.ts +427 -167
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +12 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/models/agent-catalog.d.ts.map +1 -1
  74. package/dist/models/agent-catalog.js +8 -7
  75. package/dist/models/agent-catalog.js.map +1 -1
  76. package/dist/models/agent-runtimes.d.ts +3 -1
  77. package/dist/models/agent-runtimes.d.ts.map +1 -1
  78. package/dist/models/agent-runtimes.js +10 -0
  79. package/dist/models/agent-runtimes.js.map +1 -1
  80. package/dist/models/model-order.d.ts +1 -0
  81. package/dist/models/model-order.d.ts.map +1 -1
  82. package/dist/models/model-order.js +9 -0
  83. package/dist/models/model-order.js.map +1 -1
  84. package/dist/models/model-pins.d.ts.map +1 -1
  85. package/dist/models/model-pins.js.map +1 -1
  86. package/dist/models/model-roles.d.ts +33 -18
  87. package/dist/models/model-roles.d.ts.map +1 -1
  88. package/dist/models/model-roles.js +31 -27
  89. package/dist/models/model-roles.js.map +1 -1
  90. package/dist/models/model-setups.d.ts +46 -0
  91. package/dist/models/model-setups.d.ts.map +1 -0
  92. package/dist/models/model-setups.js +86 -0
  93. package/dist/models/model-setups.js.map +1 -0
  94. package/dist/models/plan-pools.d.ts +3 -1
  95. package/dist/models/plan-pools.d.ts.map +1 -1
  96. package/dist/models/plan-pools.js +18 -6
  97. package/dist/models/plan-pools.js.map +1 -1
  98. package/dist/models/provider-specs.d.ts +20 -0
  99. package/dist/models/provider-specs.d.ts.map +1 -1
  100. package/dist/models/provider-specs.js +13 -1
  101. package/dist/models/provider-specs.js.map +1 -1
  102. package/dist/policy/command-classes.d.ts.map +1 -1
  103. package/dist/policy/command-classes.js +26 -3
  104. package/dist/policy/command-classes.js.map +1 -1
  105. package/dist/policy/control-scopes.d.ts +47 -0
  106. package/dist/policy/control-scopes.d.ts.map +1 -1
  107. package/dist/policy/control-scopes.js +16 -0
  108. package/dist/policy/control-scopes.js.map +1 -1
  109. package/dist/policy/credential-material.d.ts +6 -0
  110. package/dist/policy/credential-material.d.ts.map +1 -1
  111. package/dist/policy/credential-material.js +37 -15
  112. package/dist/policy/credential-material.js.map +1 -1
  113. package/dist/policy/device-programs.d.ts +28 -0
  114. package/dist/policy/device-programs.d.ts.map +1 -0
  115. package/dist/policy/device-programs.js +79 -0
  116. package/dist/policy/device-programs.js.map +1 -0
  117. package/dist/policy/identity-assertion.d.ts +27 -0
  118. package/dist/policy/identity-assertion.d.ts.map +1 -0
  119. package/dist/policy/identity-assertion.js +77 -0
  120. package/dist/policy/identity-assertion.js.map +1 -0
  121. package/dist/policy/sandbox-power.d.ts +34 -0
  122. package/dist/policy/sandbox-power.d.ts.map +1 -0
  123. package/dist/policy/sandbox-power.js +146 -0
  124. package/dist/policy/sandbox-power.js.map +1 -0
  125. package/dist/policy/search-globs.d.ts +3 -0
  126. package/dist/policy/search-globs.d.ts.map +1 -1
  127. package/dist/policy/search-globs.js +118 -0
  128. package/dist/policy/search-globs.js.map +1 -1
  129. package/dist/policy/secret-hosts.d.ts +1 -0
  130. package/dist/policy/secret-hosts.d.ts.map +1 -1
  131. package/dist/policy/secret-hosts.js +1 -0
  132. package/dist/policy/secret-hosts.js.map +1 -1
  133. package/dist/policy/turned-away.d.ts.map +1 -1
  134. package/dist/policy/turned-away.js +1 -0
  135. package/dist/policy/turned-away.js.map +1 -1
  136. package/dist/protocol/doors/runner-protocol.d.ts +45 -0
  137. package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
  138. package/dist/protocol/doors/runner-protocol.js +7 -0
  139. package/dist/protocol/doors/runner-protocol.js.map +1 -1
  140. package/dist/protocol/pairing-redeem.d.ts +23 -0
  141. package/dist/protocol/pairing-redeem.d.ts.map +1 -0
  142. package/dist/protocol/pairing-redeem.js +44 -0
  143. package/dist/protocol/pairing-redeem.js.map +1 -0
  144. package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
  145. package/dist/protocol/peer-mcp-server.js +1 -1
  146. package/dist/protocol/peer-mcp-server.js.map +1 -1
  147. package/dist/protocol/raw/raw-json-routes.d.ts +855 -1
  148. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  149. package/dist/protocol/raw/raw-json-routes.js +61 -1
  150. package/dist/protocol/raw/raw-json-routes.js.map +1 -1
  151. package/dist/protocol/raw/raw-routes.d.ts +17 -0
  152. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  153. package/dist/protocol/raw/raw-routes.js +5 -0
  154. package/dist/protocol/raw/raw-routes.js.map +1 -1
  155. package/dist/protocol/sse.d.ts +20 -1
  156. package/dist/protocol/sse.d.ts.map +1 -1
  157. package/dist/protocol/sse.js +112 -17
  158. package/dist/protocol/sse.js.map +1 -1
  159. package/dist/schemas/agent.d.ts +35 -0
  160. package/dist/schemas/agent.d.ts.map +1 -1
  161. package/dist/schemas/agent.js +12 -1
  162. package/dist/schemas/agent.js.map +1 -1
  163. package/dist/schemas/automations.d.ts +7 -0
  164. package/dist/schemas/automations.d.ts.map +1 -1
  165. package/dist/schemas/automations.js +11 -3
  166. package/dist/schemas/automations.js.map +1 -1
  167. package/dist/schemas/device-swaps.d.ts +19 -0
  168. package/dist/schemas/device-swaps.d.ts.map +1 -0
  169. package/dist/schemas/device-swaps.js +11 -0
  170. package/dist/schemas/device-swaps.js.map +1 -0
  171. package/dist/schemas/devices.d.ts +17 -0
  172. package/dist/schemas/devices.d.ts.map +1 -1
  173. package/dist/schemas/devices.js +2 -1
  174. package/dist/schemas/devices.js.map +1 -1
  175. package/dist/schemas/engines.d.ts +8 -1
  176. package/dist/schemas/engines.d.ts.map +1 -1
  177. package/dist/schemas/engines.js +1 -1
  178. package/dist/schemas/engines.js.map +1 -1
  179. package/dist/schemas/environment.d.ts +7 -0
  180. package/dist/schemas/environment.d.ts.map +1 -1
  181. package/dist/schemas/environment.js +2 -0
  182. package/dist/schemas/environment.js.map +1 -1
  183. package/dist/schemas/git/git-scope.d.ts +20 -0
  184. package/dist/schemas/git/git-scope.d.ts.map +1 -0
  185. package/dist/schemas/git/git-scope.js +37 -0
  186. package/dist/schemas/git/git-scope.js.map +1 -0
  187. package/dist/schemas/git/git.d.ts +1 -0
  188. package/dist/schemas/git/git.d.ts.map +1 -1
  189. package/dist/schemas/git/git.js +9 -1
  190. package/dist/schemas/git/git.js.map +1 -1
  191. package/dist/schemas/hosts.d.ts +42 -0
  192. package/dist/schemas/hosts.d.ts.map +1 -1
  193. package/dist/schemas/hosts.js +3 -0
  194. package/dist/schemas/hosts.js.map +1 -1
  195. package/dist/schemas/keep-warm.d.ts +1 -0
  196. package/dist/schemas/keep-warm.d.ts.map +1 -1
  197. package/dist/schemas/keep-warm.js +1 -1
  198. package/dist/schemas/keep-warm.js.map +1 -1
  199. package/dist/schemas/logs.d.ts +21 -0
  200. package/dist/schemas/logs.d.ts.map +1 -1
  201. package/dist/schemas/logs.js +19 -0
  202. package/dist/schemas/logs.js.map +1 -1
  203. package/dist/schemas/members.d.ts +4 -0
  204. package/dist/schemas/members.d.ts.map +1 -1
  205. package/dist/schemas/members.js +5 -0
  206. package/dist/schemas/members.js.map +1 -1
  207. package/dist/schemas/metrics.d.ts +12 -0
  208. package/dist/schemas/metrics.d.ts.map +1 -1
  209. package/dist/schemas/metrics.js +26 -8
  210. package/dist/schemas/metrics.js.map +1 -1
  211. package/dist/schemas/needs.d.ts +97 -1
  212. package/dist/schemas/needs.d.ts.map +1 -1
  213. package/dist/schemas/needs.js +53 -4
  214. package/dist/schemas/needs.js.map +1 -1
  215. package/dist/schemas/offload.d.ts +154 -0
  216. package/dist/schemas/offload.d.ts.map +1 -1
  217. package/dist/schemas/offload.js +34 -0
  218. package/dist/schemas/offload.js.map +1 -1
  219. package/dist/schemas/privacy.d.ts +19 -0
  220. package/dist/schemas/privacy.d.ts.map +1 -1
  221. package/dist/schemas/privacy.js +17 -2
  222. package/dist/schemas/privacy.js.map +1 -1
  223. package/dist/schemas/project-bring-back.d.ts +172 -0
  224. package/dist/schemas/project-bring-back.d.ts.map +1 -0
  225. package/dist/schemas/project-bring-back.js +39 -0
  226. package/dist/schemas/project-bring-back.js.map +1 -0
  227. package/dist/schemas/providers/handoff.d.ts +77 -0
  228. package/dist/schemas/providers/handoff.d.ts.map +1 -0
  229. package/dist/schemas/providers/handoff.js +85 -0
  230. package/dist/schemas/providers/handoff.js.map +1 -0
  231. package/dist/schemas/providers/plan-limits.d.ts +27 -0
  232. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  233. package/dist/schemas/providers/plan-limits.js +8 -1
  234. package/dist/schemas/providers/plan-limits.js.map +1 -1
  235. package/dist/schemas/providers/provider-oauth.d.ts +4 -0
  236. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  237. package/dist/schemas/providers/usage.d.ts +3 -1
  238. package/dist/schemas/providers/usage.d.ts.map +1 -1
  239. package/dist/schemas/providers/usage.js +7 -2
  240. package/dist/schemas/providers/usage.js.map +1 -1
  241. package/dist/schemas/restart-intent.d.ts +47 -0
  242. package/dist/schemas/restart-intent.d.ts.map +1 -0
  243. package/dist/schemas/restart-intent.js +19 -0
  244. package/dist/schemas/restart-intent.js.map +1 -0
  245. package/dist/schemas/run-targets.d.ts.map +1 -1
  246. package/dist/schemas/run-targets.js +5 -2
  247. package/dist/schemas/run-targets.js.map +1 -1
  248. package/dist/schemas/settings-history.d.ts +1 -1
  249. package/dist/schemas/settings-history.d.ts.map +1 -1
  250. package/dist/schemas/settings-history.js +21 -0
  251. package/dist/schemas/settings-history.js.map +1 -1
  252. package/dist/schemas/settings.d.ts +101 -100
  253. package/dist/schemas/settings.d.ts.map +1 -1
  254. package/dist/schemas/settings.js +43 -24
  255. package/dist/schemas/settings.js.map +1 -1
  256. package/dist/schemas/short-codes.d.ts +47 -0
  257. package/dist/schemas/short-codes.d.ts.map +1 -0
  258. package/dist/schemas/short-codes.js +30 -0
  259. package/dist/schemas/short-codes.js.map +1 -0
  260. package/dist/schemas/speech.d.ts +4 -0
  261. package/dist/schemas/speech.d.ts.map +1 -1
  262. package/dist/schemas/speech.js +1 -0
  263. package/dist/schemas/speech.js.map +1 -1
  264. package/dist/schemas/system.d.ts +176 -0
  265. package/dist/schemas/system.d.ts.map +1 -1
  266. package/dist/schemas/system.js +55 -2
  267. package/dist/schemas/system.js.map +1 -1
  268. package/dist/schemas/voice.d.ts +171 -0
  269. package/dist/schemas/voice.d.ts.map +1 -0
  270. package/dist/schemas/voice.js +99 -0
  271. package/dist/schemas/voice.js.map +1 -0
  272. package/dist/schemas/workspace/workspace-upload.d.ts +14 -0
  273. package/dist/schemas/workspace/workspace-upload.d.ts.map +1 -0
  274. package/dist/schemas/workspace/workspace-upload.js +6 -0
  275. package/dist/schemas/workspace/workspace-upload.js.map +1 -0
  276. package/dist/state/arrival.d.ts +3 -0
  277. package/dist/state/arrival.d.ts.map +1 -1
  278. package/dist/state/arrival.js +1 -0
  279. package/dist/state/arrival.js.map +1 -1
  280. package/dist/state/definition.d.ts +112 -40
  281. package/dist/state/definition.d.ts.map +1 -1
  282. package/dist/state/history-state.d.ts.map +1 -1
  283. package/dist/state/history-state.js +3 -0
  284. package/dist/state/history-state.js.map +1 -1
  285. package/dist/state/runtime-state.d.ts +5 -2
  286. package/dist/state/runtime-state.d.ts.map +1 -1
  287. package/dist/state/runtime-state.js +3 -2
  288. package/dist/state/runtime-state.js.map +1 -1
  289. package/dist/state/update-probation.d.ts +50 -0
  290. package/dist/state/update-probation.d.ts.map +1 -0
  291. package/dist/state/update-probation.js +81 -0
  292. package/dist/state/update-probation.js.map +1 -0
  293. package/dist/state/versions.d.ts +6 -0
  294. package/dist/state/versions.d.ts.map +1 -1
  295. package/dist/state/versions.js +47 -0
  296. package/dist/state/versions.js.map +1 -1
  297. package/dist/state/workspace-state.d.ts +1 -1
  298. package/dist/state/workspace-state.d.ts.map +1 -1
  299. package/dist/state/workspace-state.js +1 -1
  300. package/dist/state/workspace-state.js.map +1 -1
  301. package/dist/text/embed.d.ts +2 -1
  302. package/dist/text/embed.d.ts.map +1 -1
  303. package/dist/text/embed.js +10 -5
  304. package/dist/text/embed.js.map +1 -1
  305. package/dist/text/title.d.ts.map +1 -1
  306. package/dist/text/title.js +4 -7
  307. package/dist/text/title.js.map +1 -1
  308. package/dist/text/transcript-fold.d.ts +2 -1
  309. package/dist/text/transcript-fold.d.ts.map +1 -1
  310. package/dist/text/transcript-fold.js +21 -6
  311. package/dist/text/transcript-fold.js.map +1 -1
  312. package/package.json +39 -6
  313. package/src/chores/probes.ts +3 -1
  314. package/src/contracts/agent.contract.ts +24 -3
  315. package/src/contracts/needs.contract.ts +2 -2
  316. package/src/contracts/offload.contract.ts +21 -1
  317. package/src/contracts/privacy.contract.ts +13 -0
  318. package/src/contracts/runner.contract.ts +10 -2
  319. package/src/contracts/settings.contract.ts +23 -0
  320. package/src/events/agent-events.ts +13 -0
  321. package/src/events/agent-words.ts +2 -3
  322. package/src/events/resume.test.ts +14 -2
  323. package/src/events/resume.ts +32 -3
  324. package/src/events/sandbox-notice.ts +31 -1
  325. package/src/events/transcript.ts +27 -2
  326. package/src/ids/tunnel-ids.test.ts +30 -0
  327. package/src/index.ts +12 -0
  328. package/src/models/agent-catalog.test.ts +4 -3
  329. package/src/models/agent-catalog.ts +10 -7
  330. package/src/models/agent-runtimes.ts +30 -1
  331. package/src/models/capability-ledger.test.ts +4 -0
  332. package/src/models/model-order.ts +13 -0
  333. package/src/models/model-pins.ts +5 -4
  334. package/src/models/model-roles.test.ts +9 -31
  335. package/src/models/model-roles.ts +45 -50
  336. package/src/models/model-setups.test.ts +100 -0
  337. package/src/models/model-setups.ts +151 -0
  338. package/src/models/plan-pools.test.ts +58 -0
  339. package/src/models/plan-pools.ts +38 -7
  340. package/src/models/provider-specs.ts +20 -2
  341. package/src/policy/command-classes.ts +34 -3
  342. package/src/policy/control-scopes.ts +27 -0
  343. package/src/policy/credential-material.ts +58 -16
  344. package/src/policy/device-programs.ts +137 -0
  345. package/src/policy/identity-assertion.ts +130 -0
  346. package/src/policy/sandbox-power.ts +247 -0
  347. package/src/policy/search-globs.ts +135 -0
  348. package/src/policy/secret-hosts.ts +7 -0
  349. package/src/policy/tests/command-classes.test.ts +35 -0
  350. package/src/policy/tests/credential-material.test.ts +20 -0
  351. package/src/policy/tests/device-programs.test.ts +107 -0
  352. package/src/policy/tests/identity-assertion.test.ts +79 -0
  353. package/src/policy/tests/sandbox-power.test.ts +181 -0
  354. package/src/policy/tests/search-globs.test.ts +46 -1
  355. package/src/policy/tests/secret-hosts.test.ts +17 -1
  356. package/src/policy/turned-away.ts +2 -0
  357. package/src/protocol/doors/runner-protocol.ts +21 -1
  358. package/src/protocol/pairing-redeem.test.ts +86 -0
  359. package/src/protocol/pairing-redeem.ts +79 -0
  360. package/src/protocol/peer-mcp-server.ts +1 -2
  361. package/src/protocol/raw/raw-json-routes.test.ts +87 -0
  362. package/src/protocol/raw/raw-json-routes.ts +127 -5
  363. package/src/protocol/raw/raw-routes.ts +11 -0
  364. package/src/protocol/sse.test.ts +50 -1
  365. package/src/protocol/sse.ts +172 -26
  366. package/src/schemas/agent.ts +23 -2
  367. package/src/schemas/automations.ts +16 -5
  368. package/src/schemas/daemon-health.test.ts +28 -0
  369. package/src/schemas/device-swaps.test.ts +39 -0
  370. package/src/schemas/device-swaps.ts +26 -0
  371. package/src/schemas/devices.ts +11 -2
  372. package/src/schemas/engines.ts +1 -1
  373. package/src/schemas/environment.ts +4 -0
  374. package/src/schemas/git/git-scope.test.ts +91 -0
  375. package/src/schemas/git/git-scope.ts +71 -0
  376. package/src/schemas/git/git.ts +15 -1
  377. package/src/schemas/hosts.ts +7 -0
  378. package/src/schemas/keep-warm.ts +2 -1
  379. package/src/schemas/logs.ts +25 -0
  380. package/src/schemas/members.ts +8 -0
  381. package/src/schemas/metrics.test.ts +80 -0
  382. package/src/schemas/metrics.ts +31 -9
  383. package/src/schemas/needs.ts +80 -4
  384. package/src/schemas/offload.ts +76 -0
  385. package/src/schemas/privacy.ts +27 -3
  386. package/src/schemas/project-bring-back.test.ts +15 -0
  387. package/src/schemas/project-bring-back.ts +78 -0
  388. package/src/schemas/providers/handoff.test.ts +42 -0
  389. package/src/schemas/providers/handoff.ts +128 -0
  390. package/src/schemas/providers/plan-limits.ts +14 -1
  391. package/src/schemas/providers/usage.ts +13 -7
  392. package/src/schemas/restart-intent.test.ts +45 -0
  393. package/src/schemas/restart-intent.ts +47 -0
  394. package/src/schemas/run-targets.ts +5 -2
  395. package/src/schemas/settings-history.ts +28 -0
  396. package/src/schemas/settings.test.ts +74 -21
  397. package/src/schemas/settings.ts +80 -44
  398. package/src/schemas/short-codes.ts +59 -0
  399. package/src/schemas/speech.ts +4 -0
  400. package/src/schemas/system.ts +106 -5
  401. package/src/schemas/voice.ts +161 -0
  402. package/src/schemas/workspace/workspace-upload.ts +11 -0
  403. package/src/state/arrival.ts +3 -0
  404. package/src/state/history-state.ts +9 -0
  405. package/src/state/runtime-state.test.ts +13 -0
  406. package/src/state/runtime-state.ts +11 -5
  407. package/src/state/update-probation.test.ts +45 -0
  408. package/src/state/update-probation.ts +169 -0
  409. package/src/state/versions.test.ts +30 -1
  410. package/src/state/versions.ts +68 -0
  411. package/src/state/workspace-state.ts +4 -1
  412. package/src/text/embed.test.ts +14 -0
  413. package/src/text/embed.ts +19 -10
  414. package/src/text/title.ts +5 -9
  415. package/src/text/transcript-fold.test.ts +61 -0
  416. package/src/text/transcript-fold.ts +31 -6
@@ -1,4 +1,6 @@
1
1
  import { convertDocument } from "../documents/conversions.js";
2
+ import type { ModelRole } from "../models/model-roles.js";
3
+ import { rolePins } from "../models/model-setups.js";
2
4
  import { SETTINGS_HISTORY } from "./settings-history.js";
3
5
  import { RepoChecksFileSchema, RuleSchema, SandboxSettingsSchema, SandboxSettingsWriteSchema } from "./settings.js";
4
6
 
@@ -112,6 +114,20 @@ describe(`the folded keep-warm settings`, () => {
112
114
  });
113
115
  });
114
116
 
117
+ // Tool-result clearing lost its A/B and was removed (2026-10-12); a file that still switches it on reads, without it.
118
+ test(`a stored tool-result clearing switch is dropped, and the rest of the file kept`, () => {
119
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, { iqSearch: true, toolResultClearing: true, toolResultClearingHoldout: 0.5 }, true);
120
+ expect(converted.value).toEqual({ iqSearch: true });
121
+ expect(SandboxSettingsSchema.parse(converted.value).iqSearch).toBe(true);
122
+ });
123
+
124
+ // What a job runs on, read the way every reader reads it, over a sandbox with nothing connected: the setups a stored
125
+ // file converts into are judged by what they make each job run on, not by their shape.
126
+ const runsOn = (stored: unknown, role: ModelRole): readonly unknown[] => {
127
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, stored, true);
128
+ return rolePins(SandboxSettingsSchema.parse(converted.value), role, []);
129
+ };
130
+
115
131
  // The pre-push fix's model list went with the fix a refused push used to offer (2026-09-28). A settings file that still
116
132
  // names it must read, since an unknown role refuses the whole file, and every other job's list stays as its owner set it.
117
133
  describe(`the retired pre-push fix role`, () => {
@@ -119,14 +135,8 @@ describe(`the retired pre-push fix role`, () => {
119
135
 
120
136
  test(`is dropped from a stored file, leaving every other job's list, and the file reads`, () => {
121
137
  const stored = { modelRoles: { "pipeline-fix": [pin], "pre-push-fix": [pin] } };
122
- expect(SandboxSettingsSchema.safeParse(stored).success).toBe(false);
123
- const converted = convertDocument(SETTINGS_HISTORY, `object`, stored, true);
124
- expect(converted.value).toEqual({ modelRoles: { "pipeline-fix": [pin] } });
125
- expect(SandboxSettingsSchema.parse(converted.value).modelRoles).toEqual({ "pipeline-fix": [pin] });
126
- });
127
-
128
- test(`leaves a file without it alone`, () => {
129
- expect(convertDocument(SETTINGS_HISTORY, `object`, { modelRoles: { "pipeline-fix": [pin] } }, true).changes).toEqual([]);
138
+ expect(SandboxSettingsSchema.safeParse({ roleSetups: { "pre-push-fix": `high` } }).success).toBe(false);
139
+ expect(runsOn(stored, `pipeline-fix`)).toEqual([pin]);
130
140
  });
131
141
  });
132
142
 
@@ -138,19 +148,62 @@ describe(`the new Refactors job`, () => {
138
148
 
139
149
  test(`takes the Maintenance chores list in a file from before it, and every other list stays`, () => {
140
150
  const stored = { modelRoles: { "maintenance-chore": [opus, sol], "pipeline-fix": [sol] } };
141
- const converted = convertDocument(SETTINGS_HISTORY, `object`, stored, true);
142
- expect(converted.value).toEqual({ modelRoles: { "maintenance-chore": [opus, sol], "pipeline-fix": [sol], "refactor-run": [opus, sol] } });
143
- expect(SandboxSettingsSchema.parse(converted.value).modelRoles[`refactor-run`]).toEqual([opus, sol]);
144
- });
145
-
146
- test(`leaves a list the owner set, even an emptied one, and a file with no chores list`, () => {
147
- for (const modelRoles of [
148
- { "maintenance-chore": [opus], "refactor-run": [sol] },
149
- { "maintenance-chore": [opus], "refactor-run": [] },
150
- { "pipeline-fix": [opus] },
151
- { "maintenance-chore": [] },
152
- ]) {
153
- expect(convertDocument(SETTINGS_HISTORY, `object`, { modelRoles }, true).changes).toEqual([]);
151
+ expect(runsOn(stored, `refactor-run`)).toEqual([opus, sol]);
152
+ expect(runsOn(stored, `pipeline-fix`)).toEqual([sol]);
153
+ });
154
+
155
+ test(`leaves a list the owner set, even an emptied one`, () => {
156
+ expect(runsOn({ modelRoles: { "maintenance-chore": [opus], "refactor-run": [sol] } }, `refactor-run`)).toEqual([sol]);
157
+ expect(runsOn({ modelRoles: { "maintenance-chore": [opus], "refactor-run": [] } }, `refactor-run`)).toEqual([]);
158
+ });
159
+ });
160
+
161
+ // Setups (2026-10-12): per-job lists and the default list become named setups that jobs share.
162
+ describe(`per-job model lists`, () => {
163
+ const opus = { provider: `claude`, model: `claude-opus-5-5`, effort: `high` };
164
+ const haiku = { provider: `claude`, model: `claude-haiku-4-5` };
165
+ const sol = { provider: `codex`, model: `gpt-sol-6.1` };
166
+
167
+ test(`become setups that every job runs exactly as before on`, () => {
168
+ const cases = [
169
+ [`commit-message`, [haiku]],
170
+ [`session-title`, [haiku]],
171
+ [`pipeline-fix`, [opus, sol]],
172
+ [`refactor-run`, [opus, sol]],
173
+ [`handoff-summary`, [sol, haiku]],
174
+ [`safety-judge`, []],
175
+ ] as const satisfies readonly (readonly [ModelRole, readonly unknown[]])[];
176
+ for (const [role, pins] of cases) {
177
+ expect(runsOn({ modelRoles: Object.fromEntries(cases) }, role)).toEqual(pins);
154
178
  }
155
179
  });
180
+
181
+ test(`share a setup where they held one list, which becomes the preset most of them suit`, () => {
182
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, { modelRoles: { "commit-message": [haiku], "session-title": [haiku], "pipeline-fix": [opus], "handoff-summary": [sol] } }, true);
183
+ expect(converted.value).toEqual({
184
+ modelSetups: [
185
+ { id: `low`, pins: [haiku] },
186
+ { id: `setup-1`, name: `Setup 1`, pins: [sol] },
187
+ { id: `high`, pins: [opus] },
188
+ ],
189
+ roleSetups: { "commit-message": `low`, "session-title": `low`, "handoff-summary": `setup-1`, "pipeline-fix": `high` },
190
+ });
191
+ });
192
+
193
+ test(`carry the default list to every job that had none of its own`, () => {
194
+ const stored = { modelDefault: [sol], modelRoles: { "pipeline-fix": [opus] } };
195
+ expect(runsOn(stored, `commit-message`)).toEqual([sol]);
196
+ expect(runsOn(stored, `loop-iteration`)).toEqual([sol]);
197
+ expect(runsOn(stored, `pipeline-fix`)).toEqual([opus]);
198
+ });
199
+
200
+ test(`leave a job that never had a list on none, running exactly as before`, () => {
201
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, { modelRoles: {} }, true);
202
+ expect(converted.value).toEqual({ modelSetups: [], roleSetups: {} });
203
+ expect(runsOn({ modelRoles: {} }, `safety-judge`)).toEqual([]);
204
+ });
205
+
206
+ test(`read once, and a file already in setups is left alone`, () => {
207
+ expect(convertDocument(SETTINGS_HISTORY, `object`, { modelSetups: [{ id: `low`, pins: [haiku] }], roleSetups: {} }, true).changes).toEqual([]);
208
+ });
156
209
  });
@@ -4,9 +4,13 @@ import { STATE_DIR } from "@intentic/constants";
4
4
  import { z } from "zod";
5
5
  import { CommandJudgeModeSchema, ProjectInstallModeSchema } from "../policy/safety-policy.js";
6
6
  import { ModelRoleSchema } from "../models/model-roles.js";
7
- import { AdmissionPolicySchema, AdmissionRuleSchema, ModelPinSchema } from "./agent.js";
7
+ import { MODEL_SETUP_ID, ModelSetupSchema } from "../models/model-setups.js";
8
+ import { AdmissionPolicySchema, AdmissionRuleSchema } from "./agent.js";
9
+ import { LimitHandoffSchema } from "./providers/handoff.js";
10
+ import { SessionsPlacementSchema } from "./offload.js";
8
11
  import { LimitPolicySchema, RetryPolicySchema, RoomPolicySchema } from "./turn-break.js";
9
12
  import { ZoneSchema } from "../time/zone.js";
13
+ import { VoiceSettingsSchema } from "./voice.js";
10
14
  // Which prompt base the agent runs before this turn composes anything on top: Intentic's own (default), Claude Code's
11
15
  // preset, or the owner's text. Declared out here since both the daemon and the browser branch on it.
12
16
  // Kept in the daemon's auth root, never in SandboxSettings: an agent cannot edit its execution boundary.
@@ -316,23 +320,6 @@ export const SandboxSettingsSchema = z.object({
316
320
  .describe(
317
321
  "Your own instructions, used only when the mode above says custom. Then it is the whole of them: both built-in bases go, and so does everything this product would otherwise add, including the guidance the chat's own cards are driven by. That is the price of total control.",
318
322
  ),
319
- // Measured by the `clearing` experiment: the gateway in front of a native Claude turn replaces old tool results with
320
- // a placeholder, a chunk at a time, so the cached prompt breaks once per chunk rather than on every call
321
- // (privacy/gateway/tool-result-clearing.ts). Off by default until that experiment says it pays.
322
- toolResultClearing: z
323
- .boolean()
324
- .default(false)
325
- .describe(
326
- "Replace old tool results from a Claude conversation once it grows long, a large chunk at a time, keeping the most recent ones whole. The agent sees a short placeholder where each one was and can run the tool again. Smaller prompts cost less and answer sooner, at the risk of the agent re-reading what it already saw.",
327
- ),
328
- toolResultClearingHoldout: z
329
- .number()
330
- .min(0)
331
- .max(1)
332
- .default(0.5)
333
- .describe(
334
- "What share of conversations keep every tool result, so the two can be compared. Whole conversations rather than individual turns, because what was dropped stays dropped for the rest of the session.",
335
- ),
336
323
  iqSearch: z
337
324
  .boolean()
338
325
  .default(false)
@@ -351,18 +338,19 @@ export const SandboxSettingsSchema = z.object({
351
338
  .boolean()
352
339
  .default(false)
353
340
  .describe(
354
- "Open every conversation with a map of the project it starts in: the parts recent work touched and what each is for, the rest by name, where the agent is standing, and which folders the last three weeks of commits landed in. Worked out fresh each time from the tree and its history rather than written down anywhere, because a written layout is wrong within a fortnight. Off by default, since it spends tokens on the first message of every conversation.",
341
+ "Open every conversation with a map of the project it starts in: what is in it, what each part is for, and where the agent is standing. Worked out fresh each time rather than written down anywhere, because a written layout is wrong within a fortnight. Off by default, since it spends tokens on the first message of every conversation.",
355
342
  ),
356
343
  // Judged on the opening turn's own directory listings, not averaged across the conversation, or a twelve-turn
357
- // conversation would divide the effect by twelve. Since 2026-10-06 the held-out share gets the full map (every area
358
- // with its size) rather than none: the map-or-none comparison had settled its one effect, fewer opening listings.
344
+ // conversation would divide the effect by twelve. From 2026-10-06 to 10-12 the held-out share got the full map
345
+ // against a compact one; that changed nothing measurable, so the compact form went and the holdout opens without a
346
+ // map again.
359
347
  workspaceMapHoldout: z
360
348
  .number()
361
349
  .min(0)
362
350
  .max(1)
363
351
  .default(0)
364
352
  .describe(
365
- "What share of conversations to open with the full map instead, every area with its size, so the two forms can be compared. Whole conversations rather than individual turns, because the map is sent once and stays in the conversation's history afterwards.",
353
+ "What share of conversations to open without the map, so the two can be compared. Whole conversations rather than individual turns, because the map is sent once and stays in the conversation's history afterwards.",
366
354
  ),
367
355
  // The one composed piece that is WRITTEN rather than derived: a monthly automation rewrites it off the session
368
356
  // corpus, so it carries what no scan of the tree can (which commands really work here, what the box can take, how
@@ -402,15 +390,22 @@ export const SandboxSettingsSchema = z.object({
402
390
  .max(1)
403
391
  .default(0)
404
392
  .describe("What share of commands to leave untrimmed, so the saving can be measured against a real comparison rather than estimated."),
405
- // One key, not one field per role: a role catalog entry (`model-roles.ts`) makes a new job configurable without a
406
- // schema change here.
393
+ // The model lists jobs run on, edited once for every job put there (models/model-setups.ts). The three presets need
394
+ // no entry: absent, each is worked out from what is connected, so a sandbox runs before anyone opens this page.
395
+ modelSetups: z
396
+ .array(ModelSetupSchema)
397
+ .max(20)
398
+ .default([])
399
+ .describe(
400
+ "The model setups jobs run on: Low, Medium and High, which every sandbox has, and any you make. Each is an ordered list of models, tried in order so one spent account does not take its jobs down. A preset left out of this list, or listed without models, is worked out from the accounts you have connected. A setup runs only the jobs put on it (`roleSetups`).",
401
+ ),
407
402
  // `partialRecord`, not `record`: an exhaustive one would force every settings file to list every role, and adding
408
- // one would invalidate them all. Absent IS "no list".
409
- modelRoles: z
410
- .partialRecord(ModelRoleSchema, z.array(ModelPinSchema).max(10))
403
+ // one would invalidate them all. Absent IS "on no setup": nothing runs a job nobody placed.
404
+ roleSetups: z
405
+ .partialRecord(ModelRoleSchema, z.string().regex(MODEL_SETUP_ID))
411
406
  .default({})
412
407
  .describe(
413
- "Which models do which job, one ordered list per job: commit messages, session titles, the safety judge, pipeline fixes, and every other place this sandbox picks a model for you. Tried in order, so one spent account does not take a job down. Nothing is chosen for you: a one-shot job with no list does not run, and a whole session with no list opens on whatever your own chat is set to.",
408
+ "Which setup each job runs on: commit messages, session titles, the safety judge, pipeline fixes and every other place this sandbox picks a model for you. Nothing is chosen for you: a job left out, or on a setup with no models, has none, so a one-shot job does not run and a whole session opens on whatever your own chat is set to.",
414
409
  ),
415
410
  // Folded into the router's prompt (agent/prompt/chat-router.ts), never into a turn's own. Capped small on
416
411
  // purpose: the reading runs under a 5s deadline, and this text is paid for on every chat that opens on Auto.
@@ -457,13 +452,28 @@ export const SandboxSettingsSchema = z.object({
457
452
  memoryPolicy: RoomPolicySchema.default("resend").describe(
458
453
  "What happens to a message the sandbox held because it was short of memory: nothing ran, so nothing was spent. `resend` sends it by itself once memory has stayed free for a while, one held conversation at a time. `wait` holds it until somebody presses Send anyway. The sandbox-wide default; any one conversation can say otherwise.",
459
454
  ),
455
+ // How a held turn continues once its cache has gone cold (schemas/providers/handoff.ts). The card asks each time and
456
+ // preselects this; a booked send (a move, a resend at the reset) uses it unless a person picked on the card.
457
+ limitHandoff: LimitHandoffSchema.default("suggested").describe(
458
+ "How a turn a spent usage limit held continues, on this account at the reset or on another: `carry` resumes the session as it is (the model keeps everything and re-reads all of it), `trim` resumes a copy with older tool output cleared (every message and tool call stays), `summary` opens a fresh session with a smaller model's summary and the last exchanges word for word. `suggested` picks by the conversation's size, using the two thresholds beside this. The chat's card preselects it and lets you pick another for that turn.",
459
+ ),
460
+ // Read by the `suggested` hand-off only. Kept under its old name: before 2026-10-10 it chose between carrying and a
461
+ // short record hand-off for a move, and a saved value still means "carry under this".
460
462
  limitMoveCarryUnder: z
461
463
  .number()
462
464
  .int()
463
465
  .min(0)
464
- .default(100_000)
466
+ .default(150_000)
465
467
  .describe(
466
- "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.",
468
+ "For the suggested hand-off: carry the session as it is while the conversation's context is under this many tokens; at or above it, trim it (or summarise it, past the next threshold). Zero never suggests carrying.",
469
+ ),
470
+ handoffSummaryOver: z
471
+ .number()
472
+ .int()
473
+ .min(0)
474
+ .default(700_000)
475
+ .describe(
476
+ "For the suggested hand-off: at or above this many tokens of context, suggest a summary rather than a trimmed session, where a model is set for hand-off summaries.",
467
477
  ),
468
478
  // Off by default: every refresh spends the account's own allowance on a conversation nobody is using yet. Before
469
479
  // 2026-09-26 these were four top-level settings (settings-history.ts folds them).
@@ -493,13 +503,17 @@ export const SandboxSettingsSchema = z.object({
493
503
  // the heavy-command rule an agent's command matched (workload/heavy-commands.ts), each naming a runner id. A
494
504
  // runner that is offline, outdated or full hands the work back here, which the command's output says. Never pushed
495
505
  // to a runner itself (definition/definition.ts), which must not pass work on again.
506
+ // `sessions`: what a new conversation is told when this machine is busy and a runner has room
507
+ // (runners/fleet-advice.ts). Suggesting is the default: it costs one line above the composer, and moving a session
508
+ // without being asked is the owner's choice to make.
496
509
  offload: z
497
510
  .object({
498
511
  commands: z.record(z.string().min(1), z.string().min(1)).default({}),
512
+ sessions: SessionsPlacementSchema.default("suggest"),
499
513
  })
500
- .default({ commands: {} })
514
+ .default({ commands: {}, sessions: "suggest" })
501
515
  .describe(
502
- "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…). 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.",
516
+ "Which work runs on a runner on one of your machines instead of this sandbox. `commands`: agents' heavy commands by the kind the heavy-command rules sort them into (tests, typechecks, verify…); the code travels as it stands, uncommitted work included, the output streams back, and any file the command changed comes back with it, while a machine that is offline, outdated or busy hands the work back to this sandbox, and the output says so. `sessions`: what happens to a new conversation while this machine is busy and one of your runners has room: `suggest` offers that machine above the composer, `auto` starts it there unless you keep it here, `off` says nothing.",
503
517
  ),
504
518
  // A person answering a need is exactly the moment they want the work to go on (docs/architecture/needs.md), unlike a
505
519
  // restart nobody watched, so this one is on to begin with.
@@ -517,13 +531,15 @@ export const SandboxSettingsSchema = z.object({
517
531
  ),
518
532
  // Which repositories' own declarations (`<repo>/.intentic/checks.json`) the owner has switched on, each against the
519
533
  // fingerprint of what was declared when they did. A declaration that has since changed no longer matches its
520
- // fingerprint and is held rather than run, which is what makes adoption a decision about a command rather than a
521
- // permanent permission on a folder. Keyed by repo id, so `/` in the key is ordinary.
534
+ // fingerprint: the daemon finds the version that did in the repository's history, keeps running each check that is
535
+ // still word for word one of its checks, and holds every new or rewritten one, which is what makes adoption a
536
+ // decision about a command rather than a permanent permission on a folder, without one added check switching off
537
+ // all the others. Keyed by repo id, so `/` in the key is ordinary.
522
538
  adoptedChecks: z
523
539
  .record(z.string(), z.string())
524
540
  .default({})
525
541
  .describe(
526
- "Which repositories may run the checks they declare for themselves, and exactly which version of those checks you agreed to. A repository's declaration does nothing until it appears here, the same rule git keeps for hooks, which are never cloned; and a declaration that changes afterwards is held until you look at it again.",
542
+ "Which repositories may run the checks they declare for themselves, and exactly which version of those checks you agreed to. A repository's declaration does nothing until it appears here, the same rule git keeps for hooks, which are never cloned; and a check it adds or rewrites afterwards waits until you look at it again, while the ones you agreed to keep running.",
527
543
  ),
528
544
  // The owner's alone, since a rule can hold work. A command is refused here: it belongs to the repository it checks,
529
545
  // in that repository's `.intentic/checks.json`, never in settings.
@@ -545,6 +561,11 @@ export const SandboxSettingsSchema = z.object({
545
561
  .describe(
546
562
  "How many failures in a row before an automation switches itself off. Zero means never, which is the default, because the failure is not always the automation's fault and a job disabled at three in the morning is one nobody re-enables. Only real errors count: a guard deciding there was nothing to do, or the sandbox dying mid-run, say nothing about the automation.",
547
563
  ),
564
+ // Speaking replies aloud (schemas/voice.ts): which voice speaks each language, and whether a hands-free turn is
565
+ // answered out loud. Read by the daemon's voice engine on every utterance, so a change is heard on the next one.
566
+ voice: VoiceSettingsSchema.prefault({}).describe(
567
+ "How the agent's replies are spoken aloud: whether a hands-free conversation is answered out loud, which voice speaks each language, and a speech provider of your own if you connected one.",
568
+ ),
548
569
  // Defaults all-allow, so a fresh sandbox behaves as it did before this floor existed.
549
570
  admission: AdmissionPolicySchema.prefault({}).describe(
550
571
  "Whether work started from outside may run, per kind of trigger: let it, hold it for approval, or refuse it. Composes with each automation's own setting, and the stricter of the two wins, so holding every visitor's message needs no edit to each automation.",
@@ -558,7 +579,7 @@ export const SandboxSettingsSchema = z.object({
558
579
  .default({})
559
580
  .describe("What an agent may do out in the world, per kind of action: go ahead, ask first, or never."),
560
581
  // Command policy itself lives at .intentic/config/safety.md (edited by the Safety page), not here; this only says
561
- // whether the judge runs at all. Which model judges is the `safety-judge` role in `modelRoles`, not a field here.
582
+ // whether the judge runs at all. Which model judges is the `safety-judge` job's setup (`roleSetups`), not a field here.
562
583
  commandJudge: CommandJudgeModeSchema.default("on").describe(
563
584
  "Whether a model reads your safety policy before a flagged command runs. Off judges nothing and asks about nothing; Watch judges everything and records it without ever interrupting you, which is how you find out what your policy actually does before you let it stop anything; On lets the verdict decide. Wiping a disk or deleting under /history asks at every setting — that rule is typed rather than judged, and cannot be turned off.",
564
585
  ),
@@ -608,6 +629,20 @@ export const AudienceStateSchema = z.object({
608
629
  });
609
630
  export type AudienceState = z.infer<typeof AudienceStateSchema>;
610
631
 
632
+ // What a person has said about the editor's getting-started checklist on this sandbox, kept per member like the
633
+ // audience so it reads the same on every device. Only what the sandbox's own data cannot answer: whether a step is DONE
634
+ // is read from that data each time (a workspace with files, an agent started, a model connected, work landed), so this
635
+ // holds nothing but the reader's own choices. Step names are the editor's, opaque here.
636
+ export const GettingStartedSchema = z.object({
637
+ hidden: z.boolean().optional().describe("Whether this person put the checklist away. Absent until they have."),
638
+ skipped: z
639
+ .array(z.string().min(1).max(40))
640
+ .max(20)
641
+ .optional()
642
+ .describe("The checklist steps this person passed over, by the editor's own step names."),
643
+ });
644
+ export type GettingStarted = z.infer<typeof GettingStartedSchema>;
645
+
611
646
  // A browser telling the sandbox which clock IT is on. An offer, not an instruction: see `adoptTimezone`.
612
647
  export const TimezoneOfferSchema = z.object({
613
648
  timezone: ZoneSchema.describe("The zone the offering machine is in, as an IANA name like Europe/Warsaw."),
@@ -658,13 +693,9 @@ export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() }
658
693
  // openingListings: directory listings a turn ran to orient itself (the project map).
659
694
  // callsBeforeTarget: how far a turn walked before touching a file it went on to edit.
660
695
  // failedCalls: tool calls that ended in error (the field notes, whose largest section is a failure taxonomy).
661
- // roundTrips: model calls a turn took (tool-result clearing, whose lost results could cost re-runs).
662
- // contextPerCall: prompt tokens per model call, cached or not (tool-result clearing, which exists to shrink it).
663
- // Never cost: each mechanism moves one small part of a turn's work, inside the noise of the rest. Round trips are the
664
- // nearest a reading comes to it, and still a count of calls, not of tokens or money; the prompt a call carries is the
665
- // one exception, for the one mechanism that changes nothing else.
696
+ // Never cost: each mechanism moves one small part of a turn's work, inside the noise of the rest.
666
697
  export const TurnMetricReadingSchema = z.object({
667
- metric: z.enum(["searchCalls", "openingSearches", "openingListings", "callsBeforeTarget", "failedCalls", "roundTrips", "contextPerCall"]),
698
+ metric: z.enum(["searchCalls", "openingSearches", "openingListings", "callsBeforeTarget", "failedCalls"]),
668
699
  on: SavingsArmSchema,
669
700
  off: SavingsArmSchema,
670
701
  // Additional control turns to reach a fixed target resolution, not today's delta (which inherits noise and always
@@ -727,7 +758,6 @@ export const SavingsReportSchema = z.object({
727
758
  // Same absence rule as `search`: not measured, never zero.
728
759
  map: TurnExperimentSchema.optional(),
729
760
  notes: TurnExperimentSchema.optional(),
730
- clearing: TurnExperimentSchema.optional(),
731
761
  });
732
762
  export type SavingsReport = z.infer<typeof SavingsReportSchema>;
733
763
 
@@ -793,7 +823,13 @@ export const RepoChecksSummarySchema = z.object({
793
823
  changed: z
794
824
  .boolean()
795
825
  .describe(
796
- "Whether the declaration changed since it was adopted, which holds it until the owner looks again. True only for a repository that was adopted before.",
826
+ "Whether the declaration gained or rewrote a check since it was adopted. What changed waits until the owner looks again; the checks the owner already agreed to, word for word, keep running (`held` says which is which). True only for a repository that was adopted before.",
827
+ ),
828
+ held: z
829
+ .array(z.boolean())
830
+ .optional()
831
+ .describe(
832
+ "For a changed declaration, which of its checks wait for the owner, in the file's order: true for one that is new or rewritten since they agreed, false for one still running as agreed, or retired. Absent when nothing changed, and from a sandbox that holds a changed declaration whole.",
797
833
  ),
798
834
  error: z.string().optional().describe("Why the file could not be read, when it exists but does not parse. The checks list is empty in that case."),
799
835
  });
@@ -0,0 +1,59 @@
1
+ // short codes: the single-use 8-character code a copy-paste command carries instead of a token. The sandbox mints one
2
+ // beside each device, sync or server-connect token it hands out; the install script redeems it at the sandbox's public
3
+ // POST /system/redeem/<code>, which answers `KEY=value` lines (KIND, SANDBOX_URL, then the kind's own values).
4
+ import { z } from "zod";
5
+ import { INVENTORY_NAME_MAX, INVENTORY_NAME_RE } from "./inventory.js";
6
+
7
+ // Crockford base32 (no I, L, O, U), 8 characters: 40 bits. Redemption accepts any case and one hyphen in the middle.
8
+ export const SHORT_CODE_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
9
+ export const SHORT_CODE_LENGTH = 8;
10
+ export const ShortCodeSchema = z.string().regex(/^[0-9A-HJKMNP-TV-Z]{8}$/);
11
+
12
+ // What each kind of code redeems to, in its answer's KIND line.
13
+ export const ShortCodeKindSchema = z.enum(["device", "sync", "server"]);
14
+ export type ShortCodeKind = z.infer<typeof ShortCodeKindSchema>;
15
+
16
+ // A value that rides one `KEY=value` line: no line breaks or other control characters.
17
+ const lineValue = z
18
+ .string()
19
+ .min(1)
20
+ .max(1024)
21
+ .regex(/^[^\p{Cc}]+$/u);
22
+
23
+ // A device or sync pairing as minted: the token (unchanged) and, additively, the code that stands for it.
24
+ export const PairingAnswerSchema = z.object({
25
+ token: z.string(),
26
+ expiresIn: z.number().describe("Seconds until the token, and its code, expire."),
27
+ code: ShortCodeSchema.describe("Single-use short code a command carries instead of the token."),
28
+ });
29
+ export type PairingAnswer = z.infer<typeof PairingAnswerSchema>;
30
+
31
+ // What POST /system/<door>/pair answers on any peer door (device, browser, phone, runner): the token, and the short code
32
+ // only the device door mints beside it. `expiresIn` is optional on read since a reader meets daemons older than the
33
+ // field; the editor then shows the token without a countdown.
34
+ export const PeerPairingAnswerSchema = PairingAnswerSchema.partial({ code: true, expiresIn: true });
35
+ export type PeerPairingAnswer = z.infer<typeof PeerPairingAnswerSchema>;
36
+
37
+ // POST /system/sync/pair: the pairing with the mode the daemon granted, which may be less than the one asked for (a
38
+ // member's "sync" comes back "mirror"), and the code that stands for it.
39
+ export const SyncPairAnswerSchema = PeerPairingAnswerSchema.extend({ mode: z.enum(["sync", "mirror"]).optional() });
40
+ export type SyncPairAnswer = z.infer<typeof SyncPairAnswerSchema>;
41
+
42
+ // POST /system/sync/pair's optional body: the person's chosen local folder and takeover, handed out with the code.
43
+ // It goes to their own sandbox, never the platform.
44
+ export const SyncPairRequestSchema = z.object({
45
+ syncDir: lineValue.optional().describe("Local folder to sync, as the person chose it (`~` expanded on the device)."),
46
+ takeover: z.boolean().optional().describe("Take sync over from another machine already enrolled."),
47
+ });
48
+ export type SyncPairRequest = z.infer<typeof SyncPairRequestSchema>;
49
+
50
+ // POST /system/enroll/code: mints a server-connect code backed by a single-use enroll token.
51
+ export const EnrollCodeRequestSchema = z.object({
52
+ hostName: z.string().min(1).max(INVENTORY_NAME_MAX).regex(INVENTORY_NAME_RE).optional().describe("What the server will be called."),
53
+ });
54
+ export type EnrollCodeRequest = z.infer<typeof EnrollCodeRequestSchema>;
55
+ export const EnrollCodeAnswerSchema = z.object({
56
+ code: ShortCodeSchema,
57
+ expiresIn: z.number().describe("Seconds until the code and its enroll token expire."),
58
+ });
59
+ export type EnrollCodeAnswer = z.infer<typeof EnrollCodeAnswerSchema>;
@@ -66,6 +66,10 @@ export const SpeechStatusSchema = z.object({
66
66
  });
67
67
  export type SpeechStatus = z.infer<typeof SpeechStatusSchema>;
68
68
 
69
+ // POST /speech/transcribe: one phrase as WAV in, its words out; empty when it held none.
70
+ export const SpeechTranscriptSchema = z.object({ text: z.string() });
71
+ export type SpeechTranscript = z.infer<typeof SpeechTranscriptSchema>;
72
+
69
73
  // GET /speech/stream, a WebSocket: the composer cuts the microphone into phrases itself (its silence segmenter) and
70
74
  // streams each one as it is spoken. A text frame `begin` opens phrase `id`; binary frames that follow are its audio,
71
75
  // 16 kHz mono s16le; `end` closes it, keeping its first `keep` samples (the trailing pause trimmed off); `drop`
@@ -1,5 +1,7 @@
1
1
  import { z } from "zod";
2
- import { StatePlanSchema } from "./state-plan.js";
2
+ import { BootProgressSchema } from "../events/system-events.js";
3
+ import { AnnounceStateSchema } from "./platform-link.js";
4
+ import { StatePlanSchema, StateStatusSchema } from "./state-plan.js";
3
5
  import { AutoUpdateSchema, UpdateOutcomeSchema, WithdrawnReleaseSchema } from "./updates.js";
4
6
  // version is this build's baked-in version; latest/updateAvailable come from comparing it to the published stable
5
7
  // release.
@@ -191,8 +193,8 @@ export const ManifestRepairSchema = z.object({
191
193
  ),
192
194
  });
193
195
  export type ManifestRepair = z.infer<typeof ManifestRepairSchema>;
194
- // Steady-state browser credential (system.session), exchanged for a verified Google ID token so Google UI appears only
195
- // at sign-in. expiresAt is epoch ms; renew ahead of it without parsing the token.
196
+ // Steady-state browser credential (system.session), exchanged for a verified Google ID token or the platform's identity
197
+ // assertion, so sign-in UI appears only once. expiresAt is epoch ms; renew ahead of it without parsing the token.
196
198
  export const DaemonSessionSchema = z.object({
197
199
  token: z.string().describe("The credential every other call carries. Present it as a bearer token."),
198
200
  expiresAt: z.number().describe("When it stops working, in milliseconds, so a caller can renew ahead of it without reading the token."),
@@ -204,10 +206,18 @@ export type DaemonSession = z.infer<typeof DaemonSessionSchema>;
204
206
  // daemon is the WebAuthn relying party; these are the shapes its /system/passkeys routes and the 428 step-up carry.
205
207
 
206
208
  // How a session's identity was proven. A session carries the set it was minted from; the require-passkey policy reads
207
- // it on every request, so a session minted before the switch stops working on its next call.
208
- export const ProofMethodSchema = z.enum(["google", "ticket", "passkey", "recovery"]);
209
+ // it on every request, so a session minted before the switch stops working on its next call. `email` is the platform's
210
+ // signed word that the person controls the address (policy/identity-assertion.ts), accepted only where the owner opted
211
+ // that person in, and on hosted machines.
212
+ export const ProofMethodSchema = z.enum(["google", "ticket", "passkey", "recovery", "email"]);
209
213
  export type ProofMethod = z.infer<typeof ProofMethodSchema>;
210
214
 
215
+ // The `reason` a daemon's refusal of an identity assertion carries beside its `error`, so the editor can say what to do:
216
+ // a 403 for a person this sandbox has not opted in to email sign-in (the owner turns it on for them), a 401 when the
217
+ // daemon has not yet learned the platform's identity key (it has not reached the platform; worth a retry).
218
+ export const EMAIL_SIGN_IN_OFF = "email-sign-in-off";
219
+ export const PLATFORM_KEY_UNKNOWN = "platform-key-unknown";
220
+
211
221
  export const PasskeySummarySchema = z.object({
212
222
  id: z.string().describe("The credential id the authenticator chose, base64url."),
213
223
  email: z.string().describe("Whose passkey this is; the owner's list carries every member's, a member's only their own."),
@@ -231,6 +241,9 @@ export type PasskeysList = z.infer<typeof PasskeysListSchema>;
231
241
 
232
242
  export const PasskeyPolicySchema = z.object({ required: z.boolean() });
233
243
  export type PasskeyPolicy = z.infer<typeof PasskeyPolicySchema>;
244
+ // POST /system/passkeys/policy: switching the rule on answers with the recovery codes, off with none.
245
+ export const PasskeyPolicyAnswerSchema = PasskeyPolicySchema.extend({ codes: z.array(z.string()).optional() });
246
+ export type PasskeyPolicyAnswer = z.infer<typeof PasskeyPolicyAnswerSchema>;
234
247
 
235
248
  // Shown exactly once: the daemon keeps only their hashes.
236
249
  export const RecoveryCodesSchema = z.object({ codes: z.array(z.string()) });
@@ -316,3 +329,91 @@ export interface AuthenticationOptionsJSON {
316
329
  // pressing it can succeed.
317
330
  readonly available: boolean;
318
331
  }
332
+
333
+ // The two interfaces above read off the wire with the one check a body needs before the browser's own parser takes it
334
+ // (`parseCreationOptionsFromJSON` rejects the rest): an object with a challenge. Typed, not parsed field by field, since
335
+ // only the daemon writes them.
336
+ const WithChallengeSchema = z.object({ challenge: z.string() });
337
+ const optionsWithChallenge = <Options extends { readonly challenge: string }>() => z.custom<Options>((value) => WithChallengeSchema.safeParse(value).success);
338
+ export const RegistrationOptionsSchema = optionsWithChallenge<RegistrationOptionsJSON>();
339
+ export const AuthenticationOptionsSchema = optionsWithChallenge<AuthenticationOptionsJSON>();
340
+
341
+ // POST /system/passkeys/register: the passkey as listed, and the session upgraded on the spot when the bearer that
342
+ // registered held no passkey yet (the same proof, now carrying one).
343
+ export const PasskeyRegisteredSchema = z.object({
344
+ passkey: PasskeySummarySchema,
345
+ session: DaemonSessionSchema.optional(),
346
+ });
347
+ export type PasskeyRegistered = z.infer<typeof PasskeyRegisteredSchema>;
348
+
349
+ // POST /system/session/recover: the session the recovery code bought, and how many codes are left unspent.
350
+ export const SessionRecoveredSchema = DaemonSessionSchema.extend({ remaining: z.number() });
351
+ export type SessionRecovered = z.infer<typeof SessionRecoveredSchema>;
352
+
353
+ // Whether this sandbox's public address answers, probed by the box itself; the same shape as `announce` beside it, so
354
+ // `ic sandbox doctor` and the setup wizard can name whichever link broke.
355
+ export const ReachStateSchema = z.object({
356
+ // off: nothing to probe; checking: no verdict yet; reachable: its own public address answered with our id.
357
+ state: z.enum(["off", "checking", "reachable", "unreachable"]),
358
+ // Why, for "unreachable", already in the user's terms since the wizard renders it verbatim.
359
+ detail: z.string().optional(),
360
+ // False once the give-up window is spent; only a restart retries after that.
361
+ retrying: z.boolean().optional(),
362
+ at: z.number().optional(),
363
+ });
364
+ export type ReachState = z.infer<typeof ReachStateSchema>;
365
+
366
+ // Which build answers, so "is the fix live?" is one read.
367
+ export const BuildStampSchema = z.object({
368
+ version: z.string(),
369
+ commit: z.string().optional(),
370
+ builtAt: z.string().optional(),
371
+ });
372
+
373
+ // GET /health: the "is a daemon there" probe every flow uses (launch scripts, /setup, the browser's loopback probe,
374
+ // the platform's hosted gate, the onboarding lane), served raw before any authentication. `ic` reads it too, in Rust
375
+ // (_sandbox/ic/src/health.rs), which cannot import this and reads only `boot`, `state` and `announce` loosely.
376
+ export const DaemonHealthSchema = z.object({
377
+ ok: z.literal(true),
378
+ // The sandbox id, so a loopback probe can confirm it reached this daemon and not an unrelated process. Absent on a
379
+ // daemon with no connect token.
380
+ sandboxId: z.string().optional(),
381
+ // Which posture answers (the daemon's platform/profile.ts); needed before any authenticated read.
382
+ profile: z.string(),
383
+ // Tells "not answering" from "still converging".
384
+ boot: BootProgressSchema,
385
+ // Whether an update's state conversions are still uncommitted; the host rolls the update back when they stay open.
386
+ state: StateStatusSchema,
387
+ // Whether this daemon reached the platform.
388
+ announce: AnnounceStateSchema,
389
+ reach: ReachStateSchema,
390
+ // How the world reaches here: a dialed tunnel, or loopback only.
391
+ reachedBy: z.enum(["tunnel", "loopback"]),
392
+ build: BuildStampSchema,
393
+ // Which proofs this daemon takes to open a session, read before any sign-in since the route list arrives only after
394
+ // one. Absent on a daemon from before email sign-in: Google, and the owner ticket on a hosted machine.
395
+ signIn: z.object({ proofs: z.array(ProofMethodSchema) }).optional(),
396
+ });
397
+ export type DaemonHealth = z.infer<typeof DaemonHealthSchema>;
398
+
399
+ // The same answer as a reader outside the daemon takes it: from a daemon of any age, so every field may be missing,
400
+ // and one of an unexpected shape reads as missing rather than failing the whole answer. A daemon older than the boot
401
+ // report said `ready` at the top level instead.
402
+ export const DaemonHealthReadingSchema = z.object({
403
+ ok: z.boolean().optional().catch(undefined),
404
+ ready: z.boolean().optional().catch(undefined),
405
+ sandboxId: z.string().optional().catch(undefined),
406
+ profile: z.string().optional().catch(undefined),
407
+ boot: z.object({ ready: z.boolean().optional().catch(undefined) }).optional().catch(undefined),
408
+ state: z.object({ journal: z.string().optional().catch(undefined) }).optional().catch(undefined),
409
+ announce: AnnounceStateSchema.optional().catch(undefined),
410
+ reach: ReachStateSchema.optional().catch(undefined),
411
+ reachedBy: z.string().optional().catch(undefined),
412
+ build: BuildStampSchema.optional().catch(undefined),
413
+ // Proof names this build may not know are kept as text, so a newer daemon's list still reads.
414
+ signIn: z
415
+ .object({ proofs: z.array(z.string()).catch([]) })
416
+ .optional()
417
+ .catch(undefined),
418
+ });
419
+ export type DaemonHealthReading = z.infer<typeof DaemonHealthReadingSchema>;