@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
@@ -0,0 +1,137 @@
1
+ import { z } from "zod";
2
+
3
+ // WHICH DEVICE TOOLS CARRY A PROGRAM (2026-10-12). A command on somebody's own computer arrives by more doors than
4
+ // `run_command`: `app_start` starts a program with arguments, `open` starts whatever its target names (on Linux a
5
+ // target with spaces is split into a program and arguments and spawned), `android_shell` hands a line to a phone's
6
+ // shell, and the input tools put text where a terminal could run it. The daemon's gate used to know two of those by
7
+ // name, the machine's own checks a different few, and `app_start program=powershell args=[…]` slipped past the judge, the
8
+ // card and the unattended refusal. This is the one table both ends read: the tool, and how to lift the command line
9
+ // out of its arguments. A device tool that carries a program is added here, or the machine's test says it is missing.
10
+
11
+ /**
12
+ * What a call's program is, which decides how it is read:
13
+ * - `command`: a command line for the machine's own shell, or a program started with arguments; judged as `run_command`.
14
+ * - `phone-command`: a line for an attached phone's shell, judged as the `adb shell` program the machine runs for it.
15
+ * - `app-launch`: an application, URL or document started with nothing to interpret; the ordinary GUI launch, low risk.
16
+ * - `typed`: text put where a terminal could run it (typed, pasted, set into a field).
17
+ */
18
+ export type ProgramKind = "command" | "phone-command" | "app-launch" | "typed";
19
+
20
+ /** The program a tool call carries, as the classifier and the judge read it. */
21
+ export interface ProgramReading {
22
+ readonly tool: string;
23
+ readonly kind: ProgramKind;
24
+ /** The command line as a shell would read it. */
25
+ readonly commandLine: string;
26
+ }
27
+
28
+ // The fields of every tool in the table below. One that is the wrong type reads as absent rather than failing the
29
+ // parse: such a call never runs, since the device checks its input against its own schema before anything else.
30
+ export const ProgramArgumentsSchema = z.looseObject({
31
+ command: z.string().optional().catch(undefined),
32
+ serial: z.string().optional().catch(undefined),
33
+ program: z.string().optional().catch(undefined),
34
+ args: z.array(z.string()).optional().catch(undefined),
35
+ target: z.string().optional().catch(undefined),
36
+ action: z.string().optional().catch(undefined),
37
+ text: z.string().optional().catch(undefined),
38
+ value: z.string().optional().catch(undefined),
39
+ });
40
+ export type ProgramArguments = z.infer<typeof ProgramArgumentsSchema>;
41
+
42
+ interface ProgramCarrier {
43
+ readonly tool: string;
44
+ /** In words: where the program is in the arguments. */
45
+ readonly carries: string;
46
+ readonly read: (args: ProgramArguments) => Omit<ProgramReading, "tool"> | undefined;
47
+ }
48
+
49
+ const present = (value: string | undefined): string | undefined => (value === undefined || value.trim() === "" ? undefined : value);
50
+
51
+ // A program by the name it is known by (`rm`, `powershell`), not the path or the `.exe` it was started through.
52
+ const programName = (program: string): string => (program.split(/[/\\]/).at(-1) ?? program).replace(/\.exe$/i, "");
53
+
54
+ /**
55
+ * The program and its arguments as the one command line they amount to: the program by its name, each argument quoted
56
+ * only where a shell would need it, so the line reads the way a person would type it.
57
+ */
58
+ export const commandLineOf = (program: string, args: readonly string[]): string =>
59
+ [
60
+ programName(program),
61
+ ...args.map((arg) => (arg === "" || /[\s'"|;&<>()$`\\]/.test(arg) ? (arg.includes("'") ? `"${arg}"` : `'${arg}'`) : arg)),
62
+ ].join(" ");
63
+
64
+ // A phone's serial as adb prints one (a USB serial, `ip:port`, an mDNS name), and nothing else: a serial is spliced into
65
+ // the program judged below, where a `#` or a quote in it could make the command after it read as inert.
66
+ const ADB_SERIAL = /^[\w.:-]+$/;
67
+
68
+ // A URL, or `www.`/`mailto:` which the machine opens the same way (desktop-automation's looksLikeUrl).
69
+ const URL_TARGET = /^(?:[a-z][a-z0-9+.-]*:\/\/|www\.|mailto:)/i;
70
+
71
+ const typedBy = (tool: string, action: string, field: "text" | "value"): ProgramCarrier => ({
72
+ tool,
73
+ carries: `${field}, when action is ${action}`,
74
+ read: (args) => {
75
+ const typed = args.action === action ? present(args[field]) : undefined;
76
+ return typed === undefined ? undefined : { kind: "typed", commandLine: typed };
77
+ },
78
+ });
79
+
80
+ /** Every device tool that carries a program. */
81
+ export const DEVICE_PROGRAM_TOOLS: readonly ProgramCarrier[] = [
82
+ {
83
+ tool: "run_command",
84
+ carries: "command",
85
+ read: ({ command }) => {
86
+ const line = present(command);
87
+ return line === undefined ? undefined : { kind: "command", commandLine: line };
88
+ },
89
+ },
90
+ {
91
+ tool: "android_shell",
92
+ carries: "command, run as `adb [-s serial] shell <command>`",
93
+ read: ({ command, serial }) => {
94
+ const line = present(command);
95
+ if (line === undefined) {
96
+ return undefined;
97
+ }
98
+ return { kind: "phone-command", commandLine: `adb ${serial !== undefined && ADB_SERIAL.test(serial) ? `-s ${serial} ` : ""}shell ${line}` };
99
+ },
100
+ },
101
+ {
102
+ tool: "app_start",
103
+ carries: "program and args",
104
+ read: ({ program, args }) => {
105
+ const started = present(program);
106
+ if (started === undefined) {
107
+ return undefined;
108
+ }
109
+ // With nothing to interpret it is a launch; with arguments it is a command by another door, whatever the
110
+ // program is: `rm -rf ~`, `curl … | sh` and `powershell -c …` read the same here as in run_command.
111
+ return { kind: args === undefined || args.length === 0 ? "app-launch" : "command", commandLine: commandLineOf(started, args ?? []) };
112
+ },
113
+ },
114
+ {
115
+ tool: "open",
116
+ carries: "target",
117
+ read: ({ target }) => {
118
+ const opened = present(target);
119
+ if (opened === undefined) {
120
+ return undefined;
121
+ }
122
+ // A URL or a single word (an application name, a file path) is opened. A target with spaces that is not a URL
123
+ // can be split into a program and arguments and spawned, so it is read as the command it may be.
124
+ return { kind: URL_TARGET.test(opened) || !/\s/.test(opened.trim()) ? "app-launch" : "command", commandLine: opened };
125
+ },
126
+ },
127
+ typedBy("device", "type", "text"),
128
+ typedBy("android_act", "type", "text"),
129
+ typedBy("clipboard", "write", "text"),
130
+ typedBy("ui_act", "set_value", "value"),
131
+ ];
132
+
133
+ /** The program a `tools/call` of `tool` carries, or undefined for a tool that carries none or a call with nothing in the place the table looks. */
134
+ export const programInCall = (tool: string, args: ProgramArguments): ProgramReading | undefined => {
135
+ const read = DEVICE_PROGRAM_TOOLS.find((carrier) => carrier.tool === tool)?.read(args);
136
+ return read === undefined ? undefined : { tool, ...read };
137
+ };
@@ -0,0 +1,130 @@
1
+ import { createPrivateKey, createPublicKey, randomBytes, sign as edSign, verify as edVerify } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { SANDBOX_ID } from "../ids/tunnel-ids.js";
4
+
5
+ // The platform's signed word that a signed-in person controls an email address, spent on one sandbox's daemon. It is how
6
+ // an address no identity provider vouches for (a custom domain on Fastmail, Proton, a company's own mail server) reaches
7
+ // a sandbox: the platform confirmed the address by mailing it a code, and nobody else could. The daemon accepts it only
8
+ // where its owner opted in for that person (the owner record or the member row says `emailSignIn`), and on hosted
9
+ // machines, where the platform already holds the machine. Signed with a key of its own (IDENTITY_SIGNING_KEY), never the
10
+ // ingress key, so neither credential can stand in for the other.
11
+
12
+ const ASSERTION_PREFIX = "pa1";
13
+
14
+ // One round trip to the daemon and a retry; a lifted assertion is dead before anyone reads the log it sat in.
15
+ export const IDENTITY_ASSERTION_TTL_MS = 2 * 60_000;
16
+ // The longest life a daemon accepts whatever the claim says, so a platform minting with a wrong clock or a wrong ttl
17
+ // still cannot hand out a credential that outlives a replay window the daemon can hold in memory.
18
+ export const IDENTITY_ASSERTION_MAX_TTL_MS = 5 * 60_000;
19
+
20
+ // The env var that carries the platform's identity key on a hosted machine, which boots before its first announce. A
21
+ // sandbox on its owner's own machine learns the same key from the announce answer (`AnnounceAcceptedSchema`).
22
+ export const ENV_PLATFORM_IDENTITY_KEY = "PLATFORM_IDENTITY_KEY";
23
+
24
+ // How the platform confirmed the address it vouches for: a code it mailed, or a Google sign-in it verified.
25
+ export const IdentityAssertionMethodSchema = z.enum(["email", "google"]);
26
+ export type IdentityAssertionMethod = z.infer<typeof IdentityAssertionMethodSchema>;
27
+
28
+ // The signed claim, times in seconds like the owner ticket's. `nonce` is unique per mint: the daemon spends each once.
29
+ export interface IdentityAssertion {
30
+ readonly sandboxId: string;
31
+ readonly email: string;
32
+ readonly method: IdentityAssertionMethod;
33
+ readonly issuedAt: number;
34
+ readonly expiresAt: number;
35
+ readonly nonce: string;
36
+ }
37
+
38
+ const base64url = (bytes: Buffer): string => bytes.toString("base64url");
39
+
40
+ // The prefix is part of what is signed, so an assertion is never a valid signature over anything else the same key
41
+ // might one day sign, and an owner ticket or a grant (which sign their bare payload) is never an assertion.
42
+ const signedBytes = (payload: Buffer): Buffer => Buffer.concat([Buffer.from(`${ASSERTION_PREFIX}.`, "utf8"), payload]);
43
+
44
+ export const isIdentityAssertion = (bearer: string): boolean => bearer.startsWith(`${ASSERTION_PREFIX}.`);
45
+
46
+ export const mintIdentityAssertion = (
47
+ privateKeyPem: string,
48
+ claim: {
49
+ readonly sandboxId: string;
50
+ readonly email: string;
51
+ readonly method: IdentityAssertionMethod;
52
+ readonly issuedAtMs: number;
53
+ readonly ttlMs?: number;
54
+ },
55
+ ): string => {
56
+ if (!SANDBOX_ID.test(claim.sandboxId)) {
57
+ throw new Error(`an identity assertion names a 12-hex sandbox id, got "${claim.sandboxId}"`);
58
+ }
59
+ if (claim.email.trim() === "") {
60
+ throw new Error("an identity assertion names an address");
61
+ }
62
+ const ttlMs = claim.ttlMs ?? IDENTITY_ASSERTION_TTL_MS;
63
+ if (ttlMs <= 0 || ttlMs > IDENTITY_ASSERTION_MAX_TTL_MS) {
64
+ throw new Error(`an identity assertion lives at most ${IDENTITY_ASSERTION_MAX_TTL_MS} ms`);
65
+ }
66
+ const iat = Math.floor(claim.issuedAtMs / 1000);
67
+ const exp = Math.floor((claim.issuedAtMs + ttlMs) / 1000);
68
+ const payload = Buffer.from(
69
+ JSON.stringify({
70
+ v: 1,
71
+ sub: claim.sandboxId,
72
+ email: claim.email.trim().toLowerCase(),
73
+ amr: claim.method,
74
+ iat,
75
+ exp,
76
+ jti: base64url(randomBytes(16)),
77
+ }),
78
+ "utf8",
79
+ );
80
+ const signature = edSign(null, signedBytes(payload), createPrivateKey(privateKeyPem));
81
+ return `${ASSERTION_PREFIX}.${base64url(payload)}.${base64url(signature)}`;
82
+ };
83
+
84
+ // What the signed payload must be, read only after the signature checked out.
85
+ const AssertionPayloadSchema = z.object({
86
+ v: z.literal(1),
87
+ sub: z.string().regex(SANDBOX_ID),
88
+ email: z.string().min(1),
89
+ amr: IdentityAssertionMethodSchema,
90
+ iat: z.number(),
91
+ exp: z.number(),
92
+ jti: z.string().min(16),
93
+ });
94
+
95
+ const parsePayload = (payload: Buffer): z.infer<typeof AssertionPayloadSchema> | undefined => {
96
+ try {
97
+ return AssertionPayloadSchema.safeParse(JSON.parse(payload.toString("utf8"))).data;
98
+ } catch {
99
+ // allow(silent-catch): a payload that is not JSON is an invalid assertion, which undefined is how this says.
100
+ return undefined;
101
+ }
102
+ };
103
+
104
+ // Verifies an assertion against the platform's public key at nowMs; undefined for any way of being invalid (wrong
105
+ // prefix, bad signature, malformed claim, expired, issued in the future, living longer than the daemon allows). The
106
+ // caller checks sandboxId against its own id and spends the nonce.
107
+ export const verifyIdentityAssertion = (publicKeyPem: string, token: string, nowMs: number): IdentityAssertion | undefined => {
108
+ const [prefix, encodedPayload, encodedSignature, ...rest] = token.split(".");
109
+ if (prefix !== ASSERTION_PREFIX || encodedPayload === undefined || encodedSignature === undefined || rest.length > 0) {
110
+ return undefined;
111
+ }
112
+ const payload = Buffer.from(encodedPayload, "base64url");
113
+ try {
114
+ if (!edVerify(null, signedBytes(payload), createPublicKey(publicKeyPem), Buffer.from(encodedSignature, "base64url"))) {
115
+ return undefined;
116
+ }
117
+ } catch {
118
+ // allow(silent-catch): a key or signature that does not parse verifies nothing: the assertion is invalid, said as undefined.
119
+ return undefined;
120
+ }
121
+ const claim = parsePayload(payload);
122
+ if (claim === undefined) {
123
+ return undefined;
124
+ }
125
+ // A minute of clock skew either way, as the daemon allows Google's tokens; never a claim from further ahead.
126
+ if (claim.exp * 1000 <= nowMs || claim.iat * 1000 > nowMs + 60_000 || (claim.exp - claim.iat) * 1000 > IDENTITY_ASSERTION_MAX_TTL_MS) {
127
+ return undefined;
128
+ }
129
+ return { sandboxId: claim.sub, email: claim.email, method: claim.amr, issuedAt: claim.iat, expiresAt: claim.exp, nonce: claim.jti };
130
+ };
@@ -0,0 +1,247 @@
1
+ import { inertRegions, isLive } from "../text/shell-regions.js";
2
+ import type { CommandSpan } from "./command-classes.js";
3
+
4
+ // A SHELL COMMAND THAT STARTS, STOPS OR REPLACES A SANDBOX CONTAINER (2026-10-12). `ic sandbox restart work` and
5
+ // `docker stop intentic-sandbox-work` end the same turns the device tools manage_sandbox and swap_sandbox end, but they
6
+ // arrive as text in run_command, where no tool name says what they are. This reads that text once, for both ends that
7
+ // have to answer it: the daemon, which asks the owner before an agent cuts the sandbox it runs in while others are
8
+ // working (hosts/host-restart-guard.ts), and the device agent, whose "Manage sandboxes" switch decides whether anything
9
+ // may do this at all (the machine's tools/shell.ts). The same verbs and the same wording, so a command and the tool
10
+ // that does the same job cannot drift apart.
11
+
12
+ /** The sandbox operations a command can name, in the words `ic sandbox` spells them. */
13
+ export type SandboxPowerOp =
14
+ | "start"
15
+ | "stop"
16
+ | "restart"
17
+ | "sleep"
18
+ | "remove"
19
+ | "update"
20
+ | "prepare"
21
+ | "rebuild"
22
+ | "rollback"
23
+ | "dev"
24
+ | "resize"
25
+ | "restore"
26
+ | "connect";
27
+
28
+ /** The owner's own word for an operation, as a card or a refusal says it ("<doing> this sandbox now?"). */
29
+ export const SANDBOX_POWER_DOING = {
30
+ start: "Start",
31
+ stop: "Stop",
32
+ restart: "Restart",
33
+ sleep: "Stop",
34
+ remove: "Remove",
35
+ update: "Update",
36
+ prepare: "Prepare an update for",
37
+ rebuild: "Rebuild",
38
+ rollback: "Roll back",
39
+ dev: "Update",
40
+ resize: "Restart with new resources",
41
+ restore: "Restore a backup over",
42
+ connect: "Reinstall",
43
+ } as const satisfies Record<SandboxPowerOp, string>;
44
+
45
+ /** One command that manages a sandbox container, and what it aims at. */
46
+ export interface SandboxPowerCommand {
47
+ readonly via: "ic" | "docker";
48
+ readonly op: SandboxPowerOp;
49
+ /** Whether it ends the turns of a sandbox that is running; false for a start, a download, a resource ask saved for later. */
50
+ readonly stops: boolean;
51
+ /** The slugs (`ic`) or container names (`docker`) it names, as written; an expansion (`$x`, `$(…)`, `*`) stays as typed. */
52
+ readonly targets: readonly string[];
53
+ /** Names none: `ic sandbox restart` alone (the only sandbox the device runs), `remove --all`, `docker stop` fed from a pipe. */
54
+ readonly unnamed: boolean;
55
+ /** The fragment, so a card can point at it. */
56
+ readonly span: CommandSpan;
57
+ /** Whether a shell would run it, as against a quoted argument of echo or a comment. */
58
+ readonly live: boolean;
59
+ }
60
+
61
+ // `ic sandbox <verb>`: the program may be a full path, `ic.exe`, or quoted (`& 'C:\…\ic.exe' sandbox restart work`).
62
+ const IC_INVOCATION = /(?<![\w.$-])ic(?:\.exe)?["']?\s+sandbox\s+([a-z][a-z-]*)(?![\w-])/gi;
63
+ // `docker stop`, `docker container rm`, podman alike. A global option between the program and the verb
64
+ // (`docker -H host stop x`) is not read: the verb then follows an option, and the command is left unrecognised.
65
+ const DOCKER_INVOCATION = /(?<![\w.$-])(?:docker|podman)(?:\.exe)?["']?\s+(?:container\s+)?(stop|restart|rm|remove|kill)(?![\w-])/gi;
66
+
67
+ const IC_VERBS = new Map<string, SandboxPowerOp>([
68
+ ["start", "start"],
69
+ ["stop", "stop"],
70
+ ["restart", "restart"],
71
+ ["sleep", "sleep"],
72
+ ["remove", "remove"],
73
+ ["rm", "remove"],
74
+ ["update", "update"],
75
+ ["prepare", "prepare"],
76
+ ["rebuild", "rebuild"],
77
+ ["rollback", "rollback"],
78
+ ["dev", "dev"],
79
+ ["reshape", "resize"],
80
+ ["shape", "resize"],
81
+ ["backup-restore", "restore"],
82
+ ["connect", "connect"],
83
+ ]);
84
+
85
+ const DOCKER_VERBS = new Map<string, SandboxPowerOp>([
86
+ ["stop", "stop"],
87
+ ["kill", "stop"],
88
+ ["restart", "restart"],
89
+ ["rm", "remove"],
90
+ ["remove", "remove"],
91
+ ]);
92
+
93
+ // The flags of each program that take their value as the next word, so the value is not read as a slug.
94
+ const IC_VALUE_FLAGS: ReadonlySet<string> = new Set([
95
+ "--channel",
96
+ "--to",
97
+ "--set",
98
+ "--when",
99
+ "--memory",
100
+ "--cpus",
101
+ "--privileged",
102
+ "--gpus",
103
+ "--idle",
104
+ "--source",
105
+ "--code",
106
+ "--snapshot",
107
+ "--into",
108
+ "--confirm",
109
+ "--accept",
110
+ "--tail",
111
+ ]);
112
+ const DOCKER_VALUE_FLAGS: ReadonlySet<string> = new Set(["-t", "--time", "-s", "--signal"]);
113
+
114
+ // The simple command that starts the text: up to the first `|`, `;`, `&` or newline outside quotes. A `&` right after
115
+ // `>` or `<` is a redirection (`2>&1`), which ends nothing.
116
+ const SIMPLE_COMMAND = /^(?:[^|;&\n\r"']|(?<=[<>])&|"[^"]*"?|'[^']*'?)*/;
117
+ // One word, quoted pieces included. Backslashes stay as written: they are Windows path separators as often as escapes,
118
+ // and a slug has neither.
119
+ const WORD = /(?:[^\s"']|"[^"]*"?|'[^']*'?)+/g;
120
+ const REDIRECTION = /^\d*(?:>>?|<)/;
121
+ const BARE_REDIRECTION = /^\d*(?:>>?|<)$/;
122
+
123
+ const wordsOf = (text: string): string[] => [...(SIMPLE_COMMAND.exec(text)?.[0] ?? "").matchAll(WORD)].map((word) => word[0].replace(/["']/g, ""));
124
+
125
+ // A redirection (`2>&1`, `> log`, `>>out`) is not an operand; a bare `>` takes the word after it with it.
126
+ const withoutRedirections = (words: readonly string[]): string[] => {
127
+ const kept: string[] = [];
128
+ let skipNext = false;
129
+ for (const word of words) {
130
+ if (skipNext) {
131
+ skipNext = false;
132
+ } else if (BARE_REDIRECTION.test(word)) {
133
+ skipNext = true;
134
+ } else if (!REDIRECTION.test(word)) {
135
+ kept.push(word);
136
+ }
137
+ }
138
+ return kept;
139
+ };
140
+
141
+ interface Operands {
142
+ readonly positionals: readonly string[];
143
+ /** Flags as written, `--flag=value` split: name → value (undefined for a switch). */
144
+ readonly flags: ReadonlyMap<string, string | undefined>;
145
+ }
146
+
147
+ const operandsOf = (words: readonly string[], valueFlags: ReadonlySet<string>): Operands => {
148
+ const positionals: string[] = [];
149
+ const flags = new Map<string, string | undefined>();
150
+ const list = withoutRedirections(words);
151
+ for (const [index, word] of list.entries()) {
152
+ const equals = word.indexOf("=");
153
+ if (!word.startsWith("-") || word === "-") {
154
+ // The word after a flag that takes a value is that value, not an operand.
155
+ if (!valueFlags.has(list[index - 1] ?? "")) {
156
+ positionals.push(word);
157
+ }
158
+ } else if (word.startsWith("--") && equals !== -1) {
159
+ flags.set(word.slice(0, equals), word.slice(equals + 1));
160
+ } else {
161
+ flags.set(word, valueFlags.has(word) ? list[index + 1] : undefined);
162
+ }
163
+ }
164
+ return { positionals, flags };
165
+ };
166
+
167
+ interface Aim {
168
+ readonly stops: boolean;
169
+ readonly targets: readonly string[];
170
+ readonly all: boolean;
171
+ }
172
+
173
+ // What `ic sandbox <op>` with these operands does to a running sandbox, and which sandboxes it aims at. The verbs that
174
+ // restart nothing say so: a start, a download, a resource ask saved for the next restart, a backup restored to a folder.
175
+ const icAim = (op: SandboxPowerOp, { positionals, flags }: Operands): Aim => {
176
+ const all = flags.has("--all") || flags.has("-a");
177
+ if (op === "start" || op === "prepare") {
178
+ return { stops: false, targets: positionals, all };
179
+ }
180
+ if (op === "resize") {
181
+ // `reshape --later` and `--forget` save an ask and restart nothing; `shape` restarts only with `--when now`.
182
+ const when = flags.get("--when");
183
+ const saves = flags.has("--later") || flags.has("--forget") || (when !== undefined && when !== "now");
184
+ return { stops: !saves, targets: positionals, all };
185
+ }
186
+ if (op === "restore") {
187
+ // A backup restored into a folder touches no sandbox; `--into <slug>` stops that one to write its volumes.
188
+ const into = flags.get("--into");
189
+ return into === undefined ? { stops: false, targets: [], all: false } : { stops: true, targets: [into], all: false };
190
+ }
191
+ if (op === "connect") {
192
+ return { stops: flags.has("--replace"), targets: [], all: false };
193
+ }
194
+ return { stops: true, targets: positionals, all };
195
+ };
196
+
197
+ /**
198
+ * Every command in `command` that starts, stops or replaces a sandbox container, through `ic sandbox` or `docker`.
199
+ * A scan for the programs by name, not a shell parse: it is meant to over-include, since a caller that mistakes
200
+ * a mention for a command asks the owner once, and one that misses a command cuts a fleet's turns unasked.
201
+ */
202
+ export const sandboxPowerCommands = (command: string): SandboxPowerCommand[] => {
203
+ const regions = inertRegions(command);
204
+ const found: SandboxPowerCommand[] = [];
205
+ for (const invocation of command.matchAll(IC_INVOCATION)) {
206
+ const op = IC_VERBS.get((invocation[1] ?? "").toLowerCase());
207
+ if (op !== undefined) {
208
+ const span = { start: invocation.index, end: invocation.index + invocation[0].length };
209
+ const aim = icAim(op, operandsOf(wordsOf(command.slice(span.end)), IC_VALUE_FLAGS));
210
+ found.push({ via: "ic", op, stops: aim.stops, targets: aim.targets, unnamed: aim.targets.length === 0 || aim.all, span, live: isLive(span, regions) });
211
+ }
212
+ }
213
+ for (const invocation of command.matchAll(DOCKER_INVOCATION)) {
214
+ const op = DOCKER_VERBS.get((invocation[1] ?? "").toLowerCase());
215
+ if (op !== undefined) {
216
+ const span = { start: invocation.index, end: invocation.index + invocation[0].length };
217
+ const { positionals } = operandsOf(wordsOf(command.slice(span.end)), DOCKER_VALUE_FLAGS);
218
+ found.push({ via: "docker", op, stops: true, targets: positionals, unnamed: positionals.length === 0, span, live: isLive(span, regions) });
219
+ }
220
+ }
221
+ return found.toSorted((left, right) => left.span.start - right.span.start);
222
+ };
223
+
224
+ // A word whose value exists only when the shell runs it: `$(docker ps -q)`, `$NAME`, `*`, PowerShell's `(…)` and `%X%`.
225
+ const isExpansion = (word: string): boolean => /[$`*?%]|^\(/.test(word);
226
+
227
+ /** The sandbox a caller means to protect: its slug, and the container name docker knows it by. */
228
+ export interface OwnSandbox {
229
+ readonly slug: string;
230
+ readonly container: string;
231
+ }
232
+
233
+ /**
234
+ * Whether `hit` may stop `own`. Deliberately generous: it names it, names none (the device's only sandbox, `--all`, a
235
+ * pipe), or names something that is only worked out when the command runs. A target spelled as the bare slug counts for
236
+ * `docker` too, which knows the container by a longer name.
237
+ */
238
+ export const mayReachSandbox = (hit: SandboxPowerCommand, own: OwnSandbox): boolean =>
239
+ hit.unnamed || hit.targets.some((target) => target === own.slug || target === own.container || isExpansion(target));
240
+
241
+ /**
242
+ * Whether `hit` manages a sandbox that a device's "Manage sandboxes" switch covers, whichever sandbox it is: any
243
+ * `ic sandbox` verb here, and a `docker` verb aimed at a container whose name starts with `containerPrefix` (a sandbox's
244
+ * own, or its tunnel's). A docker target worked out at run time is not read: the device cannot know what it will be.
245
+ */
246
+ export const managesSandbox = (hit: SandboxPowerCommand, containerPrefix: string): boolean =>
247
+ hit.via === "ic" || hit.targets.some((target) => target.startsWith(containerPrefix));
@@ -58,3 +58,138 @@ export const includeGlobs = (include: string | undefined): IncludeGlobs => {
58
58
  notGlobs: segments.filter((segment) => segment.startsWith(`!`)).flatMap((segment) => expand(segment.slice(1))),
59
59
  };
60
60
  };
61
+
62
+ // ONE GLOB MATCHER, with Bun.Glob's reading of every construct, so the daemon, the desktop folder server (which runs
63
+ // Bun.Glob itself) and the demo admit the same files for the same pattern. Before this the daemon's copy took
64
+ // `[!t]` as a set of the characters `!` and `t`, where Bun.Glob reads "anything but t".
65
+ // - `*` is any run within one path segment, `?` one character of it
66
+ // - `**` as a whole segment is any number of segments, none included (`**/x` matches `x`); inside a segment it is `*`
67
+ // - `[abc]`, `[a-z]`, and `[!abc]` or `[^abc]` for "none of these"; a `]` first in the set is literal
68
+ // - `{a,b}` is either, nested groups and globs inside included; `\` makes the next character literal
69
+ // - an unclosed `[` or `{` is literal, where Bun.Glob matches nothing: a half-typed pattern then still finds its text
70
+ const REGEXP_SPECIAL = /[.*+?^${}()|[\]\\/]/;
71
+
72
+ const literal = (char: string): string => (REGEXP_SPECIAL.test(char) ? `\\${char}` : char);
73
+
74
+ // The index of the `]` closing the set opened at `open`, or -1.
75
+ const setEnd = (glob: string, open: number): number => {
76
+ let index = open + 1;
77
+ if (glob[index] === `!` || glob[index] === `^`) {
78
+ index += 1;
79
+ }
80
+ // A `]` straight after the opening (or its negation) is a member, not the close.
81
+ if (glob[index] === `]`) {
82
+ index += 1;
83
+ }
84
+ for (; index < glob.length; index += 1) {
85
+ if (glob[index] === `\\`) {
86
+ index += 1;
87
+ } else if (glob[index] === `]`) {
88
+ return index;
89
+ }
90
+ }
91
+ return -1;
92
+ };
93
+
94
+ const setSource = (body: string): string => {
95
+ const negated = body.startsWith(`!`) || body.startsWith(`^`);
96
+ const members = (negated ? body.slice(1) : body).replaceAll(/\\(.)/g, `$1`).replaceAll(/[\\\]^[]/g, (char) => `\\${char}`);
97
+ return `[${negated ? `^` : ``}${members}]`;
98
+ };
99
+
100
+ // The index of the `}` closing the group opened at `open`, and where its top-level commas fall; undefined if unclosed.
101
+ const groupOf = (glob: string, open: number): { readonly end: number; readonly commas: readonly number[] } | undefined => {
102
+ const commas: number[] = [];
103
+ let depth = 0;
104
+ for (let index = open; index < glob.length; index += 1) {
105
+ const char = glob[index];
106
+ if (char === `\\`) {
107
+ index += 1;
108
+ } else if (char === `[`) {
109
+ const end = setEnd(glob, index);
110
+ index = end === -1 ? index : end;
111
+ } else if (char === `{`) {
112
+ depth += 1;
113
+ } else if (char === `}`) {
114
+ depth -= 1;
115
+ if (depth === 0) {
116
+ return { end: index, commas };
117
+ }
118
+ } else if (char === `,` && depth === 1) {
119
+ commas.push(index);
120
+ }
121
+ }
122
+ return undefined;
123
+ };
124
+
125
+ // `**` filling a whole segment: the source it stands for and how many characters of the glob it consumed.
126
+ const globstar = (glob: string, index: number, segmentStart: boolean): { readonly source: string; readonly length: number } | undefined => {
127
+ if (!segmentStart || glob[index + 1] !== `*`) {
128
+ return undefined;
129
+ }
130
+ const after = glob[index + 2];
131
+ if (after === `/`) {
132
+ return { source: `(?:[^/]*/)*`, length: 3 };
133
+ }
134
+ return after === undefined ? { source: `.*`, length: 2 } : undefined;
135
+ };
136
+
137
+ const compileGlob = (glob: string, segmentStart: boolean): string => {
138
+ let source = ``;
139
+ let atStart = segmentStart;
140
+ for (let index = 0; index < glob.length; index += 1) {
141
+ const char = glob[index]!;
142
+ const star = char === `*` ? globstar(glob, index, atStart) : undefined;
143
+ if (star !== undefined) {
144
+ source += star.source;
145
+ index += star.length - 1;
146
+ atStart = true;
147
+ continue;
148
+ }
149
+ if (char === `*`) {
150
+ // A run of stars inside a segment is one star.
151
+ while (glob[index + 1] === `*`) {
152
+ index += 1;
153
+ }
154
+ source += `[^/]*`;
155
+ } else if (char === `?`) {
156
+ source += `[^/]`;
157
+ } else if (char === `\\` && index + 1 < glob.length) {
158
+ index += 1;
159
+ source += literal(glob[index]!);
160
+ } else if (char === `[` && setEnd(glob, index) !== -1) {
161
+ const end = setEnd(glob, index);
162
+ source += setSource(glob.slice(index + 1, end));
163
+ index = end;
164
+ } else if (char === `{` && groupOf(glob, index) !== undefined) {
165
+ const group = groupOf(glob, index)!;
166
+ const bounds = [index, ...group.commas, group.end];
167
+ const alternatives = bounds.slice(1).map((end, at) => compileGlob(glob.slice(bounds[at]! + 1, end), atStart));
168
+ source += `(?:${alternatives.join(`|`)})`;
169
+ index = group.end;
170
+ } else {
171
+ source += literal(char);
172
+ }
173
+ atStart = char === `/`;
174
+ }
175
+ return source;
176
+ };
177
+
178
+ // The whole path must match: `*.ts` is a name at the root, not a suffix anywhere.
179
+ export const globRegExp = (glob: string): RegExp => new RegExp(`^${compileGlob(glob, true)}$`);
180
+
181
+ // A matcher for slash-separated relative paths, compiled once.
182
+ export const globMatcher = (glob: string): ((path: string) => boolean) => {
183
+ const pattern = globRegExp(glob);
184
+ return (path) => pattern.test(path);
185
+ };
186
+
187
+ // The search box's whole second field as one test over a relative path: admitted by any include (or by none being
188
+ // typed), and by no exclude. An anchored include's `./` is the root, so it is dropped before matching.
189
+ export const includeMatcher = (include: string | undefined): ((path: string) => boolean) => {
190
+ const { globs, notGlobs } = includeGlobs(include);
191
+ const rooted = (glob: string): string => glob.replace(/^\.\//, ``);
192
+ const admits = globs.map((glob) => globMatcher(rooted(glob)));
193
+ const refuses = notGlobs.map((glob) => globMatcher(rooted(glob)));
194
+ return (path) => (admits.length === 0 || admits.some((test) => test(path))) && !refuses.some((test) => test(path));
195
+ };
@@ -42,6 +42,13 @@ export const normalizeHost = (raw: string): string | undefined => {
42
42
  export const hostMatches = (pattern: string, host: string): boolean =>
43
43
  pattern.startsWith(WILDCARD) ? host.endsWith(pattern.slice(1)) : host === pattern;
44
44
 
45
+ // Whether one entry of a domain list covers a host: a plain entry names a domain and every host under it at any depth
46
+ // (`example.com` covers `example.com` and `api.example.com`, never `notexample.com`), a wildcard only the hosts under it.
47
+ // Both sides are expected normalized. The wallet's allow and deny lists read their entries this way; a secret's host
48
+ // guard reads its own exactly (hostMatches).
49
+ export const hostCovers = (entry: string, host: string): boolean =>
50
+ entry.startsWith(WILDCARD) ? hostMatches(entry, host) : host === entry || host.endsWith(`.${entry}`);
51
+
45
52
  export const hostAllowed = (patterns: readonly string[], host: string): boolean => patterns.some((pattern) => hostMatches(pattern, host));
46
53
 
47
54
  // Whether every host `pattern` matches is already matched by `patterns`: an exact host by any pattern that matches it, a