@intentic/sandbox-contract 1.312.0 → 1.313.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 +19 -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 +139 -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 +1036 -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 +16 -3
  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 +132 -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 +29 -5
  166. package/dist/schemas/hosts.d.ts.map +1 -1
  167. package/dist/schemas/hosts.js +20 -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 +22 -0
  174. package/dist/schemas/metrics.d.ts.map +1 -1
  175. package/dist/schemas/metrics.js +18 -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 +32 -6
  294. package/src/schemas/automations.ts +4 -2
  295. package/src/schemas/devices.ts +106 -39
  296. package/src/schemas/extension-updates.ts +6 -0
  297. package/src/schemas/hosts.ts +62 -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 +26 -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()
@@ -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,32 @@ 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 (`ic sandbox reshape` flags); `set-shape` carries a whole shape instead.
14
14
  export const SandboxResourcesAskFieldsSchema = z.object({
15
15
  memoryGib: z.int().positive().nullable().optional(),
16
16
  cpus: z.int().positive().nullable().optional(),
17
17
  privileged: z.boolean().optional(),
18
18
  gpu: z.boolean().optional(),
19
19
  });
20
+ // 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
21
+ // the contract's default (the share derived from the engine, or every core). The switches are the owner's ask, not
22
+ // docker's answer: a directive the approved environment demands rides beside it (`overlayRuntime`), and a host
23
+ // without the NVIDIA runtime drops the GPU. `ic` stores it, validates it against the image's run contract when it is
24
+ // saved, and prints it in `ic sandbox list --json`; nobody else merges anything into it.
25
+ export const SandboxShapeSchema = z.object({
26
+ memoryGib: z.int().positive().nullable(),
27
+ cpus: z.int().positive().nullable(),
28
+ privileged: z.boolean(),
29
+ gpu: z.boolean(),
30
+ });
31
+ export type SandboxShape = z.infer<typeof SandboxShapeSchema>;
32
+ // When a `set-shape` takes effect: `now` restarts the sandbox onto it; `nextRestart` saves it in ic's record and
33
+ // restarts nothing, and the next restart through ic applies it — Restart or Start from any door, an update, a rollback
34
+ // or a rebuild. Docker restarting the container by itself (after a crash or a reboot) does not: only ic recreates one.
35
+ export const SandboxShapeWhenSchema = z.enum(["now", "nextRestart"]);
36
+ export type SandboxShapeWhen = z.infer<typeof SandboxShapeWhenSchema>;
37
+
20
38
  export const SandboxResourcesSchema = z.object({
21
39
  // The cgroup memory ceiling in bytes; absent when docker imposes none (the hosted shape).
22
40
  memoryBytes: z.number().optional(),
@@ -26,14 +44,19 @@ export const SandboxResourcesSchema = z.object({
26
44
  gpu: z.boolean(),
27
45
  hostRuntime: z.array(z.string()),
28
46
  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.
47
+ // The shape the container runs with (the owner's ask it carries). Absent from an agent older than `set-shape`.
48
+ shape: SandboxShapeSchema.optional(),
49
+ // The shape saved for the sandbox's next restart through ic, and not yet in force. Absent when nothing is saved.
50
+ desired: SandboxShapeSchema.optional(),
51
+ // READ-ONLY, from agents older than `set-shape`: their saved share, a delta against what runs. Nothing new writes
52
+ // it; kept one release so a newer page can still show what an older agent saved. Remove with `later`.
31
53
  saved: SandboxResourcesAskFieldsSchema.optional(),
32
54
  });
33
55
  export type SandboxResources = z.infer<typeof SandboxResourcesSchema>;
34
56
 
35
57
  // 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), {
58
+ // Strict like every device input (DeviceSandboxFlowSchema says why): it is only ever sent to a device.
59
+ export const SandboxResourcesAskSchema = SandboxResourcesAskFieldsSchema.strict().refine((ask) => Object.values(ask).some((value) => value !== undefined), {
37
60
  message: "a reshape must change at least one thing",
38
61
  });
39
62
  export type SandboxResourcesAsk = z.infer<typeof SandboxResourcesAskSchema>;
@@ -50,10 +73,14 @@ export const DeviceSandboxSchema = z.object({
50
73
  tunnelRunning: z.boolean().optional(),
51
74
  // Absent when the reader only ran a cheap `docker ps` listing without inspecting the container.
52
75
  resources: SandboxResourcesSchema.optional(),
76
+ // The update `ic sandbox prepare` built and left waiting for this sandbox; absent when nothing is staged.
77
+ staged: z.object({ image: z.string(), version: z.string().optional(), channel: z.string().optional() }).optional(),
53
78
  });
54
79
  export type DeviceSandbox = z.infer<typeof DeviceSandboxSchema>;
55
80
  // 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.
81
+ // update without touching the container. `set-shape` sets its resources and privileges, now or for the next restart;
82
+ // `forget-shape` drops a shape saved for the next restart. `start`/`restart` apply a saved shape (all through `ic`).
83
+ // `reshape` is the older spelling of `set-shape`, kept one release for pages served by older sandboxes.
57
84
  // `runner-up`/`runner-remove` act on a runner (this sandbox's own container), not a person's sandbox; gated by the
58
85
  // `sandboxes` switch, not removal, since a runner holds no separate workspace to lose.
59
86
  export const DeviceSandboxOpSchema = z.enum([
@@ -65,6 +92,8 @@ export const DeviceSandboxOpSchema = z.enum([
65
92
  "rebuild",
66
93
  "rollback",
67
94
  "reshape",
95
+ "set-shape",
96
+ "forget-shape",
68
97
  "remove",
69
98
  "logs",
70
99
  // The two ops that redeem a fresh setup code; every other one recreates a container from its own existing env.
@@ -76,7 +105,11 @@ export const DeviceSandboxOpSchema = z.enum([
76
105
  "runner-remove",
77
106
  ]);
78
107
  export type DeviceSandboxOp = z.infer<typeof DeviceSandboxOpSchema>;
79
- export const DeviceSandboxFlowSchema = z.object({
108
+ // STRICT, like every input a device is sent: an agent rejects a field it does not know instead of dropping it and doing
109
+ // something else (an older agent once dropped `later` and restarted a sandbox it had been asked to leave running). New
110
+ // behaviour is a new `op`, which an older agent refuses by name, and the daemon refuses an op the agent has not
111
+ // advertised before sending it (device-reports.ts).
112
+ export const DeviceSandboxFlowSchema = z.strictObject({
80
113
  op: DeviceSandboxOpSchema,
81
114
  // Sandbox slug, the runner's name for the runner ops (as `/system/runners`/`ic runner list` know it), or for
82
115
  // `create` the name the claim is about to produce — the one op where the slug is an expectation rather than a
@@ -84,11 +117,14 @@ export const DeviceSandboxFlowSchema = z.object({
84
117
  slug: z.string().min(1),
85
118
  // Approved overlay's sha256, required only by `rebuild`; only content matching it is ever built.
86
119
  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.
120
+ // `set-shape` only, both required by it: the whole shape, and when it takes effect.
121
+ // Strict too, unlike the same shape read off a report (where a newer ic's extra field must not fail an older
122
+ // reader): an order carrying a field this agent does not know is refused rather than carried out without it.
123
+ shape: z.strictObject(SandboxShapeSchema.shape).optional(),
124
+ when: SandboxShapeWhenSchema.optional(),
125
+ // READ-ONLY for one release, from pages served by sandboxes older than `set-shape`: the old `reshape` op's delta,
126
+ // and `later` to save it for the next restart instead. Nothing new sends either.
89
127
  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
128
  later: z.boolean().optional(),
93
129
  // `runner-up` only, daemon-filled, never by the caller: the browser never holds the pairing credential.
94
130
  parentUrl: z.string().optional(),
@@ -104,6 +140,26 @@ export const DeviceSandboxFlowSchema = z.object({
104
140
  overlayHash: z.string().optional(),
105
141
  });
106
142
  export type DeviceSandboxFlow = z.infer<typeof DeviceSandboxFlowSchema>;
143
+ // The `ic` argv for the shape and power ops, the one place any caller spells them: the machine agent runs it and the
144
+ // Devices view prints it for a person to type when the app's route to the machine is shut. The shape travels as the
145
+ // contract's own object, one `--set <field>=<JSON value>` per field, so no caller maps a field onto a flag of its own:
146
+ // ic reads the pairs back into the contract's shape and refuses a field it does not know, as every device input does.
147
+ // `when` is the contract's own word too. A field left out keeps what ic already has for it (the shape saved for the
148
+ // next restart, else what runs): the Devices view always sends the whole shape, a model's tool call may name one
149
+ // field. Nothing is judged here; ic checks the shape against the image's run contract. Needs an ic that takes `--set`
150
+ // (the `set-shape` feature is advertised only then).
151
+ export type SandboxShapeFields = { readonly [K in keyof SandboxShape]?: SandboxShape[K] | undefined };
152
+ export const icShapeArgs = (slug: string, fields: SandboxShapeFields, when: SandboxShapeWhen): string[] => [
153
+ "sandbox",
154
+ "shape",
155
+ slug,
156
+ ...Object.entries(fields).flatMap(([field, value]) => (value === undefined ? [] : ["--set", `${field}=${JSON.stringify(value)}`])),
157
+ "--when",
158
+ when,
159
+ ];
160
+ export const icForgetShapeArgs = (slug: string): string[] => ["sandbox", "shape", slug, "--forget"];
161
+ // Power goes through ic too, so a Start or Restart from any door applies the shape saved for the next restart.
162
+ export const icPowerArgs = (op: "start" | "stop" | "restart", slug: string): string[] => ["sandbox", op, slug];
107
163
  // Adds the device id to the flow input; the daemon looks up machines by id.
108
164
  export const DeviceSandboxFlowInputSchema = DeviceSandboxFlowSchema.extend({ id: z.string().min(1) });
109
165
  export type DeviceSandboxFlowInput = z.infer<typeof DeviceSandboxFlowInputSchema>;
@@ -123,7 +179,7 @@ export type DeviceFlowLine = z.infer<typeof DeviceFlowLineSchema>;
123
179
  // dropping one is always somebody's decision.
124
180
  export const DeviceAgentOpSchema = z.enum(["upgrade", "restart", "forget-unreachable"]);
125
181
  export type DeviceAgentOp = z.infer<typeof DeviceAgentOpSchema>;
126
- export const DeviceAgentFlowSchema = z.object({ op: DeviceAgentOpSchema });
182
+ export const DeviceAgentFlowSchema = z.strictObject({ op: DeviceAgentOpSchema });
127
183
  export type DeviceAgentFlow = z.infer<typeof DeviceAgentFlowSchema>;
128
184
  // Adds the device id to the flow input.
129
185
  export const DeviceAgentFlowInputSchema = DeviceAgentFlowSchema.extend({ id: z.string().min(1) });
@@ -362,8 +418,10 @@ export const AGENT_STALL_AFTER_MS = 60_000;
362
418
  export const agentStalled = (agent: DeviceAgent, now: number): boolean =>
363
419
  agent.running && agent.lastTickAt !== undefined && now - agent.lastTickAt > AGENT_STALL_AFTER_MS;
364
420
  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.
421
+ // The computer this reading is from (MachineIdSchema): what a sync enrollment is stamped with when its agent posts,
422
+ // and so what joins a machine seen through sync to the same machine seen through its card. Absent from an older agent.
423
+ machineId: MachineIdSchema.optional(),
424
+ // OS hostname, for the owner to read; not a join key (a WSL distro inherits the Windows machine's name).
367
425
  hostname: z.string(),
368
426
  os: z.string(),
369
427
  // Present only inside a WSL distro; the same fact rides the connect-time facts, which no scope can withhold.
@@ -420,9 +478,8 @@ export const DeviceGapSchema = z.enum([
420
478
  "unreported",
421
479
  ]);
422
480
  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.
481
+ // A machine may be reachable via desktop sync and a host capability at once; the two are one row when both say the same
482
+ // `machineId` and the same environment, and separate rows whenever either has not said (an agent too old to carry it).
426
483
  // `machine` is the enrollment's name for the box (the ssh key's comment): what reports are filed under and what the
427
484
  // revoke route takes. Two machines sharing a key comment share one enrollment identity.
428
485
  export const DeviceSyncSchema = z.object({
@@ -431,6 +488,10 @@ export const DeviceSyncSchema = z.object({
431
488
  mode: z.enum(["sync", "mirror"]),
432
489
  // When this machine last used its enrollment; absent means never, not merely unknown, and is not healthy.
433
490
  seenAt: z.number().optional(),
491
+ // The computer and the environment on it that hold this enrollment, as its agent said when enrolling or in a report
492
+ // since; absent until an agent new enough to say has done so.
493
+ machineId: MachineIdSchema.optional(),
494
+ environment: z.string().optional(),
434
495
  });
435
496
  export type DeviceSync = z.infer<typeof DeviceSyncSchema>;
436
497
  export const DeviceSchema = z.object({
@@ -440,8 +501,14 @@ export const DeviceSchema = z.object({
440
501
  label: z.string(),
441
502
  // Desktop-sync enrollment with this sandbox; absent when reached only via a host capability.
442
503
  sync: DeviceSyncSchema.optional(),
443
- // The host capability's id, when this machine is also a connected device. Absent otherwise.
504
+ // The host connection's key, when this machine is also a connected device. Absent otherwise.
444
505
  hostId: z.string().optional(),
506
+ // The card that connection is of, beside the key rather than parsed out of it: the machine's name, a label. Absent
507
+ // with `hostId`, and from a daemon older than the field (`deviceCard` reads the key then).
508
+ card: z.string().optional(),
509
+ // The computer this row is an environment of, from whichever door has said (MachineIdSchema): what `machinesOf`
510
+ // groups by. Absent when no door has, which leaves the row a computer of its own.
511
+ machineId: MachineIdSchema.optional(),
445
512
  // Host-capability liveness; absent when there is no host capability, not the same as offline.
446
513
  online: z.boolean().optional(),
447
514
  // Carried beside the report so a device with no report still has an OS; `platform` is the normalised slug, `facts`
@@ -480,7 +547,7 @@ export const deviceHostname = (device: Device): string | undefined => device.fac
480
547
  // and that is all an environment nobody has reached since this daemon booted can say about itself: liveness resets on
481
548
  // restart, so facts and reports are absent on a side that is merely asleep.
482
549
  export const deviceEnvironment = (device: Device): string | undefined =>
483
- environmentOf(device.facts, device.report) ?? (device.hostId === undefined ? undefined : hostEnvironmentOf(device.hostId));
550
+ environmentOf(device.facts, device.report) ?? (device.hostId === undefined ? undefined : parseHostConnection(device.hostId).environment);
484
551
 
485
552
  const WSL_PREFIX = "wsl:";
486
553
  export const isWslDevice = (device: Device): boolean => deviceEnvironment(device)?.startsWith(WSL_PREFIX) === true;
@@ -495,24 +562,25 @@ export const deviceDistro = (device: Device): string | undefined => {
495
562
  const byEnvironment = (a: Device, b: Device): number =>
496
563
  Number(isWslDevice(a)) - Number(isWslDevice(b)) || (deviceDistro(a) ?? a.label).localeCompare(deviceDistro(b) ?? b.label);
497
564
 
498
- const hostnameKey = (device: Device): string | undefined => deviceHostname(device)?.toLowerCase();
499
-
500
565
  // The card a device's door hangs off: one card is one computer, so two doors naming the same card are one machine
501
566
  // 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
- ];
567
+ // The row's own label, else its key read by the one parser there is (a row from a daemon older than `card`).
568
+ export const deviceCard = (device: Pick<Device, "card" | "hostId">): string | undefined =>
569
+ device.card ?? (device.hostId === undefined ? undefined : parseHostConnection(device.hostId).card);
570
+
571
+ // The computer a device is on, by whichever door said: its row, its connect-time facts, its report, its sync enrollment.
572
+ export const deviceMachineId = (device: Device): string | undefined =>
573
+ device.machineId ?? device.facts?.machineId ?? device.report?.machineId ?? device.sync?.machineId;
574
+
575
+ // What makes two devices one computer: a shared card, or a shared machine id. Nothing else: a hostname is shared by a
576
+ // PC and its WSL distros and by two unrelated machines with the same name, so it joined things by accident.
577
+ const tokensOf = (device: Device): string[] => {
578
+ const card = deviceCard(device);
579
+ const machine = deviceMachineId(device);
580
+ return [...(card === undefined ? [] : [`card:${card.toLowerCase()}`]), ...(machine === undefined ? [] : [`machine:${machine}`])];
513
581
  };
514
582
 
515
- // Components over both joins at once: a device holding a card token and a hostname token is the evidence that merges
583
+ // Components over both joins at once: a device holding a card token and a machine token is the evidence that merges
516
584
  // the machines those tokens named, so a distro connected under a card of its own still lands on the PC it runs on.
517
585
  interface Component {
518
586
  readonly tokens: Set<string>;
@@ -520,10 +588,9 @@ interface Component {
520
588
  }
521
589
 
522
590
  const componentsOf = (devices: readonly Device[]): Component[] => {
523
- const distroNames = new Set(devices.filter(isWslDevice).map(hostnameKey).filter((name) => name !== undefined));
524
591
  const components: Component[] = [];
525
592
  for (const device of devices) {
526
- const tokens = tokensOf(device, distroNames);
593
+ const tokens = tokensOf(device);
527
594
  const [head, ...merged] = components.filter((component) => tokens.some((token) => component.tokens.has(token)));
528
595
  if (head === undefined) {
529
596
  components.push({ tokens: new Set(tokens), devices: [device] });
@@ -544,7 +611,7 @@ const componentsOf = (devices: readonly Device[]): Component[] => {
544
611
 
545
612
  // Addressed by the card of the side that owns the screen, an address that does not change when a second environment
546
613
  // 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
614
+ // machine id its doors share, and an uncarded lone device its own key. Called what the owner calls it: the name the
548
615
  // leading environment carries, unless that is nothing but its door id, which reads as a PC named after one of its
549
616
  // own sides.
550
617
  const machineOf = (environments: readonly Device[]): Machine => {
@@ -553,8 +620,8 @@ const machineOf = (environments: readonly Device[]): Machine => {
553
620
  if (first === undefined) {
554
621
  return { key: "", label: "", environments };
555
622
  }
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;
623
+ const named = environments.map(deviceCard).find((key) => key !== undefined) ?? (rest.length === 0 ? undefined : deviceMachineId(first));
624
+ const label = first.label === first.hostId ? (deviceCard(first) ?? first.label) : first.label;
558
625
  return { key: named ?? first.key, label, environments };
559
626
  };
560
627
 
@@ -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.