@intentic/sandbox-contract 1.312.0 → 1.314.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 (352) hide show
  1. package/README.md +26 -3
  2. package/dist/chores/index.d.ts +0 -1
  3. package/dist/chores/index.d.ts.map +1 -1
  4. package/dist/chores/index.js +0 -1
  5. package/dist/chores/index.js.map +1 -1
  6. package/dist/contracts/accounts.contract.d.ts +57 -0
  7. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  8. package/dist/contracts/agent.contract.d.ts +436 -0
  9. package/dist/contracts/agent.contract.d.ts.map +1 -1
  10. package/dist/contracts/agent.contract.js +11 -1
  11. package/dist/contracts/agent.contract.js.map +1 -1
  12. package/dist/contracts/agents.contract.d.ts +123 -96
  13. package/dist/contracts/agents.contract.d.ts.map +1 -1
  14. package/dist/contracts/capabilities.contract.js +1 -1
  15. package/dist/contracts/capabilities.contract.js.map +1 -1
  16. package/dist/contracts/device.contract.d.ts +20 -4
  17. package/dist/contracts/device.contract.d.ts.map +1 -1
  18. package/dist/contracts/device.contract.js +1 -1
  19. package/dist/contracts/device.contract.js.map +1 -1
  20. package/dist/contracts/extensions.contract.d.ts +11 -0
  21. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  22. package/dist/contracts/git.contract.d.ts +2 -0
  23. package/dist/contracts/git.contract.d.ts.map +1 -1
  24. package/dist/contracts/personas.contract.d.ts +2 -0
  25. package/dist/contracts/personas.contract.d.ts.map +1 -1
  26. package/dist/contracts/runner.contract.d.ts +138 -0
  27. package/dist/contracts/runner.contract.d.ts.map +1 -1
  28. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  29. package/dist/contracts/secrets.contract.js +1 -0
  30. package/dist/contracts/secrets.contract.js.map +1 -1
  31. package/dist/contracts/sessions.contract.d.ts +59 -0
  32. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  33. package/dist/contracts/settings.contract.d.ts +12 -8
  34. package/dist/contracts/settings.contract.d.ts.map +1 -1
  35. package/dist/contracts/settings.contract.js +2 -2
  36. package/dist/contracts/settings.contract.js.map +1 -1
  37. package/dist/contracts/system.contract.d.ts +141 -19
  38. package/dist/contracts/system.contract.d.ts.map +1 -1
  39. package/dist/contracts/translator.contract.d.ts +72 -0
  40. package/dist/contracts/translator.contract.d.ts.map +1 -1
  41. package/dist/contracts/workspace.contract.d.ts +85 -14
  42. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  43. package/dist/documents/index.d.ts +1 -0
  44. package/dist/documents/index.d.ts.map +1 -1
  45. package/dist/documents/index.js +1 -0
  46. package/dist/documents/index.js.map +1 -1
  47. package/dist/documents/read.d.ts +24 -0
  48. package/dist/documents/read.d.ts.map +1 -0
  49. package/dist/documents/read.js +81 -0
  50. package/dist/documents/read.js.map +1 -0
  51. package/dist/events/agent-events.d.ts +235 -0
  52. package/dist/events/agent-events.d.ts.map +1 -1
  53. package/dist/events/agent-events.js +3 -0
  54. package/dist/events/agent-events.js.map +1 -1
  55. package/dist/events/child-run.d.ts +9 -0
  56. package/dist/events/child-run.d.ts.map +1 -0
  57. package/dist/events/child-run.js +41 -0
  58. package/dist/events/child-run.js.map +1 -0
  59. package/dist/events/errands.d.ts +5 -0
  60. package/dist/events/errands.d.ts.map +1 -0
  61. package/dist/events/errands.js +19 -0
  62. package/dist/events/errands.js.map +1 -0
  63. package/dist/events/requests.d.ts +152 -0
  64. package/dist/events/requests.d.ts.map +1 -1
  65. package/dist/events/requests.js +26 -1
  66. package/dist/events/requests.js.map +1 -1
  67. package/dist/events/system-events.d.ts +8 -12
  68. package/dist/events/system-events.d.ts.map +1 -1
  69. package/dist/events/transcript.d.ts +387 -0
  70. package/dist/events/transcript.d.ts.map +1 -1
  71. package/dist/events/transcript.js +3 -0
  72. package/dist/events/transcript.js.map +1 -1
  73. package/dist/front/browser-wire.d.ts +6 -0
  74. package/dist/front/browser-wire.d.ts.map +1 -0
  75. package/dist/front/browser-wire.js +4 -0
  76. package/dist/front/browser-wire.js.map +1 -0
  77. package/dist/front/front-wire.d.ts +6 -0
  78. package/dist/front/front-wire.d.ts.map +1 -0
  79. package/dist/front/front-wire.js +5 -0
  80. package/dist/front/front-wire.js.map +1 -0
  81. package/dist/front/generated/browser-wire.d.ts +19 -0
  82. package/dist/front/generated/browser-wire.d.ts.map +1 -0
  83. package/dist/front/generated/browser-wire.js +2 -0
  84. package/dist/front/generated/browser-wire.js.map +1 -0
  85. package/dist/front/generated/wire.d.ts +36 -30
  86. package/dist/front/generated/wire.d.ts.map +1 -1
  87. package/dist/ids/conversation-ids.d.ts +1 -0
  88. package/dist/ids/conversation-ids.d.ts.map +1 -1
  89. package/dist/ids/conversation-ids.js +2 -2
  90. package/dist/ids/conversation-ids.js.map +1 -1
  91. package/dist/ids/session-names.d.ts +3 -0
  92. package/dist/ids/session-names.d.ts.map +1 -1
  93. package/dist/ids/session-names.js +3 -0
  94. package/dist/ids/session-names.js.map +1 -1
  95. package/dist/index.d.ts +1038 -168
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +8 -0
  98. package/dist/index.js.map +1 -1
  99. package/dist/models/agent-catalog.d.ts.map +1 -1
  100. package/dist/models/agent-catalog.js +4 -6
  101. package/dist/models/agent-catalog.js.map +1 -1
  102. package/dist/models/agent-runtimes.d.ts +2 -1
  103. package/dist/models/agent-runtimes.d.ts.map +1 -1
  104. package/dist/models/agent-runtimes.js +7 -1
  105. package/dist/models/agent-runtimes.js.map +1 -1
  106. package/dist/models/plan-pools.d.ts +22 -1
  107. package/dist/models/plan-pools.d.ts.map +1 -1
  108. package/dist/models/plan-pools.js +86 -0
  109. package/dist/models/plan-pools.js.map +1 -1
  110. package/dist/policy/request-status.d.ts +1 -1
  111. package/dist/policy/request-status.d.ts.map +1 -1
  112. package/dist/policy/request-status.js +4 -11
  113. package/dist/policy/request-status.js.map +1 -1
  114. package/dist/policy/reserved-servers.d.ts.map +1 -1
  115. package/dist/policy/reserved-servers.js +2 -1
  116. package/dist/policy/reserved-servers.js.map +1 -1
  117. package/dist/policy/turned-away.d.ts.map +1 -1
  118. package/dist/policy/turned-away.js +1 -0
  119. package/dist/policy/turned-away.js.map +1 -1
  120. package/dist/protocol/edge-verdict.d.ts +2 -1
  121. package/dist/protocol/edge-verdict.d.ts.map +1 -1
  122. package/dist/protocol/edge-verdict.js +1 -1
  123. package/dist/protocol/edge-verdict.js.map +1 -1
  124. package/dist/protocol/ingress-contract.d.ts +1 -1
  125. package/dist/protocol/ingress-contract.d.ts.map +1 -1
  126. package/dist/protocol/ingress-contract.js +1 -1
  127. package/dist/protocol/ingress-contract.js.map +1 -1
  128. package/dist/protocol/raw-routes.d.ts +18 -40
  129. package/dist/protocol/raw-routes.d.ts.map +1 -1
  130. package/dist/protocol/raw-routes.js +5 -16
  131. package/dist/protocol/raw-routes.js.map +1 -1
  132. package/dist/protocol/route-meta.d.ts +20 -0
  133. package/dist/protocol/route-meta.d.ts.map +1 -1
  134. package/dist/protocol/route-meta.js +16 -0
  135. package/dist/protocol/route-meta.js.map +1 -1
  136. package/dist/protocol/tunnel-bulk.d.ts +2 -0
  137. package/dist/protocol/tunnel-bulk.d.ts.map +1 -0
  138. package/dist/protocol/tunnel-bulk.js +3 -0
  139. package/dist/protocol/tunnel-bulk.js.map +1 -0
  140. package/dist/protocol/x402.d.ts +17 -0
  141. package/dist/protocol/x402.d.ts.map +1 -0
  142. package/dist/protocol/x402.js +37 -0
  143. package/dist/protocol/x402.js.map +1 -0
  144. package/dist/schemas/agent.d.ts +25 -0
  145. package/dist/schemas/agent.d.ts.map +1 -1
  146. package/dist/schemas/agent.js +14 -2
  147. package/dist/schemas/agent.js.map +1 -1
  148. package/dist/schemas/agents.d.ts +17 -18
  149. package/dist/schemas/agents.d.ts.map +1 -1
  150. package/dist/schemas/agents.js +17 -4
  151. package/dist/schemas/agents.js.map +1 -1
  152. package/dist/schemas/automations.d.ts +4 -6
  153. package/dist/schemas/automations.d.ts.map +1 -1
  154. package/dist/schemas/automations.js.map +1 -1
  155. package/dist/schemas/devices.d.ts +134 -7
  156. package/dist/schemas/devices.d.ts.map +1 -1
  157. package/dist/schemas/devices.js +43 -18
  158. package/dist/schemas/devices.js.map +1 -1
  159. package/dist/schemas/extension-updates.d.ts +22 -0
  160. package/dist/schemas/extension-updates.d.ts.map +1 -1
  161. package/dist/schemas/extension-updates.js +4 -0
  162. package/dist/schemas/extension-updates.js.map +1 -1
  163. package/dist/schemas/git/git.d.ts +3 -0
  164. package/dist/schemas/git/git.d.ts.map +1 -1
  165. package/dist/schemas/hosts.d.ts +35 -5
  166. package/dist/schemas/hosts.d.ts.map +1 -1
  167. package/dist/schemas/hosts.js +21 -7
  168. package/dist/schemas/hosts.js.map +1 -1
  169. package/dist/schemas/keep-warm.d.ts +2 -12
  170. package/dist/schemas/keep-warm.d.ts.map +1 -1
  171. package/dist/schemas/keep-warm.js +8 -10
  172. package/dist/schemas/keep-warm.js.map +1 -1
  173. package/dist/schemas/metrics.d.ts +24 -0
  174. package/dist/schemas/metrics.d.ts.map +1 -1
  175. package/dist/schemas/metrics.js +22 -3
  176. package/dist/schemas/metrics.js.map +1 -1
  177. package/dist/schemas/personas.d.ts +3 -0
  178. package/dist/schemas/personas.d.ts.map +1 -1
  179. package/dist/schemas/personas.js +9 -0
  180. package/dist/schemas/personas.js.map +1 -1
  181. package/dist/schemas/providers/plan-limits.d.ts +140 -0
  182. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  183. package/dist/schemas/providers/plan-limits.js +47 -1
  184. package/dist/schemas/providers/plan-limits.js.map +1 -1
  185. package/dist/schemas/providers/provider-oauth.d.ts +57 -0
  186. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  187. package/dist/schemas/providers/provider-oauth.js +6 -1
  188. package/dist/schemas/providers/provider-oauth.js.map +1 -1
  189. package/dist/schemas/settings-history.d.ts +28 -1
  190. package/dist/schemas/settings-history.d.ts.map +1 -1
  191. package/dist/schemas/settings-history.js +35 -1
  192. package/dist/schemas/settings-history.js.map +1 -1
  193. package/dist/schemas/settings.d.ts +184 -4
  194. package/dist/schemas/settings.d.ts.map +1 -1
  195. package/dist/schemas/settings.js +31 -23
  196. package/dist/schemas/settings.js.map +1 -1
  197. package/dist/schemas/speaker.d.ts +36 -0
  198. package/dist/schemas/speaker.d.ts.map +1 -0
  199. package/dist/schemas/speaker.js +62 -0
  200. package/dist/schemas/speaker.js.map +1 -0
  201. package/dist/schemas/state-plan.d.ts +46 -0
  202. package/dist/schemas/state-plan.d.ts.map +1 -0
  203. package/dist/schemas/state-plan.js +31 -0
  204. package/dist/schemas/state-plan.js.map +1 -0
  205. package/dist/schemas/system.d.ts +48 -8
  206. package/dist/schemas/system.d.ts.map +1 -1
  207. package/dist/schemas/system.js +10 -14
  208. package/dist/schemas/system.js.map +1 -1
  209. package/dist/schemas/workspace/mainline.d.ts +206 -64
  210. package/dist/schemas/workspace/mainline.d.ts.map +1 -1
  211. package/dist/schemas/workspace/mainline.js +73 -25
  212. package/dist/schemas/workspace/mainline.js.map +1 -1
  213. package/dist/state/contract-lock.d.ts +1 -0
  214. package/dist/state/contract-lock.d.ts.map +1 -1
  215. package/dist/state/contract-lock.js +47 -1
  216. package/dist/state/contract-lock.js.map +1 -1
  217. package/dist/state/definition.d.ts +24 -16
  218. package/dist/state/definition.d.ts.map +1 -1
  219. package/dist/state/fix-stance.d.ts +1 -1
  220. package/dist/state/fix-stance.d.ts.map +1 -1
  221. package/dist/state/fix-stance.js +2 -1
  222. package/dist/state/fix-stance.js.map +1 -1
  223. package/dist/state/history-state.d.ts.map +1 -1
  224. package/dist/state/history-state.js +2 -0
  225. package/dist/state/history-state.js.map +1 -1
  226. package/dist/state/landed-commit.d.ts +1 -0
  227. package/dist/state/landed-commit.d.ts.map +1 -1
  228. package/dist/state/landed-commit.js +2 -0
  229. package/dist/state/landed-commit.js.map +1 -1
  230. package/dist/state/red-streak.d.ts +7 -0
  231. package/dist/state/red-streak.d.ts.map +1 -0
  232. package/dist/state/red-streak.js +11 -0
  233. package/dist/state/red-streak.js.map +1 -0
  234. package/dist/state/workspace-state.d.ts +1 -0
  235. package/dist/state/workspace-state.d.ts.map +1 -1
  236. package/dist/state/workspace-state.js +6 -2
  237. package/dist/state/workspace-state.js.map +1 -1
  238. package/dist/text/shell-regions.d.ts +1 -0
  239. package/dist/text/shell-regions.d.ts.map +1 -1
  240. package/dist/text/shell-regions.js +1 -1
  241. package/dist/text/shell-regions.js.map +1 -1
  242. package/dist/text/transcript-fold.d.ts.map +1 -1
  243. package/dist/text/transcript-fold.js +14 -7
  244. package/dist/text/transcript-fold.js.map +1 -1
  245. package/package.json +28 -29
  246. package/src/chores/index.ts +0 -1
  247. package/src/contracts/agent.contract.ts +14 -0
  248. package/src/contracts/capabilities.contract.ts +2 -2
  249. package/src/contracts/device.contract.ts +5 -3
  250. package/src/contracts/secrets.contract.ts +2 -0
  251. package/src/contracts/settings.contract.ts +2 -1
  252. package/src/documents/conversions.ts +3 -3
  253. package/src/documents/index.ts +3 -1
  254. package/src/documents/read.ts +114 -0
  255. package/src/events/agent-events.ts +5 -0
  256. package/src/events/child-run.test.ts +81 -0
  257. package/src/events/child-run.ts +73 -0
  258. package/src/events/errands.test.ts +22 -0
  259. package/src/events/errands.ts +27 -0
  260. package/src/events/requests.ts +40 -1
  261. package/src/events/transcript.ts +8 -0
  262. package/src/front/browser-wire.ts +20 -0
  263. package/src/front/front-wire.ts +15 -0
  264. package/src/front/generated/browser-wire.json +138 -0
  265. package/src/front/generated/browser-wire.ts +16 -0
  266. package/src/front/generated/front-wire.json +793 -0
  267. package/src/front/generated/tunnel.json +38 -0
  268. package/src/front/generated/wire.ts +21 -24
  269. package/src/front/wire-manifests.test.ts +61 -0
  270. package/src/ids/conversation-ids.ts +6 -5
  271. package/src/ids/session-names.ts +7 -0
  272. package/src/index.ts +8 -1
  273. package/src/models/agent-catalog.test.ts +6 -4
  274. package/src/models/agent-catalog.ts +4 -6
  275. package/src/models/agent-runtimes.ts +22 -7
  276. package/src/models/capability-ledger.test.ts +2 -0
  277. package/src/models/plan-pools.test.ts +121 -2
  278. package/src/models/plan-pools.ts +152 -2
  279. package/src/policy/request-status.ts +4 -12
  280. package/src/policy/reserved-servers.test.ts +6 -0
  281. package/src/policy/reserved-servers.ts +13 -10
  282. package/src/policy/turned-away.ts +3 -0
  283. package/src/protocol/edge-verdict.ts +13 -11
  284. package/src/protocol/ingress-contract.fixture.json +13 -0
  285. package/src/protocol/ingress-contract.ts +9 -5
  286. package/src/protocol/raw-routes.ts +10 -21
  287. package/src/protocol/route-meta.ts +36 -1
  288. package/src/protocol/tunnel-bulk.test.ts +37 -0
  289. package/src/protocol/tunnel-bulk.ts +7 -0
  290. package/src/protocol/x402.test.ts +29 -0
  291. package/src/protocol/x402.ts +61 -0
  292. package/src/schemas/agent.ts +28 -3
  293. package/src/schemas/agents.ts +33 -7
  294. package/src/schemas/automations.ts +4 -2
  295. package/src/schemas/devices.ts +107 -39
  296. package/src/schemas/extension-updates.ts +6 -0
  297. package/src/schemas/hosts.ts +68 -19
  298. package/src/schemas/ic-shape-args.test.ts +64 -0
  299. package/src/schemas/keep-warm.test.ts +6 -13
  300. package/src/schemas/keep-warm.ts +15 -20
  301. package/src/schemas/metrics.ts +32 -3
  302. package/src/schemas/personas.ts +15 -0
  303. package/src/schemas/providers/plan-limits.ts +71 -1
  304. package/src/schemas/providers/provider-oauth.ts +10 -1
  305. package/src/schemas/settings-history.ts +50 -3
  306. package/src/schemas/settings.test.ts +54 -1
  307. package/src/schemas/settings.ts +49 -31
  308. package/src/schemas/speaker.ts +89 -0
  309. package/src/schemas/state-plan.test.ts +69 -0
  310. package/src/schemas/state-plan.ts +47 -0
  311. package/src/schemas/system.ts +20 -16
  312. package/src/schemas/workspace/mainline.test.ts +28 -0
  313. package/src/schemas/workspace/mainline.ts +111 -44
  314. package/src/state/contract-lock.test.ts +65 -4
  315. package/src/state/contract-lock.ts +70 -2
  316. package/src/state/fix-stance.test.ts +8 -0
  317. package/src/state/fix-stance.ts +3 -3
  318. package/src/state/history-state.ts +5 -1
  319. package/src/state/landed-commit.test.ts +12 -1
  320. package/src/state/landed-commit.ts +5 -0
  321. package/src/state/red-streak.test.ts +28 -0
  322. package/src/state/red-streak.ts +28 -0
  323. package/src/state/workspace-state.test.ts +7 -1
  324. package/src/state/workspace-state.ts +10 -2
  325. package/src/text/shell-regions.test.ts +38 -0
  326. package/src/text/shell-regions.ts +3 -3
  327. package/src/text/transcript-fold.test.ts +13 -0
  328. package/src/text/transcript-fold.ts +17 -8
  329. package/dist/chores/fix-deps.d.ts +0 -10
  330. package/dist/chores/fix-deps.d.ts.map +0 -1
  331. package/dist/chores/fix-deps.js +0 -19
  332. package/dist/chores/fix-deps.js.map +0 -1
  333. package/dist/front/terminal-frames.d.ts +0 -17
  334. package/dist/front/terminal-frames.d.ts.map +0 -1
  335. package/dist/front/terminal-frames.js +0 -79
  336. package/dist/front/terminal-frames.js.map +0 -1
  337. package/dist/protocol/tunnel-lanes.d.ts +0 -10
  338. package/dist/protocol/tunnel-lanes.d.ts.map +0 -1
  339. package/dist/protocol/tunnel-lanes.js +0 -16
  340. package/dist/protocol/tunnel-lanes.js.map +0 -1
  341. package/dist/protocol/upgrade-relay.d.ts +0 -4
  342. package/dist/protocol/upgrade-relay.d.ts.map +0 -1
  343. package/dist/protocol/upgrade-relay.js +0 -25
  344. package/dist/protocol/upgrade-relay.js.map +0 -1
  345. package/src/chores/fix-deps.ts +0 -23
  346. package/src/front/terminal-frames.fixture.json +0 -55
  347. package/src/front/terminal-frames.test.ts +0 -63
  348. package/src/front/terminal-frames.ts +0 -102
  349. package/src/protocol/tunnel-lanes.json +0 -136
  350. package/src/protocol/tunnel-lanes.test.ts +0 -30
  351. package/src/protocol/tunnel-lanes.ts +0 -32
  352. package/src/protocol/upgrade-relay.ts +0 -28
@@ -1,3 +1,4 @@
1
+ import { TurnErrandSchema } from "./speaker.js";
1
2
  import { z } from "zod";
2
3
  import { CONVERSATION_ID } from "../ids/conversation-ids.js";
3
4
  import { ModelRoleSchema } from "../models/model-roles.js";
@@ -129,6 +130,9 @@ export type ForkedFrom = z.infer<typeof ForkedFromSchema>;
129
130
  // The turn's fields before the cross-field refinements below, so a subset (TurnProfileSchema) can be picked from them.
130
131
  const AgentTurnFieldsSchema = z.object({
131
132
  prompt: z.string().describe("What to say to the agent. May be empty if you are only attaching files."),
133
+ errand: TurnErrandSchema.optional().describe(
134
+ "What the words are for, when the app or the sandbox composed them rather than a person typing them: a land conflict to resolve, a red main line to fix. Shown as the sandbox's words, not yours. Leave it out for your own words.",
135
+ ),
132
136
  // Minted by the sender so a send whose answer was lost can be sent again without being delivered twice.
133
137
  messageId: z
134
138
  .string()
@@ -164,8 +168,14 @@ const AgentTurnFieldsSchema = z.object({
164
168
  // Which agentic loop runs the turn, absent = provider's own; "claude-code" forces the SDK loop for any
165
169
  // provider.
166
170
  harness: AgentHarnessSchema.optional().describe("Which agentic loop runs the turn. Leave it out to use each provider's own."),
167
- // Which connected account of that provider serves the turn; absent = the provider's first account.
168
- account: z.string().optional().describe("Which of that provider's connected accounts pays for the turn. Leave it out for the first one."),
171
+ // Intent, not a guess: absent continues on the conversation's own account, or picks by serviceability where there is
172
+ // none on this provider yet (agent/providers/accounts/routing.ts).
173
+ account: z
174
+ .string()
175
+ .optional()
176
+ .describe(
177
+ "Which of that provider's connected accounts pays for the turn. Leave it out to continue on the account the conversation runs on, or, for its first turn on this provider, to take whichever account can serve with the most room. To move a running conversation, use `switchAccount`.",
178
+ ),
169
179
  // Distinct from `account` (who pays): whose name the turn posts as; absent means something different for each
170
180
  // mode.
171
181
  actsAs: entryId.optional().describe("Which persona the turn speaks as out in the world. Not the same as which account pays for it."),
@@ -317,6 +327,19 @@ export const AgentTurnSchema = AgentTurnFieldsSchema
317
327
  message: 'forkOf.files "then" requires isolated',
318
328
  });
319
329
  export type AgentTurn = z.infer<typeof AgentTurnSchema>;
330
+ // What a turn naming no provider or loop runs on, stated once. Applied where a request comes in (the daemon's turn port),
331
+ // so nothing downstream defaults them again: the engine takes a turn with both named (withRuntimeDefaults).
332
+ /** A turn with its provider and loop named, as the engine takes it past the door. */
333
+ export type RoutedAgentTurn = AgentTurn & { readonly agent: AgentProvider; readonly harness: AgentHarness };
334
+ export const DEFAULT_PROVIDER: AgentProvider = "claude";
335
+ export const DEFAULT_HARNESS: AgentHarness = "native";
336
+ export const withRuntimeDefaults = <T extends { readonly agent?: AgentProvider | undefined; readonly harness?: AgentHarness | undefined }>(
337
+ turn: T,
338
+ ): T & { readonly agent: AgentProvider; readonly harness: AgentHarness } => ({
339
+ ...turn,
340
+ agent: turn.agent ?? DEFAULT_PROVIDER,
341
+ harness: turn.harness ?? DEFAULT_HARNESS,
342
+ });
320
343
  // Which turn this is, as opposed to what it says: who serves it and how (agent, harness, account, model and its knobs),
321
344
  // as whom (actsAs), where (isolated), whether anyone watches (unattended) and for which job (runRole). A turn that
322
345
  // continues another (a resume, a wake, a nudge, a child's follow-up) carries this whole, never a hand-picked part.
@@ -357,7 +380,9 @@ export const AgentRunPickSchema = z
357
380
  account: z
358
381
  .string()
359
382
  .optional()
360
- .describe("Which connected account of that provider pays, by its daemon-minted id. Leave it out for whichever has headroom."),
383
+ .describe(
384
+ "Which connected account of that provider pays, by its daemon-minted id. Leave it out to take whichever account can serve with the most room.",
385
+ ),
361
386
  harness: AgentHarnessSchema.optional().describe("Which agentic loop runs it. Leave it out to use the provider's own."),
362
387
  effort: z
363
388
  .string()
@@ -56,7 +56,7 @@ export const AgentAttentionSchema = z.object({
56
56
  plan: z.boolean().describe("It has proposed a plan and is waiting for a yes."),
57
57
  question: z.boolean().describe("It has asked you something."),
58
58
  permission: z.boolean().describe("It wants to use a tool it needs permission for."),
59
- // A missing capability (capabilities/capability-offer.ts); lets the lane say "setup needed" rather than a generic
59
+ // A missing capability (capabilities/offers/capability-offer.ts); lets the lane say "setup needed" rather than a generic
60
60
  // pause.
61
61
  capability: z.boolean().describe("It needs something connected that is not connected yet."),
62
62
  // A gated credential parked on a named person's click; the one pause the board's reader may not be able to clear
@@ -136,6 +136,12 @@ export const LandedMessageSchema = z.object({
136
136
  .string()
137
137
  .optional()
138
138
  .describe("Why a test this change weakened is meant to be weaker, in the conversation's own words. Nearly always absent."),
139
+ // Present only when the conversation declared an exception a check should grant this change (`Allow: <check> — <reason>`);
140
+ // the check after the land and the push read it off the commit.
141
+ allows: z
142
+ .array(z.string())
143
+ .optional()
144
+ .describe("Exceptions the conversation declared for this change, each `<check> — <reason>` in its own words. Nearly always absent."),
139
145
  });
140
146
  export type LandedMessage = z.infer<typeof LandedMessageSchema>;
141
147
  // One model's turn in the drafting walk (agent/role-model.ts tries connected models in order), in spend order:
@@ -258,6 +264,12 @@ export const AgentSummarySchema = z.object({
258
264
  // Both latched at the first turn, so a board scoped to one project can say which conversations are its own.
259
265
  startIn: z.string().optional().describe("Which folder it opened in, relative to the workspace root. Absent means the root."),
260
266
  actsAs: z.string().optional().describe("Which persona its first turn acted as. Absent for an ordinary chat."),
267
+ lastActsAs: z
268
+ .string()
269
+ .optional()
270
+ .describe(
271
+ "Which persona its last turn acted as: each turn runs as the persona it names, so this is who the conversation speaks as now, and what a view groups it under. Absent for an ordinary chat.",
272
+ ),
261
273
  // Per-conversation, so opening it restores its own choices rather than another tab's; `fast` here is what was asked
262
274
  // for, not what was served.
263
275
  model: z
@@ -356,10 +368,12 @@ export const AgentSummarySchema = z.object({
356
368
  .describe(
357
369
  "When the date written into the agent's prompt next changes, in milliseconds (midnight where the agent runs). Past it the next turn sends a different prompt, so nothing kept before it is read again.",
358
370
  ),
359
- keepable: z
360
- .boolean()
371
+ keepableUntil: z
372
+ .number()
361
373
  .optional()
362
- .describe("Whether this sandbox can keep this cache warm: it holds the last turn's exact request to replay, on a Claude subscription account."),
374
+ .describe(
375
+ "Present when this sandbox can keep this cache warm (its provider can replay the last turn's prefix): the furthest instant a hold can reach, in milliseconds, where refreshing would cost more than the cold resume it saves or the date in the prompt changes, whichever comes first.",
376
+ ),
363
377
  })
364
378
  .optional()
365
379
  .describe(
@@ -405,7 +419,7 @@ export const AgentSummarySchema = z.object({
405
419
  "When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.",
406
420
  ),
407
421
  attention: AgentAttentionSchema.describe("Which kinds of waiting-for-you it is doing."),
408
- // Only the causes that still hold, re-read live (agents/land/standing.ts), never the stored report's own list: a
422
+ // Only the causes that still hold, re-read live (conversations/land/standing.ts), never the stored report's own list: a
409
423
  // blocker the user has since cleared is not something to offer them an action about.
410
424
  conflictCauses: z
411
425
  .array(LandConflictReasonSchema)
@@ -482,6 +496,14 @@ export const AgentSummarySchema = z.object({
482
496
  .describe(
483
497
  "The workflow run this conversation is a step of. Without it, a four-step run reads as four unrelated conversations that happen to have started together.",
484
498
  ),
499
+ // The one reading of "runs again by itself" (the daemon's awaitingWake, conversation-state.ts): status, fixStance and
500
+ // the card read it, never the watch list, which is only what the card lists. Absent from a daemon older than it.
501
+ awaitingWake: z
502
+ .boolean()
503
+ .optional()
504
+ .describe(
505
+ "Whether this conversation runs again by itself with nobody pressing anything: a watch is armed on it, or words the sandbox or another agent sent wait for it. A finished-looking card that is awaiting a wake is not finished yet.",
506
+ ),
485
507
  // Outside conditions a conversation is parked on; a finished-looking card can restart itself when one fires, and an
486
508
  // armed watch keeps a hosted machine (and its bill) awake. The check command itself never rides the wire, since it
487
509
  // may hold a secret.
@@ -508,7 +530,7 @@ export const AgentSummarySchema = z.object({
508
530
  startedAt: z.number().describe("When it started, in milliseconds."),
509
531
  endedAt: z.number().optional().describe("When the command exited, in milliseconds. Absent while it runs."),
510
532
  exitCode: z.number().int().optional().describe("The code it exited with. Absent while it runs, or when its exit left none."),
511
- // What the turn that left it running decided about it (agent/tools/job-fates.ts): one of these, or none
533
+ // What the turn that left it running decided about it (agent/tools/jobs/job-fates.ts): one of these, or none
512
534
  // while its turn is still going.
513
535
  watch: z
514
536
  .string()
@@ -520,7 +542,7 @@ export const AgentSummarySchema = z.object({
520
542
  .boolean()
521
543
  .optional()
522
544
  .describe(
523
- "Left running for the person: the turn's reply gave its address, so it outlives the turn, wakes nothing and is theirs to stop.",
545
+ "Left running for the person: the agent kept it for them with the `keep` tool (or it holds a port this conversation already left them), so it outlives the turn, wakes nothing and is theirs to stop.",
524
546
  ),
525
547
  ports: z.array(z.number().int()).optional().describe("The ports it was listening on when its turn ended."),
526
548
  stoppedBy: z
@@ -545,6 +567,10 @@ export const AgentSummarySchema = z.object({
545
567
  ),
546
568
  });
547
569
  export type AgentSummary = z.infer<typeof AgentSummarySchema>;
570
+ // Whether a conversation runs again by itself: the daemon's own reading, or, from a daemon older than it, whether a watch
571
+ // is armed, which was all that one could say.
572
+ export const awaitsWake = (agent: { readonly awaitingWake?: boolean | undefined; readonly watches?: readonly unknown[] | undefined }): boolean =>
573
+ agent.awaitingWake ?? (agent.watches?.length ?? 0) > 0;
548
574
  // One armed watch as a card carries it; derived from AgentSummarySchema.watches so the two shapes can't drift apart.
549
575
  export type AgentWatch = NonNullable<AgentSummary["watches"]>[number];
550
576
  // One background job as a card carries it.
@@ -297,8 +297,10 @@ export const AutomationSchema = z.object({
297
297
  .describe(
298
298
  "Which models this automation may run on, best first. Required, and nothing is chosen for you: work that fires while nobody is watching spends a real allowance, so it names the models it spends rather than inheriting one. Tried in order, so a spent account does not silently stop the job.",
299
299
  ),
300
- // Absent means the provider's first account; pinning matters more here than for a chat, since nobody is watching to
301
- // notice a stuck one.
300
+ // Absent means the daemon's pick: the account its conversation runs on for a continuing thread (moved off one that
301
+ // can no longer serve), else the one that can serve with the most room. Named, it is a pin, run whatever its state,
302
+ // so a refused account says so rather than being swapped; pinning matters more here than for a chat, since nobody
303
+ // is watching to notice a stuck one.
302
304
  account: z.string().optional().describe("Which account pays for it."),
303
305
  // Absent means the wake reaches no logged-in account at all, the strictest default here, since nobody is at the
304
306
  // composer when it fires.
@@ -1,6 +1,6 @@
1
1
  // What one of the user's own machines is running.
2
2
  import { z } from "zod";
3
- import { hostEntryOf, hostEnvironmentOf, type DeviceFacts, DeviceFactsSchema, userDistrosOf, WslEnvironmentSchema } from "./hosts.js";
3
+ import { parseHostConnection, type DeviceFacts, DeviceFactsSchema, MachineIdSchema, userDistrosOf, WslEnvironmentSchema } from "./hosts.js";
4
4
  import { DEV_VERSION } from "../state/versions.js";
5
5
  // Desktop-sync report shape shared by the agent, daemon and browser, produced only by the agent's own `deviceReport`.
6
6
  // The agent never reports `sandboxes`; the docker half is filled in by whoever reads the report, scoped to the reader's
@@ -9,14 +9,33 @@ import { DEV_VERSION } from "../state/versions.js";
9
9
  // One sandbox's resource share as docker currently enforces it, read off the container by the machine agent.
10
10
  // `overlayRuntime` is the environment's locked demand; `hostRuntime` is the owner's addition; `privileged`/`gpu` are
11
11
  // docker's enforced truth.
12
- // Reshape request, turned into `ic sandbox reshape` flags: absent means leave it, `null` on the two caps means back to
13
- // the default; at least one key must be set.
12
+ // A delta against what runs: absent means leave it, `null` on the two caps means back to the default; at least one key
13
+ // must be set. What the old `reshape` op carries, which the machine agent carries out as `ic sandbox shape --set` (its
14
+ // `olderResizePlan`); `set-shape` carries a whole shape instead.
14
15
  export const SandboxResourcesAskFieldsSchema = z.object({
15
16
  memoryGib: z.int().positive().nullable().optional(),
16
17
  cpus: z.int().positive().nullable().optional(),
17
18
  privileged: z.boolean().optional(),
18
19
  gpu: z.boolean().optional(),
19
20
  });
21
+ // A sandbox's shape, WHOLE: the owner's own ask for its share of the machine, every field a value. `null` on a cap is
22
+ // the contract's default (the share derived from the engine, or every core). The switches are the owner's ask, not
23
+ // docker's answer: a directive the approved environment demands rides beside it (`overlayRuntime`), and a host
24
+ // without the NVIDIA runtime drops the GPU. `ic` stores it, validates it against the image's run contract when it is
25
+ // saved, and prints it in `ic sandbox list --json`; nobody else merges anything into it.
26
+ export const SandboxShapeSchema = z.object({
27
+ memoryGib: z.int().positive().nullable(),
28
+ cpus: z.int().positive().nullable(),
29
+ privileged: z.boolean(),
30
+ gpu: z.boolean(),
31
+ });
32
+ export type SandboxShape = z.infer<typeof SandboxShapeSchema>;
33
+ // When a `set-shape` takes effect: `now` restarts the sandbox onto it; `nextRestart` saves it in ic's record and
34
+ // restarts nothing, and the next restart through ic applies it — Restart or Start from any door, an update, a rollback
35
+ // or a rebuild. Docker restarting the container by itself (after a crash or a reboot) does not: only ic recreates one.
36
+ export const SandboxShapeWhenSchema = z.enum(["now", "nextRestart"]);
37
+ export type SandboxShapeWhen = z.infer<typeof SandboxShapeWhenSchema>;
38
+
20
39
  export const SandboxResourcesSchema = z.object({
21
40
  // The cgroup memory ceiling in bytes; absent when docker imposes none (the hosted shape).
22
41
  memoryBytes: z.number().optional(),
@@ -26,14 +45,19 @@ export const SandboxResourcesSchema = z.object({
26
45
  gpu: z.boolean(),
27
46
  hostRuntime: z.array(z.string()),
28
47
  overlayRuntime: z.array(z.string()),
29
- // A reshape saved for the sandbox's next restart (`ic sandbox reshape --later`) and not yet in force: the next
30
- // update, rollback, rebuild, reshape or Restart applies it. Absent when nothing is saved.
48
+ // The shape the container runs with (the owner's ask it carries). Absent from an agent older than `set-shape`.
49
+ shape: SandboxShapeSchema.optional(),
50
+ // The shape saved for the sandbox's next restart through ic, and not yet in force. Absent when nothing is saved.
51
+ desired: SandboxShapeSchema.optional(),
52
+ // READ-ONLY, from agents older than `set-shape`: their saved share, a delta against what runs. Nothing new writes
53
+ // it; kept one release so a newer page can still show what an older agent saved. Remove with `later`.
31
54
  saved: SandboxResourcesAskFieldsSchema.optional(),
32
55
  });
33
56
  export type SandboxResources = z.infer<typeof SandboxResourcesSchema>;
34
57
 
35
58
  // Fields also exported separately for callers that compose them and enforce the at-least-one rule themselves.
36
- export const SandboxResourcesAskSchema = SandboxResourcesAskFieldsSchema.refine((ask) => Object.values(ask).some((value) => value !== undefined), {
59
+ // Strict like every device input (DeviceSandboxFlowSchema says why): it is only ever sent to a device.
60
+ export const SandboxResourcesAskSchema = SandboxResourcesAskFieldsSchema.strict().refine((ask) => Object.values(ask).some((value) => value !== undefined), {
37
61
  message: "a reshape must change at least one thing",
38
62
  });
39
63
  export type SandboxResourcesAsk = z.infer<typeof SandboxResourcesAskSchema>;
@@ -50,10 +74,14 @@ export const DeviceSandboxSchema = z.object({
50
74
  tunnelRunning: z.boolean().optional(),
51
75
  // Absent when the reader only ran a cheap `docker ps` listing without inspecting the container.
52
76
  resources: SandboxResourcesSchema.optional(),
77
+ // The update `ic sandbox prepare` built and left waiting for this sandbox; absent when nothing is staged.
78
+ staged: z.object({ image: z.string(), version: z.string().optional(), channel: z.string().optional() }).optional(),
53
79
  });
54
80
  export type DeviceSandbox = z.infer<typeof DeviceSandboxSchema>;
55
81
  // One operation on one sandbox, streamed as lines ending in a `result` or `error` frame. `prepare` builds the pending
56
- // update without touching the container; `reshape` changes only its resources and privileges, not the image.
82
+ // update without touching the container. `set-shape` sets its resources and privileges, now or for the next restart;
83
+ // `forget-shape` drops a shape saved for the next restart. `start`/`restart` apply a saved shape (all through `ic`).
84
+ // `reshape` is the older spelling of `set-shape`, kept one release for pages served by older sandboxes.
57
85
  // `runner-up`/`runner-remove` act on a runner (this sandbox's own container), not a person's sandbox; gated by the
58
86
  // `sandboxes` switch, not removal, since a runner holds no separate workspace to lose.
59
87
  export const DeviceSandboxOpSchema = z.enum([
@@ -65,6 +93,8 @@ export const DeviceSandboxOpSchema = z.enum([
65
93
  "rebuild",
66
94
  "rollback",
67
95
  "reshape",
96
+ "set-shape",
97
+ "forget-shape",
68
98
  "remove",
69
99
  "logs",
70
100
  // The two ops that redeem a fresh setup code; every other one recreates a container from its own existing env.
@@ -76,7 +106,11 @@ export const DeviceSandboxOpSchema = z.enum([
76
106
  "runner-remove",
77
107
  ]);
78
108
  export type DeviceSandboxOp = z.infer<typeof DeviceSandboxOpSchema>;
79
- export const DeviceSandboxFlowSchema = z.object({
109
+ // STRICT, like every input a device is sent: an agent rejects a field it does not know instead of dropping it and doing
110
+ // something else (an older agent once dropped `later` and restarted a sandbox it had been asked to leave running). New
111
+ // behaviour is a new `op`, which an older agent refuses by name, and the daemon refuses an op the agent has not
112
+ // advertised before sending it (device-reports.ts).
113
+ export const DeviceSandboxFlowSchema = z.strictObject({
80
114
  op: DeviceSandboxOpSchema,
81
115
  // Sandbox slug, the runner's name for the runner ops (as `/system/runners`/`ic runner list` know it), or for
82
116
  // `create` the name the claim is about to produce — the one op where the slug is an expectation rather than a
@@ -84,11 +118,14 @@ export const DeviceSandboxFlowSchema = z.object({
84
118
  slug: z.string().min(1),
85
119
  // Approved overlay's sha256, required only by `rebuild`; only content matching it is ever built.
86
120
  hash: z.string().optional(),
87
- // What `reshape` should change, meaningless to the rest. Absent on a reshape applies what is saved for the next
88
- // restart now; absent with `later` forgets what is saved.
121
+ // `set-shape` only, both required by it: the whole shape, and when it takes effect.
122
+ // Strict too, unlike the same shape read off a report (where a newer ic's extra field must not fail an older
123
+ // reader): an order carrying a field this agent does not know is refused rather than carried out without it.
124
+ shape: z.strictObject(SandboxShapeSchema.shape).optional(),
125
+ when: SandboxShapeWhenSchema.optional(),
126
+ // READ-ONLY for one release, from pages served by sandboxes older than `set-shape`: the old `reshape` op's delta,
127
+ // and `later` to save it for the next restart instead. Nothing new sends either.
89
128
  resources: SandboxResourcesAskSchema.optional(),
90
- // `reshape` only: save `resources` for the sandbox's next restart instead of restarting it now, replacing
91
- // whatever was saved before (`ic sandbox reshape --later`).
92
129
  later: z.boolean().optional(),
93
130
  // `runner-up` only, daemon-filled, never by the caller: the browser never holds the pairing credential.
94
131
  parentUrl: z.string().optional(),
@@ -104,6 +141,26 @@ export const DeviceSandboxFlowSchema = z.object({
104
141
  overlayHash: z.string().optional(),
105
142
  });
106
143
  export type DeviceSandboxFlow = z.infer<typeof DeviceSandboxFlowSchema>;
144
+ // The `ic` argv for the shape and power ops, the one place any caller spells them: the machine agent runs it and the
145
+ // Devices view prints it for a person to type when the app's route to the machine is shut. The shape travels as the
146
+ // contract's own object, one `--set <field>=<JSON value>` per field, so no caller maps a field onto a flag of its own:
147
+ // ic reads the pairs back into the contract's shape and refuses a field it does not know, as every device input does.
148
+ // `when` is the contract's own word too. A field left out keeps what ic already has for it (the shape saved for the
149
+ // next restart, else what runs): the Devices view always sends the whole shape, a model's tool call may name one
150
+ // field. Nothing is judged here; ic checks the shape against the image's run contract. Needs an ic that takes `--set`
151
+ // (the `set-shape` feature is advertised only then).
152
+ export type SandboxShapeFields = { readonly [K in keyof SandboxShape]?: SandboxShape[K] | undefined };
153
+ export const icShapeArgs = (slug: string, fields: SandboxShapeFields, when: SandboxShapeWhen): string[] => [
154
+ "sandbox",
155
+ "shape",
156
+ slug,
157
+ ...Object.entries(fields).flatMap(([field, value]) => (value === undefined ? [] : ["--set", `${field}=${JSON.stringify(value)}`])),
158
+ "--when",
159
+ when,
160
+ ];
161
+ export const icForgetShapeArgs = (slug: string): string[] => ["sandbox", "shape", slug, "--forget"];
162
+ // Power goes through ic too, so a Start or Restart from any door applies the shape saved for the next restart.
163
+ export const icPowerArgs = (op: "start" | "stop" | "restart", slug: string): string[] => ["sandbox", op, slug];
107
164
  // Adds the device id to the flow input; the daemon looks up machines by id.
108
165
  export const DeviceSandboxFlowInputSchema = DeviceSandboxFlowSchema.extend({ id: z.string().min(1) });
109
166
  export type DeviceSandboxFlowInput = z.infer<typeof DeviceSandboxFlowInputSchema>;
@@ -123,7 +180,7 @@ export type DeviceFlowLine = z.infer<typeof DeviceFlowLineSchema>;
123
180
  // dropping one is always somebody's decision.
124
181
  export const DeviceAgentOpSchema = z.enum(["upgrade", "restart", "forget-unreachable"]);
125
182
  export type DeviceAgentOp = z.infer<typeof DeviceAgentOpSchema>;
126
- export const DeviceAgentFlowSchema = z.object({ op: DeviceAgentOpSchema });
183
+ export const DeviceAgentFlowSchema = z.strictObject({ op: DeviceAgentOpSchema });
127
184
  export type DeviceAgentFlow = z.infer<typeof DeviceAgentFlowSchema>;
128
185
  // Adds the device id to the flow input.
129
186
  export const DeviceAgentFlowInputSchema = DeviceAgentFlowSchema.extend({ id: z.string().min(1) });
@@ -362,8 +419,10 @@ export const AGENT_STALL_AFTER_MS = 60_000;
362
419
  export const agentStalled = (agent: DeviceAgent, now: number): boolean =>
363
420
  agent.running && agent.lastTickAt !== undefined && now - agent.lastTickAt > AGENT_STALL_AFTER_MS;
364
421
  export const DeviceReportSchema = z.object({
365
- // OS hostname; the join key that dedupes a machine seen via sync and via its `host` capability. Not unique on its
366
- // own: a WSL distro inherits the Windows machine's name, so `wsl` below is what tells those apart.
422
+ // The computer this reading is from (MachineIdSchema): what a sync enrollment is stamped with when its agent posts,
423
+ // and so what joins a machine seen through sync to the same machine seen through its card. Absent from an older agent.
424
+ machineId: MachineIdSchema.optional(),
425
+ // OS hostname, for the owner to read; not a join key (a WSL distro inherits the Windows machine's name).
367
426
  hostname: z.string(),
368
427
  os: z.string(),
369
428
  // Present only inside a WSL distro; the same fact rides the connect-time facts, which no scope can withhold.
@@ -420,9 +479,8 @@ export const DeviceGapSchema = z.enum([
420
479
  "unreported",
421
480
  ]);
422
481
  export type DeviceGap = z.infer<typeof DeviceGapSchema>;
423
- // A machine may be reachable via desktop sync and a host capability at once; the two are reconciled on `hostname` plus
424
- // the environment it came from (`environmentOf`), and left as separate rows when there is nothing to reconcile them by
425
- // or when the environments positively disagree.
482
+ // A machine may be reachable via desktop sync and a host capability at once; the two are one row when both say the same
483
+ // `machineId` and the same environment, and separate rows whenever either has not said (an agent too old to carry it).
426
484
  // `machine` is the enrollment's name for the box (the ssh key's comment): what reports are filed under and what the
427
485
  // revoke route takes. Two machines sharing a key comment share one enrollment identity.
428
486
  export const DeviceSyncSchema = z.object({
@@ -431,6 +489,10 @@ export const DeviceSyncSchema = z.object({
431
489
  mode: z.enum(["sync", "mirror"]),
432
490
  // When this machine last used its enrollment; absent means never, not merely unknown, and is not healthy.
433
491
  seenAt: z.number().optional(),
492
+ // The computer and the environment on it that hold this enrollment, as its agent said when enrolling or in a report
493
+ // since; absent until an agent new enough to say has done so.
494
+ machineId: MachineIdSchema.optional(),
495
+ environment: z.string().optional(),
434
496
  });
435
497
  export type DeviceSync = z.infer<typeof DeviceSyncSchema>;
436
498
  export const DeviceSchema = z.object({
@@ -440,8 +502,14 @@ export const DeviceSchema = z.object({
440
502
  label: z.string(),
441
503
  // Desktop-sync enrollment with this sandbox; absent when reached only via a host capability.
442
504
  sync: DeviceSyncSchema.optional(),
443
- // The host capability's id, when this machine is also a connected device. Absent otherwise.
505
+ // The host connection's key, when this machine is also a connected device. Absent otherwise.
444
506
  hostId: z.string().optional(),
507
+ // The card that connection is of, beside the key rather than parsed out of it: the machine's name, a label. Absent
508
+ // with `hostId`, and from a daemon older than the field (`deviceCard` reads the key then).
509
+ card: z.string().optional(),
510
+ // The computer this row is an environment of, from whichever door has said (MachineIdSchema): what `machinesOf`
511
+ // groups by. Absent when no door has, which leaves the row a computer of its own.
512
+ machineId: MachineIdSchema.optional(),
445
513
  // Host-capability liveness; absent when there is no host capability, not the same as offline.
446
514
  online: z.boolean().optional(),
447
515
  // Carried beside the report so a device with no report still has an OS; `platform` is the normalised slug, `facts`
@@ -480,7 +548,7 @@ export const deviceHostname = (device: Device): string | undefined => device.fac
480
548
  // and that is all an environment nobody has reached since this daemon booted can say about itself: liveness resets on
481
549
  // restart, so facts and reports are absent on a side that is merely asleep.
482
550
  export const deviceEnvironment = (device: Device): string | undefined =>
483
- environmentOf(device.facts, device.report) ?? (device.hostId === undefined ? undefined : hostEnvironmentOf(device.hostId));
551
+ environmentOf(device.facts, device.report) ?? (device.hostId === undefined ? undefined : parseHostConnection(device.hostId).environment);
484
552
 
485
553
  const WSL_PREFIX = "wsl:";
486
554
  export const isWslDevice = (device: Device): boolean => deviceEnvironment(device)?.startsWith(WSL_PREFIX) === true;
@@ -495,24 +563,25 @@ export const deviceDistro = (device: Device): string | undefined => {
495
563
  const byEnvironment = (a: Device, b: Device): number =>
496
564
  Number(isWslDevice(a)) - Number(isWslDevice(b)) || (deviceDistro(a) ?? a.label).localeCompare(deviceDistro(b) ?? b.label);
497
565
 
498
- const hostnameKey = (device: Device): string | undefined => deviceHostname(device)?.toLowerCase();
499
-
500
566
  // The card a device's door hangs off: one card is one computer, so two doors naming the same card are one machine
501
567
  // whatever either has said about itself — which is the whole of what an environment that has never connected says.
502
- const cardKey = (device: Device): string | undefined => (device.hostId === undefined ? undefined : hostEntryOf(device.hostId));
503
-
504
- // What makes two devices one computer: a shared card, or a shared hostname where a distro answers to it, since WSL
505
- // hands a distro the Windows machine's name. Two native installs that merely share a name stay two machines.
506
- const tokensOf = (device: Device, distroNames: ReadonlySet<string>): string[] => {
507
- const card = cardKey(device);
508
- const hostname = hostnameKey(device);
509
- return [
510
- ...(card === undefined ? [] : [`card:${card.toLowerCase()}`]),
511
- ...(hostname !== undefined && distroNames.has(hostname) ? [`host:${hostname}`] : []),
512
- ];
568
+ // The row's own label, else its key read by the one parser there is (a row from a daemon older than `card`).
569
+ export const deviceCard = (device: Pick<Device, "card" | "hostId">): string | undefined =>
570
+ device.card ?? (device.hostId === undefined ? undefined : parseHostConnection(device.hostId).card);
571
+
572
+ // The computer a device is on, by whichever door said: its row, its connect-time facts, its report, its sync enrollment.
573
+ export const deviceMachineId = (device: Device): string | undefined =>
574
+ device.machineId ?? device.facts?.machineId ?? device.report?.machineId ?? device.sync?.machineId;
575
+
576
+ // What makes two devices one computer: a shared card, or a shared machine id. Nothing else: a hostname is shared by a
577
+ // PC and its WSL distros and by two unrelated machines with the same name, so it joined things by accident.
578
+ const tokensOf = (device: Device): string[] => {
579
+ const card = deviceCard(device);
580
+ const machine = deviceMachineId(device);
581
+ return [...(card === undefined ? [] : [`card:${card.toLowerCase()}`]), ...(machine === undefined ? [] : [`machine:${machine}`])];
513
582
  };
514
583
 
515
- // Components over both joins at once: a device holding a card token and a hostname token is the evidence that merges
584
+ // Components over both joins at once: a device holding a card token and a machine token is the evidence that merges
516
585
  // the machines those tokens named, so a distro connected under a card of its own still lands on the PC it runs on.
517
586
  interface Component {
518
587
  readonly tokens: Set<string>;
@@ -520,10 +589,9 @@ interface Component {
520
589
  }
521
590
 
522
591
  const componentsOf = (devices: readonly Device[]): Component[] => {
523
- const distroNames = new Set(devices.filter(isWslDevice).map(hostnameKey).filter((name) => name !== undefined));
524
592
  const components: Component[] = [];
525
593
  for (const device of devices) {
526
- const tokens = tokensOf(device, distroNames);
594
+ const tokens = tokensOf(device);
527
595
  const [head, ...merged] = components.filter((component) => tokens.some((token) => component.tokens.has(token)));
528
596
  if (head === undefined) {
529
597
  components.push({ tokens: new Set(tokens), devices: [device] });
@@ -544,7 +612,7 @@ const componentsOf = (devices: readonly Device[]): Component[] => {
544
612
 
545
613
  // Addressed by the card of the side that owns the screen, an address that does not change when a second environment
546
614
  // connects — unlike a row key, which is a hostname another environment can take first. An uncarded fold has only the
547
- // hostname its doors share, and an uncarded lone device its own key. Called what the owner calls it: the name the
615
+ // machine id its doors share, and an uncarded lone device its own key. Called what the owner calls it: the name the
548
616
  // leading environment carries, unless that is nothing but its door id, which reads as a PC named after one of its
549
617
  // own sides.
550
618
  const machineOf = (environments: readonly Device[]): Machine => {
@@ -553,8 +621,8 @@ const machineOf = (environments: readonly Device[]): Machine => {
553
621
  if (first === undefined) {
554
622
  return { key: "", label: "", environments };
555
623
  }
556
- const named = environments.map(cardKey).find((key) => key !== undefined) ?? (rest.length === 0 ? undefined : deviceHostname(first));
557
- const label = first.label === first.hostId ? (cardKey(first) ?? first.label) : first.label;
624
+ const named = environments.map(deviceCard).find((key) => key !== undefined) ?? (rest.length === 0 ? undefined : deviceMachineId(first));
625
+ const label = first.label === first.hostId ? (deviceCard(first) ?? first.label) : first.label;
558
626
  return { key: named ?? first.key, label, environments };
559
627
  };
560
628
 
@@ -177,6 +177,12 @@ export const ExtensionSummarySchema = z.object({
177
177
  })
178
178
  .optional()
179
179
  .describe("Present only for an extension that ships a server half."),
180
+ problems: z
181
+ .array(z.string())
182
+ .optional()
183
+ .describe(
184
+ "Declarations in its manifest the sandbox refused at load, each a sentence saying what and why, such as a listener for a provider another extension already owns. The rest of it still loads. Absent when nothing was refused.",
185
+ ),
180
186
  // Update lifecycle, present only for a git-installed extension (absent for builtin/workspace):
181
187
  // update: the badge.
182
188
  // advisory: the alarm.