@intentic/sandbox-contract 1.231.0 → 1.233.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 (500) hide show
  1. package/README.md +13 -2
  2. package/dist/agent-catalog.d.ts +2 -1
  3. package/dist/agent-catalog.d.ts.map +1 -1
  4. package/dist/agent-catalog.js +1 -1
  5. package/dist/agent-catalog.js.map +1 -1
  6. package/dist/chores/chores.d.ts +1 -1
  7. package/dist/chores/chores.d.ts.map +1 -1
  8. package/dist/chores/probes.d.ts +1 -1
  9. package/dist/chores/probes.d.ts.map +1 -1
  10. package/dist/chores/stack.d.ts.map +1 -1
  11. package/dist/chores/stack.js +2 -1
  12. package/dist/chores/stack.js.map +1 -1
  13. package/dist/chores/verdict.d.ts +1 -1
  14. package/dist/chores/verdict.d.ts.map +1 -1
  15. package/dist/command-classes.d.ts +14 -0
  16. package/dist/command-classes.d.ts.map +1 -0
  17. package/dist/command-classes.js +168 -0
  18. package/dist/command-classes.js.map +1 -0
  19. package/dist/contracts/activity.contract.js +1 -1
  20. package/dist/contracts/activity.contract.js.map +1 -1
  21. package/dist/contracts/agent.contract.d.ts +51 -2
  22. package/dist/contracts/agent.contract.d.ts.map +1 -1
  23. package/dist/contracts/agent.contract.js +13 -1
  24. package/dist/contracts/agent.contract.js.map +1 -1
  25. package/dist/contracts/agents.contract.d.ts +24 -0
  26. package/dist/contracts/agents.contract.d.ts.map +1 -1
  27. package/dist/contracts/agents.contract.js +5 -1
  28. package/dist/contracts/agents.contract.js.map +1 -1
  29. package/dist/contracts/automations.contract.js +2 -1
  30. package/dist/contracts/automations.contract.js.map +1 -1
  31. package/dist/contracts/capabilities.contract.d.ts +63 -0
  32. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  33. package/dist/contracts/capabilities.contract.js +3 -1
  34. package/dist/contracts/capabilities.contract.js.map +1 -1
  35. package/dist/contracts/chores.contract.d.ts.map +1 -1
  36. package/dist/contracts/chores.contract.js +2 -1
  37. package/dist/contracts/chores.contract.js.map +1 -1
  38. package/dist/contracts/ci.contract.js +2 -1
  39. package/dist/contracts/ci.contract.js.map +1 -1
  40. package/dist/contracts/claude.contract.js +2 -1
  41. package/dist/contracts/claude.contract.js.map +1 -1
  42. package/dist/contracts/cursor.contract.js +2 -1
  43. package/dist/contracts/cursor.contract.js.map +1 -1
  44. package/dist/contracts/drafts.contract.d.ts.map +1 -1
  45. package/dist/contracts/drafts.contract.js +2 -1
  46. package/dist/contracts/drafts.contract.js.map +1 -1
  47. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  48. package/dist/contracts/endpoints.contract.js +2 -1
  49. package/dist/contracts/endpoints.contract.js.map +1 -1
  50. package/dist/contracts/exit.contract.d.ts.map +1 -1
  51. package/dist/contracts/exit.contract.js +2 -1
  52. package/dist/contracts/exit.contract.js.map +1 -1
  53. package/dist/contracts/extensions.contract.d.ts +42 -0
  54. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  55. package/dist/contracts/extensions.contract.js +4 -1
  56. package/dist/contracts/extensions.contract.js.map +1 -1
  57. package/dist/contracts/git.contract.d.ts.map +1 -1
  58. package/dist/contracts/git.contract.js +4 -1
  59. package/dist/contracts/git.contract.js.map +1 -1
  60. package/dist/contracts/grok.contract.d.ts.map +1 -1
  61. package/dist/contracts/grok.contract.js +2 -1
  62. package/dist/contracts/grok.contract.js.map +1 -1
  63. package/dist/contracts/history.contract.d.ts.map +1 -1
  64. package/dist/contracts/history.contract.js +2 -1
  65. package/dist/contracts/history.contract.js.map +1 -1
  66. package/dist/contracts/host.contract.d.ts +11 -0
  67. package/dist/contracts/host.contract.d.ts.map +1 -1
  68. package/dist/contracts/host.contract.js +4 -1
  69. package/dist/contracts/host.contract.js.map +1 -1
  70. package/dist/contracts/intentic.contract.d.ts.map +1 -1
  71. package/dist/contracts/intentic.contract.js +2 -1
  72. package/dist/contracts/intentic.contract.js.map +1 -1
  73. package/dist/contracts/inventory.contract.js +1 -1
  74. package/dist/contracts/inventory.contract.js.map +1 -1
  75. package/dist/contracts/logs.contract.d.ts.map +1 -1
  76. package/dist/contracts/logs.contract.js +1 -1
  77. package/dist/contracts/logs.contract.js.map +1 -1
  78. package/dist/contracts/loops.contract.js +2 -1
  79. package/dist/contracts/loops.contract.js.map +1 -1
  80. package/dist/contracts/panels.contract.d.ts.map +1 -1
  81. package/dist/contracts/panels.contract.js +2 -1
  82. package/dist/contracts/panels.contract.js.map +1 -1
  83. package/dist/contracts/personas.contract.js +2 -1
  84. package/dist/contracts/personas.contract.js.map +1 -1
  85. package/dist/contracts/ports.contract.d.ts.map +1 -1
  86. package/dist/contracts/ports.contract.js +2 -1
  87. package/dist/contracts/ports.contract.js.map +1 -1
  88. package/dist/contracts/prepush.contract.d.ts.map +1 -1
  89. package/dist/contracts/prepush.contract.js +2 -1
  90. package/dist/contracts/prepush.contract.js.map +1 -1
  91. package/dist/contracts/providers.contract.d.ts.map +1 -1
  92. package/dist/contracts/providers.contract.js +2 -1
  93. package/dist/contracts/providers.contract.js.map +1 -1
  94. package/dist/contracts/public.contract.d.ts.map +1 -1
  95. package/dist/contracts/public.contract.js +2 -1
  96. package/dist/contracts/public.contract.js.map +1 -1
  97. package/dist/contracts/push.contract.d.ts.map +1 -1
  98. package/dist/contracts/push.contract.js +2 -1
  99. package/dist/contracts/push.contract.js.map +1 -1
  100. package/dist/contracts/runner.contract.d.ts +151 -43
  101. package/dist/contracts/runner.contract.d.ts.map +1 -1
  102. package/dist/contracts/runner.contract.js +14 -1
  103. package/dist/contracts/runner.contract.js.map +1 -1
  104. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  105. package/dist/contracts/secrets.contract.js +2 -1
  106. package/dist/contracts/secrets.contract.js.map +1 -1
  107. package/dist/contracts/sessions.contract.js +1 -1
  108. package/dist/contracts/sessions.contract.js.map +1 -1
  109. package/dist/contracts/settings.contract.d.ts +20 -0
  110. package/dist/contracts/settings.contract.d.ts.map +1 -1
  111. package/dist/contracts/settings.contract.js +3 -1
  112. package/dist/contracts/settings.contract.js.map +1 -1
  113. package/dist/contracts/share.contract.d.ts.map +1 -1
  114. package/dist/contracts/share.contract.js +2 -1
  115. package/dist/contracts/share.contract.js.map +1 -1
  116. package/dist/contracts/skills.contract.d.ts.map +1 -1
  117. package/dist/contracts/skills.contract.js +2 -1
  118. package/dist/contracts/skills.contract.js.map +1 -1
  119. package/dist/contracts/system.contract.d.ts +35 -16
  120. package/dist/contracts/system.contract.d.ts.map +1 -1
  121. package/dist/contracts/system.contract.js +6 -1
  122. package/dist/contracts/system.contract.js.map +1 -1
  123. package/dist/contracts/translator.contract.d.ts.map +1 -1
  124. package/dist/contracts/translator.contract.js +4 -1
  125. package/dist/contracts/translator.contract.js.map +1 -1
  126. package/dist/contracts/usage.contract.js +1 -1
  127. package/dist/contracts/usage.contract.js.map +1 -1
  128. package/dist/contracts/vpn.contract.d.ts.map +1 -1
  129. package/dist/contracts/vpn.contract.js +2 -1
  130. package/dist/contracts/vpn.contract.js.map +1 -1
  131. package/dist/contracts/webext.contract.d.ts +45 -0
  132. package/dist/contracts/webext.contract.d.ts.map +1 -0
  133. package/dist/contracts/webext.contract.js +12 -0
  134. package/dist/contracts/webext.contract.js.map +1 -0
  135. package/dist/contracts/workflows.contract.js +2 -1
  136. package/dist/contracts/workflows.contract.js.map +1 -1
  137. package/dist/contracts/workspace.contract.d.ts +2 -2
  138. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  139. package/dist/contracts/workspace.contract.js +7 -1
  140. package/dist/contracts/workspace.contract.js.map +1 -1
  141. package/dist/definition-action.d.ts +7 -0
  142. package/dist/definition-action.d.ts.map +1 -0
  143. package/dist/definition-action.js +3 -0
  144. package/dist/definition-action.js.map +1 -0
  145. package/dist/definition.d.ts +1064 -0
  146. package/dist/definition.d.ts.map +1 -0
  147. package/dist/definition.js +93 -0
  148. package/dist/definition.js.map +1 -0
  149. package/dist/events.d.ts +109 -0
  150. package/dist/events.d.ts.map +1 -1
  151. package/dist/events.js +30 -1
  152. package/dist/events.js.map +1 -1
  153. package/dist/fast-tier.d.ts +1 -1
  154. package/dist/fast-tier.d.ts.map +1 -1
  155. package/dist/history-state.d.ts.map +1 -1
  156. package/dist/history-state.js +1 -0
  157. package/dist/history-state.js.map +1 -1
  158. package/dist/index.d.ts +371 -103
  159. package/dist/index.d.ts.map +1 -1
  160. package/dist/index.js +52 -1
  161. package/dist/index.js.map +1 -1
  162. package/dist/listener-protocol.js +1 -1
  163. package/dist/listener-protocol.js.map +1 -1
  164. package/dist/quick-model.d.ts +1 -1
  165. package/dist/quick-model.d.ts.map +1 -1
  166. package/dist/runner-protocol.d.ts +18 -0
  167. package/dist/runner-protocol.d.ts.map +1 -1
  168. package/dist/runner-protocol.js +8 -4
  169. package/dist/runner-protocol.js.map +1 -1
  170. package/dist/schemas/activity.d.ts +112 -0
  171. package/dist/schemas/activity.d.ts.map +1 -0
  172. package/dist/schemas/activity.js +70 -0
  173. package/dist/schemas/activity.js.map +1 -0
  174. package/dist/schemas/agent.d.ts +189 -0
  175. package/dist/schemas/agent.d.ts.map +1 -0
  176. package/dist/schemas/agent.js +170 -0
  177. package/dist/schemas/agent.js.map +1 -0
  178. package/dist/schemas/agents.d.ts +690 -0
  179. package/dist/schemas/agents.d.ts.map +1 -0
  180. package/dist/schemas/agents.js +314 -0
  181. package/dist/schemas/agents.js.map +1 -0
  182. package/dist/schemas/automations.d.ts +736 -0
  183. package/dist/schemas/automations.d.ts.map +1 -0
  184. package/dist/schemas/automations.js +218 -0
  185. package/dist/schemas/automations.js.map +1 -0
  186. package/dist/schemas/capabilities.d.ts +753 -0
  187. package/dist/schemas/capabilities.d.ts.map +1 -0
  188. package/dist/schemas/capabilities.js +269 -0
  189. package/dist/schemas/capabilities.js.map +1 -0
  190. package/dist/schemas/ci.d.ts +179 -0
  191. package/dist/schemas/ci.d.ts.map +1 -0
  192. package/dist/schemas/ci.js +99 -0
  193. package/dist/schemas/ci.js.map +1 -0
  194. package/dist/schemas/claude-gate.d.ts +13 -0
  195. package/dist/schemas/claude-gate.d.ts.map +1 -0
  196. package/dist/schemas/claude-gate.js +8 -0
  197. package/dist/schemas/claude-gate.js.map +1 -0
  198. package/dist/schemas/codebase-health.d.ts +56 -0
  199. package/dist/schemas/codebase-health.d.ts.map +1 -0
  200. package/dist/schemas/codebase-health.js +43 -0
  201. package/dist/schemas/codebase-health.js.map +1 -0
  202. package/dist/schemas/computers.d.ts +386 -0
  203. package/dist/schemas/computers.d.ts.map +1 -0
  204. package/dist/schemas/computers.js +107 -0
  205. package/dist/schemas/computers.js.map +1 -0
  206. package/dist/schemas/drafts.d.ts +84 -0
  207. package/dist/schemas/drafts.d.ts.map +1 -0
  208. package/dist/schemas/drafts.js +44 -0
  209. package/dist/schemas/drafts.js.map +1 -0
  210. package/dist/schemas/environment.d.ts +349 -0
  211. package/dist/schemas/environment.d.ts.map +1 -0
  212. package/dist/schemas/environment.js +111 -0
  213. package/dist/schemas/environment.js.map +1 -0
  214. package/dist/schemas/exit.d.ts +151 -0
  215. package/dist/schemas/exit.d.ts.map +1 -0
  216. package/dist/schemas/exit.js +115 -0
  217. package/dist/schemas/exit.js.map +1 -0
  218. package/dist/schemas/extension-updates.d.ts +1014 -0
  219. package/dist/schemas/extension-updates.d.ts.map +1 -0
  220. package/dist/schemas/extension-updates.js +197 -0
  221. package/dist/schemas/extension-updates.js.map +1 -0
  222. package/dist/schemas/fast-mode.d.ts +8 -0
  223. package/dist/schemas/fast-mode.d.ts.map +1 -0
  224. package/dist/schemas/fast-mode.js +3 -0
  225. package/dist/schemas/fast-mode.js.map +1 -0
  226. package/dist/schemas/git-history.d.ts +238 -0
  227. package/dist/schemas/git-history.d.ts.map +1 -0
  228. package/dist/schemas/git-history.js +152 -0
  229. package/dist/schemas/git-history.js.map +1 -0
  230. package/dist/schemas/git.d.ts +471 -0
  231. package/dist/schemas/git.d.ts.map +1 -0
  232. package/dist/schemas/git.js +204 -0
  233. package/dist/schemas/git.js.map +1 -0
  234. package/dist/schemas/history.d.ts +100 -0
  235. package/dist/schemas/history.d.ts.map +1 -0
  236. package/dist/schemas/history.js +54 -0
  237. package/dist/schemas/history.js.map +1 -0
  238. package/dist/schemas/hosts.d.ts +41 -0
  239. package/dist/schemas/hosts.d.ts.map +1 -0
  240. package/dist/schemas/hosts.js +18 -0
  241. package/dist/schemas/hosts.js.map +1 -0
  242. package/dist/schemas/intentic.d.ts +5 -0
  243. package/dist/schemas/intentic.d.ts.map +1 -0
  244. package/dist/schemas/intentic.js +3 -0
  245. package/dist/schemas/intentic.js.map +1 -0
  246. package/dist/schemas/internal.d.ts +8 -0
  247. package/dist/schemas/internal.d.ts.map +1 -0
  248. package/dist/schemas/internal.js +12 -0
  249. package/dist/schemas/internal.js.map +1 -0
  250. package/dist/schemas/inventory.d.ts +171 -0
  251. package/dist/schemas/inventory.d.ts.map +1 -0
  252. package/dist/schemas/inventory.js +51 -0
  253. package/dist/schemas/inventory.js.map +1 -0
  254. package/dist/schemas/logs.d.ts +67 -0
  255. package/dist/schemas/logs.d.ts.map +1 -0
  256. package/dist/schemas/logs.js +54 -0
  257. package/dist/schemas/logs.js.map +1 -0
  258. package/dist/schemas/loops.d.ts +415 -0
  259. package/dist/schemas/loops.d.ts.map +1 -0
  260. package/dist/schemas/loops.js +188 -0
  261. package/dist/schemas/loops.js.map +1 -0
  262. package/dist/schemas/maintenance.d.ts +557 -0
  263. package/dist/schemas/maintenance.d.ts.map +1 -0
  264. package/dist/schemas/maintenance.js +201 -0
  265. package/dist/schemas/maintenance.js.map +1 -0
  266. package/dist/schemas/marketplace.d.ts +64 -0
  267. package/dist/schemas/marketplace.d.ts.map +1 -0
  268. package/dist/schemas/marketplace.js +17 -0
  269. package/dist/schemas/marketplace.js.map +1 -0
  270. package/dist/schemas/panels.d.ts +61 -0
  271. package/dist/schemas/panels.d.ts.map +1 -0
  272. package/dist/schemas/panels.js +51 -0
  273. package/dist/schemas/panels.js.map +1 -0
  274. package/dist/schemas/personas.d.ts +122 -0
  275. package/dist/schemas/personas.d.ts.map +1 -0
  276. package/dist/schemas/personas.js +102 -0
  277. package/dist/schemas/personas.js.map +1 -0
  278. package/dist/schemas/plan-limits.d.ts +172 -0
  279. package/dist/schemas/plan-limits.d.ts.map +1 -0
  280. package/dist/schemas/plan-limits.js +126 -0
  281. package/dist/schemas/plan-limits.js.map +1 -0
  282. package/dist/schemas/ports.d.ts +71 -0
  283. package/dist/schemas/ports.d.ts.map +1 -0
  284. package/dist/schemas/ports.js +42 -0
  285. package/dist/schemas/ports.js.map +1 -0
  286. package/dist/schemas/provider-oauth.d.ts +128 -0
  287. package/dist/schemas/provider-oauth.d.ts.map +1 -0
  288. package/dist/schemas/provider-oauth.js +91 -0
  289. package/dist/schemas/provider-oauth.js.map +1 -0
  290. package/dist/schemas/provider-subscriptions.d.ts +9 -0
  291. package/dist/schemas/provider-subscriptions.d.ts.map +1 -0
  292. package/dist/schemas/provider-subscriptions.js +3 -0
  293. package/dist/schemas/provider-subscriptions.js.map +1 -0
  294. package/dist/schemas/public.d.ts +32 -0
  295. package/dist/schemas/public.d.ts.map +1 -0
  296. package/dist/schemas/public.js +29 -0
  297. package/dist/schemas/public.js.map +1 -0
  298. package/dist/schemas/push.d.ts +55 -0
  299. package/dist/schemas/push.d.ts.map +1 -0
  300. package/dist/schemas/push.js +60 -0
  301. package/dist/schemas/push.js.map +1 -0
  302. package/dist/schemas/secrets.d.ts +86 -0
  303. package/dist/schemas/secrets.d.ts.map +1 -0
  304. package/dist/schemas/secrets.js +51 -0
  305. package/dist/schemas/secrets.js.map +1 -0
  306. package/dist/schemas/sessions.d.ts +32 -0
  307. package/dist/schemas/sessions.d.ts.map +1 -0
  308. package/dist/schemas/sessions.js +11 -0
  309. package/dist/schemas/sessions.js.map +1 -0
  310. package/dist/schemas/settings.d.ts +548 -0
  311. package/dist/schemas/settings.d.ts.map +1 -0
  312. package/dist/schemas/settings.js +259 -0
  313. package/dist/schemas/settings.js.map +1 -0
  314. package/dist/schemas/share.d.ts +49 -0
  315. package/dist/schemas/share.d.ts.map +1 -0
  316. package/dist/schemas/share.js +26 -0
  317. package/dist/schemas/share.js.map +1 -0
  318. package/dist/schemas/shared.d.ts +22 -0
  319. package/dist/schemas/shared.d.ts.map +1 -0
  320. package/dist/schemas/shared.js +16 -0
  321. package/dist/schemas/shared.js.map +1 -0
  322. package/dist/schemas/system.d.ts +86 -0
  323. package/dist/schemas/system.d.ts.map +1 -0
  324. package/dist/schemas/system.js +64 -0
  325. package/dist/schemas/system.js.map +1 -0
  326. package/dist/schemas/terminal.d.ts +230 -0
  327. package/dist/schemas/terminal.d.ts.map +1 -0
  328. package/dist/schemas/terminal.js +133 -0
  329. package/dist/schemas/terminal.js.map +1 -0
  330. package/dist/schemas/usage.d.ts +113 -0
  331. package/dist/schemas/usage.d.ts.map +1 -0
  332. package/dist/schemas/usage.js +91 -0
  333. package/dist/schemas/usage.js.map +1 -0
  334. package/dist/schemas/vpn.d.ts +229 -0
  335. package/dist/schemas/vpn.d.ts.map +1 -0
  336. package/dist/schemas/vpn.js +115 -0
  337. package/dist/schemas/vpn.js.map +1 -0
  338. package/dist/schemas/webext.d.ts +98 -0
  339. package/dist/schemas/webext.d.ts.map +1 -0
  340. package/dist/schemas/webext.js +36 -0
  341. package/dist/schemas/webext.js.map +1 -0
  342. package/dist/schemas/workflows.d.ts +881 -0
  343. package/dist/schemas/workflows.d.ts.map +1 -0
  344. package/dist/schemas/workflows.js +164 -0
  345. package/dist/schemas/workflows.js.map +1 -0
  346. package/dist/schemas/workspace-repos.d.ts +143 -0
  347. package/dist/schemas/workspace-repos.d.ts.map +1 -0
  348. package/dist/schemas/workspace-repos.js +87 -0
  349. package/dist/schemas/workspace-repos.js.map +1 -0
  350. package/dist/schemas/workspace-search.d.ts +170 -0
  351. package/dist/schemas/workspace-search.d.ts.map +1 -0
  352. package/dist/schemas/workspace-search.js +85 -0
  353. package/dist/schemas/workspace-search.js.map +1 -0
  354. package/dist/schemas/workspace-setup.d.ts +48 -0
  355. package/dist/schemas/workspace-setup.d.ts.map +1 -0
  356. package/dist/schemas/workspace-setup.js +26 -0
  357. package/dist/schemas/workspace-setup.js.map +1 -0
  358. package/dist/schemas/workspace-tree.d.ts +120 -0
  359. package/dist/schemas/workspace-tree.d.ts.map +1 -0
  360. package/dist/schemas/workspace-tree.js +111 -0
  361. package/dist/schemas/workspace-tree.js.map +1 -0
  362. package/dist/webext-links.d.ts +13 -0
  363. package/dist/webext-links.d.ts.map +1 -0
  364. package/dist/webext-links.js +24 -0
  365. package/dist/webext-links.js.map +1 -0
  366. package/dist/webext-protocol.d.ts +8 -0
  367. package/dist/webext-protocol.d.ts.map +1 -0
  368. package/dist/webext-protocol.js +7 -0
  369. package/dist/webext-protocol.js.map +1 -0
  370. package/dist/workflow-faults.d.ts +1 -1
  371. package/dist/workflow-faults.d.ts.map +1 -1
  372. package/dist/workspace-state.d.ts +11 -2
  373. package/dist/workspace-state.d.ts.map +1 -1
  374. package/dist/workspace-state.js +4 -2
  375. package/dist/workspace-state.js.map +1 -1
  376. package/package.json +17 -6
  377. package/src/agent-catalog.test.ts +1 -1
  378. package/src/agent-catalog.ts +4 -11
  379. package/src/chores/chores.ts +1 -1
  380. package/src/chores/probes.ts +1 -1
  381. package/src/chores/stack.test.ts +19 -0
  382. package/src/chores/stack.ts +19 -3
  383. package/src/chores/verdict.test.ts +11 -1
  384. package/src/chores/verdict.ts +1 -1
  385. package/src/command-classes.test.ts +378 -0
  386. package/src/command-classes.ts +378 -0
  387. package/src/contracts/activity.contract.ts +1 -1
  388. package/src/contracts/agent.contract.ts +22 -12
  389. package/src/contracts/agents.contract.ts +6 -6
  390. package/src/contracts/automations.contract.ts +2 -2
  391. package/src/contracts/capabilities.contract.ts +3 -4
  392. package/src/contracts/chores.contract.ts +2 -1
  393. package/src/contracts/ci.contract.ts +2 -2
  394. package/src/contracts/claude.contract.ts +2 -2
  395. package/src/contracts/cursor.contract.ts +2 -2
  396. package/src/contracts/drafts.contract.ts +2 -1
  397. package/src/contracts/endpoints.contract.ts +2 -1
  398. package/src/contracts/exit.contract.ts +2 -1
  399. package/src/contracts/extensions.contract.ts +5 -5
  400. package/src/contracts/git.contract.ts +19 -18
  401. package/src/contracts/grok.contract.ts +2 -1
  402. package/src/contracts/history.contract.ts +2 -1
  403. package/src/contracts/host.contract.ts +4 -1
  404. package/src/contracts/intentic.contract.ts +2 -1
  405. package/src/contracts/inventory.contract.ts +1 -1
  406. package/src/contracts/logs.contract.ts +7 -1
  407. package/src/contracts/loops.contract.ts +2 -2
  408. package/src/contracts/panels.contract.ts +2 -1
  409. package/src/contracts/personas.contract.ts +3 -3
  410. package/src/contracts/ports.contract.ts +3 -2
  411. package/src/contracts/prepush.contract.ts +2 -1
  412. package/src/contracts/providers.contract.ts +2 -1
  413. package/src/contracts/public.contract.ts +3 -2
  414. package/src/contracts/push.contract.ts +2 -1
  415. package/src/contracts/runner.contract.ts +33 -1
  416. package/src/contracts/secrets.contract.ts +2 -1
  417. package/src/contracts/sessions.contract.ts +1 -1
  418. package/src/contracts/settings.contract.ts +3 -9
  419. package/src/contracts/share.contract.ts +3 -2
  420. package/src/contracts/skills.contract.ts +2 -1
  421. package/src/contracts/system.contract.ts +6 -9
  422. package/src/contracts/translator.contract.ts +4 -1
  423. package/src/contracts/usage.contract.ts +1 -1
  424. package/src/contracts/vpn.contract.ts +2 -8
  425. package/src/contracts/webext.contract.ts +37 -0
  426. package/src/contracts/workflows.contract.ts +2 -2
  427. package/src/contracts/workspace.contract.ts +10 -12
  428. package/src/conversation-ids.test.ts +1 -1
  429. package/src/definition-action.ts +8 -0
  430. package/src/definition.ts +235 -0
  431. package/src/events.ts +128 -22
  432. package/src/fast-tier.ts +1 -1
  433. package/src/history-state.ts +5 -0
  434. package/src/host-protocol.ts +1 -1
  435. package/src/hostnames.test.ts +2 -2
  436. package/src/index.ts +61 -1
  437. package/src/listener-protocol.ts +1 -1
  438. package/src/quick-model.ts +1 -1
  439. package/src/runner-protocol.ts +35 -5
  440. package/src/schemas/activity.ts +123 -0
  441. package/src/schemas/agent.ts +409 -0
  442. package/src/schemas/agents.ts +802 -0
  443. package/src/schemas/automations.ts +510 -0
  444. package/src/schemas/capabilities.ts +729 -0
  445. package/src/schemas/ci.ts +235 -0
  446. package/src/schemas/claude-gate.ts +13 -0
  447. package/src/schemas/codebase-health.ts +64 -0
  448. package/src/schemas/computers.ts +287 -0
  449. package/src/schemas/drafts.ts +108 -0
  450. package/src/schemas/environment.ts +276 -0
  451. package/src/schemas/exit.ts +222 -0
  452. package/src/schemas/extension-updates.ts +322 -0
  453. package/src/schemas/fast-mode.ts +8 -0
  454. package/src/schemas/git-history.ts +265 -0
  455. package/src/schemas/git.ts +430 -0
  456. package/src/schemas/history.ts +112 -0
  457. package/src/schemas/hosts.ts +37 -0
  458. package/src/schemas/intentic.ts +2 -0
  459. package/src/schemas/internal.ts +17 -0
  460. package/src/schemas/inventory.ts +68 -0
  461. package/src/schemas/logs.ts +96 -0
  462. package/src/schemas/loops.ts +401 -0
  463. package/src/schemas/maintenance.ts +421 -0
  464. package/src/schemas/marketplace.ts +23 -0
  465. package/src/schemas/panels.ts +99 -0
  466. package/src/schemas/personas.ts +281 -0
  467. package/src/schemas/plan-limits.ts +255 -0
  468. package/src/schemas/ports.ts +84 -0
  469. package/src/schemas/provider-oauth.ts +191 -0
  470. package/src/schemas/provider-subscriptions.ts +11 -0
  471. package/src/schemas/public.ts +52 -0
  472. package/src/schemas/push.ts +112 -0
  473. package/src/schemas/secrets.ts +68 -0
  474. package/src/schemas/sessions.ts +16 -0
  475. package/src/schemas/settings.ts +932 -0
  476. package/src/schemas/share.ts +71 -0
  477. package/src/schemas/shared.ts +32 -0
  478. package/src/schemas/system.ts +156 -0
  479. package/src/schemas/terminal.ts +286 -0
  480. package/src/schemas/usage.ts +303 -0
  481. package/src/{schemas.test.ts → schemas/version-seam.test.ts} +4 -2
  482. package/src/schemas/vpn.ts +215 -0
  483. package/src/schemas/webext.ts +79 -0
  484. package/src/schemas/workflows.ts +413 -0
  485. package/src/schemas/workspace-repos.ts +123 -0
  486. package/src/schemas/workspace-search.ts +141 -0
  487. package/src/schemas/workspace-setup.ts +43 -0
  488. package/src/schemas/workspace-tree.ts +234 -0
  489. package/src/title.test.ts +1 -1
  490. package/src/webext-links.ts +80 -0
  491. package/src/webext-protocol.ts +27 -0
  492. package/src/workflow-faults.test.ts +1 -1
  493. package/src/workflow-faults.ts +1 -1
  494. package/src/workspace-state.test.ts +7 -3
  495. package/src/workspace-state.ts +27 -13
  496. package/dist/schemas.d.ts +0 -9800
  497. package/dist/schemas.d.ts.map +0 -1
  498. package/dist/schemas.js +0 -4252
  499. package/dist/schemas.js.map +0 -1
  500. package/src/schemas.ts +0 -9999
@@ -0,0 +1,932 @@
1
+ // settings: per-sandbox agent settings (.intentic/config/settings.json)
2
+ import { z } from "zod";
3
+ import { AdmissionPolicySchema, AdmissionRuleSchema, CommandClassSchema } from "./agent.js";
4
+ // Which prompt the agent is, before this turn composes anything on top. Two built-in bases and an escape
5
+ // hatch: Intentic's own (the default), Claude Code's preset, or the owner's text. Declared out here rather
6
+ // than inline in the settings object because both sides of the wire branch on it, the daemon to build the
7
+ // turn, the browser to decide which base it can show you.
8
+ export const SystemPromptModeSchema = z.enum(["intentic", "claude", "custom"]);
9
+ export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
10
+ // The two bases a user can READ and fork, "custom" is excluded because there is nothing to fetch: it is
11
+ // whatever they have already typed into the settings field.
12
+ export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
13
+ // How far the dependency-freshness check may go, see the settings field of the same name for why the middle
14
+ // state exists. Declared out here because the daemon branches on it in three places (whether to wire the hook,
15
+ // whether to consult the successor list, what the notice may say) and the browser branches on it to draw the
16
+ // row: a bare inline enum would have each of those spelling the literals for itself.
17
+ export const DependencyFreshnessSchema = z.enum(["off", "versions", "full"]);
18
+ export type DependencyFreshness = z.infer<typeof DependencyFreshnessSchema>;
19
+ /* ---- rules: "at this moment, if this is true, do this" ------------------------------------------------------
20
+ *
21
+ * The one table behind every standing instruction the owner gives the sandbox about its own work. It replaces
22
+ * three settings that were the same idea built three ways, ask for proof before a turn ends, run a command
23
+ * before a push, hold or release finished work, and the point of replacing them is that a FOURTH is now a row
24
+ * in this table rather than a release.
25
+ *
26
+ * The moments are named to sit in one family with WorkspaceEventKind (`turn.settled`, `agent.landed`), because
27
+ * chores already wake on those and folding them into this table later must not mean renaming what users wrote.
28
+ */
29
+
30
+ // WHERE a rule can stand. Three, and each is a place the daemon already stopped to make a decision, this
31
+ // names those decisions rather than inventing new ones.
32
+ export const RuleMomentSchema = z.enum([
33
+ // The assistant is about to stop. A rule here can send it back to work, which is the only moment that can.
34
+ "turn.ending",
35
+ // Code is about to leave the machine. A rule here gates the push on its own exit code.
36
+ "push.starting",
37
+ // An agent's turn is over and its delta is sitting on its branch. A rule here decides whether it lands.
38
+ "agent.finished",
39
+ ]);
40
+ export type RuleMoment = z.infer<typeof RuleMomentSchema>;
41
+ /* WHAT A RULE DOES. Four shapes, and the split is functional rather than tidy: the three settings this table
42
+ * replaces need three DIFFERENT ones, which is the evidence that a single "run this command" table would have
43
+ * mangled at least two of them.
44
+ *
45
+ * command, run a shell command; its exit code is the verdict. What the pre-push check always was.
46
+ * instruct, say something to the assistant, so it acts before it finishes.
47
+ * verdict, allow or hold the thing that is about to happen. The vocabulary the permission rules already
48
+ * speak, and the honest shape of "land finished work automatically": nothing extra RUNS at that
49
+ * moment, a pass that always runs is told which way to go.
50
+ * builtin, invoke a named daemon behaviour. The escape hatch that keeps this table from having to express
51
+ * machinery it has no business expressing: the proof ledger behind "verify before finishing"
52
+ * tracks what a turn edited against what it ran, which is not a shell command and never will be.
53
+ */
54
+ /* The named behaviours a rule can invoke. Each reads a record only the daemon keeps, which is what makes them
55
+ * built-ins rather than commands: `verify-edits` weighs what a turn edited against what it ran,
56
+ * `verify-removals` weighs what a turn DELETED against what the repository's history says about those lines,
57
+ * which is a question `git log` answers and no shell one-liner an owner could type would, and
58
+ * `verify-ui-edits` weighs the rendered surfaces a turn changed against whether it ever looked at one, the one
59
+ * question a passing suite is structurally unable to answer. */
60
+ export const RuleBuiltinSchema = z.enum(["verify-edits", "verify-removals", "verify-ui-edits"]);
61
+ export type RuleBuiltin = z.infer<typeof RuleBuiltinSchema>;
62
+ export const RuleActionSchema = z.discriminatedUnion("kind", [
63
+ z.object({
64
+ kind: z.literal("command"),
65
+ command: z.string().max(500),
66
+ // Ceiling on one run, after which the child's whole process group is killed and the run is `failed`.
67
+ // Never a pass: a command that did not finish has said nothing, and a green light nobody earned is the
68
+ // one outcome a check exists to prevent.
69
+ timeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
70
+ }),
71
+ z.object({ kind: z.literal("instruct"), text: z.string().min(1).max(4000) }),
72
+ z.object({ kind: z.literal("verdict"), verdict: z.enum(["allow", "hold"]) }),
73
+ z.object({ kind: z.literal("builtin"), name: RuleBuiltinSchema }),
74
+ ]);
75
+ export type RuleAction = z.infer<typeof RuleActionSchema>;
76
+ // WHEN a rule narrows. Three keys, chosen because they cover the two things people reach for on day one,
77
+ // "only this repo" and "don't bother for a docs-only change", without opening a query language. Every key
78
+ // absent ⇒ the rule always matches at its moment, which is what the three replaced settings each did.
79
+ export const RuleConditionSchema = z.object({
80
+ // A workspace repo id, or "root". Absent ⇒ any.
81
+ repo: z.string().min(1).optional(),
82
+ // Globs the change has to touch for the rule to fire. Absent/empty ⇒ any.
83
+ paths: z.array(z.string().min(1)).max(20).optional(),
84
+ // How the turn ended. Absent/empty ⇒ any.
85
+ outcome: z.array(z.enum(["clean", "error", "conflict"])).optional(),
86
+ });
87
+ export type RuleCondition = z.infer<typeof RuleConditionSchema>;
88
+ /* ONE RULE. `id` is stable and owner-visible: it is what the activity feed names when the rule fires and what
89
+ * the last-fired store is keyed by, so it survives a relabel.
90
+ *
91
+ * WHICH ACTIONS FIT WHICH MOMENT is checked here rather than left to the consumer, because the alternative is
92
+ * a rule that saves cleanly and then quietly does nothing, the failure mode a settings screen can least
93
+ * afford. A verdict at `turn.ending` has nothing to decide; a command at `agent.finished` has no defined place
94
+ * in the landing pass and would be a promise this stage cannot keep. */
95
+ const MOMENT_ACTIONS: Record<RuleMoment, readonly RuleAction["kind"][]> = {
96
+ "turn.ending": ["builtin", "instruct", "command"],
97
+ "push.starting": ["command"],
98
+ "agent.finished": ["verdict"],
99
+ };
100
+ export const RuleSchema = z
101
+ .object({
102
+ id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
103
+ label: z.string().min(1).max(80),
104
+ moment: RuleMomentSchema,
105
+ when: RuleConditionSchema.optional(),
106
+ action: RuleActionSchema,
107
+ enabled: z.boolean().default(true),
108
+ })
109
+ .refine((rule) => MOMENT_ACTIONS[rule.moment].includes(rule.action.kind), {
110
+ message: "that action cannot stand at that moment",
111
+ path: ["action"],
112
+ });
113
+ export type Rule = z.infer<typeof RuleSchema>;
114
+ // When a rule last did something, keyed by rule id, read by the settings list so a rule nobody has seen fire
115
+ // in three weeks is visible as such. Kept out of the settings object on purpose: a firing is not an edit, and
116
+ // writing the owner's config on every push would make every run a settings save.
117
+ export const RuleFiringsSchema = z.record(z.string(), z.number());
118
+ export type RuleFirings = z.infer<typeof RuleFiringsSchema>;
119
+ /* WHERE A SKILL CAME FROM, the fact that decides everything else about its row.
120
+ *
121
+ * A skill is inert text the agent reads, and this sandbox grows them from seven directions at once: the daemon
122
+ * writes one per baked tool and one per core feature that has a cheatsheet, connecting a tool or a machine
123
+ * writes one for that connection, the owner writes their own, a persona carries its own in its kit, an
124
+ * installed extension ships some inside its checkout, and a plugin capability clones a repo full of them.
125
+ * Nothing used to LIST the result, which is the whole gap this vocabulary closes, "what does my agent know
126
+ * right now" had no answer on screen, and a skill spends the agent's attention whether or not anyone remembers
127
+ * adding it.
128
+ *
129
+ * builtin this image ships it, a baked tool's cheatsheet, or a core feature's
130
+ * own the owner wrote it (.intentic/config/skills/), and only these are editable here
131
+ * capability something connected brought it: a CLI tool, a machine, a browser account, a VPN
132
+ * extension an installed extension ships it inside its checkout
133
+ * plugin a plugin capability cloned a repo that holds it
134
+ * persona one card's own kit carries it, and only turns wearing that card ever see it
135
+ * dropped it is simply sitting in the loaded folder, put there by hand, or by the agent itself
136
+ *
137
+ * `persona` is the one origin that is not on for everybody, which is why it needs its own word rather than
138
+ * being filed under `own`: it says "the agent knows this when it is wearing that card", and a list that showed
139
+ * it as an ordinary skill of the owner's would be claiming it applies to every chat.
140
+ *
141
+ * `dropped` is the honest bottom of the list rather than a category anything creates on purpose: the promise
142
+ * this surface makes is that it shows EVERYTHING the agent knows, so a file nothing else claims has to list as
143
+ * the loose file it is instead of being quietly left out.
144
+ *
145
+ * Deliberately NOT a capability kind. A capability holds a credential, can be broken right now, and wants a
146
+ * status light; a skill either exists or it does not. See _sandbox/sandbox/src/settings/skill-inventory.ts. */
147
+ export const SkillOriginSchema = z.enum(["builtin", "own", "capability", "extension", "plugin", "persona", "dropped"]);
148
+ export type SkillOrigin = z.infer<typeof SkillOriginSchema>;
149
+ /* A skill's own name, the directory it lives in and the word the agent invokes it by. Same slug shape the SDK's
150
+ * loader accepts, checked here so a bad name is a refused save rather than a skill that silently never loads. */
151
+ export const SkillNameSchema = z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "a skill name is lowercase letters, digits and dashes");
152
+ export const SkillSummarySchema = z.object({
153
+ /* The handle the read/remove routes take. An `own` or `builtin` skill IS its name (they share one directory,
154
+ * so names there are already unique); one that belongs to something else is `<origin>:<owner>:<name>`,
155
+ * because two plugins may each ship a `review` and the list has to be able to tell them apart. */
156
+ id: z
157
+ .string()
158
+ .describe(
159
+ "Its handle, which reading and deleting take. A skill of your own is simply its name; one belonging to something else is qualified, because two packages may each ship a review.",
160
+ ),
161
+ name: z.string().describe("Its name."),
162
+ // The frontmatter line the agent routes on, empty when a shipped skill declares none, which is worth
163
+ // showing as the blank it is rather than papering over: a skill with no description is rarely picked.
164
+ description: z
165
+ .string()
166
+ .describe(
167
+ "What it is for, which is the line the agent reads to decide whether to reach for it. Empty when the skill declares none, which is worth showing as the blank it is: a skill with no description is rarely picked.",
168
+ ),
169
+ origin: SkillOriginSchema.describe("Where it came from."),
170
+ // Who ships it, as the row names it, an extension's title, a plugin capability's id, a setting's name.
171
+ owner: z.string().optional().describe("Who ships it, as the row would name them."),
172
+ enabled: z.boolean().describe("Whether the agent can reach it."),
173
+ /* Whether THIS surface can switch it. True only for the skills the settings `skills` list governs (baked
174
+ * tools and the owner's own): everything else is on because its extension, its plugin or another setting is,
175
+ * and a switch here that silently did nothing would be worse than no switch at all, the row names its
176
+ * owner instead. */
177
+ switchable: z
178
+ .boolean()
179
+ .describe(
180
+ "Whether this surface can switch it. Everything else is on because its extension or its plugin is, and a switch here that silently did nothing would be worse than none, so the row names its owner instead.",
181
+ ),
182
+ // Whether the owner may rewrite the text here. Their own skills only, a shipped one is its author's, and
183
+ // editing it in place would be undone the next time the thing that ships it reconciles.
184
+ editable: z
185
+ .boolean()
186
+ .describe(
187
+ "Whether it can be rewritten here. Your own only: editing somebody else's in place would be undone the next time the thing that ships it catches up.",
188
+ ),
189
+ /* Whether it can be deleted from this surface. Wider than `editable` by exactly one case: a skill someone
190
+ * dropped into the loaded folder is not the owner's to edit (its home is that folder, not their store) but is
191
+ * absolutely theirs to clear out, and with no switch and no owning extension there would otherwise be no way
192
+ * to get rid of it short of the file tree. */
193
+ removable: z.boolean(),
194
+ });
195
+ export type SkillSummary = z.infer<typeof SkillSummarySchema>;
196
+ export const SkillsListSchema = z.array(SkillSummarySchema);
197
+ // One skill's full text, for reading it on screen. Its own route rather than a field on the summary: bodies run
198
+ // to thousands of words and a list of twenty would cost a hundred kilobytes to draw a group of one-line rows.
199
+ export const SkillBodySchema = z.object({
200
+ id: z.string().describe("The skill's id, which can carry the owner it came from."),
201
+ name: z.string().describe("Its name."),
202
+ // Everything after the frontmatter, the instructions themselves, as written.
203
+ body: z.string().describe("The instructions themselves, as written."),
204
+ });
205
+ export type SkillBody = z.infer<typeof SkillBodySchema>;
206
+ export const SkillIdSchema = z.object({
207
+ id: z
208
+ .string()
209
+ .min(1)
210
+ .describe(
211
+ "Which skill. It travels in the query rather than the address, because an id can name the owner it came from and that will not fit in a path.",
212
+ ),
213
+ });
214
+ /* A skill the owner writes. Three fields because a skill IS three things, what it is called, when to reach for
215
+ * it, and what to do, and the daemon assembles the frontmatter from the first two so a saved skill can never
216
+ * be one the loader skips over. `description` is required for the reason above: it is the only part the model
217
+ * reads before deciding whether to open the rest. */
218
+ export const SkillDraftSchema = z.object({
219
+ name: SkillNameSchema.describe("What to call it. Saving over an existing name rewrites it, which is also how one is renamed."),
220
+ description: z.string().min(1).max(1024).describe("What it is for, which is what the agent reads to decide whether to reach for it."),
221
+ body: z.string().min(1).describe("The skill itself."),
222
+ });
223
+ export type SkillDraft = z.infer<typeof SkillDraftSchema>;
224
+ export const SkillRemoveSchema = z.object({
225
+ name: SkillNameSchema.describe("Which skill to delete. The text and the enabled list are both updated, so nothing is left half done."),
226
+ });
227
+ // Small user-owned config the /settings routes edit and streamAgent reads, all opt-in booleans the owner
228
+ // toggles in the UI (so each can be A/B benchmarked):
229
+ // stableSystemPrompt, keeps the system prompt byte-stable across turns (the delegation note rides the user
230
+ // message instead of the preset `append`) so the provider prompt cache survives.
231
+ // skills , names of baked-tool skills to load into .agents/skills so the agent reaches for them
232
+ // (e.g. "lsp". TS rename + diagnostics over the language service); a name absent ⇒ its
233
+ // skill file isn't written, so the agent doesn't reach for it. Data-driven: a new baked
234
+ // tool is one daemon-side registry entry, not a new settings field.
235
+ // hashlineEdits , swaps the native Read/Edit/Write for hash-anchored edits on the Claude path (stale-file
236
+ // guard + fewer output tokens); off ⇒ the native file tools.
237
+ // terseOutput , appends a concise-response steer to the end of the system prompt (a stable suffix, so it
238
+ // composes with stableSystemPrompt) to cut the model's OWN output tokens.
239
+ // systemPromptMode , which base the agent's prompt is: "intentic" (default), "claude", or "custom".
240
+ // systemPrompt , the owner's own prompt text, used only by "custom" mode, where it is the ENTIRE system
241
+ // prompt and nothing the daemon would otherwise append rides with it, see its own note.
242
+ // iqSearch , loads the image-baked iq Claude Code plugin (skill + SessionStart nudge) so the agent
243
+ // prefers the iq CLI over grep/find/Glob; off ⇒ plugin not loaded, native search tools
244
+ // only. Opt-in (default off); the browser Search box uses iq regardless.
245
+ // iqSearchHoldout , conversation-level measurement control for iqSearch (UsageTurn.iqSearchArm). The arm
246
+ // stays fixed because teaching already loaded into a session cannot be removed next turn.
247
+ // workspaceMap , computes an AREA index of the project a run starts in and prepends it to the
248
+ // conversation's opening message, so the turn does not have to buy its own orientation
249
+ // with a directory listing. Generated from the filesystem every time, never stored.
250
+ // sidecars , the background pass converging a markdown shadow of every binary workspace file
251
+ // (docx/pdf/images/audio → .intentic/local/cache/derived/) the moment it lands, via
252
+ // the baked fileq CLI, so reasoning-time reads are pre-derived. The CLI itself is
253
+ // always available; this gates only the eager watcher-driven derivation.
254
+ // dependencyFreshness, whether a version the agent is about to pin is checked against the package's own
255
+ // registry before it lands, and what the check is allowed to say: "off" (no hook is
256
+ // wired at all), "versions" (registry-derivable facts only), "full" (also names a
257
+ // maintained successor, where one is known and the registry corroborates it).
258
+ // outputCleaners , the Bash output-cleaner spec (agent-output-filter): "off" = filter disabled,
259
+ // "" = all cleaners on (default), else an iq-style allow-list / default-minus
260
+ // spec ("git,pnpm" = only those; "-cap" = all except). Threaded to the filter via env.
261
+ // outputHoldout , measurement control: a fraction [0,1] of Bash commands whose output bypasses cleaning
262
+ // (recorded raw as `heldOut`), so the savings report compares a real cleaned-vs-raw
263
+ // population instead of an estimate. 0 = no holdout (default).
264
+ // rules , the standing "at this moment, if this is true, do this" table (RuleSchema): what proves
265
+ // a turn's work, what runs before a push, whether finished work lands by itself. Empty
266
+ // (the default) means none of those happen, which is the shape a fresh sandbox has.
267
+ // automationFailureLimit, consecutive `error` runs after which an automation is disabled rather than left
268
+ // firing forever; 0 (default) ⇒ never.
269
+ // subagentsAtOnce / subagentsPerTurn / subagentDepth, the harness's own ceilings on delegation, raised or
270
+ // lowered from one place; each defaults to what the CLI enforces on its own.
271
+ // The booleans default off, outputCleaners defaults "" (cleaning on) and outputHoldout 0; iqSearch stays off
272
+ // until the owner enables it. `skills` is the exception and defaults to the
273
+ // baked tools worth having on: a skill file is the ONLY thing that tells the agent a baked binary exists, and
274
+ // with the list empty `lsp` went used once in 866 sessions, not declined, never learned about.
275
+ //
276
+ // Every field carries that default IN THE SCHEMA, so a settings object written before a field existed still
277
+ // parses, the absent key reads as its default. That is not a compatibility layer, it is the seam this shape
278
+ // spans: the browser ships with the platform while the daemon ships inside the user's sandbox image, so a web
279
+ // build is routinely NEWER than the daemon answering it. Requiring the key instead makes the whole settings
280
+ // surface fail to parse the moment a toggle is added, which reaches the user as a page of switches that are
281
+ // silently dead, not as an error. It also means an older on-disk manifest keeps the owner's other picks rather
282
+ // than being discarded whole.
283
+
284
+ export const SandboxSettingsSchema = z.object({
285
+ stableSystemPrompt: z
286
+ .boolean()
287
+ .default(false)
288
+ .describe(
289
+ "Keep the instructions identical between turns so the provider can cache them, moving anything that varies into the message instead. Cheaper, at the cost of some flexibility.",
290
+ ),
291
+ skills: z.array(z.string()).default(["lsp", "fileq"]).describe("Which skills are switched on."),
292
+ hashlineEdits: z
293
+ .boolean()
294
+ .default(false)
295
+ .describe(
296
+ "Have the agent edit files by line number rather than by quoting the text it wants replaced. Cheaper on large files, and less forgiving of a stale read.",
297
+ ),
298
+ terseOutput: z.boolean().default(false).describe("Ask the agent to say less. It changes how much it narrates, not how much it does."),
299
+ /* Measurement control for the terse steer, at TURN level, the same trick `outputHoldout` plays over
300
+ * commands, one layer up. A fraction [0,1] of otherwise-eligible turns run WITHOUT the steer and record
301
+ * which arm they ran on (UsageTurn.terse), so the savings report can compare two real populations.
302
+ *
303
+ * It has to be an experiment: unlike a cleaned command, which yields its own raw baseline in the same
304
+ * event, a turn cannot be re-run to see what it would have said unsteered. 0 ⇒ no measurement (every
305
+ * eligible turn is steered), which is the default because the control costs the very tokens it measures. */
306
+ terseHoldout: z
307
+ .number()
308
+ .min(0)
309
+ .max(1)
310
+ .default(0)
311
+ .describe(
312
+ "What share of turns to run without that instruction, so the two can be compared honestly. It has to be measured this way, because a turn cannot be re-run to see what it would have said. Zero means no measurement, which is the default, since the comparison costs the very tokens it is measuring.",
313
+ ),
314
+ /* WHICH SYSTEM PROMPT THE AGENT RUNS ON, the base, before anything this turn composes.
315
+ *
316
+ * intentic. Intentic's own prompt, tuned for this harness (intentic-prompt.ts). The default.
317
+ * claude . Claude Code's preset, as shipped in the CLI this sandbox runs. Not a copy stored here, so
318
+ * picking it tracks whatever the installed CLI's prompt is rather than freezing at a snapshot.
319
+ * custom , `systemPrompt` below, and nothing else at all.
320
+ *
321
+ * The first two are peers: both get the harness's own guidance appended (the AskUserQuestion/plan blocks
322
+ * the chat's cards need, the checklist guidance behind the todo panel, the browser-tool guidance), plus the
323
+ * delegation note and the terse steer. `custom` is the one that does not, by the owner's explicit choice,
324
+ * see the field below. */
325
+ systemPromptMode: SystemPromptModeSchema.default("intentic").describe(
326
+ "Which instructions the agent starts from: intentic's own, the ones the installed Claude Code carries, or your own. The first two both get this product's own guidance added on top; your own gets nothing added, which is the point of it.",
327
+ ),
328
+ /* The owner's own prompt, used only when `systemPromptMode` is "custom". Then it is the ENTIRE system
329
+ * prompt: both built-in bases are gone and so is everything the daemon would otherwise append, the widget
330
+ * guidance the chat's cards are driven by, and the terse-output steer (whose toggle goes inert). That is
331
+ * the price of total control, and the UI states it at the moment of the edit rather than letting the
332
+ * widgets go quietly dark. Only the cross-provider delegation note survives, because it has a home outside
333
+ * the system prompt already (the user-message preamble stableSystemPrompt puts it in).
334
+ *
335
+ * Cap is roomy, the bases it stands in for are ~6.8k characters, but finite, because every turn pays it. */
336
+ systemPrompt: z
337
+ .string()
338
+ .max(20000)
339
+ .default("")
340
+ .describe(
341
+ "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.",
342
+ ),
343
+ iqSearch: z
344
+ .boolean()
345
+ .default(false)
346
+ .describe("Teach the agent how to use this workspace's own search tool, rather than leaving it to grep around."),
347
+ /* Measurement control for the iq search teaching, at CONVERSATION level. A fraction [0,1] of conversations
348
+ * run without the plugin/instruction and stamp that stable arm on every turn. Per-turn randomization is not
349
+ * a valid control here: once the teaching enters a provider session, withholding it from the next request
350
+ * does not make the model forget it. 0 ⇒ no measurement and every conversation receives the teaching. */
351
+ iqSearchHoldout: z
352
+ .number()
353
+ .min(0)
354
+ .max(1)
355
+ .default(0)
356
+ .describe(
357
+ "What share of conversations to run without that teaching, so the two can be compared. Whole conversations rather than individual turns, because once the teaching is in a session, withholding it from the next request does not make the model forget it.",
358
+ ),
359
+ /* THE MAP THE TURN OPENS WITH, which areas the project a run starts in has, one derived line on what each
360
+ * is for, and where the run is standing among them (agent/workspace-map.ts).
361
+ *
362
+ * It answers the question every first turn has whatever it was asked, "what is this and where am I in
363
+ * it", which across a hundred sessions of this workspace was being bought with a directory listing in two
364
+ * turns out of five, and with ~5.3k tokens of tool results before the job was touched.
365
+ *
366
+ * ROOTED AT THE RUN'S STARTING FOLDER rather than at the workspace: a persona's start folder, an isolated
367
+ * conversation's worktree, or wherever the turn's cwd is. It maps the project containing that folder and
368
+ * names the rest of the workspace on one line, because a run three levels inside one project is not asking
369
+ * about the others.
370
+ *
371
+ * REGENERATED, NEVER STORED, which is the whole reason it is a mechanism rather than a paragraph in the
372
+ * system prompt or a hand-written CLAUDE.md: in the ten days that motivated it this repo's two busiest
373
+ * top-level directories stopped existing, and every written-down copy of the layout was wrong by the end of
374
+ * the window. Off by default, it spends its tokens on the opening message of every conversation. */
375
+ workspaceMap: z
376
+ .boolean()
377
+ .default(false)
378
+ .describe(
379
+ "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.",
380
+ ),
381
+ /* THE MARKDOWN SHADOWS OF BINARY FILES, the eager half of fileq (_sandbox/fileq). The lazy half — the
382
+ * `fileq` CLI an agent runs mid-task — is always on PATH and gated only by its skill; this switch is
383
+ * about the BACKGROUND pass: the daemon watching /work and converging a sidecar under
384
+ * .intentic/local/cache/derived/ for every docx/xlsx/pptx/pdf/image/audio file the moment it lands or
385
+ * changes, so reasoning-time reads hit a shadow that already exists. Off by default like every boolean
386
+ * here: it spends CPU unasked, on every file that lands, which is the owner's call to make. */
387
+ sidecars: z
388
+ .boolean()
389
+ .default(false)
390
+ .describe(
391
+ "Keep an up-to-date markdown rendering of every document, image and audio file in the workspace, made in the background as files land, so the agent reads a pre-derived text instead of paying to parse the file mid-task. Costs background CPU on a document-heavy workspace, so it is a switch rather than a default.",
392
+ ),
393
+ /* IS THE VERSION ABOUT TO BE PINNED THE ONE THE REGISTRY ACTUALLY HAS?
394
+ *
395
+ * A model writes a version from memory, and memory has a publication date. Across this workspace's own
396
+ * session history the failure is small, steady, and never self-correcting: of the version literals an
397
+ * agent typed into a manifest or an install command, more than half were already behind the newest
398
+ * release at the moment they were typed, and a third were written without any registry ever being asked.
399
+ * Nothing downstream catches it either — a stale pin installs cleanly, type-checks, and passes the suite,
400
+ * so every gate this sandbox already has says yes to it.
401
+ *
402
+ * THREE STATES, and the middle one is the whole feature. The two halves of "this dependency choice is
403
+ * behind" are different KINDS of claim:
404
+ * versions, a measurement. The registry publishes what the newest release is and when; comparing is
405
+ * arithmetic, it works for any package, and there is nothing to keep current.
406
+ * full , a measurement plus an opinion. Naming `date-fns` as what to reach for instead of `moment`
407
+ * is a judgement somebody made, and judgements rot. An owner is entitled to take the facts
408
+ * and decline the opinions, which is exactly what the two settings are.
409
+ * The successor list never asserts staleness on its own: an entry surfaces only where the registry
410
+ * corroborates it (deprecated, or nothing published in eighteen months), so the curation supplies the
411
+ * NAME of the replacement and the measurement supplies the reason. That split is what stops it decaying
412
+ * into a list of last year's preferences.
413
+ *
414
+ * IT INFORMS, IT NEVER BLOCKS, and that is not timidity. Matching a version the workspace already pins is
415
+ * the common case and it is CORRECT: a new package inside a monorepo should take the catalog's
416
+ * `typescript`, not the newest one on the registry. A gate that refused would fight legitimate work
417
+ * several times for every mistake it caught, so the fact rides back as context and the model decides.
418
+ *
419
+ * Off by default like every other flag here, and off means genuinely nothing: no hook is wired, and the
420
+ * sandbox makes no network call it would not otherwise have made. */
421
+ dependencyFreshness: DependencyFreshnessSchema.default("off")
422
+ .describe(
423
+ "Whether a version the agent is about to pin is checked against the package's own registry first. Facts only, or facts plus the name of a maintained replacement where the registry agrees the current choice has been abandoned. It tells the agent and lets it decide rather than refusing, because matching a version your project already uses is usually the right answer and a gate would fight it.",
424
+ ),
425
+ outputCleaners: z
426
+ .string()
427
+ .default("")
428
+ .describe("Which command outputs to trim before the agent reads them, cutting the noise a build tool prints without cutting what it said."),
429
+ outputHoldout: z
430
+ .number()
431
+ .min(0)
432
+ .max(1)
433
+ .default(0)
434
+ .describe("What share of commands to leave untrimmed, so the saving can be measured against a real comparison rather than estimated."),
435
+ /* The models behind the small automatic jobs that are not a conversation, today the commit message
436
+ * written when an agent's work lands. An ORDERED list of `${provider}:${modelId}`, tried top to bottom, or
437
+ * EMPTY for Auto.
438
+ *
439
+ * A LIST rather than a pick, because the single interesting failure of this feature is a model that is
440
+ * connected and simply will not answer today: the account's allowance went on the chat, and one spent
441
+ * provider then takes the job down for hours while the others sit idle. Written in order, the daemon steps
442
+ * over the spent one and the message still gets written (agent/quick-model.ts walks it).
443
+ *
444
+ * Empty is the default and still the interesting case: Auto is resolved from whatever accounts are
445
+ * connected at the moment it is read (resolveQuickModels), so it can never name a provider this sandbox has
446
+ * no credential for, it improves by itself when one is added, and it is a ladder too, the cheapest rung of
447
+ * every connected provider, best first. Storing resolved ids here instead would go stale exactly like a
448
+ * pinned model does. */
449
+ quickModel: z
450
+ .array(z.string())
451
+ .max(10)
452
+ .default([])
453
+ .describe(
454
+ "Which models do the small automatic jobs that are not a conversation, such as writing a commit message. A list rather than one pick, tried in order, because the interesting failure is a model that is connected and simply will not answer today. Empty means work it out from whatever is connected, which improves by itself as accounts are added.",
455
+ ),
456
+ /* WHICH REPOS KEEP A CHANGELOG, the repos whose commits carry a `Release-Note:` trailer, written by the
457
+ * same quick model that drafts the subject (git/commit-message.ts) and harvested at release time.
458
+ *
459
+ * A LIST OF REPOS RATHER THAN A FLAG, and EMPTY BY DEFAULT, because this daemon runs on the user's repos
460
+ * rather than on ours. The commit drafter's one standing rule is that house style is INFERRED, never
461
+ * prescribed, it reads the last handful of subjects and matches them, so a repo that spells its commits
462
+ * some other way is never argued with. A note trailer is the one thing that cannot be inferred that way: a
463
+ * repo which has never written one gives the model nothing to copy, so asking for it has to be somebody's
464
+ * explicit decision. Empty means every repo behaves exactly as it did before this existed.
465
+ *
466
+ * Named by repo id ("root", or the root-relative dir discoverRepos reports), because a workspace holds
467
+ * several repos and a commit can span them: the trailer is written when the commit touches a repo that
468
+ * asked for one, and a repo that did not ask never gets a line it has to explain to its reviewers. */
469
+ changelogRepos: z
470
+ .array(z.string())
471
+ .max(50)
472
+ .default([])
473
+ .describe(
474
+ "Which repositories keep a changelog, and so get a user-facing note written alongside each merge. A list rather than a switch, and empty by default, because the commit writer's standing rule is to copy the house style rather than impose one, and a repository that has never written such a note gives it nothing to copy.",
475
+ ),
476
+ /* WHAT AN AGENT RUN OPENS ON, the tier above quickModel, and the answer for every turn a SURFACE starts
477
+ * rather than a person at a composer: Fix with agent on a pipeline or a deployment, a Maintenance chore, a
478
+ * Documentation or Acceptance run, the fix a failed pre-push check proposes. An ORDERED list of
479
+ * `${provider}:${model}` (quickModelKey) plus the reasoning effort beside it; EMPTY ⇒ whatever the chat
480
+ * composer would have started with, which is the honest floor because it is the model the user already
481
+ * chose to work with.
482
+ *
483
+ * A LIST, for the reason quickModel is one: the account at the head runs out, and every surface-started run
484
+ * in the sandbox then fails on a credential the user cannot see from the row they pressed. Written in order,
485
+ * the next one down catches it (turn-resume.ts walks it).
486
+ *
487
+ * PINNED, NOT DERIVED, the deliberate difference from quickModel one line above, and the reason these are
488
+ * two settings rather than one. A quick helper exists to stay OFF the frontier tier, so cheapest-connected
489
+ * is the right automatic answer and an empty list resolves to Auto. An agent run has to read a failing
490
+ * suite, or a container log, or a story, and repair the thing: the tier is a judgement about how much the
491
+ * job is worth, nothing here can make it, and a wrong guess is billed in whole sessions rather than in
492
+ * tokens. So an empty list here resolves to NOTHING and the composer's own pick answers instead.
493
+ *
494
+ * The daemon applies this to any turn flagged `unattended` that names no model of its own, one rule, so a
495
+ * surface added tomorrow inherits it by saying what it is instead of re-deriving where models come from. A
496
+ * surface MAY still name one (the shared run button's caret, Acceptance's per-run pick), and that wins. */
497
+ agentRunModels: z
498
+ .array(z.string())
499
+ .max(10)
500
+ .default([])
501
+ .describe(
502
+ "Which models run the work a screen starts rather than a person: fixing a red pipeline, a maintenance chore, an acceptance run. Tried in order, so one spent account does not take every such run down. Empty falls back to whatever the chat would have used, which is the honest floor because it is the model you already chose to work with.",
503
+ ),
504
+ agentRunEffort: z.string().default("").describe("How hard those runs should think."),
505
+ /* AUTOMATIC TIER SELECTION: may the daemon run an easy-looking turn on a cheaper rung of the provider the
506
+ * user is already on, instead of on the model they picked?
507
+ *
508
+ * THREE STATES RATHER THAN A TOGGLE, because the middle one is the only honest way to reach the third.
509
+ * Nobody, this repo included, can name a sensible cutoff for "easy enough" without traffic to fit it
510
+ * against, and a routing threshold guessed in advance is how a cost feature quietly becomes a quality
511
+ * regression. So:
512
+ * off — the judge never runs. Nothing is scored, nothing is recorded, turns run on the user's pick.
513
+ * shadow — the judge runs and its verdict is written to the spend ledger beside what the turn actually
514
+ * cost, and NOTHING IS ROUTED. This is the default: it spends no tokens, changes no behaviour,
515
+ * and is the only thing that can turn the weights in prompt-complexity.ts from a hypothesis
516
+ * into a measurement.
517
+ * on — a turn judged fast runs on the cheap rung (fast-tier.ts), when the provider publishes one.
518
+ *
519
+ * IT CAN ONLY EVER ROUTE DOWN. There is no "which model is the standard tier" setting because the standard
520
+ * tier is the model the user already chose, so the worst case of a wrong verdict is one turn's quality on a
521
+ * model they can see on the card and correct, never a bill they did not ask for. That asymmetry is why this
522
+ * can default to shadow rather than to off: shadow costs nothing and `on` cannot overspend. */
523
+ autoTier: z
524
+ .enum(["off", "shadow", "on"])
525
+ .default("shadow")
526
+ .describe(
527
+ "Whether an easy-looking turn may run on a cheaper model from the same provider. Three states rather than a switch, because the middle one is the only honest road to the third: it scores every turn and routes nothing, so the guess can become a measurement before it changes anything. It can only ever route down, so the worst case is one turn's quality rather than a bill nobody asked for.",
528
+ ),
529
+ /* HOW EAGER THE JUDGE IS, the one dial this feature exposes and the answer to what the Measure mode is for:
530
+ * the numbers say how many turns were called simple, and this is the control that acts on them.
531
+ *
532
+ * Three named stops rather than a number, because the number means nothing to anyone who has not read the
533
+ * weights, while "only the unmistakable" / "the default" / "an easy question about real code too" are three
534
+ * sentences an owner can actually hold an opinion about (FAST_CEILINGS spells out each). It moves the
535
+ * cutoff and nothing else: the rule that a downgrade needs something POSITIVE to have been said holds at
536
+ * every stop, so no setting of this can start downgrading short vague requests.
537
+ *
538
+ * `balanced` is the default and is what every verdict recorded before this existed was judged against, so
539
+ * the shadow history stays comparable across the change rather than silently becoming two populations. */
540
+ autoTierEagerness: z
541
+ .enum(["cautious", "balanced", "eager"])
542
+ .default("balanced")
543
+ .describe(
544
+ "How readily a turn counts as simple enough for the cheaper model. It moves only the cutoff: at every setting a turn still has to say something positively easy, so nothing here can downgrade a short vague request.",
545
+ ),
546
+ /* WHICH CHEAP MODEL A DOWNGRADED TURN LANDS ON, an ordered list of `${provider}:${model}` keys
547
+ * (quickModelKey), or EMPTY for Auto.
548
+ *
549
+ * Empty is the default and the interesting case, exactly as quickModel's is: Auto is the cheapest row the
550
+ * turn's own provider publishes, read through the same cheap-end order (compareCheapestFirst), so the two
551
+ * features can never disagree about which rung is the cheap one, and connecting an account tomorrow
552
+ * improves the answer by itself.
553
+ *
554
+ * A LIST, so a sandbox working across several providers can name the rung it wants on each. But unlike the
555
+ * two lists above this one is NOT a failure ladder: entries naming a provider other than the turn's own are
556
+ * dropped rather than tried, because switching provider retires the conversation's session (turnRequest.ts
557
+ * `resumes`), and starting the conversation over to save a fraction of a cent is not a saving. The first
558
+ * entry that names this provider AND is genuinely cheaper than the pick wins; if none does, Auto answers. */
559
+ autoFastModels: z
560
+ .array(z.string())
561
+ .max(10)
562
+ .default([])
563
+ .describe(
564
+ "Which cheaper model a downgraded turn lands on. A list so a sandbox spanning providers can name a rung on each, but not a fallback ladder: an entry naming a different provider than the turn is on is skipped rather than tried, because switching provider retires the conversation and starting over to save a fraction of a penny is not a saving. Empty picks the cheapest the turn's own provider publishes.",
565
+ ),
566
+ // How long a finished agent stays on the board before it is archived automatically (days; 0 ⇒ never).
567
+ // Unlike every other flag here this one defaults ON, because the lane it governs is the board's only
568
+ // terminal state: without a sweep the Finished lane grows for the life of the sandbox, and each card it
569
+ // holds is a live worktree checkout, not just a row.
570
+ agentRetentionDays: z
571
+ .number()
572
+ .min(0)
573
+ .max(365)
574
+ .default(3)
575
+ .describe(
576
+ "How many days a finished conversation stays on the board before being put away. Zero means never. The one setting here that defaults on, because each card left behind is a real working copy on disk, not just a row.",
577
+ ),
578
+ /* THE SANDBOX-WIDE DEFAULT for "when a turn dies because the MODEL PROVIDER was failing (500/502/503, a
579
+ * 529 at capacity, a dropped socket), re-run it on an escalating backoff until it goes through or the
580
+ * attempts are spent".
581
+ *
582
+ * A DEFAULT, not the whole answer: any one conversation may override it (AgentSummarySchema
583
+ * .resumeAfterOutage), and the chat's own offer at the moment of failure writes THAT rather than this.
584
+ * This toggle is the standing policy for every agent that has not said otherwise, which is why it lives in
585
+ * settings and is not reachable by a single press from inside one chat, flipping how the whole board
586
+ * behaves should be a thing somebody went to do.
587
+ *
588
+ * OFF by default, on the same reasoning that keeps a spent usage limit out of this pair entirely: a resume
589
+ * re-runs a turn the user sent once, on their own allowance, and only they can say whether the turn was
590
+ * worth paying for twice. Starting off costs nothing, because the failed turn is remembered whatever the
591
+ * toggle says (recordOutageFailure), the failure frame reports an "available" resume and the chat's offer
592
+ * arms that very turn the moment it is armed for that conversation. Worth turning ON for a sandbox whose
593
+ * turns mostly have nobody in the room (automation wakes, Discord, webhooks), which is the case no browser
594
+ * could rescue and the case a per-conversation press cannot reach. */
595
+ resumeAfterOutage: z
596
+ .boolean()
597
+ .default(false)
598
+ .describe(
599
+ "Whether a turn killed by the model provider failing is re-run automatically, backing off between attempts. The sandbox-wide default; any one conversation can say otherwise. Off to begin with, because a retry spends your allowance on a turn you sent once and only you can say whether it was worth paying for twice. Worth turning on for a sandbox whose work mostly happens with nobody in the room.",
600
+ ),
601
+ /* When the daemon dies under a running turn, re-run that turn once it is back (agent/turn-journal.ts records
602
+ * every in-flight turn; the boot pass in agent/turn-resume.ts re-runs what survived). OFF by default, like
603
+ * the outage resume above and for the same reason: a boot that re-runs turns spends the user's allowance on
604
+ * work they are not watching, and edits the workspace while they are still waiting for the sandbox to come
605
+ * back. Worth turning on for the case it was built for, the container is recreated on every update, every
606
+ * environment approval and every dev-sandbox.sh swap, so approving the Dockerfile change an agent asked for
607
+ * otherwise costs the run that asked for it.
608
+ *
609
+ * OFF still records the interruption: the fleet card reads `interrupted` (see AgentStatusSchema) and an
610
+ * automation's row shows an `interrupted` run, nothing is re-run, but nothing is silently lost either. */
611
+ autoResumeOnRestart: z
612
+ .boolean()
613
+ .default(false)
614
+ .describe(
615
+ "Whether a turn killed by the sandbox restarting is re-run once it comes back. Off to begin with, for the same reason: it would spend your allowance on work you are not watching and edit files while you are still waiting for the sandbox to return. Either way the interruption is recorded rather than silently lost.",
616
+ ),
617
+ /* THE RULE TABLE, every standing instruction the owner gives the sandbox about its own work: ask for
618
+ * proof before a turn ends, run a command before a push, hold or release finished work, and whatever they
619
+ * add next. See RuleSchema for the shape and for why the four action kinds are four.
620
+ *
621
+ * EMPTY IS THE DEFAULT, and it is exactly the behaviour a fresh sandbox had when these were three separate
622
+ * flags: no proof is asked for, no command runs at a push, and finished work waits on its branch. That is
623
+ * not a coincidence to preserve by hand, each of those defaults is what "no rule matched" means at its
624
+ * moment, so the empty table IS the old default rather than a reconstruction of it.
625
+ *
626
+ * Rules live here, in the owner's own settings, rather than in the workspace: a rule can hold work back and
627
+ * gate a push, so the first version answers to the person whose sandbox it is and to nobody else. Repo-
628
+ * committed and extension-contributed rules are worth having and are deliberately not here yet, they need
629
+ * the question of what a rule from somewhere else may WIDEN answered first. */
630
+ rules: z
631
+ .array(RuleSchema)
632
+ .max(50)
633
+ .default([])
634
+ .describe(
635
+ "Standing instructions you give the sandbox about its own work: ask for proof before a turn ends, run something before a push, hold or release finished work. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment.",
636
+ ),
637
+ /* STOP AN AUTOMATION THAT ONLY EVER FAILS. After this many consecutive `error` runs the scheduler disables
638
+ * it and says so on the row, instead of firing a job that has proven it cannot succeed every minute until
639
+ * someone notices. 0 ⇒ never, which is the default.
640
+ *
641
+ * Off by default because quarantining edits the USER'S OWN configuration, and the failure it reacts to is
642
+ * not always the automation's fault: an hourly poll against an API having a bad afternoon is broken for
643
+ * three fires and fine on the fourth, and a job disabled at 3 a.m. is one nobody re-enables until they
644
+ * notice it stopped. So the mechanism exists for the case it is unambiguously right for, a misconfigured
645
+ * job burning a turn's worth of tokens on every tick, and the owner is the one who decides their
646
+ * automations are the kind that should be stopped rather than retried.
647
+ *
648
+ * Only `error` counts. A `skipped` run is a guard doing its job, and an `interrupted` one means the daemon
649
+ * died mid-fire, which says nothing about the automation, counting either would quarantine healthy jobs. */
650
+ automationFailureLimit: z
651
+ .number()
652
+ .min(0)
653
+ .max(20)
654
+ .default(0)
655
+ .describe(
656
+ "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.",
657
+ ),
658
+ /* WHO MAY START A SESSION WITHOUT YOU, the admission floor, per wake source (see AdmissionPolicySchema).
659
+ * Defaults all-allow, so a fresh sandbox behaves exactly as before the floor existed and the per-automation
660
+ * `requireApproval` stays the way most owners meet holds. */
661
+ admission: AdmissionPolicySchema.prefault({}).describe(
662
+ "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.",
663
+ ),
664
+ /* THE SNIFFER'S RULEBOOK, verdicts for in-turn actions the outbound gate classifies, keyed by
665
+ * `<provider>.<type>` ("discord.message.send") with `<provider>.*` as the per-provider wildcard; exact key
666
+ * wins. An action with no rule is allowed, the empty default wires no hook at all, so an unconfigured
667
+ * workspace pays nothing. "hold" cannot park a running turn (nobody may be there to answer); it refuses the
668
+ * live call and points the agent at the drafts outbox, which IS the held form of a send.
669
+ *
670
+ * The CHILD-AGENT surface reads the same book: `agents.spawn` covers starting, steering and answering
671
+ * child agents on every provider, `agents.spawn.<provider>` singles one out (the specific key wins), and
672
+ * the daemon's own taint floor holds a spawn from a turn that has taken in outside content unless the
673
+ * owner wrote an explicit allow (guard/actions.ts childSpawn). "hold" refuses with the owner named, the
674
+ * same translation a send gets. */
675
+ actionRules: z
676
+ .record(z.string(), AdmissionRuleSchema)
677
+ .default({})
678
+ .describe("What an agent may do out in the world, per kind of action: go ahead, ask first, or never."),
679
+ /* THE COMMAND GATE'S RULEBOOK, a verdict per CommandClass, for shell commands the agent runs itself. This
680
+ * is the layer that still applies once a session is already running: the admission floor above decides who
681
+ * may wake the agent, and after that every command it types is inside one already-admitted session.
682
+ *
683
+ * "hold" means what it says here, unlike in actionRules: the gate raises a permission card and the command
684
+ * waits for a real answer, in EVERY posture, hooks fire under bypassPermissions, where the card machinery
685
+ * on its own never would. An UNATTENDED turn has nobody to answer, so a hold there refuses instead and says
686
+ * why; that is the honest form of "ask me" when there is no me.
687
+ *
688
+ * An unlisted class is allowed, WITH ONE FLOOR UNDER IT: the classes nothing brings back (FLOOR_CLASSES in
689
+ * command-classes.ts, `system.destructive` today) are held where the owner wrote nothing, so a workspace
690
+ * that has never opened this page is not one mistyped path away from a formatted disk. An explicit `allow`
691
+ * still wins, it is a decision about that exact class and the floor must not override the person who made
692
+ * it. Everything else stays as it was: unlisted is allowed and ordinary work is never asked about.
693
+ *
694
+ * Keys are the CommandClass enum, so a typo is a settings error rather than a rule that silently never
695
+ * matches. */
696
+ commandRules: z
697
+ .partialRecord(CommandClassSchema, AdmissionRuleSchema)
698
+ .default({})
699
+ .describe(
700
+ "What an agent may run inside the sandbox, for the six kinds of command that are hard to take back: rewriting git history, deleting recursively, wiping a disk or a container volume, reading credential files, publishing a package, reaching out to the network. Everything else is recoverable in a container that is itself disposable, and gating it would be friction bought with nothing. Leaving a kind unset is not the same as allowing it: wiping a disk is held for your approval until you say otherwise, because nothing here brings that back.",
701
+ ),
702
+ /* WHETHER A HELD COMMAND IS TRANSLATED BEFORE YOU ANSWER FOR IT. Off by default, and the default is the
703
+ * argument: the card is already complete without this, and switching it on spends one quick-model call per
704
+ * card raised, on the owner's own connected account.
705
+ *
706
+ * It buys the case the card is worst at. A held command is regularly a hundred-plus characters of pipeline
707
+ * the agent assembled, and the question the card actually asks, "do you want this to happen", is not
708
+ * answerable by reading shell quickly. The classifier already marks WHICH fragment held it (the card paints
709
+ * those); a sentence is what says what the rest of it does and why the agent wanted it.
710
+ *
711
+ * NEVER THE AGENT'S OWN WORDS. The sentence comes from the quick model reading the command text, not from
712
+ * the model being gated: a card that let the asking agent write its own justification would be a safety
713
+ * prompt whose persuasive half is authored by the thing it is meant to gate.
714
+ *
715
+ * The command itself is never replaced by it, only folded behind a disclosure the sentence sits above, and
716
+ * the marked fragments stay on the card either way, see ChatMessageView's permission card. */
717
+ explainCommands: z
718
+ .boolean()
719
+ .default(false)
720
+ .describe(
721
+ "Have the quick model describe each held command in one plain sentence, above the command itself. Costs one quick-model call per card raised, on your own connected account. The command is always still there to read.",
722
+ ),
723
+ /* HOW MUCH AN AGENT MAY DELEGATE, the three ceilings the Claude Code harness enforces on its own Agent
724
+ * tool, surfaced here because their defaults are tuned for a laptop and this is a container the owner sized.
725
+ *
726
+ * They are three settings rather than one because they stop different things, and a fan-out that clears one
727
+ * lands on the next: `subagentsAtOnce` is the parallel width of a single fan-out, `subagentsPerTurn` is the
728
+ * lifetime budget of one conversation, and `subagentDepth` is how far a child may itself delegate. Raising
729
+ * the width alone is what makes a wide sweep hit the lifetime cap two rounds later, which reads to the user
730
+ * as the same wall in a new place.
731
+ *
732
+ * Each default is what the CLI does with no env set, so a sandbox that has never opened this group behaves
733
+ * exactly as it always did, these are not our numbers, they are the harness's, restated so they can move.
734
+ * The ceilings are ours: an agent is told to stop and NOT retry when it hits one, so the cost of a number
735
+ * set too high is a real fleet of models running at once, and the cost of one set too low is a wall.
736
+ *
737
+ * The refusal an agent sees names the env var (`ask them to raise CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS`),
738
+ * which is why these three exist as settings at all: without them the only answer to that ask is editing
739
+ * the container's environment and restarting the daemon. */
740
+ subagentsAtOnce: z.number().min(1).max(200).default(20).describe("How many helper agents may work at the same time."),
741
+ subagentsPerTurn: z.number().min(1).max(2000).default(200).describe("How many a single turn may start in total."),
742
+ // Depth 1 = an agent may delegate, but its children may not. The CLI's own default is 3, and it is the one
743
+ // of the three whose runaway case is unbounded rather than merely wide, each level multiplies the last.
744
+ subagentDepth: z
745
+ .number()
746
+ .min(1)
747
+ .max(10)
748
+ .default(3)
749
+ .describe("How many levels deep the delegation may go, since a helper can start helpers of its own."),
750
+ });
751
+ export type SandboxSettings = z.infer<typeof SandboxSettingsSchema>;
752
+ // One of the two built-in bases, as text: Intentic's own prompt, or Claude Code's preset read out of the CLI
753
+ // this sandbox runs (preset-prompt.ts captures it rather than storing a transcription). What the settings page
754
+ // shows behind "View" and drops into the editor behind "Edit a copy".
755
+ //
756
+ // `version` is the CLI build a captured preset came from, so the UI can say WHICH text the user is looking at:
757
+ // a custom prompt forked from an older build is a snapshot, and the version is the only honest way to tell.
758
+ // Empty for Intentic's prompt, which ships with the app and has no version of its own to report.
759
+ export const BuiltinPromptTextSchema = z.object({ text: z.string(), version: z.string() });
760
+ export type BuiltinPromptText = z.infer<typeof BuiltinPromptTextSchema>;
761
+ /* ---- savings report: what each token-reduction mechanism actually saved ----
762
+ *
763
+ * TWO FAMILIES, deliberately never one list of bars. They are measured differently, and a chart that ranks
764
+ * them side by side claims a confidence and a denominator that only one of them has:
765
+ *
766
+ * input , shell output the cleaners trimmed before the model ever saw it. Both sides of the comparison come
767
+ * off the SAME command (raw in, emitted out), so the counterfactual is observed rather than
768
+ * estimated: exact, per command, no sample size to argue about.
769
+ * output, the model's own tokens under the terse steer. There is no second run of the same turn to compare
770
+ * against, so the only honest number is an experiment: a turn-level holdout, an n per arm, and a
771
+ * margin. It is absent entirely until both arms are large enough for the delta to mean anything.
772
+ *
773
+ * The two are also in different units of value, a saved tool-output token is saved again on every later
774
+ * request of that conversation, an output token is saved once but costs several times as much, which is the
775
+ * other reason they are separate sections with separate totals rather than one number.
776
+ */
777
+
778
+ // One mechanism's realized saving, biggest first. `savedTokens` is what THIS stage removed from what reached
779
+ // it in pipeline order, sequential attribution, which is why the stages sum exactly to raw − emitted and can
780
+ // be drawn as one stacked bar. It is NOT "what turning this cleaner off would cost you": the cap downstream
781
+ // would have eaten some of the same lines. `commands` is how many commands the stage ran on. Negative for the
782
+ // `footer` stage, which adds the retrieval pointer back, a cost on the same ledger as what it bought.
783
+ export const SavingsStageSchema = z.object({ id: z.string(), commands: z.number(), savedTokens: z.number() });
784
+ // What the cleaners saved on the way in, aggregated from historyRoot/logs/filter-stats.jsonl, one row per
785
+ // agent Bash command, written by agent-output-filter. Every number here is windowed on the ledger's own
786
+ // calendar (the UTC day each command ran), so the reader's date range and the figures above it agree.
787
+ export const InputSavingsSchema = z.object({
788
+ // When the ledger last recorded a command (epoch ms), so the card can show its age instead of implying
789
+ // freshness it doesn't have. Absent when the ledger has never been written.
790
+ updatedAt: z.number().optional(),
791
+ commands: z.number(),
792
+ rawTokens: z.number(),
793
+ emittedTokens: z.number(),
794
+ savedPct: z.number(),
795
+ // Per-stage attribution, biggest first.
796
+ perCleaner: z.array(SavingsStageSchema),
797
+ // The measured control, commands the holdout left raw, against the cleaned population. A real saved-%
798
+ // for the pipeline as a whole rather than an estimate, and the only whole-pipeline counterfactual there is.
799
+ holdout: z.object({ cleaned: z.number(), heldOut: z.number(), measuredSavedPct: z.number().optional() }),
800
+ /* High-volume commands that matched no cleaner: where the next handler is worth writing. GROUPED by the
801
+ * command text, `commands` is how many times it ran and `tokens` their total, because the question this
802
+ * list is read for is "what is worth a handler", and a handler is worth writing for a command that costs
803
+ * 5k twenty times over, not for the single 60k outlier that happened to sort first. */
804
+ gaps: z.array(z.object({ command: z.string(), commands: z.number(), tokens: z.number() })),
805
+ });
806
+ export type InputSavings = z.infer<typeof InputSavingsSchema>;
807
+ // One arm of a turn-level experiment: the turns that ran with the mechanism, and the turns the holdout ran
808
+ // without it. A mean PER TURN, because the arms never hold the same number of turns.
809
+ export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() });
810
+ /* ONE METRIC'S READING of a turn-level experiment: the two arms, and whatever the arithmetic over them will
811
+ * stand behind. An experiment can carry several, see TurnExperimentSchema.
812
+ *
813
+ * `metric` says what `mean` counts and what `deltaPct` is a delta in, and choosing it is most of the work.
814
+ * proseChars , the terse steer: the thing it steers, and the only part of the model's output that
815
+ * responds to being asked to be brief (UsageTurn.proseChars has why output tokens cannot).
816
+ * searchCalls , the search teaching: the searches a turn ran, which the teaching directly changes.
817
+ * openingSearches, the same, narrower: the searches before the turn first touched a file.
818
+ * Search mechanisms must not be judged on COST. Cost is a whole turn's work, a search mechanism moves one part
819
+ * of it, and the part sits inside the noise of the rest exactly as the steer's effect once sat inside its
820
+ * tool-call arguments. */
821
+ export const TurnMetricReadingSchema = z.object({
822
+ metric: z.enum(["proseChars", "searchCalls", "openingSearches"]),
823
+ on: SavingsArmSchema,
824
+ off: SavingsArmSchema,
825
+ /* HOW MUCH LONGER, when the margin spans zero and the honest answer is "keep collecting": the additional
826
+ * CONTROL turns at which the resolution would reach a width worth acting on (turn-experiments.ts sets it),
827
+ * holding the spread where it sits today.
828
+ *
829
+ * It is aimed at a FIXED resolution rather than at today's delta on purpose. Sized against the observed
830
+ * effect it reported fourteen more turns for an experiment that had gone nine days without resolving, an
831
+ * estimate divided by noise inherits the noise and promises an answer next week indefinitely. Against a
832
+ * fixed target the same ledger asks for a few hundred, which is the fact the reader needs: this holdout is
833
+ * not close, and waiting is not the move.
834
+ *
835
+ * An order-of-magnitude figure, and it reads as one, the point is telling "a few more days" apart from
836
+ * "not at this holdout", which is a decision, where "measuring…" forever is not.
837
+ *
838
+ * Absent ⇒ nothing to wait for: the arms are under `minTurns`, the delta is published, or the resolution is
839
+ * already good enough and the effect is simply smaller than it. */
840
+ controlTurnsNeeded: z.number().optional(),
841
+ /* THE RESOLUTION, present as soon as both arms clear `minTurns`: ± percentage points at 95% (Welch,
842
+ * unequal variances and unequal arms). Present even when the delta below is withheld, because "whatever
843
+ * this mechanism does, it is smaller than ±35 points" is a true and useful thing to be told, it is the
844
+ * reading that says to keep collecting rather than to act. */
845
+ marginPct: z.number().optional(),
846
+ /* THE CLAIM, present only once there is one. Both together, and only when the margin does NOT span zero.
847
+ *
848
+ * A schema that can't express a half-measured experiment is how a 34%-that-becomes-8%-tomorrow never
849
+ * reaches the screen, and clearing `minTurns` turned out not to be enough to buy that. The terse steer
850
+ * crossed its thirtieth control turn and immediately reported +31.2% ± 35.1pp: a confidence interval
851
+ * running from −3.4% to +66.7%, which is to say no effect was measured at all, rendered as an alarming
852
+ * number pointing the wrong way. Thirty turns is where the normal approximation starts to hold, not where
853
+ * this much per-turn spread resolves an effect; requiring the interval to exclude zero is the same
854
+ * withhold-until-it-means-something rule applied to the thing that actually decides whether it does.
855
+ * deltaPct, change in the metric's mean per turn under the mechanism; negative is a saving.
856
+ * saved , what the delta is worth over the turns that actually ran with it, in this window, in the
857
+ * metric's own unit (characters, or searches). */
858
+ deltaPct: z.number().optional(),
859
+ saved: z.number().optional(),
860
+ });
861
+ export type TurnMetricReading = z.infer<typeof TurnMetricReadingSchema>;
862
+ /* A turn-level A/B, the one shape both of this sandbox's turn experiments report in, because they differ in
863
+ * nothing but which flag flips and what the turns are judged on. Only turns the mechanism was ELIGIBLE for are
864
+ * counted: a turn under a custom system prompt drops the terse steer along with everything else the daemon
865
+ * appends, so it belongs to neither arm.
866
+ *
867
+ * ONE COIN FLIP, SEVERAL READINGS. `metrics` is a list because the search teaching is judged on two, the
868
+ * searches a turn ran, and the ones it ran before touching a file, and they are two readings of the SAME
869
+ * experiment, not two experiments. Splitting them into separate entries would duplicate the arm assignment and
870
+ * let a screen show a turn count on one that disagrees with the other. Headline first: the screens read
871
+ * `metrics[0]` for the big number and the rest as supporting lines. */
872
+ export const TurnExperimentSchema = z.object({
873
+ // A head and a tail rather than a plain array, because an experiment judged on nothing is not an experiment:
874
+ // the screens take the first reading for their headline and stack the rest under it, and this is what makes
875
+ // "there is always a headline" a fact the type carries instead of a check every screen repeats. (`.nonempty()`
876
+ // would not do it, in zod 4 it adds a min-length rule and leaves the inferred type a plain array.)
877
+ metrics: z.tuple([TurnMetricReadingSchema], TurnMetricReadingSchema),
878
+ // Turns per arm before a delta is reported at all. Carried on the wire so the screen's "measuring…" state
879
+ // counts toward the daemon's real threshold instead of a number the browser guessed. Shared by every
880
+ // reading: they are the same turns counted differently, so they clear it together.
881
+ minTurns: z.number(),
882
+ // The randomized unit behind the arm counts. Turn mechanisms default to turns; teaching loaded into a
883
+ // provider session randomizes and analyzes whole conversations so repeated turns are not false replicas.
884
+ sampleUnit: z.enum(["turns", "conversations"]).optional(),
885
+ // Content-addressed treatment version. Present where mixing rows from two instruction revisions would turn
886
+ // one experiment into two unnamed ones; the reader filters to this (latest) cohort.
887
+ cohort: z.string().optional(),
888
+ });
889
+ export type TurnExperiment = z.infer<typeof TurnExperimentSchema>;
890
+ // `output`/`search` are absent when that experiment isn't running at all (its flag off, or no holdout set), a
891
+ // section that isn't there reads as "not measured", which is the truth, while zeros would read as "measured,
892
+ // worth nothing".
893
+ /* WHAT THE COMPLEXITY JUDGE HAS BEEN SAYING, read back off the spend ledger's tier fields (UsageTurn.tierScore
894
+ * and friends) over the requested window. The three numbers docs/model-routing-design.md §4 says the feature
895
+ * cannot be defended without, plus the veto count, and nothing else: no counterfactual "you would have saved
896
+ * $X", because the ledger holds what turns COST, not what they would have cost on a model they never ran.
897
+ *
898
+ * NOT a TurnExperiment, deliberately. The experiments compare two randomized arms of one population; this is a
899
+ * tally of what one mechanism observed and did. Dressing it in arms and margins would claim a control group that
900
+ * does not exist (routing follows the settings mode, which follows time, not a coin flip).
901
+ *
902
+ * The whole section is absent when no turn in the window was judged at all (autoTier "off" throughout), which a
903
+ * screen renders as absence: "not measured" is the truth, zeros would read as "measured, found nothing". */
904
+ export const TierReportSchema = z.object({
905
+ // Turns the judge ran on in the window, the denominator under everything below.
906
+ judged: z.number(),
907
+ // …of which landed at or below FAST_CEILING: the turns that looked simple. fast ÷ judged is the fast share.
908
+ fast: z.number(),
909
+ /* What the fast-judged turns that STAYED on the user's pick actually cost, the money measure mode is
910
+ * pointing at. An upper bound on any saving, never an estimate of one: moving those turns to the cheap rung
911
+ * would have cost something too, and this schema refuses to guess how much. */
912
+ atStakeUsd: z.number(),
913
+ // Turns that actually ran the cheap rung, and what they cost there. Realized, not projected.
914
+ routed: z.number(),
915
+ routedUsd: z.number(),
916
+ /* THE GUARDRAIL: fast-judged turns whose conversation's very next ledger row asked for a dearer model, the
917
+ * user reaching for the model picker right after a turn the judge called simple. The strongest negative
918
+ * signal the ledger can carry (§4's first calibration row). Past a few percent of `fast`, the judge is
919
+ * costing more in retries and trust than it saves in tokens. */
920
+ escalated: z.number(),
921
+ // Fast-judged turns the user vetoed outright (UsageTurn.tierDenied): the same signal, said even louder.
922
+ denied: z.number(),
923
+ });
924
+ export type TierReport = z.infer<typeof TierReportSchema>;
925
+ export const SavingsReportSchema = z.object({
926
+ input: InputSavingsSchema,
927
+ output: TurnExperimentSchema.optional(),
928
+ search: TurnExperimentSchema.optional(),
929
+ // Automatic tier selection's readout, see TierReportSchema. Absent ⇒ nothing was judged in the window.
930
+ tier: TierReportSchema.optional(),
931
+ });
932
+ export type SavingsReport = z.infer<typeof SavingsReportSchema>;