@intentic/sandbox-contract 1.331.0 → 1.333.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/README.md +23 -1
  2. package/dist/chores/probes.d.ts.map +1 -1
  3. package/dist/chores/probes.js.map +1 -1
  4. package/dist/contracts/accounts.contract.d.ts +11 -0
  5. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.d.ts +38 -0
  7. package/dist/contracts/agent.contract.d.ts.map +1 -1
  8. package/dist/contracts/agent.contract.js +14 -3
  9. package/dist/contracts/agent.contract.js.map +1 -1
  10. package/dist/contracts/agents.contract.d.ts +29 -0
  11. package/dist/contracts/agents.contract.d.ts.map +1 -1
  12. package/dist/contracts/automations.contract.d.ts +3 -0
  13. package/dist/contracts/automations.contract.d.ts.map +1 -1
  14. package/dist/contracts/device.contract.d.ts +7 -0
  15. package/dist/contracts/device.contract.d.ts.map +1 -1
  16. package/dist/contracts/git.contract.d.ts +2 -0
  17. package/dist/contracts/git.contract.d.ts.map +1 -1
  18. package/dist/contracts/logs.contract.d.ts +10 -0
  19. package/dist/contracts/logs.contract.d.ts.map +1 -1
  20. package/dist/contracts/needs.contract.d.ts +36 -1
  21. package/dist/contracts/needs.contract.d.ts.map +1 -1
  22. package/dist/contracts/needs.contract.js +2 -2
  23. package/dist/contracts/needs.contract.js.map +1 -1
  24. package/dist/contracts/offload.contract.d.ts +70 -0
  25. package/dist/contracts/offload.contract.d.ts.map +1 -1
  26. package/dist/contracts/offload.contract.js +10 -1
  27. package/dist/contracts/offload.contract.js.map +1 -1
  28. package/dist/contracts/privacy.contract.d.ts +14 -0
  29. package/dist/contracts/privacy.contract.d.ts.map +1 -1
  30. package/dist/contracts/privacy.contract.js +11 -1
  31. package/dist/contracts/privacy.contract.js.map +1 -1
  32. package/dist/contracts/providers.contract.d.ts +3 -0
  33. package/dist/contracts/providers.contract.d.ts.map +1 -1
  34. package/dist/contracts/runner.contract.d.ts +103 -2
  35. package/dist/contracts/runner.contract.d.ts.map +1 -1
  36. package/dist/contracts/runner.contract.js +2 -2
  37. package/dist/contracts/runner.contract.js.map +1 -1
  38. package/dist/contracts/sessions.contract.d.ts +1 -0
  39. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  40. package/dist/contracts/settings.contract.d.ts +106 -94
  41. package/dist/contracts/settings.contract.d.ts.map +1 -1
  42. package/dist/contracts/settings.contract.js +19 -1
  43. package/dist/contracts/settings.contract.js.map +1 -1
  44. package/dist/contracts/system.contract.d.ts +17 -0
  45. package/dist/contracts/system.contract.d.ts.map +1 -1
  46. package/dist/contracts/translator.contract.d.ts +4 -0
  47. package/dist/contracts/translator.contract.d.ts.map +1 -1
  48. package/dist/events/agent-events.d.ts +46 -0
  49. package/dist/events/agent-events.d.ts.map +1 -1
  50. package/dist/events/agent-events.js +6 -0
  51. package/dist/events/agent-events.js.map +1 -1
  52. package/dist/events/agent-words.d.ts.map +1 -1
  53. package/dist/events/agent-words.js +1 -1
  54. package/dist/events/agent-words.js.map +1 -1
  55. package/dist/events/resume.d.ts +3 -0
  56. package/dist/events/resume.d.ts.map +1 -1
  57. package/dist/events/resume.js +17 -2
  58. package/dist/events/resume.js.map +1 -1
  59. package/dist/events/sandbox-notice.d.ts +18 -0
  60. package/dist/events/sandbox-notice.d.ts.map +1 -1
  61. package/dist/events/sandbox-notice.js +27 -1
  62. package/dist/events/sandbox-notice.js.map +1 -1
  63. package/dist/events/system-events.d.ts +2 -0
  64. package/dist/events/system-events.d.ts.map +1 -1
  65. package/dist/events/transcript.d.ts +64 -0
  66. package/dist/events/transcript.d.ts.map +1 -1
  67. package/dist/events/transcript.js +17 -1
  68. package/dist/events/transcript.js.map +1 -1
  69. package/dist/index.d.ts +427 -167
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +12 -0
  72. package/dist/index.js.map +1 -1
  73. package/dist/models/agent-catalog.d.ts.map +1 -1
  74. package/dist/models/agent-catalog.js +8 -7
  75. package/dist/models/agent-catalog.js.map +1 -1
  76. package/dist/models/agent-runtimes.d.ts +3 -1
  77. package/dist/models/agent-runtimes.d.ts.map +1 -1
  78. package/dist/models/agent-runtimes.js +10 -0
  79. package/dist/models/agent-runtimes.js.map +1 -1
  80. package/dist/models/model-order.d.ts +1 -0
  81. package/dist/models/model-order.d.ts.map +1 -1
  82. package/dist/models/model-order.js +9 -0
  83. package/dist/models/model-order.js.map +1 -1
  84. package/dist/models/model-pins.d.ts.map +1 -1
  85. package/dist/models/model-pins.js.map +1 -1
  86. package/dist/models/model-roles.d.ts +33 -18
  87. package/dist/models/model-roles.d.ts.map +1 -1
  88. package/dist/models/model-roles.js +31 -27
  89. package/dist/models/model-roles.js.map +1 -1
  90. package/dist/models/model-setups.d.ts +46 -0
  91. package/dist/models/model-setups.d.ts.map +1 -0
  92. package/dist/models/model-setups.js +86 -0
  93. package/dist/models/model-setups.js.map +1 -0
  94. package/dist/models/plan-pools.d.ts +3 -1
  95. package/dist/models/plan-pools.d.ts.map +1 -1
  96. package/dist/models/plan-pools.js +18 -6
  97. package/dist/models/plan-pools.js.map +1 -1
  98. package/dist/models/provider-specs.d.ts +20 -0
  99. package/dist/models/provider-specs.d.ts.map +1 -1
  100. package/dist/models/provider-specs.js +13 -1
  101. package/dist/models/provider-specs.js.map +1 -1
  102. package/dist/policy/command-classes.d.ts.map +1 -1
  103. package/dist/policy/command-classes.js +26 -3
  104. package/dist/policy/command-classes.js.map +1 -1
  105. package/dist/policy/control-scopes.d.ts +47 -0
  106. package/dist/policy/control-scopes.d.ts.map +1 -1
  107. package/dist/policy/control-scopes.js +16 -0
  108. package/dist/policy/control-scopes.js.map +1 -1
  109. package/dist/policy/credential-material.d.ts +6 -0
  110. package/dist/policy/credential-material.d.ts.map +1 -1
  111. package/dist/policy/credential-material.js +37 -15
  112. package/dist/policy/credential-material.js.map +1 -1
  113. package/dist/policy/device-programs.d.ts +28 -0
  114. package/dist/policy/device-programs.d.ts.map +1 -0
  115. package/dist/policy/device-programs.js +79 -0
  116. package/dist/policy/device-programs.js.map +1 -0
  117. package/dist/policy/identity-assertion.d.ts +27 -0
  118. package/dist/policy/identity-assertion.d.ts.map +1 -0
  119. package/dist/policy/identity-assertion.js +77 -0
  120. package/dist/policy/identity-assertion.js.map +1 -0
  121. package/dist/policy/sandbox-power.d.ts +34 -0
  122. package/dist/policy/sandbox-power.d.ts.map +1 -0
  123. package/dist/policy/sandbox-power.js +146 -0
  124. package/dist/policy/sandbox-power.js.map +1 -0
  125. package/dist/policy/search-globs.d.ts +3 -0
  126. package/dist/policy/search-globs.d.ts.map +1 -1
  127. package/dist/policy/search-globs.js +118 -0
  128. package/dist/policy/search-globs.js.map +1 -1
  129. package/dist/policy/secret-hosts.d.ts +1 -0
  130. package/dist/policy/secret-hosts.d.ts.map +1 -1
  131. package/dist/policy/secret-hosts.js +1 -0
  132. package/dist/policy/secret-hosts.js.map +1 -1
  133. package/dist/policy/turned-away.d.ts.map +1 -1
  134. package/dist/policy/turned-away.js +1 -0
  135. package/dist/policy/turned-away.js.map +1 -1
  136. package/dist/protocol/doors/runner-protocol.d.ts +45 -0
  137. package/dist/protocol/doors/runner-protocol.d.ts.map +1 -1
  138. package/dist/protocol/doors/runner-protocol.js +7 -0
  139. package/dist/protocol/doors/runner-protocol.js.map +1 -1
  140. package/dist/protocol/pairing-redeem.d.ts +23 -0
  141. package/dist/protocol/pairing-redeem.d.ts.map +1 -0
  142. package/dist/protocol/pairing-redeem.js +44 -0
  143. package/dist/protocol/pairing-redeem.js.map +1 -0
  144. package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
  145. package/dist/protocol/peer-mcp-server.js +1 -1
  146. package/dist/protocol/peer-mcp-server.js.map +1 -1
  147. package/dist/protocol/raw/raw-json-routes.d.ts +855 -1
  148. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  149. package/dist/protocol/raw/raw-json-routes.js +61 -1
  150. package/dist/protocol/raw/raw-json-routes.js.map +1 -1
  151. package/dist/protocol/raw/raw-routes.d.ts +17 -0
  152. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  153. package/dist/protocol/raw/raw-routes.js +5 -0
  154. package/dist/protocol/raw/raw-routes.js.map +1 -1
  155. package/dist/protocol/sse.d.ts +20 -1
  156. package/dist/protocol/sse.d.ts.map +1 -1
  157. package/dist/protocol/sse.js +112 -17
  158. package/dist/protocol/sse.js.map +1 -1
  159. package/dist/schemas/agent.d.ts +35 -0
  160. package/dist/schemas/agent.d.ts.map +1 -1
  161. package/dist/schemas/agent.js +12 -1
  162. package/dist/schemas/agent.js.map +1 -1
  163. package/dist/schemas/automations.d.ts +7 -0
  164. package/dist/schemas/automations.d.ts.map +1 -1
  165. package/dist/schemas/automations.js +11 -3
  166. package/dist/schemas/automations.js.map +1 -1
  167. package/dist/schemas/device-swaps.d.ts +19 -0
  168. package/dist/schemas/device-swaps.d.ts.map +1 -0
  169. package/dist/schemas/device-swaps.js +11 -0
  170. package/dist/schemas/device-swaps.js.map +1 -0
  171. package/dist/schemas/devices.d.ts +17 -0
  172. package/dist/schemas/devices.d.ts.map +1 -1
  173. package/dist/schemas/devices.js +2 -1
  174. package/dist/schemas/devices.js.map +1 -1
  175. package/dist/schemas/engines.d.ts +8 -1
  176. package/dist/schemas/engines.d.ts.map +1 -1
  177. package/dist/schemas/engines.js +1 -1
  178. package/dist/schemas/engines.js.map +1 -1
  179. package/dist/schemas/environment.d.ts +7 -0
  180. package/dist/schemas/environment.d.ts.map +1 -1
  181. package/dist/schemas/environment.js +2 -0
  182. package/dist/schemas/environment.js.map +1 -1
  183. package/dist/schemas/git/git-scope.d.ts +20 -0
  184. package/dist/schemas/git/git-scope.d.ts.map +1 -0
  185. package/dist/schemas/git/git-scope.js +37 -0
  186. package/dist/schemas/git/git-scope.js.map +1 -0
  187. package/dist/schemas/git/git.d.ts +1 -0
  188. package/dist/schemas/git/git.d.ts.map +1 -1
  189. package/dist/schemas/git/git.js +9 -1
  190. package/dist/schemas/git/git.js.map +1 -1
  191. package/dist/schemas/hosts.d.ts +42 -0
  192. package/dist/schemas/hosts.d.ts.map +1 -1
  193. package/dist/schemas/hosts.js +3 -0
  194. package/dist/schemas/hosts.js.map +1 -1
  195. package/dist/schemas/keep-warm.d.ts +1 -0
  196. package/dist/schemas/keep-warm.d.ts.map +1 -1
  197. package/dist/schemas/keep-warm.js +1 -1
  198. package/dist/schemas/keep-warm.js.map +1 -1
  199. package/dist/schemas/logs.d.ts +21 -0
  200. package/dist/schemas/logs.d.ts.map +1 -1
  201. package/dist/schemas/logs.js +19 -0
  202. package/dist/schemas/logs.js.map +1 -1
  203. package/dist/schemas/members.d.ts +4 -0
  204. package/dist/schemas/members.d.ts.map +1 -1
  205. package/dist/schemas/members.js +5 -0
  206. package/dist/schemas/members.js.map +1 -1
  207. package/dist/schemas/metrics.d.ts +12 -0
  208. package/dist/schemas/metrics.d.ts.map +1 -1
  209. package/dist/schemas/metrics.js +26 -8
  210. package/dist/schemas/metrics.js.map +1 -1
  211. package/dist/schemas/needs.d.ts +97 -1
  212. package/dist/schemas/needs.d.ts.map +1 -1
  213. package/dist/schemas/needs.js +53 -4
  214. package/dist/schemas/needs.js.map +1 -1
  215. package/dist/schemas/offload.d.ts +154 -0
  216. package/dist/schemas/offload.d.ts.map +1 -1
  217. package/dist/schemas/offload.js +34 -0
  218. package/dist/schemas/offload.js.map +1 -1
  219. package/dist/schemas/privacy.d.ts +19 -0
  220. package/dist/schemas/privacy.d.ts.map +1 -1
  221. package/dist/schemas/privacy.js +17 -2
  222. package/dist/schemas/privacy.js.map +1 -1
  223. package/dist/schemas/project-bring-back.d.ts +172 -0
  224. package/dist/schemas/project-bring-back.d.ts.map +1 -0
  225. package/dist/schemas/project-bring-back.js +39 -0
  226. package/dist/schemas/project-bring-back.js.map +1 -0
  227. package/dist/schemas/providers/handoff.d.ts +77 -0
  228. package/dist/schemas/providers/handoff.d.ts.map +1 -0
  229. package/dist/schemas/providers/handoff.js +85 -0
  230. package/dist/schemas/providers/handoff.js.map +1 -0
  231. package/dist/schemas/providers/plan-limits.d.ts +27 -0
  232. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  233. package/dist/schemas/providers/plan-limits.js +8 -1
  234. package/dist/schemas/providers/plan-limits.js.map +1 -1
  235. package/dist/schemas/providers/provider-oauth.d.ts +4 -0
  236. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  237. package/dist/schemas/providers/usage.d.ts +3 -1
  238. package/dist/schemas/providers/usage.d.ts.map +1 -1
  239. package/dist/schemas/providers/usage.js +7 -2
  240. package/dist/schemas/providers/usage.js.map +1 -1
  241. package/dist/schemas/restart-intent.d.ts +47 -0
  242. package/dist/schemas/restart-intent.d.ts.map +1 -0
  243. package/dist/schemas/restart-intent.js +19 -0
  244. package/dist/schemas/restart-intent.js.map +1 -0
  245. package/dist/schemas/run-targets.d.ts.map +1 -1
  246. package/dist/schemas/run-targets.js +5 -2
  247. package/dist/schemas/run-targets.js.map +1 -1
  248. package/dist/schemas/settings-history.d.ts +1 -1
  249. package/dist/schemas/settings-history.d.ts.map +1 -1
  250. package/dist/schemas/settings-history.js +21 -0
  251. package/dist/schemas/settings-history.js.map +1 -1
  252. package/dist/schemas/settings.d.ts +101 -100
  253. package/dist/schemas/settings.d.ts.map +1 -1
  254. package/dist/schemas/settings.js +43 -24
  255. package/dist/schemas/settings.js.map +1 -1
  256. package/dist/schemas/short-codes.d.ts +47 -0
  257. package/dist/schemas/short-codes.d.ts.map +1 -0
  258. package/dist/schemas/short-codes.js +30 -0
  259. package/dist/schemas/short-codes.js.map +1 -0
  260. package/dist/schemas/speech.d.ts +4 -0
  261. package/dist/schemas/speech.d.ts.map +1 -1
  262. package/dist/schemas/speech.js +1 -0
  263. package/dist/schemas/speech.js.map +1 -1
  264. package/dist/schemas/system.d.ts +176 -0
  265. package/dist/schemas/system.d.ts.map +1 -1
  266. package/dist/schemas/system.js +55 -2
  267. package/dist/schemas/system.js.map +1 -1
  268. package/dist/schemas/voice.d.ts +171 -0
  269. package/dist/schemas/voice.d.ts.map +1 -0
  270. package/dist/schemas/voice.js +99 -0
  271. package/dist/schemas/voice.js.map +1 -0
  272. package/dist/schemas/workspace/workspace-upload.d.ts +14 -0
  273. package/dist/schemas/workspace/workspace-upload.d.ts.map +1 -0
  274. package/dist/schemas/workspace/workspace-upload.js +6 -0
  275. package/dist/schemas/workspace/workspace-upload.js.map +1 -0
  276. package/dist/state/arrival.d.ts +3 -0
  277. package/dist/state/arrival.d.ts.map +1 -1
  278. package/dist/state/arrival.js +1 -0
  279. package/dist/state/arrival.js.map +1 -1
  280. package/dist/state/definition.d.ts +112 -40
  281. package/dist/state/definition.d.ts.map +1 -1
  282. package/dist/state/history-state.d.ts.map +1 -1
  283. package/dist/state/history-state.js +3 -0
  284. package/dist/state/history-state.js.map +1 -1
  285. package/dist/state/runtime-state.d.ts +5 -2
  286. package/dist/state/runtime-state.d.ts.map +1 -1
  287. package/dist/state/runtime-state.js +3 -2
  288. package/dist/state/runtime-state.js.map +1 -1
  289. package/dist/state/update-probation.d.ts +50 -0
  290. package/dist/state/update-probation.d.ts.map +1 -0
  291. package/dist/state/update-probation.js +81 -0
  292. package/dist/state/update-probation.js.map +1 -0
  293. package/dist/state/versions.d.ts +6 -0
  294. package/dist/state/versions.d.ts.map +1 -1
  295. package/dist/state/versions.js +47 -0
  296. package/dist/state/versions.js.map +1 -1
  297. package/dist/state/workspace-state.d.ts +1 -1
  298. package/dist/state/workspace-state.d.ts.map +1 -1
  299. package/dist/state/workspace-state.js +1 -1
  300. package/dist/state/workspace-state.js.map +1 -1
  301. package/dist/text/embed.d.ts +2 -1
  302. package/dist/text/embed.d.ts.map +1 -1
  303. package/dist/text/embed.js +10 -5
  304. package/dist/text/embed.js.map +1 -1
  305. package/dist/text/title.d.ts.map +1 -1
  306. package/dist/text/title.js +4 -7
  307. package/dist/text/title.js.map +1 -1
  308. package/dist/text/transcript-fold.d.ts +2 -1
  309. package/dist/text/transcript-fold.d.ts.map +1 -1
  310. package/dist/text/transcript-fold.js +21 -6
  311. package/dist/text/transcript-fold.js.map +1 -1
  312. package/package.json +39 -6
  313. package/src/chores/probes.ts +3 -1
  314. package/src/contracts/agent.contract.ts +24 -3
  315. package/src/contracts/needs.contract.ts +2 -2
  316. package/src/contracts/offload.contract.ts +21 -1
  317. package/src/contracts/privacy.contract.ts +13 -0
  318. package/src/contracts/runner.contract.ts +10 -2
  319. package/src/contracts/settings.contract.ts +23 -0
  320. package/src/events/agent-events.ts +13 -0
  321. package/src/events/agent-words.ts +2 -3
  322. package/src/events/resume.test.ts +14 -2
  323. package/src/events/resume.ts +32 -3
  324. package/src/events/sandbox-notice.ts +31 -1
  325. package/src/events/transcript.ts +27 -2
  326. package/src/ids/tunnel-ids.test.ts +30 -0
  327. package/src/index.ts +12 -0
  328. package/src/models/agent-catalog.test.ts +4 -3
  329. package/src/models/agent-catalog.ts +10 -7
  330. package/src/models/agent-runtimes.ts +30 -1
  331. package/src/models/capability-ledger.test.ts +4 -0
  332. package/src/models/model-order.ts +13 -0
  333. package/src/models/model-pins.ts +5 -4
  334. package/src/models/model-roles.test.ts +9 -31
  335. package/src/models/model-roles.ts +45 -50
  336. package/src/models/model-setups.test.ts +100 -0
  337. package/src/models/model-setups.ts +151 -0
  338. package/src/models/plan-pools.test.ts +58 -0
  339. package/src/models/plan-pools.ts +38 -7
  340. package/src/models/provider-specs.ts +20 -2
  341. package/src/policy/command-classes.ts +34 -3
  342. package/src/policy/control-scopes.ts +27 -0
  343. package/src/policy/credential-material.ts +58 -16
  344. package/src/policy/device-programs.ts +137 -0
  345. package/src/policy/identity-assertion.ts +130 -0
  346. package/src/policy/sandbox-power.ts +247 -0
  347. package/src/policy/search-globs.ts +135 -0
  348. package/src/policy/secret-hosts.ts +7 -0
  349. package/src/policy/tests/command-classes.test.ts +35 -0
  350. package/src/policy/tests/credential-material.test.ts +20 -0
  351. package/src/policy/tests/device-programs.test.ts +107 -0
  352. package/src/policy/tests/identity-assertion.test.ts +79 -0
  353. package/src/policy/tests/sandbox-power.test.ts +181 -0
  354. package/src/policy/tests/search-globs.test.ts +46 -1
  355. package/src/policy/tests/secret-hosts.test.ts +17 -1
  356. package/src/policy/turned-away.ts +2 -0
  357. package/src/protocol/doors/runner-protocol.ts +21 -1
  358. package/src/protocol/pairing-redeem.test.ts +86 -0
  359. package/src/protocol/pairing-redeem.ts +79 -0
  360. package/src/protocol/peer-mcp-server.ts +1 -2
  361. package/src/protocol/raw/raw-json-routes.test.ts +87 -0
  362. package/src/protocol/raw/raw-json-routes.ts +127 -5
  363. package/src/protocol/raw/raw-routes.ts +11 -0
  364. package/src/protocol/sse.test.ts +50 -1
  365. package/src/protocol/sse.ts +172 -26
  366. package/src/schemas/agent.ts +23 -2
  367. package/src/schemas/automations.ts +16 -5
  368. package/src/schemas/daemon-health.test.ts +28 -0
  369. package/src/schemas/device-swaps.test.ts +39 -0
  370. package/src/schemas/device-swaps.ts +26 -0
  371. package/src/schemas/devices.ts +11 -2
  372. package/src/schemas/engines.ts +1 -1
  373. package/src/schemas/environment.ts +4 -0
  374. package/src/schemas/git/git-scope.test.ts +91 -0
  375. package/src/schemas/git/git-scope.ts +71 -0
  376. package/src/schemas/git/git.ts +15 -1
  377. package/src/schemas/hosts.ts +7 -0
  378. package/src/schemas/keep-warm.ts +2 -1
  379. package/src/schemas/logs.ts +25 -0
  380. package/src/schemas/members.ts +8 -0
  381. package/src/schemas/metrics.test.ts +80 -0
  382. package/src/schemas/metrics.ts +31 -9
  383. package/src/schemas/needs.ts +80 -4
  384. package/src/schemas/offload.ts +76 -0
  385. package/src/schemas/privacy.ts +27 -3
  386. package/src/schemas/project-bring-back.test.ts +15 -0
  387. package/src/schemas/project-bring-back.ts +78 -0
  388. package/src/schemas/providers/handoff.test.ts +42 -0
  389. package/src/schemas/providers/handoff.ts +128 -0
  390. package/src/schemas/providers/plan-limits.ts +14 -1
  391. package/src/schemas/providers/usage.ts +13 -7
  392. package/src/schemas/restart-intent.test.ts +45 -0
  393. package/src/schemas/restart-intent.ts +47 -0
  394. package/src/schemas/run-targets.ts +5 -2
  395. package/src/schemas/settings-history.ts +28 -0
  396. package/src/schemas/settings.test.ts +74 -21
  397. package/src/schemas/settings.ts +80 -44
  398. package/src/schemas/short-codes.ts +59 -0
  399. package/src/schemas/speech.ts +4 -0
  400. package/src/schemas/system.ts +106 -5
  401. package/src/schemas/voice.ts +161 -0
  402. package/src/schemas/workspace/workspace-upload.ts +11 -0
  403. package/src/state/arrival.ts +3 -0
  404. package/src/state/history-state.ts +9 -0
  405. package/src/state/runtime-state.test.ts +13 -0
  406. package/src/state/runtime-state.ts +11 -5
  407. package/src/state/update-probation.test.ts +45 -0
  408. package/src/state/update-probation.ts +169 -0
  409. package/src/state/versions.test.ts +30 -1
  410. package/src/state/versions.ts +68 -0
  411. package/src/state/workspace-state.ts +4 -1
  412. package/src/text/embed.test.ts +14 -0
  413. package/src/text/embed.ts +19 -10
  414. package/src/text/title.ts +5 -9
  415. package/src/text/transcript-fold.test.ts +61 -0
  416. package/src/text/transcript-fold.ts +31 -6
@@ -656,3 +656,38 @@ describe("git.branch-switch", () => {
656
656
  expect(marked("cd repo && git switch main && pnpm build", "git.branch-switch")).toEqual(["git switch"]);
657
657
  });
658
658
  });
659
+
660
+ describe("Windows' own shells", () => {
661
+ test("PowerShell's and cmd's recursive deletes are files.destructive, the way a device's run_command meets them", () => {
662
+ for (const command of [
663
+ "Remove-Item -Recurse -Force C:\\Users\\me\\thesis",
664
+ "Remove-Item C:\\x -Recurse",
665
+ "Remove-Item -Recurse:$true x",
666
+ "ri -r C:\\x",
667
+ "rm -Recurse x",
668
+ "rd /s /q C:\\x",
669
+ "rmdir /S C:\\x",
670
+ "del /s /q *.tmp",
671
+ "[System.IO.Directory]::Delete('C:\\x', $true)",
672
+ "powershell -Command 'Remove-Item -Recurse -Force C:\\x'",
673
+ "cmd /c 'rmdir /s /q C:\\x'",
674
+ ]) {
675
+ expect(classify(command), command).toContain("files.destructive");
676
+ }
677
+ });
678
+
679
+ test("what only looks like one is not: a single file, a read that recurses, POSIX rm's asking -r", () => {
680
+ for (const command of ["Remove-Item C:\\x\\file.txt", "del file.txt", "Get-ChildItem -Recurse C:\\x", "Copy-Item -Recurse a b", "rmdir empty", "rm -r build", "git rm -r --cached x"]) {
681
+ expect(classify(command), command).not.toContain("files.destructive");
682
+ }
683
+ });
684
+
685
+ test("a volume formatted or a disk cleared is system.destructive", () => {
686
+ for (const command of ["Format-Volume -DriveLetter D", "Clear-Disk -Number 1 -RemoveData", "format D: /q"]) {
687
+ expect(classify(command), command).toContain("system.destructive");
688
+ }
689
+ for (const command of ["npm run format", "prettier --write . && echo format done", "dotnet format"]) {
690
+ expect(classify(command), command).not.toContain("system.destructive");
691
+ }
692
+ });
693
+ });
@@ -202,6 +202,26 @@ describe("a credential token in text that is not a credential file", () => {
202
202
  expect(holdsCredentialToken("-----BEGIN PRIVATE KEY-----\nMIIEvQ")).toBe(true);
203
203
  });
204
204
 
205
+ // The issuers the terminal filter already knew; the two tables are one list now (ISSUERS).
206
+ test.each([
207
+ ["an opaque bearer", "curl -H 'Authorization: Bearer 8f3kQ2mZ9xLp4RtV7wNb' https://api.example.com", "curl -H 'Authorization: Bearer ***' https://api.example.com"],
208
+ // Prefixes split from bodies so push protection does not read these fixtures as live keys.
209
+ ["a shopify admin token", `token ${"shpat_"}0123456789abcdef0123456789abcdef in use`, "token *** in use"],
210
+ ["a sendgrid key", `key ${"SG."}aB3dE5fG7hJ9kL1mN3pQ5r.sT7uV9wX1yZ3aB5cD7eF9gH1jK3lM5nP7qR9sT1uV3w set`, "key *** set"],
211
+ ["a google oauth token", "refreshed ya29.a0AfB_byC3dE5fG7hJ9kL1mN3pQ5 ok", "refreshed *** ok"],
212
+ ["a public-prefixed key", "langfuse pk-lf-0a1b2c3d-4e5f-6a7b-8c9d now", "langfuse *** now"],
213
+ ])("names itself and masks: %s", (_label, text, masked) => {
214
+ expect(holdsCredentialToken(text)).toBe(true);
215
+ expect(maskCredentialMaterial(text)).toBe(masked);
216
+ expect(holdsCredentialToken(masked)).toBe(false);
217
+ });
218
+
219
+ test("a bearer in prose or a template is not one", () => {
220
+ for (const text of ["Authorization: Bearer <token>", "Authorization: Bearer YOUR_API_TOKEN_HERE", "Bearer ${TOKEN}", "the Bearer scheme"]) {
221
+ expect(holdsCredentialToken(text), text).toBe(false);
222
+ }
223
+ });
224
+
205
225
  test("is never a credential-named key beside a value", () => {
206
226
  expect(holdsCredentialToken("const config={apiKey:process.env.API_KEY,password:hunter22x}")).toBe(false);
207
227
  expect(holdsCredentialMaterial("const config={apiKey:process.env.API_KEY,password:hunter22x}")).toBe(true);
@@ -0,0 +1,107 @@
1
+ import { commandLineOf, DEVICE_PROGRAM_TOOLS, ProgramArgumentsSchema, programInCall } from "../device-programs.js";
2
+
3
+ // As the bridge hands it over: the arguments of a tools/call, parsed by the table's own schema.
4
+ const read = (tool: string, args: Record<string, unknown>) => programInCall(tool, ProgramArgumentsSchema.parse(args));
5
+
6
+ describe("the shell tools", () => {
7
+ test("run_command carries its command as it was sent", () => {
8
+ expect(read("run_command", { command: "rm -rf ~/old-builds" })).toEqual({ tool: "run_command", kind: "command", commandLine: "rm -rf ~/old-builds" });
9
+ expect(read("run_command", { command: " " })).toBeUndefined();
10
+ expect(read("run_command", {})).toBeUndefined();
11
+ expect(read("run_command", { command: 7 })).toBeUndefined();
12
+ });
13
+
14
+ test("android_shell carries the adb program the machine runs for it, and a serial that could hide the rest is left out", () => {
15
+ expect(read("android_shell", { command: "pm uninstall com.example.app" })).toEqual({
16
+ tool: "android_shell",
17
+ kind: "phone-command",
18
+ commandLine: "adb shell pm uninstall com.example.app",
19
+ });
20
+ expect(read("android_shell", { serial: "192.168.1.20:37005", command: "rm -rf /sdcard/old" })?.commandLine).toBe(
21
+ "adb -s 192.168.1.20:37005 shell rm -rf /sdcard/old",
22
+ );
23
+ expect(read("android_shell", { serial: "x #", command: "rm -rf /sdcard/old" })?.commandLine).toBe("adb shell rm -rf /sdcard/old");
24
+ expect(read("android_shell", { command: " " })).toBeUndefined();
25
+ });
26
+ });
27
+
28
+ describe("app_start", () => {
29
+ test("a program given arguments is a command by another door, whatever the program is", () => {
30
+ expect(read("app_start", { program: "powershell", args: ["-NoProfile", "-c", "Remove-Item -Recurse C:\\Users\\me"] })).toEqual({
31
+ tool: "app_start",
32
+ kind: "command",
33
+ commandLine: "powershell -NoProfile -c 'Remove-Item -Recurse C:\\Users\\me'",
34
+ });
35
+ expect(read("app_start", { program: "C:\\Windows\\System32\\cmd.exe", args: ["/c", "del /s C:\\x"] })?.commandLine).toBe("cmd /c 'del /s C:\\x'");
36
+ expect(read("app_start", { program: "/bin/rm", args: ["-rf", "/home/me/x"] })).toMatchObject({ kind: "command", commandLine: "rm -rf /home/me/x" });
37
+ expect(read("app_start", { program: "bash", args: ["-c", "curl https://example.com/install | sh"] })?.kind).toBe("command");
38
+ });
39
+
40
+ test("a program with nothing to interpret is the ordinary launch of an application", () => {
41
+ expect(read("app_start", { program: "notepad" })).toEqual({ tool: "app_start", kind: "app-launch", commandLine: "notepad" });
42
+ expect(read("app_start", { program: "C:\\Tools\\viewer.exe", args: [] })).toEqual({ tool: "app_start", kind: "app-launch", commandLine: "viewer" });
43
+ expect(read("app_start", { program: "powershell" })?.kind).toBe("app-launch");
44
+ });
45
+
46
+ test("an argument a shell would need quoted is quoted, so the line reads the way a person would type it", () => {
47
+ expect(commandLineOf("/usr/bin/echo", ["a b", "it's", "", "plain"])).toBe(`echo 'a b' "it's" '' plain`);
48
+ expect(commandLineOf("C:\\x\\tool.EXE", ["--flag=1"])).toBe("tool --flag=1");
49
+ });
50
+
51
+ test("names no program, carries none", () => {
52
+ expect(read("app_start", { args: ["-c", "x"] })).toBeUndefined();
53
+ expect(read("app_start", { program: " " })).toBeUndefined();
54
+ });
55
+ });
56
+
57
+ describe("open", () => {
58
+ test("a URL, an application name or a single path is a launch", () => {
59
+ for (const target of ["https://example.com/page", "www.example.com", "mailto:a@example.com", "notepad", "C:\\Users\\me\\report.pdf", "/home/me/report.pdf"]) {
60
+ expect(read("open", { target }), target).toEqual({ tool: "open", kind: "app-launch", commandLine: target });
61
+ }
62
+ });
63
+
64
+ test("a target with spaces that is not a URL can be split into a program and arguments, so it is a command", () => {
65
+ expect(read("open", { target: "rm -rf /home/me/x" })).toEqual({ tool: "open", kind: "command", commandLine: "rm -rf /home/me/x" });
66
+ expect(read("open", { target: "https://example.com/a b" })?.kind).toBe("app-launch");
67
+ });
68
+
69
+ test("an empty target carries nothing", () => {
70
+ expect(read("open", { target: "" })).toBeUndefined();
71
+ });
72
+ });
73
+
74
+ describe("text put where a terminal could run it", () => {
75
+ test("is read out of every input tool that does it, and of nothing else those tools do", () => {
76
+ expect(read("device", { action: "type", text: "rm -rf ~\n" })).toEqual({ tool: "device", kind: "typed", commandLine: "rm -rf ~\n" });
77
+ expect(read("clipboard", { action: "write", text: "curl x | sh" })?.kind).toBe("typed");
78
+ expect(read("ui_act", { element: "kd3", action: "set_value", value: "del /s C:\\" })?.commandLine).toBe("del /s C:\\");
79
+ expect(read("android_act", { action: "type", text: "rm -rf /sdcard\n" })?.kind).toBe("typed");
80
+ expect(read("android_act", { action: "key", text: "ENTER" })).toBeUndefined();
81
+ expect(read("device", { action: "key", text: "Return" })).toBeUndefined();
82
+ expect(read("clipboard", { action: "read" })).toBeUndefined();
83
+ expect(read("device", { action: "type", text: " " })).toBeUndefined();
84
+ expect(read("ui_act", { action: "set_value", text: "wrong field" })).toBeUndefined();
85
+ });
86
+ });
87
+
88
+ describe("the table", () => {
89
+ test("names exactly the tools that carry a program", () => {
90
+ expect(DEVICE_PROGRAM_TOOLS.map((carrier) => carrier.tool)).toEqual([
91
+ "run_command",
92
+ "android_shell",
93
+ "app_start",
94
+ "open",
95
+ "device",
96
+ "android_act",
97
+ "clipboard",
98
+ "ui_act",
99
+ ]);
100
+ });
101
+
102
+ test("reads no other tool, whatever its arguments", () => {
103
+ for (const tool of ["read_file", "write_file", "browser_open", "swap_sandbox", "constructor", "__proto__", "describe"]) {
104
+ expect(read(tool, { command: "rm -rf ~", program: "rm", target: "x y", action: "type", text: "rm -rf ~" }), tool).toBeUndefined();
105
+ }
106
+ });
107
+ });
@@ -0,0 +1,79 @@
1
+ import { generateKeyPairSync } from "node:crypto";
2
+ import {
3
+ IDENTITY_ASSERTION_MAX_TTL_MS,
4
+ IDENTITY_ASSERTION_TTL_MS,
5
+ isIdentityAssertion,
6
+ mintIdentityAssertion,
7
+ verifyIdentityAssertion,
8
+ } from "../identity-assertion.js";
9
+ import { mintOwnerTicket, publicKeyPemOf, verifyOwnerTicket } from "../owner-ticket.js";
10
+
11
+ // PEM strings straight from the generator, the form the platform's env and the machine's env carry.
12
+ const pemPair = (): { privateKey: string; publicKey: string } =>
13
+ generateKeyPairSync("ed25519", { publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs8", format: "pem" } });
14
+ const { privateKey: privatePem, publicKey: publicPem } = pemPair();
15
+ const NOW = 1_800_000_000_000;
16
+ const SANDBOX = "0123456789ab";
17
+
18
+ const mint = (overrides: Partial<Parameters<typeof mintIdentityAssertion>[1]> = {}): string =>
19
+ mintIdentityAssertion(privatePem, { sandboxId: SANDBOX, email: "Ada@Example.org", method: "email", issuedAtMs: NOW, ...overrides });
20
+
21
+ describe("identity assertion", () => {
22
+ it("round-trips the claim, lowercased, and expires when it says", () => {
23
+ const assertion = mint();
24
+ expect(isIdentityAssertion(assertion)).toBe(true);
25
+ expect(verifyIdentityAssertion(publicPem, assertion, NOW + 1_000)).toEqual({
26
+ sandboxId: SANDBOX,
27
+ email: "ada@example.org",
28
+ method: "email",
29
+ issuedAt: NOW / 1000,
30
+ expiresAt: (NOW + IDENTITY_ASSERTION_TTL_MS) / 1000,
31
+ nonce: expect.stringMatching(/^[A-Za-z0-9_-]{22}$/),
32
+ });
33
+ expect(verifyIdentityAssertion(publicPem, assertion, NOW + IDENTITY_ASSERTION_TTL_MS)).toBeUndefined();
34
+ });
35
+
36
+ it("gives every mint its own nonce, so a daemon can spend each once", () => {
37
+ const first = verifyIdentityAssertion(publicPem, mint(), NOW);
38
+ const second = verifyIdentityAssertion(publicPem, mint(), NOW);
39
+ expect(first?.nonce).not.toBe(second?.nonce);
40
+ });
41
+
42
+ it("verifies against the public half derived from the platform's private key", () => {
43
+ expect(verifyIdentityAssertion(publicKeyPemOf(privatePem), mint({ method: "google" }), NOW)).toMatchObject({ method: "google" });
44
+ });
45
+
46
+ it("refuses another key's signature, a tampered claim, and garbage", () => {
47
+ const other = pemPair().publicKey;
48
+ const assertion = mint();
49
+ expect(verifyIdentityAssertion(other, assertion, NOW)).toBeUndefined();
50
+ const [prefix, , signature] = assertion.split(".");
51
+ const forged = Buffer.from(
52
+ JSON.stringify({ v: 1, sub: SANDBOX, email: "eve@example.org", amr: "email", iat: NOW / 1000, exp: NOW / 1000 + 60, jti: "x".repeat(22) }),
53
+ ).toString("base64url");
54
+ expect(verifyIdentityAssertion(publicPem, `${prefix}.${forged}.${signature}`, NOW)).toBeUndefined();
55
+ expect(verifyIdentityAssertion(publicPem, "pa1.not.base64", NOW)).toBeUndefined();
56
+ expect(verifyIdentityAssertion(publicPem, `${assertion}.extra`, NOW)).toBeUndefined();
57
+ expect(isIdentityAssertion("ot1.x.y")).toBe(false);
58
+ });
59
+
60
+ it("refuses a claim issued more than a minute ahead of the daemon's clock", () => {
61
+ expect(verifyIdentityAssertion(publicPem, mint({ issuedAtMs: NOW + 61_000 }), NOW)).toBeUndefined();
62
+ expect(verifyIdentityAssertion(publicPem, mint({ issuedAtMs: NOW + 59_000 }), NOW)).toMatchObject({ email: "ada@example.org" });
63
+ });
64
+
65
+ /* One key could sign tickets and assertions if an operator reused it; neither may pass as the other. */
66
+ it("is never an owner ticket, and an owner ticket is never an assertion", () => {
67
+ const ticket = mintOwnerTicket(privatePem, { sandboxId: SANDBOX, email: "ada@example.org", issuedAtMs: NOW });
68
+ expect(verifyIdentityAssertion(publicPem, ticket, NOW)).toBeUndefined();
69
+ expect(verifyIdentityAssertion(publicPem, `pa1.${ticket.slice("ot1.".length)}`, NOW)).toBeUndefined();
70
+ expect(verifyOwnerTicket(publicPem, mint(), NOW)).toBeUndefined();
71
+ });
72
+
73
+ it("refuses to mint for anything but a real sandbox id, an address, and a short life", () => {
74
+ expect(() => mint({ sandboxId: "nope" })).toThrow("12-hex sandbox id");
75
+ expect(() => mint({ email: " " })).toThrow("names an address");
76
+ expect(() => mint({ ttlMs: IDENTITY_ASSERTION_MAX_TTL_MS + 1 })).toThrow("lives at most");
77
+ expect(() => mint({ ttlMs: 0 })).toThrow("lives at most");
78
+ });
79
+ });
@@ -0,0 +1,181 @@
1
+ import { managesSandbox, mayReachSandbox, SANDBOX_POWER_DOING, type SandboxPowerCommand, sandboxPowerCommands } from "../sandbox-power.js";
2
+
3
+ const OWN = { slug: "work", container: "intentic-sandbox-work" };
4
+ const PREFIX = "intentic-sandbox-";
5
+
6
+ // What a command aims at, without the spans: the verb, whether it ends a running sandbox's turns, and who it names.
7
+ const aimOf = (hit: SandboxPowerCommand | undefined) => hit && { via: hit.via, op: hit.op, stops: hit.stops, targets: hit.targets, unnamed: hit.unnamed };
8
+ const first = (command: string) => aimOf(sandboxPowerCommands(command)[0]);
9
+
10
+ describe("ic sandbox", () => {
11
+ test("reads every verb that ends a running sandbox's turns, with the slug it names", () => {
12
+ expect(first("ic sandbox restart work")).toEqual({ via: "ic", op: "restart", stops: true, targets: ["work"], unnamed: false });
13
+ expect(first("ic sandbox stop work")).toEqual({ via: "ic", op: "stop", stops: true, targets: ["work"], unnamed: false });
14
+ expect(first("ic sandbox rm work")).toEqual({ via: "ic", op: "remove", stops: true, targets: ["work"], unnamed: false });
15
+ expect(first("ic sandbox remove work -y")).toEqual({ via: "ic", op: "remove", stops: true, targets: ["work"], unnamed: false });
16
+ expect(first("ic sandbox rebuild work 3f2a")).toEqual({ via: "ic", op: "rebuild", stops: true, targets: ["work", "3f2a"], unnamed: false });
17
+ expect(first("ic sandbox update work --no-resume")).toEqual({ via: "ic", op: "update", stops: true, targets: ["work"], unnamed: false });
18
+ expect(first("ic sandbox rollback work --to 1.315.0")).toEqual({ via: "ic", op: "rollback", stops: true, targets: ["work"], unnamed: false });
19
+ expect(first("ic sandbox sleep work")).toEqual({ via: "ic", op: "sleep", stops: true, targets: ["work"], unnamed: false });
20
+ expect(first("ic sandbox dev work")).toEqual({ via: "ic", op: "dev", stops: true, targets: ["work"], unnamed: false });
21
+ });
22
+
23
+ test("a start, a download and a resource ask saved for later are managing the sandbox but ending nothing", () => {
24
+ expect(first("ic sandbox start work")).toEqual({ via: "ic", op: "start", stops: false, targets: ["work"], unnamed: false });
25
+ expect(first("ic sandbox prepare work --auto")).toEqual({ via: "ic", op: "prepare", stops: false, targets: ["work"], unnamed: false });
26
+ expect(first("ic sandbox reshape work --memory 12g --later")?.stops).toBe(false);
27
+ expect(first("ic sandbox reshape work --forget")?.stops).toBe(false);
28
+ expect(first("ic sandbox shape work --set cpus=4 --when next-restart")?.stops).toBe(false);
29
+ expect(first("ic sandbox shape work --set cpus=4 --when nextRestart")?.stops).toBe(false);
30
+ });
31
+
32
+ test("a resource change applied now restarts, and so does a bare reshape, which applies what was saved", () => {
33
+ expect(first("ic sandbox reshape work --memory 12g")).toEqual({ via: "ic", op: "resize", stops: true, targets: ["work"], unnamed: false });
34
+ expect(first("ic sandbox reshape work")?.stops).toBe(true);
35
+ expect(first("ic sandbox shape work --set cpus=4 --when now")).toEqual({ via: "ic", op: "resize", stops: true, targets: ["work"], unnamed: false });
36
+ expect(first("ic sandbox shape work --when=now --set cpus=4")?.stops).toBe(true);
37
+ });
38
+
39
+ test("a flag's value is not a slug, so a command naming no sandbox reads as naming none", () => {
40
+ expect(first("ic sandbox update --channel stable")).toEqual({ via: "ic", op: "update", stops: true, targets: [], unnamed: true });
41
+ expect(first("ic sandbox rollback --to 1.315.0")).toEqual({ via: "ic", op: "rollback", stops: true, targets: [], unnamed: true });
42
+ expect(first("ic sandbox restart")).toEqual({ via: "ic", op: "restart", stops: true, targets: [], unnamed: true });
43
+ expect(first("ic sandbox restart 2>&1")?.unnamed).toBe(true);
44
+ expect(first("ic sandbox remove --all -y")?.unnamed).toBe(true);
45
+ });
46
+
47
+ test("a backup restored into a sandbox stops it; restored to a folder it touches none", () => {
48
+ expect(first("ic sandbox backup-restore work --snapshot latest --into work -y")).toEqual({
49
+ via: "ic",
50
+ op: "restore",
51
+ stops: true,
52
+ targets: ["work"],
53
+ unnamed: false,
54
+ });
55
+ expect(first("ic sandbox backup-restore work --snapshot latest --into other")?.targets).toEqual(["other"]);
56
+ expect(first("ic sandbox backup-restore work --snapshot latest --to /tmp/look")?.stops).toBe(false);
57
+ });
58
+
59
+ test("reinstalling over a sandbox ends it, and names no slug the command line shows", () => {
60
+ expect(first("ic sandbox connect --replace -y abc123")).toEqual({ via: "ic", op: "connect", stops: true, targets: [], unnamed: true });
61
+ expect(first("ic sandbox connect -y abc123")?.stops).toBe(false);
62
+ });
63
+
64
+ test("reads the program however a shell is told to find it", () => {
65
+ for (const command of [
66
+ "ic.exe sandbox restart work",
67
+ "& 'C:\\Users\\radar\\.intentic\\bin\\ic.exe' sandbox restart work",
68
+ '& "C:\\Users\\radar\\.intentic\\bin\\ic.exe" sandbox restart work',
69
+ "~/.intentic/bin/ic sandbox restart work",
70
+ "sudo ic sandbox restart work",
71
+ "cd /tmp && ic sandbox restart work",
72
+ "wsl -d archlinux -- ic sandbox restart work",
73
+ "IC SANDBOX RESTART work",
74
+ ]) {
75
+ expect(first(command), command).toMatchObject({ via: "ic", op: "restart", targets: ["work"] });
76
+ }
77
+ });
78
+
79
+ test("leaves alone what only reads, and words that merely end in ic", () => {
80
+ for (const command of [
81
+ "ic sandbox list --json",
82
+ "ic sandbox logs work --tail 50",
83
+ "ic sandbox doctor work",
84
+ "ic sandbox versions work",
85
+ "intentic sandbox restart work",
86
+ "magic sandbox restart work",
87
+ "ic runner remove build",
88
+ "git commit -m 'document how ic works'",
89
+ ]) {
90
+ expect(sandboxPowerCommands(command), command).toEqual([]);
91
+ }
92
+ });
93
+
94
+ test("stops reading at the end of the simple command, so a later command's words are not this one's operands", () => {
95
+ expect(first("ic sandbox restart work; echo done")?.targets).toEqual(["work"]);
96
+ expect(first("ic sandbox restart | tee log")?.unnamed).toBe(true);
97
+ expect(first("ic sandbox restart work && ic sandbox start other")?.targets).toEqual(["work"]);
98
+ expect(sandboxPowerCommands("ic sandbox restart work && ic sandbox start other").map((hit) => hit.targets)).toEqual([["work"], ["other"]]);
99
+ });
100
+
101
+ test("marks a command that only appears as text as not live", () => {
102
+ expect(sandboxPowerCommands("echo 'ic sandbox restart work'")[0]?.live).toBe(false);
103
+ expect(sandboxPowerCommands("# ic sandbox restart work")[0]?.live).toBe(false);
104
+ expect(sandboxPowerCommands("ic sandbox restart work")[0]?.live).toBe(true);
105
+ expect(sandboxPowerCommands("ic sandbox restart work")[0]?.span).toEqual({ start: 0, end: "ic sandbox restart".length });
106
+ });
107
+ });
108
+
109
+ describe("docker", () => {
110
+ test("reads a stop, kill, restart or remove of a container, with the names it gives", () => {
111
+ expect(first("docker stop intentic-sandbox-work")).toEqual({ via: "docker", op: "stop", stops: true, targets: ["intentic-sandbox-work"], unnamed: false });
112
+ expect(first("docker kill intentic-sandbox-work")?.op).toBe("stop");
113
+ expect(first("docker restart intentic-sandbox-work")?.op).toBe("restart");
114
+ expect(first("docker rm -f intentic-sandbox-work")).toEqual({ via: "docker", op: "remove", stops: true, targets: ["intentic-sandbox-work"], unnamed: false });
115
+ expect(first("docker container stop -t 60 a b")).toEqual({ via: "docker", op: "stop", stops: true, targets: ["a", "b"], unnamed: false });
116
+ expect(first("docker.exe stop a")?.targets).toEqual(["a"]);
117
+ expect(first("podman rm a")?.via).toBe("docker");
118
+ });
119
+
120
+ test("leaves alone docker that stops nothing", () => {
121
+ for (const command of ["docker ps", "docker logs intentic-sandbox-work", "docker start intentic-sandbox-work", "docker exec c kill -9 1", "docker compose stop"]) {
122
+ expect(sandboxPowerCommands(command), command).toEqual([]);
123
+ }
124
+ });
125
+
126
+ test("a stop fed from a pipe names nothing", () => {
127
+ expect(first("docker ps -q | xargs docker stop")).toEqual({ via: "docker", op: "stop", stops: true, targets: [], unnamed: true });
128
+ });
129
+ });
130
+
131
+ describe("whether a command may reach this sandbox", () => {
132
+ const reaches = (command: string): boolean => sandboxPowerCommands(command).some((hit) => mayReachSandbox(hit, OWN));
133
+
134
+ test("names it by slug or by container", () => {
135
+ expect(reaches("ic sandbox restart work")).toBe(true);
136
+ expect(reaches("docker stop intentic-sandbox-work")).toBe(true);
137
+ // docker knows no such container, but a card for a mistaken match costs less than a fleet cut unasked
138
+ expect(reaches("docker stop work")).toBe(true);
139
+ });
140
+
141
+ test("names none, or something that is only worked out when it runs", () => {
142
+ expect(reaches("ic sandbox update")).toBe(true);
143
+ expect(reaches("ic sandbox remove --all")).toBe(true);
144
+ expect(reaches("docker stop $(docker ps -q)")).toBe(true);
145
+ expect(reaches('docker stop "$SANDBOX"')).toBe(true);
146
+ expect(reaches("docker stop (docker ps -q)")).toBe(true);
147
+ expect(reaches("docker rm intentic-sandbox-*")).toBe(true);
148
+ expect(reaches("ic sandbox restart %NAME%")).toBe(true);
149
+ });
150
+
151
+ test("names another sandbox, or another container", () => {
152
+ expect(reaches("ic sandbox restart other")).toBe(false);
153
+ expect(reaches("ic sandbox rebuild other 3f2a")).toBe(false);
154
+ expect(reaches("docker stop intentic-sandbox-other")).toBe(false);
155
+ expect(reaches("docker restart postgres")).toBe(false);
156
+ expect(reaches("ic sandbox update --channel stable other")).toBe(false);
157
+ });
158
+ });
159
+
160
+ describe("whether a command manages a sandbox the switch covers", () => {
161
+ const manages = (command: string): boolean => sandboxPowerCommands(command).some((hit) => managesSandbox(hit, PREFIX));
162
+
163
+ test("any ic sandbox verb, whichever sandbox it names", () => {
164
+ expect(manages("ic sandbox restart other")).toBe(true);
165
+ expect(manages("ic sandbox start other")).toBe(true);
166
+ expect(manages("ic sandbox update")).toBe(true);
167
+ });
168
+
169
+ test("docker only for a container that is a sandbox's, since the device cannot know a name worked out at run time", () => {
170
+ expect(manages("docker stop intentic-sandbox-other")).toBe(true);
171
+ expect(manages("docker rm -f intentic-sandbox-tunnel-abc")).toBe(true);
172
+ expect(manages("docker restart postgres")).toBe(false);
173
+ expect(manages("docker stop $(docker ps -q)")).toBe(false);
174
+ });
175
+ });
176
+
177
+ test("every operation has the owner's word for it", () => {
178
+ expect(SANDBOX_POWER_DOING.restart).toBe("Restart");
179
+ expect(SANDBOX_POWER_DOING.resize).toBe("Restart with new resources");
180
+ expect(SANDBOX_POWER_DOING.rollback).toBe("Roll back");
181
+ });
@@ -1,4 +1,4 @@
1
- import { includeGlobs } from "../search-globs.js";
1
+ import { globMatcher, includeGlobs, includeMatcher } from "../search-globs.js";
2
2
 
3
3
  test("an empty field scopes nothing", () => {
4
4
  expect(includeGlobs(undefined)).toEqual({ globs: [], notGlobs: [] });
@@ -43,3 +43,48 @@ test("a leading ! excludes instead", () => {
43
43
  // A lone "!" excludes nothing: expanded, it would have matched everything.
44
44
  expect(includeGlobs(`!`)).toEqual({ globs: [], notGlobs: [] });
45
45
  });
46
+
47
+ describe("globMatcher", () => {
48
+ // Every construct, held against Bun.Glob itself: the desktop folder server matches with Bun.Glob, so any
49
+ // disagreement here is two machines admitting different files for one pattern.
50
+ const patterns = [
51
+ `*.ts`, `**/*.ts`, `**/*.[!t]s`, `**/*.[^t]s`, `src/**`, `src/**/*.ts`, `a/**/b`, `a?c`, `[a-c]x`, `[!a]`, `[]]`,
52
+ `*.{ts,js}`, `{a,{b,c}}/*`, `a/{b,c/**}`, `{**/x,y}`, `a**b`, `**`, `**/`, `**/**/x`, `{,a}b`, `\\*x`, `**.ts`, `a/**b`,
53
+ ];
54
+ const paths = [
55
+ `a.ts`, `a.js`, `a/b.ts`, `a/b.js`, `.hidden.ts`, `.x/y.ts`, `src`, `src/a.ts`, `src/a/b.ts`, `a/b`, `a/x/b`, `abc`, `a/c`,
56
+ `bx`, `dx`, `/`, `a`, `]`, `c/x`, `a/c/d`, `a/x`, `x`, `y`, `axxb`, `ax/xb`, `b`, `ab`, `*x`, `a/xb`, `a/c/xb`, `a/`, `a/b/`,
57
+ ];
58
+ for (const pattern of patterns) {
59
+ test(`reads ${pattern} as Bun.Glob does`, () => {
60
+ const glob = new Bun.Glob(pattern);
61
+ const matches = globMatcher(pattern);
62
+ expect(paths.filter((path) => matches(path))).toEqual(paths.filter((path) => glob.match(path)));
63
+ });
64
+ }
65
+
66
+ test("[!t] means anything but t", () => {
67
+ const matches = globMatcher(`**/*.[!t]s`);
68
+ expect([matches(`src/a.js`), matches(`src/a.ts`)]).toEqual([true, false]);
69
+ });
70
+
71
+ test("an unclosed set or group is literal", () => {
72
+ expect([globMatcher(`[ab`)(`[ab`), globMatcher(`{a,b`)(`{a,b`)]).toEqual([true, true]);
73
+ });
74
+ });
75
+
76
+ describe("includeMatcher", () => {
77
+ test("admits by any include and refuses by any exclude", () => {
78
+ const matches = includeMatcher(`src, !*.test.ts`);
79
+ expect([`src/a.ts`, `src/a.test.ts`, `lib/src/b.ts`, `docs/c.md`].map(matches)).toEqual([true, false, true, false]);
80
+ });
81
+
82
+ test("an anchored include matches from the root only", () => {
83
+ const matches = includeMatcher(`./src`);
84
+ expect([matches(`src/a.ts`), matches(`lib/src/a.ts`)]).toEqual([true, false]);
85
+ });
86
+
87
+ test("nothing typed admits every path", () => {
88
+ expect(includeMatcher(undefined)(`any/where.ts`)).toBe(true);
89
+ });
90
+ });
@@ -1,4 +1,4 @@
1
- import { hostAllowed, hostMatches, normalizeHost, normalizeHostPattern, tightensHostGuard } from "../secret-hosts.js";
1
+ import { hostAllowed, hostCovers, hostMatches, normalizeHost, normalizeHostPattern, tightensHostGuard } from "../secret-hosts.js";
2
2
 
3
3
  // The pattern a person writes against the host a use would reach. Every boundary a limit turns on is pinned by value:
4
4
  // the apex a wildcard leaves out, the look-alike that only shares a suffix, and which edits take hosts away.
@@ -87,3 +87,19 @@ describe("tightensHostGuard", () => {
87
87
  expect(tightensHostGuard(on("api.github.com"), on("*.api.github.com"))).toBe(false);
88
88
  });
89
89
  });
90
+
91
+ describe("hostCovers", () => {
92
+ it("reads a plain entry as a domain and every host under it, never a lookalike", () => {
93
+ expect(hostCovers("example.com", "example.com")).toBe(true);
94
+ expect(hostCovers("example.com", "api.example.com")).toBe(true);
95
+ expect(hostCovers("example.com", "a.b.example.com")).toBe(true);
96
+ expect(hostCovers("example.com", "notexample.com")).toBe(false);
97
+ expect(hostCovers("example.com", "example.com.evil.net")).toBe(false);
98
+ });
99
+
100
+ it("reads a wildcard as the hosts under it only", () => {
101
+ expect(hostCovers("*.example.com", "api.example.com")).toBe(true);
102
+ expect(hostCovers("*.example.com", "example.com")).toBe(false);
103
+ expect(hostCovers("*.example.com", "notexample.com")).toBe(false);
104
+ });
105
+ });
@@ -13,6 +13,8 @@ const TURNED_AWAY: ReadonlySet<string> = new Set([
13
13
  "model-helper-only",
14
14
  // The privacy shield refused an untrusted provider on a runtime it cannot read by content at all.
15
15
  "privacy-unshielded",
16
+ // The turn carries a tool list its runtime cannot keep.
17
+ "tools-unenforced",
16
18
  // The protected execution boundary refused admission, not an executed provider/runtime failure.
17
19
  "agent-domain-refused",
18
20
  // The privacy shield found personal data in instructions a runtime loads itself, where it could not mask them.
@@ -20,6 +20,10 @@ export const RunnerHelloSchema = z.object({
20
20
  // Compared against what the parent runs to report parity (outdated → rebuild action); an outdated runner still runs
21
21
  // turns rather than being refused.
22
22
  image: z.string(),
23
+ // The image `image` was composed from (SANDBOX_BASE_IMAGE). Parity compares this, not `image`: an overlay build's
24
+ // tag names its own container (`intentic-sandbox-env-<slug>:<hash>`), so two sides on one base and one overlay
25
+ // never share it. Absent on an old image, which is then compared by `image` alone.
26
+ baseImage: z.string().optional(),
23
27
  channel: z.string().optional(),
24
28
  overlayHash: z.string().optional(),
25
29
  // Runner's declared settings-only sandbox.toml (definition.ts), diffed against the parent's to itemize drift;
@@ -106,13 +110,22 @@ export type RunnerCredentialRefreshRequest = z.infer<typeof RunnerCredentialRefr
106
110
  export const RunnerCredentialRefreshSchema = z.object({ accessToken: z.string().optional() });
107
111
  export type RunnerCredentialRefresh = z.infer<typeof RunnerCredentialRefreshSchema>;
108
112
 
109
- // What a runner is, hardware-wise: what the placement picker shows and a future scheduler would weigh.
113
+ // What a runner is, hardware-wise, and how full it is right now: what the placement picker shows and the fleet advice
114
+ // (runners/fleet-advice.ts) weighs. Re-asked on demand rather than only at connect, so the live half stays live.
110
115
  export const RunnerFactsSchema = z.object({
111
116
  cpus: z.number().int().positive(),
112
117
  memoryMb: z.number().int().positive(),
113
118
  freeDiskMb: z.number().int().nonnegative(),
114
119
  // 0..1 over the last minute; a coarse busy signal for a picker, not a metrics feed.
115
120
  load: z.number().nonnegative(),
121
+ // The runner's own resource budget, the figures its turn door admits by: what is free under its memory limit, and
122
+ // that limit. Absent from a runner that predates them, which is then sized by `memoryMb` alone.
123
+ memoryFreeMb: z.number().int().nonnegative().optional(),
124
+ memoryLimitMb: z.number().int().positive().optional(),
125
+ // Pressure stall over the last ten seconds, in percent: anything waiting for a CPU, everything waiting on memory.
126
+ // Absent where the kernel does not report it.
127
+ cpuPressure: z.number().nonnegative().optional(),
128
+ memoryStall: z.number().nonnegative().optional(),
116
129
  });
117
130
  export type RunnerFacts = z.infer<typeof RunnerFactsSchema>;
118
131
 
@@ -141,6 +154,13 @@ export const RunnerSummarySchema = z.object({
141
154
  drift: z.array(NeedsActionSchema).optional(),
142
155
  });
143
156
  export type RunnerSummary = z.infer<typeof RunnerSummarySchema>;
157
+ // GET /system/runners: every enrolled runner, the one list the Devices view, the placement picker and the Access tab read.
158
+ export const RunnersListSchema = z.object({ runners: z.array(RunnerSummarySchema) });
159
+ export type RunnersList = z.infer<typeof RunnersListSchema>;
160
+ // What a runner answers when it takes a definition (`applyDefinition`), and POST /system/runners/{id}/definition/sync
161
+ // relays: the settings it now runs.
162
+ export const RunnerDefinitionAppliedSchema = z.object({ settings: z.array(z.string()) });
163
+ export type RunnerDefinitionApplied = z.infer<typeof RunnerDefinitionAppliedSchema>;
144
164
 
145
165
  // A workspace sync, narrated line by line as it happens (first contact clones whole repos). `op`: `pull` brings the
146
166
  // runner's checkout current before a turn, `push` returns the branch after; `repos` is the parent's own record, since