@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
package/src/index.ts CHANGED
@@ -164,6 +164,8 @@ export * from "./models/agent-catalog.js";
164
164
  export * from "./policy/capability-env.js";
165
165
  export * from "./policy/command-classes.js";
166
166
  export * from "./policy/command-run.js";
167
+ export * from "./policy/device-programs.js";
168
+ export * from "./policy/sandbox-power.js";
167
169
  export * from "./policy/safety-policy.js";
168
170
  export * from "./policy/reserved-servers.js";
169
171
  export * from "./policy/credential-material.js";
@@ -188,6 +190,7 @@ export * from "./models/model-order.js";
188
190
  export * from "./models/model-offer.js";
189
191
  export * from "./models/model-pins.js";
190
192
  export * from "./models/model-roles.js";
193
+ export * from "./models/model-setups.js";
191
194
  export * from "./models/plan-pools.js";
192
195
  export * from "./text/path-refs.js";
193
196
  export * from "./text/archives.js";
@@ -204,6 +207,7 @@ export * from "./policy/persona-home.js";
204
207
  export * from "./schemas/agents.js";
205
208
  export * from "./schemas/speaker.js";
206
209
  export * from "./schemas/speech.js";
210
+ export * from "./schemas/voice.js";
207
211
  export * from "./schemas/approvals.js";
208
212
  export * from "./schemas/automations.js";
209
213
  export * from "./schemas/agents-list.js";
@@ -214,6 +218,7 @@ export * from "./schemas/providers/claude-gate.js";
214
218
  export * from "./schemas/codebase-health.js";
215
219
  export * from "./schemas/context-trim.js";
216
220
  export * from "./schemas/devices.js";
221
+ export * from "./schemas/device-swaps.js";
217
222
  export * from "./schemas/project-delivery.js";
218
223
  export * from "./schemas/device-artifacts.js";
219
224
  export * from "./schemas/run-targets.js";
@@ -224,6 +229,7 @@ export * from "./schemas/extension-updates.js";
224
229
  export * from "./schemas/providers/fast-mode.js";
225
230
  export * from "./schemas/diff.js";
226
231
  export * from "./schemas/git/git.js";
232
+ export * from "./schemas/git/git-scope.js";
227
233
  export * from "./schemas/git/git-history.js";
228
234
  export * from "./schemas/history.js";
229
235
  export * from "./schemas/hosts.js";
@@ -242,7 +248,9 @@ export * from "./schemas/panels.js";
242
248
  export * from "./schemas/personas.js";
243
249
  export * from "./schemas/privacy.js";
244
250
  export * from "./schemas/providers/plan-limits.js";
251
+ export * from "./schemas/providers/handoff.js";
245
252
  export * from "./schemas/platform-link.js";
253
+ export * from "./schemas/project-bring-back.js";
246
254
  export * from "./schemas/ports.js";
247
255
  export * from "./schemas/providers/provider-oauth.js";
248
256
  export * from "./schemas/providers/provider-subscriptions.js";
@@ -257,9 +265,11 @@ export * from "./schemas/settings.js";
257
265
  export * from "./schemas/settings-history.js";
258
266
  export * from "./schemas/share.js";
259
267
  export * from "./schemas/shared.js";
268
+ export * from "./schemas/short-codes.js";
260
269
  export * from "./schemas/system-prompt.js";
261
270
  export * from "./schemas/system.js";
262
271
  export * from "./schemas/state-plan.js";
272
+ export * from "./schemas/restart-intent.js";
263
273
  export * from "./schemas/updates.js";
264
274
  export * from "./schemas/terminal.js";
265
275
  export * from "./schemas/turn-break.js";
@@ -275,11 +285,13 @@ export * from "./schemas/workspace/workspace-repos.js";
275
285
  export * from "./schemas/workspace/workspace-search.js";
276
286
  export * from "./schemas/workspace/workspace-setup.js";
277
287
  export * from "./schemas/workspace/workspace-tree.js";
288
+ export * from "./schemas/workspace/workspace-upload.js";
278
289
  export * from "./state/arrival.js";
279
290
  export * from "./state/definition.js";
280
291
  export * from "./policy/search-globs.js";
281
292
  export * from "./state/starter.js";
282
293
  export * from "./text/title.js";
294
+ export * from "./state/update-probation.js";
283
295
  export * from "./state/versions.js";
284
296
  export * from "./text/model-answer.js";
285
297
  export * from "./text/shell-regions.js";
@@ -29,7 +29,7 @@ describe("every provider/harness pair declares what it can do", () => {
29
29
  it.each(pairs)("$provider on the $harness harness", ({ provider, harness }) => {
30
30
  const capabilities = capabilitiesOf(provider, harness);
31
31
 
32
- expect(["claude-code", "codex", "cursor", "opencode", "opencode-gemini", "acp", "pi"]).toContain(capabilities.runtime);
32
+ expect(["claude-code", "codex", "cursor", "opencode", "opencode-gemini", "acp", "devin", "pi"]).toContain(capabilities.runtime);
33
33
  expect(["modes", "plan"]).toContain(capabilities.permissions);
34
34
  expect(["full", "tools", "http", "none"]).toContain(capabilities.mcp);
35
35
  expect(capabilities.execution).toContain("shell");
@@ -163,11 +163,12 @@ test("every axis a record can lack has words for it", () => {
163
163
  rulebook: "none",
164
164
  secrets: "none",
165
165
  privacy: "none",
166
+ toolAllowlist: false,
166
167
  };
167
168
 
168
- // Thirteen disclosable axes, thirteen sentences; `fastMode` is disclosed via fastAllowed, and `warm` (keep-warm, off by
169
+ // Fourteen disclosable axes, fourteen sentences; `fastMode` is disclosed via fastAllowed, and `warm` (keep-warm, off by
169
170
  // default) is no limit on the turn itself.
170
- expect(limitationsOf(nothing)).toHaveLength(13);
171
+ expect(limitationsOf(nothing)).toHaveLength(14);
171
172
  expect(limitationsOf(nothing).join(" ")).not.toContain("fast");
172
173
  });
173
174
 
@@ -106,19 +106,21 @@ export const modesFor = (capabilities: AgentCapabilities): readonly PermissionMo
106
106
  export const clampMode = (mode: PermissionMode, capabilities: AgentCapabilities): PermissionMode =>
107
107
  modesFor(capabilities).includes(mode) ? mode : "bypassPermissions";
108
108
 
109
+ // What each reach of `mcp` leaves out, said once per answer.
110
+ const MCP_LIMITS = {
111
+ none: ["no MCP tools or plugins"],
112
+ http: ["MCP servers only, none of the daemon's own tools, no plugins"],
113
+ tools: ["no plugins"],
114
+ full: [],
115
+ } as const satisfies Record<AgentCapabilities["mcp"], readonly string[]>;
116
+
109
117
  // What this pair cannot do, phrased for the sender; empty means the full ceiling. `fastMode` is deliberately not
110
118
  // disclosed here, since it also depends on the route and model; fastAllowed answers that instead.
111
119
  export const limitationsOf = (capabilities: AgentCapabilities): string[] => [
112
120
  ...(capabilities.permissions === "plan" ? ["no per-tool approvals"] : []),
113
121
  ...(capabilities.questions ? [] : ["no clarifying questions"]),
114
122
  ...(capabilities.steering ? [] : ["no mid-turn steering"]),
115
- ...(capabilities.mcp === "none"
116
- ? ["no MCP tools or plugins"]
117
- : capabilities.mcp === "http"
118
- ? ["MCP servers only, none of the daemon's own tools, no plugins"]
119
- : capabilities.mcp === "tools"
120
- ? ["no plugins"]
121
- : []),
123
+ ...MCP_LIMITS[capabilities.mcp],
122
124
  ...(capabilities.execution.includes("js") ? [] : ["no code runs, shell only"]),
123
125
  ...(capabilities.effort ? [] : ["no effort control"]),
124
126
  ...(capabilities.commands ? [] : ["no slash commands"]),
@@ -131,6 +133,7 @@ export const limitationsOf = (capabilities: AgentCapabilities): string[] => [
131
133
  ...(capabilities.rulebook === "refuse-only" ? ["command rules can refuse but not hold"] : []),
132
134
  ...(capabilities.rulebook === "none" ? ["command rules not applied"] : []),
133
135
  ...(capabilities.secrets === "none" ? ["secrets reach the model unmasked"] : []),
136
+ ...(capabilities.toolAllowlist ? [] : ["no tool lists: an automation limited to some tools won't run here"]),
134
137
  ];
135
138
 
136
139
  // Claude's compile-time model floor, shared by daemon and web pre-load; versioned ids only, never a tier alias.
@@ -8,7 +8,7 @@ export type ExecutionBackend = "shell" | "js";
8
8
 
9
9
  export interface AgentCapabilities {
10
10
  // Which loop serves the turn; `opencode-gemini` stays separate since adapter health keys off this field.
11
- readonly runtime: "claude-code" | "codex" | "opencode" | "opencode-gemini" | "acp" | "pi" | "cursor";
11
+ readonly runtime: "claude-code" | "codex" | "opencode" | "opencode-gemini" | "acp" | "devin" | "pi" | "cursor";
12
12
  // Mid-turn injection (the SteeringQueue behind /agent/steer); needs the SDK's streaming-input mode.
13
13
  readonly steering: boolean;
14
14
  // How much of the permission-mode axis the runtime honours: every PermissionMode, or just propose-then-approve.
@@ -59,6 +59,10 @@ export interface AgentCapabilities {
59
59
  // configuration the daemon does not own and reads files it never sees. Only a `none` loop on an untrusted provider is
60
60
  // refused while the shield is on.
61
61
  readonly privacy: "gateway" | "hooks" | "none";
62
+ // Whether a turn's tool list (TurnPolicy.allowedTools, an automation's `allowedTools`) is a real boundary here:
63
+ // every tool call the loop makes passes a check the daemon answers, or the loop is built with only the listed tools.
64
+ // A runtime that runs some tool without asking anyone cannot keep it, so a turn carrying a list never starts on one.
65
+ readonly toolAllowlist: boolean;
62
66
  }
63
67
 
64
68
  // The Claude Code Agent SDK loop, the ceiling every other runtime is measured against: the only one that owns the whole
@@ -85,6 +89,9 @@ export const CLAUDE_CODE: AgentCapabilities = {
85
89
  secrets: "masked",
86
90
  // Every request goes to ANTHROPIC_BASE_URL, which the daemon sets per turn.
87
91
  privacy: "gateway",
92
+ // A PreToolUse hook refuses every call off the list, under bypassPermissions and in subagents too
93
+ // (agent/tools/tool-allowlist.ts).
94
+ toolAllowlist: true,
88
95
  };
89
96
 
90
97
  // Codex app-server: item-level events, the turn's remote MCP servers over http, and four seams (steer, a question
@@ -112,6 +119,9 @@ export const CODEX: AgentCapabilities = {
112
119
  secrets: "none",
113
120
  // Its model provider's base_url is the daemon's, written into the per-thread config.
114
121
  privacy: "gateway",
122
+ // Codex runs the commands it judges read-only without asking (its known-safe list), and every tool it has is the
123
+ // shell, so no answer the daemon gives can keep a call off a list.
124
+ toolAllowlist: false,
115
125
  };
116
126
 
117
127
  // OpenCode (the Grok runtime): its own agentic loop and tools, with the turn's remote MCP servers mounted on the shared
@@ -140,6 +150,10 @@ export const OPENCODE: AgentCapabilities = {
140
150
  secrets: "none",
141
151
  // The shared server's provider baseURLs are written by the daemon at spawn.
142
152
  privacy: "gateway",
153
+ // Its session rules could deny every action off the list, but they apply after the server's and the last match wins,
154
+ // so allowing a listed shell command there would override the owner's rulebook asking about it. Not kept until the
155
+ // permission judge (opencode-permissions.ts) checks the list on every call instead.
156
+ toolAllowlist: false,
143
157
  };
144
158
 
145
159
  // The same OpenCode loop, serving Gemini instead of xAI, identical abilities. The Claude Code loop announces itself in
@@ -173,6 +187,16 @@ export const ACP: AgentCapabilities = {
173
187
  secrets: "none",
174
188
  // The agent picks its own provider and endpoint from its own login and environment.
175
189
  privacy: "none",
190
+ // Which calls the agent asks permission for is its own choice, and most run reads without asking.
191
+ toolAllowlist: false,
192
+ };
193
+
194
+ // Devin's own CLI (`devin acp`), the ACP floor with one seam an installed agent lacks: the daemon picks the model, from
195
+ // the account's own list, through the session's model option. Its own runtime name, as OpenCode's Gemini twin has, since
196
+ // adapter health keys off this field and Devin's readiness is an account and an engine, not an installed card.
197
+ export const DEVIN: AgentCapabilities = {
198
+ ...ACP,
199
+ runtime: "devin",
176
200
  };
177
201
 
178
202
  // Pi driven over its RPC mode: above the ACP floor and below the Claude Code ceiling. Real mid-turn steering, an effort
@@ -199,6 +223,8 @@ export const PI: AgentCapabilities = {
199
223
  secrets: "none",
200
224
  // Pi calls its provider from its own login; the daemon names no endpoint.
201
225
  privacy: "none",
226
+ // No seam before a tool runs.
227
+ toolAllowlist: false,
202
228
  };
203
229
 
204
230
  // Cursor's own runtime, driven through `@cursor/sdk` from this daemon's process; an embedding surface, so most seams
@@ -242,4 +268,7 @@ export const CURSOR: AgentCapabilities = {
242
268
  // to mask the output, the tools that read past both (grep, ls, glob, semantic search, fetch, lints) are withheld,
243
269
  // and MCP goes through the masking proxy (runtimes/cursor/cursor-shield.ts).
244
270
  privacy: "hooks",
271
+ // Its hooks are installed only for a shielded turn, and a hook that cannot tell which turn is asking allows; the
272
+ // built-in tool set is withheld by name, not granted. None of that is a list nothing gets past.
273
+ toolAllowlist: false,
245
274
  };
@@ -37,6 +37,10 @@ const LEDGER: Record<keyof AgentCapabilities, Backing> = {
37
37
  // agent/run/decide/turn-decision.ts refuses a "none" runtime on an untrusted provider while the privacy shield is
38
38
  // on; "gateway" runtimes get the daemon's gateway as their base URL (privacy/gateway).
39
39
  privacy: "enforced",
40
+ // agent/run/decide/turn-decision.ts refuses a turn carrying a tool list on a runtime that cannot keep one, the
41
+ // automation ladder passes such rungs over, and agent/tools/tool-allowlist.ts refuses every off-list call on the
42
+ // Claude Code loop.
43
+ toolAllowlist: "enforced",
40
44
 
41
45
  // Descriptive: true of the runtime but nothing consults these; wiring one up moves it to enforced.
42
46
  questions: "descriptive",
@@ -147,3 +147,16 @@ export const compareUnrankedModelIds = (left: string, right: string): number =>
147
147
  // True only for an id that spells out a level above the quiet end, so an unannotated id is never accused of it.
148
148
  // Exported so a settings row can label a pinned thinking variant.
149
149
  export const namesThinking = (id: string): boolean => thinkingRankOf(familyOf(id)) > UNSTATED_THINKING;
150
+
151
+ // Which of three rungs an id stands on: 0 the frontier, 1 the everyday workhorse, 2 the cheap end. A tier word decides
152
+ // where there is one, a release-local ladder (sol, terra, luna) where there is not, and an id with neither is read as
153
+ // the frontier, as the catalog order already leads with it. What the preset setups are worked out from.
154
+ export const modelRungOf = (id: string): number => {
155
+ const family = familyOf(id);
156
+ const tier = tierRankOf(family);
157
+ if (tier !== UNRANKED) {
158
+ return tier;
159
+ }
160
+ const release = releaseTierRankOf(family);
161
+ return release === UNRANKED ? 0 : release;
162
+ };
@@ -1,8 +1,8 @@
1
1
  import type { AgentProvider, ModelPin } from "../schemas/agent.js";
2
2
 
3
- // The order to try models in for a role or job (settings.modelRoles), shared by daemon and browser so both agree what a
4
- // job will spend before it runs. An empty list resolves to nothing for every role: nothing is derived or auto-selected.
5
- // The caller decides what an empty answer means (a helper is off; a run falls back to the chat's own pick).
3
+ // The order to try models in for a job's setup (model-setups.ts), shared by daemon and browser so both agree what a job
4
+ // will spend before it runs. An empty list resolves to nothing: the caller decides what that means (a helper is off; a
5
+ // run falls back to the chat's own pick).
6
6
 
7
7
  // One provider's standing in the decision: can a turn go, and what its catalog holds. ACP agents have no rung here (an
8
8
  // empty model id), but endpoint/<id> providers must be included, matching the picker's own options.
@@ -11,7 +11,8 @@ export interface ModelSource {
11
11
  readonly provider: AgentProvider;
12
12
  // Same connection predicate every surface gates on; a nonempty catalog does not imply ready to send.
13
13
  readonly ready: boolean;
14
- // Nothing here reads this (a pin is verbatim); kept because it's what a source is, and callers still gather it.
14
+ // The models it can run a turn on, newest-first order not assumed. readyChain never reads it (a pin is verbatim);
15
+ // a preset setup is worked out from it (model-setups.ts `presetPins`), so a helper-only model is left out.
15
16
  readonly models: readonly string[];
16
17
  }
17
18
 
@@ -1,37 +1,15 @@
1
- import { MODEL_ROLE_BLOCKS, MODEL_ROLES, type ModelRoleSpec } from "./model-roles.js";
1
+ import { MODEL_ROLES, PRESET_SETUPS } from "./model-roles.js";
2
2
 
3
- // The catalog draws the settings page directly, so its properties (every role in one block) must hold of the table
4
- // itself, not be remembered by whoever adds a row.
3
+ // The catalog draws the settings page directly, so its properties must hold of the table itself, not be remembered by
4
+ // whoever adds a row.
5
5
 
6
- // Typed at its declared width, not the literal tuple: trigger is absent from a helper's type, under test here.
7
- const roles: readonly ModelRoleSpec[] = MODEL_ROLES;
6
+ test("the page reads one-shots first, then whole sessions, in the table's own order", () => {
7
+ const kinds = MODEL_ROLES.map((role) => role.kind);
8
8
 
9
- test("every whole session says what starts it, and it is the block it is drawn in", () => {
10
- const blockOf = new Map(MODEL_ROLE_BLOCKS.flatMap((block) => block.roles.map((role) => [role.id, block.id] as const)));
11
-
12
- for (const role of roles) {
13
- expect(role.kind === `run` ? role.trigger : role.kind, role.id).toBe(blockOf.get(role.id));
14
- }
15
- });
16
-
17
- test("a one-shot declares no trigger at all: nothing presses a commit subject into being", () => {
18
- for (const helper of roles.filter((role) => role.kind === `helper`)) {
19
- expect(Object.keys(helper), helper.id).not.toContain(`trigger`);
20
- }
21
- });
22
-
23
- test("the blocks hold every role once, in the table's own order", () => {
24
- const blocked = MODEL_ROLE_BLOCKS.flatMap((block) => block.roles.map((role) => role.id));
25
-
26
- expect(blocked.toSorted()).toEqual(MODEL_ROLES.map((role) => role.id).toSorted());
27
- // The block order mirrors reach ordering (unpicked jobs, then click-started sessions, then auto-started ones).
28
- expect(blocked).toEqual(MODEL_ROLES.map((role) => role.id));
9
+ expect(kinds.indexOf(`run`)).toBe(kinds.lastIndexOf(`helper`) + 1);
29
10
  });
30
11
 
31
- test("each block says what it is, so the page keeps no headings of its own", () => {
32
- for (const block of MODEL_ROLE_BLOCKS) {
33
- expect(block.label, block.id).toMatch(/\S/);
34
- // A block with nothing in it would draw a heading over an empty surface.
35
- expect(block.roles.length, block.id).toBeGreaterThan(1);
36
- }
12
+ test("every job is suggested a preset, and the cheap one is never suggested for a whole session", () => {
13
+ expect(MODEL_ROLES.every((role) => PRESET_SETUPS.includes(role.setup))).toBe(true);
14
+ expect(new Set<string>(MODEL_ROLES.filter((role) => role.kind === `run`).map((role) => role.setup)).has(`low`)).toBe(false);
37
15
  });
@@ -1,15 +1,17 @@
1
1
  import { z } from "zod";
2
2
 
3
- // Every job that picks a model, one row per job; the unit is the role, not a difficulty tier. Each list is an ordered
4
- // fallback ladder; empty means the job is off (helper) or falls back to the caller's own chat model (run). Adding a
5
- // role is one row here, settings, resolver and daemon all read this table.
3
+ // Every job that picks a model, one row per job; the unit is the role, not a difficulty tier. A job runs on the setup it
4
+ // was put on (model-setups.ts), an ordered fallback ladder it shares with every other job put there; a job on none, or
5
+ // on an empty one, is off (helper) or falls back to the caller's own chat model (run). Adding a role is one row here, settings, resolver
6
+ // and daemon all read this table.
6
7
 
7
8
  export const ModelRoleKindSchema = z.enum(["helper", "run"]);
8
9
  export type ModelRoleKind = z.infer<typeof ModelRoleKindSchema>;
9
10
 
10
- // Which block a run role sorts into on the settings page: pressed by the owner, or started unprompted; helpers declare
11
- // none.
12
- export type ModelRoleTrigger = "pressed" | "unprompted";
11
+ // The three setups every sandbox has, cheapest first: worked out from what is connected until the owner edits one
12
+ // (model-setups.ts `presetPins`). Ids are stored, so they are wire-stable.
13
+ export const PRESET_SETUPS = ["low", "medium", "high"] as const;
14
+ export type PresetSetup = (typeof PRESET_SETUPS)[number];
13
15
 
14
16
  // `id` is a bare string here; narrowed to the role union on `ModelRoleSpec` below, since that union derives from this
15
17
  // table via `satisfies`.
@@ -20,13 +22,14 @@ interface ModelRoleRow {
20
22
  // Must add information beyond the label, not restate it.
21
23
  readonly blurb: string;
22
24
  readonly kind: ModelRoleKind;
23
- /** Required when `kind` is "run"; unset for helpers. */
24
- readonly trigger?: ModelRoleTrigger;
25
25
  // Icon name from the shared icon set; kept here, not in a separate web-side map keyed by id.
26
26
  readonly icon: string;
27
+ // The preset suggested for this job, offered when it is placed: what the work needs, not what it costs today. Only
28
+ // a suggestion: a job runs on nothing until someone puts it on a setup.
29
+ readonly setup: PresetSetup;
27
30
  }
28
31
 
29
- // Ids are wire-stable (`AgentTurn.runRole`); order matches the settings page's block grouping below.
32
+ // Ids are wire-stable (`AgentTurn.runRole`); order is the settings page's: one-shot helpers first, then whole sessions.
30
33
  export const MODEL_ROLES = [
31
34
  {
32
35
  id: "commit-message",
@@ -34,6 +37,7 @@ export const MODEL_ROLES = [
34
37
  blurb: "The subject written when an agent's work lands, and the release note under it.",
35
38
  kind: "helper",
36
39
  icon: "file-edit",
40
+ setup: "low",
37
41
  },
38
42
  {
39
43
  id: "session-title",
@@ -41,6 +45,7 @@ export const MODEL_ROLES = [
41
45
  blurb: "The name a conversation wears on the board, written a second into its first turn.",
42
46
  kind: "helper",
43
47
  icon: "pencil",
48
+ setup: "low",
44
49
  },
45
50
  {
46
51
  // Prompt includes the command about to run, which may be attacker-controlled; a small model can be swayed.
@@ -49,6 +54,7 @@ export const MODEL_ROLES = [
49
54
  blurb: "Which model reads your safety policy before a flagged command runs.",
50
55
  kind: "helper",
51
56
  icon: "shield",
57
+ setup: "medium",
52
58
  },
53
59
  {
54
60
  id: "loop-verdict",
@@ -56,6 +62,26 @@ export const MODEL_ROLES = [
56
62
  blurb: "Whether a loop's iteration met the goal, or the loop goes round again.",
57
63
  kind: "helper",
58
64
  icon: "check-square",
65
+ setup: "medium",
66
+ },
67
+ {
68
+ // Reads a whole conversation (capped tool output) once, so a small model with a large window suits it best.
69
+ id: "handoff-summary",
70
+ label: "Hand-off summaries",
71
+ blurb: "The summary a fresh session opens with when you choose to summarise a long conversation that hit a usage limit, instead of re-reading it all.",
72
+ kind: "helper",
73
+ icon: "align-left",
74
+ setup: "low",
75
+ },
76
+ {
77
+ // Reads one finished reply (and the ask it answered) and writes what a person hears in hands-free voice: a few
78
+ // short sentences, never markdown. A fast model matters more than a strong one, since the person is waiting.
79
+ id: "spoken-reply",
80
+ label: "Spoken replies",
81
+ blurb: "The few sentences you hear when you talk to the agent hands-free: what it did and what it needs from you, while the full reply stays on screen.",
82
+ kind: "helper",
83
+ icon: "volume-up",
84
+ setup: "low",
59
85
  },
60
86
  {
61
87
  // One reading answering both halves of "what does this chat open on": an offered `provider:model` line with an
@@ -67,79 +93,79 @@ export const MODEL_ROLES = [
67
93
  blurb: "Which model reads a new chat's first message and picks what it opens on: the model, effort and account, and the persona.",
68
94
  kind: "helper",
69
95
  icon: "sparkles",
96
+ setup: "low",
70
97
  },
71
98
  {
72
99
  id: "pipeline-fix",
73
100
  label: "Pipeline fixes",
74
101
  blurb: "The agent started by Fix on a failed pipeline.",
75
102
  kind: "run",
76
- trigger: "pressed",
77
103
  icon: "wave-pulse",
104
+ setup: "high",
78
105
  },
79
106
  {
80
107
  id: "deployment-fix",
81
108
  label: "Deployment fixes",
82
109
  blurb: "The agent started by Fix on a deployment that is down.",
83
110
  kind: "run",
84
- trigger: "pressed",
85
111
  icon: "server",
112
+ setup: "high",
86
113
  },
87
114
  {
88
115
  id: "maintenance-chore",
89
116
  label: "Maintenance chores",
90
117
  blurb: "A chore run started from the Maintenance board.",
91
118
  kind: "run",
92
- trigger: "pressed",
93
119
  icon: "wrench",
120
+ setup: "medium",
94
121
  },
95
122
  {
96
123
  id: "refactor-run",
97
124
  label: "Refactors",
98
125
  blurb: "The agent started from a hotspot or key module on a repository's Health tab.",
99
126
  kind: "run",
100
- trigger: "pressed",
101
127
  icon: "hammer",
128
+ setup: "high",
102
129
  },
103
130
  {
104
131
  id: "documentation-run",
105
132
  label: "Documentation runs",
106
133
  blurb: "A pass over a repo's own documentation.",
107
134
  kind: "run",
108
- trigger: "pressed",
109
135
  icon: "book",
136
+ setup: "medium",
110
137
  },
111
138
  {
112
139
  id: "acceptance-run",
113
140
  label: "Acceptance runs",
114
141
  blurb: "One session per story in an acceptance fan-out.",
115
142
  kind: "run",
116
- trigger: "pressed",
117
143
  icon: "list-check",
144
+ setup: "high",
118
145
  },
119
146
  {
120
- // Pressed trigger: the owner's approval click starts this turn; the queue between is just machinery.
121
147
  id: "approval-queue",
122
148
  label: "Approvals queue",
123
149
  blurb: "The turn that publishes or acts on what you approved.",
124
150
  kind: "run",
125
- trigger: "pressed",
126
151
  icon: "check-circle",
152
+ setup: "medium",
127
153
  },
128
154
  {
129
155
  id: "extension-review",
130
156
  label: "Extension update reviews",
131
157
  blurb: "The agent that reads an extension update before it is applied.",
132
158
  kind: "run",
133
- trigger: "unprompted",
134
159
  icon: "box",
160
+ setup: "medium",
135
161
  },
136
162
  {
137
163
  id: "loop-iteration",
138
164
  label: "Loop iterations",
139
165
  blurb: "Each round of a loop working towards its goal.",
140
166
  kind: "run",
141
- trigger: "unprompted",
142
167
  icon: "repeat",
168
+ setup: "high",
143
169
  },
144
170
  ] as const satisfies readonly ModelRoleRow[];
145
171
 
@@ -150,34 +176,3 @@ export const MODEL_ROLE_IDS = MODEL_ROLES.map((role) => role.id) as readonly Mod
150
176
 
151
177
  // Enum, not a string: an id outside this table names nothing, so it should fail loudly rather than be ignored.
152
178
  export const ModelRoleSchema = z.enum(MODEL_ROLE_IDS as [ModelRole, ...ModelRole[]]);
153
-
154
- // Groups MODEL_ROLES for the settings page by kind and trigger; each role belongs to exactly one block, in table order.
155
- export type ModelRoleBlockId = "helper" | "pressed" | "unprompted";
156
-
157
- export interface ModelRoleBlock {
158
- readonly id: ModelRoleBlockId;
159
- /** Heading shown on the settings page. */
160
- readonly label: string;
161
- /** Its roles, in MODEL_ROLES order. */
162
- readonly roles: readonly ModelRoleSpec[];
163
- }
164
-
165
- const rolesWhere = (match: (role: ModelRoleSpec) => boolean): readonly ModelRoleSpec[] => MODEL_ROLES.filter((role) => match(role));
166
-
167
- export const MODEL_ROLE_BLOCKS: readonly ModelRoleBlock[] = [
168
- {
169
- id: "helper",
170
- label: "Automatic helpers",
171
- roles: rolesWhere((role) => role.kind === "helper"),
172
- },
173
- {
174
- id: "pressed",
175
- label: "Runs you start",
176
- roles: rolesWhere((role) => role.kind === "run" && role.trigger === "pressed"),
177
- },
178
- {
179
- id: "unprompted",
180
- label: "Runs that start themselves",
181
- roles: rolesWhere((role) => role.kind === "run" && role.trigger === "unprompted"),
182
- },
183
- ];
@@ -0,0 +1,100 @@
1
+ import type { ModelPin } from "../schemas/agent.js";
2
+ import type { ModelSource } from "./model-pins.js";
3
+ import { endpointProvider } from "./agent-catalog.js";
4
+ import type { ModelRole } from "./model-roles.js";
5
+ import { ladderIsEmpty, presetPins, roleLadder, rolePins, roleSetupId, setupsFromRoleLists } from "./model-setups.js";
6
+
7
+ // What a job runs on is read here by the daemon and the browser alike, so the rules are pinned once: where a job starts,
8
+ // what a preset is worked out from, and what a written setup overrides.
9
+
10
+ const opus: ModelPin = { provider: `claude`, model: `claude-opus-5-5`, effort: `high` };
11
+ const haiku: ModelPin = { provider: `claude`, model: `claude-haiku-4-5` };
12
+
13
+ const CLAUDE: ModelSource = { provider: `claude`, ready: true, models: [`claude-haiku-4-5-20251001`, `claude-opus-4-8`, `claude-opus-5`, `claude-sonnet-5`] };
14
+ const CODEX: ModelSource = { provider: `codex`, ready: true, models: [`gpt-5.2-codex`, `gpt-5.6-luna`, `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5-mini`] };
15
+ const GEMINI: ModelSource = { provider: `gemini`, ready: true, models: [`gemini-3-flash`, `gemini-3-flash-lite`, `gemini-3-pro`] };
16
+
17
+ const NOTHING_SET = { modelSetups: [], roleSetups: {} };
18
+
19
+ describe(`a preset worked out from what is connected`, () => {
20
+ test(`takes each connected provider's newest model on the preset's rung, in the order they come`, () => {
21
+ expect(presetPins(`high`, [CLAUDE, CODEX, GEMINI]).map((pin) => pin.model)).toEqual([`claude-opus-5`, `gpt-5.6-sol`, `gemini-3-pro`]);
22
+ expect(presetPins(`medium`, [CLAUDE, CODEX, GEMINI]).map((pin) => pin.model)).toEqual([`claude-sonnet-5`, `gpt-5.6-terra`, `gemini-3-flash`]);
23
+ expect(presetPins(`low`, [CLAUDE, CODEX, GEMINI]).map((pin) => pin.model)).toEqual([`claude-haiku-4-5-20251001`, `gpt-5.6-luna`, `gemini-3-flash-lite`]);
24
+ });
25
+
26
+ test(`leaves out a provider that is not connected, a local endpoint, and anything past three`, () => {
27
+ const local: ModelSource = { provider: endpointProvider(`ollama`), ready: true, models: [`qwen-coder`] };
28
+ const extra: ModelSource = { provider: `grok`, ready: true, models: [`grok-5-heavy`] };
29
+ expect(presetPins(`high`, [{ ...CLAUDE, ready: false }, local, CODEX, GEMINI, extra]).map((pin) => pin.provider)).toEqual([`codex`, `gemini`, `grok`]);
30
+ expect(presetPins(`high`, [CLAUDE, CODEX, GEMINI, extra])).toHaveLength(3);
31
+ });
32
+
33
+ test(`takes a provider's own model over one it serves from another maker`, () => {
34
+ const google: ModelSource = { provider: `gemini`, ready: true, models: [`claude-opus-4-6-thinking`, `gemini-3-pro`, `gemini-3-flash`] };
35
+ expect(presetPins(`high`, [google])).toEqual([{ provider: `gemini`, model: `gemini-3-pro` }]);
36
+ // With none of its own on the rung, the borrowed one still serves.
37
+ expect(presetPins(`high`, [{ ...google, models: [`claude-opus-4-6-thinking`, `gemini-3-flash`, `gemini-3-flash-lite`] }])).toEqual([
38
+ { provider: `gemini`, model: `claude-opus-4-6-thinking` },
39
+ ]);
40
+ });
41
+
42
+ test(`is empty while nothing is connected`, () => {
43
+ expect(presetPins(`low`, [])).toEqual([]);
44
+ });
45
+ });
46
+
47
+ describe(`which setup a job runs on`, () => {
48
+ test(`a job nobody placed runs on nothing, however much is connected`, () => {
49
+ expect(roleSetupId(NOTHING_SET, `commit-message`)).toBeUndefined();
50
+ expect(ladderIsEmpty(roleLadder(NOTHING_SET, `safety-judge`))).toBe(true);
51
+ expect(rolePins(NOTHING_SET, `pipeline-fix`, [CLAUDE, CODEX, GEMINI])).toEqual([]);
52
+ });
53
+
54
+ test(`a job put on a preset nobody edited runs what is connected, and follows it`, () => {
55
+ const settings = { modelSetups: [], roleSetups: { "commit-message": `low` } };
56
+ expect(roleLadder(settings, `commit-message`)).toEqual({ kind: `preset`, preset: `low` });
57
+ expect(rolePins(settings, `commit-message`, [CLAUDE])).toEqual([{ provider: `claude`, model: `claude-haiku-4-5-20251001` }]);
58
+ });
59
+
60
+ test(`a written preset is run as written, and stops following what is connected`, () => {
61
+ const settings = { modelSetups: [{ id: `low`, pins: [opus] }], roleSetups: { "commit-message": `low` } };
62
+ expect(rolePins(settings, `commit-message`, [CLAUDE, CODEX])).toEqual([opus]);
63
+ });
64
+
65
+ test(`a preset that was only renamed still follows what is connected`, () => {
66
+ const settings = { modelSetups: [{ id: `low`, name: `Cheap` }], roleSetups: { "commit-message": `low` } };
67
+ expect(roleLadder(settings, `commit-message`)).toEqual({ kind: `preset`, preset: `low` });
68
+ });
69
+
70
+ test(`a job put on a setup of the owner's runs that setup's models`, () => {
71
+ const settings = { modelSetups: [{ id: `setup-1`, name: `Reviews`, pins: [opus, haiku] }], roleSetups: { "commit-message": `setup-1` } };
72
+ expect(rolePins(settings, `commit-message`, [CLAUDE])).toEqual([opus, haiku]);
73
+ });
74
+
75
+ test(`a job put on a setup that is gone is on none`, () => {
76
+ const settings = { modelSetups: [], roleSetups: { "commit-message": `setup-9` } };
77
+ expect(roleSetupId(settings, `commit-message`)).toBeUndefined();
78
+ });
79
+
80
+ test(`a setup of the owner's with no models is empty, never a preset`, () => {
81
+ const settings = { modelSetups: [{ id: `setup-1`, name: `Later` }], roleSetups: { "pipeline-fix": `setup-1` } };
82
+ expect(ladderIsEmpty(roleLadder(settings, `pipeline-fix`))).toBe(true);
83
+ });
84
+ });
85
+
86
+ test(`per-job lists become setups every job runs exactly as before on`, () => {
87
+ const cases = [
88
+ [`commit-message`, [haiku]],
89
+ [`session-title`, [haiku]],
90
+ [`pipeline-fix`, [opus]],
91
+ [`safety-judge`, []],
92
+ ] as const satisfies readonly (readonly [ModelRole, readonly ModelPin[]])[];
93
+ const settings = setupsFromRoleLists(Object.fromEntries(cases));
94
+
95
+ for (const [role, pins] of cases) {
96
+ expect(rolePins(settings, role, [CLAUDE, CODEX])).toEqual(pins);
97
+ }
98
+ // A job that never had a list, or had an empty one, ran on nothing and is on none.
99
+ expect([settings.roleSetups[`loop-iteration`], settings.roleSetups[`safety-judge`]]).toEqual([undefined, undefined]);
100
+ });