@intentic/sandbox-contract 1.304.0 → 1.306.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 (328) hide show
  1. package/README.md +5 -3
  2. package/dist/contracts/agent.contract.d.ts +195 -41
  3. package/dist/contracts/agent.contract.d.ts.map +1 -1
  4. package/dist/contracts/agent.contract.js +8 -8
  5. package/dist/contracts/agent.contract.js.map +1 -1
  6. package/dist/contracts/agents.contract.d.ts +203 -3
  7. package/dist/contracts/agents.contract.d.ts.map +1 -1
  8. package/dist/contracts/agents.contract.js +30 -2
  9. package/dist/contracts/agents.contract.js.map +1 -1
  10. package/dist/contracts/capabilities.contract.d.ts +18 -5
  11. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  12. package/dist/contracts/capabilities.contract.js +3 -3
  13. package/dist/contracts/capabilities.contract.js.map +1 -1
  14. package/dist/contracts/{host.contract.d.ts → device.contract.d.ts} +75 -2
  15. package/dist/contracts/device.contract.d.ts.map +1 -0
  16. package/dist/contracts/device.contract.js +17 -0
  17. package/dist/contracts/device.contract.js.map +1 -0
  18. package/dist/contracts/extensions.contract.d.ts +2 -2
  19. package/dist/contracts/git.contract.d.ts +24 -0
  20. package/dist/contracts/git.contract.d.ts.map +1 -1
  21. package/dist/contracts/panels.contract.d.ts +1 -1
  22. package/dist/contracts/personas.contract.d.ts +0 -9
  23. package/dist/contracts/personas.contract.d.ts.map +1 -1
  24. package/dist/contracts/personas.contract.js +1 -10
  25. package/dist/contracts/personas.contract.js.map +1 -1
  26. package/dist/contracts/runner.contract.d.ts +138 -98
  27. package/dist/contracts/runner.contract.d.ts.map +1 -1
  28. package/dist/contracts/runner.contract.js +1 -0
  29. package/dist/contracts/runner.contract.js.map +1 -1
  30. package/dist/contracts/secrets.contract.js +2 -2
  31. package/dist/contracts/secrets.contract.js.map +1 -1
  32. package/dist/contracts/sessions.contract.d.ts +23 -3
  33. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  34. package/dist/contracts/settings.contract.d.ts +0 -23
  35. package/dist/contracts/settings.contract.d.ts.map +1 -1
  36. package/dist/contracts/system.contract.d.ts +106 -8
  37. package/dist/contracts/system.contract.d.ts.map +1 -1
  38. package/dist/contracts/system.contract.js +11 -2
  39. package/dist/contracts/system.contract.js.map +1 -1
  40. package/dist/events/agent-events.d.ts +85 -10
  41. package/dist/events/agent-events.d.ts.map +1 -1
  42. package/dist/events/agent-events.js +26 -10
  43. package/dist/events/agent-events.js.map +1 -1
  44. package/dist/events/agent-words.d.ts +20 -0
  45. package/dist/events/agent-words.d.ts.map +1 -0
  46. package/dist/events/agent-words.js +82 -0
  47. package/dist/events/agent-words.js.map +1 -0
  48. package/dist/events/{cards.d.ts → requests.d.ts} +18 -18
  49. package/dist/events/requests.d.ts.map +1 -0
  50. package/dist/events/{cards.js → requests.js} +26 -26
  51. package/dist/events/requests.js.map +1 -0
  52. package/dist/events/resume.d.ts +1 -0
  53. package/dist/events/resume.d.ts.map +1 -1
  54. package/dist/events/resume.js +2 -0
  55. package/dist/events/resume.js.map +1 -1
  56. package/dist/events/system-events.d.ts +19 -3
  57. package/dist/events/system-events.d.ts.map +1 -1
  58. package/dist/events/transcript.d.ts +165 -25
  59. package/dist/events/transcript.d.ts.map +1 -1
  60. package/dist/events/transcript.js +34 -15
  61. package/dist/events/transcript.js.map +1 -1
  62. package/dist/events/watch-wake.d.ts.map +1 -1
  63. package/dist/events/watch-wake.js +4 -0
  64. package/dist/events/watch-wake.js.map +1 -1
  65. package/dist/index.d.ts +581 -99
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +9 -4
  68. package/dist/index.js.map +1 -1
  69. package/dist/models/model-offer.d.ts +2 -1
  70. package/dist/models/model-offer.d.ts.map +1 -1
  71. package/dist/models/model-offer.js +5 -4
  72. package/dist/models/model-offer.js.map +1 -1
  73. package/dist/models/model-roles.d.ts +2 -9
  74. package/dist/models/model-roles.d.ts.map +1 -1
  75. package/dist/models/model-roles.js +2 -9
  76. package/dist/models/model-roles.js.map +1 -1
  77. package/dist/policy/command-classes.d.ts +1 -0
  78. package/dist/policy/command-classes.d.ts.map +1 -1
  79. package/dist/policy/command-classes.js +8 -0
  80. package/dist/policy/command-classes.js.map +1 -1
  81. package/dist/policy/request-status.d.ts +5 -0
  82. package/dist/policy/request-status.d.ts.map +1 -0
  83. package/dist/policy/{card-status.js → request-status.js} +3 -3
  84. package/dist/policy/request-status.js.map +1 -0
  85. package/dist/policy/safety-policy.d.ts +1 -1
  86. package/dist/policy/safety-policy.d.ts.map +1 -1
  87. package/dist/policy/safety-policy.js +1 -0
  88. package/dist/policy/safety-policy.js.map +1 -1
  89. package/dist/policy/turned-away.d.ts +2 -0
  90. package/dist/policy/turned-away.d.ts.map +1 -0
  91. package/dist/policy/turned-away.js +13 -0
  92. package/dist/policy/turned-away.js.map +1 -0
  93. package/dist/protocol/peer-dial.d.ts +9 -0
  94. package/dist/protocol/peer-dial.d.ts.map +1 -1
  95. package/dist/protocol/peer-dial.js +12 -4
  96. package/dist/protocol/peer-dial.js.map +1 -1
  97. package/dist/protocol/peer-mcp-server.d.ts +1 -0
  98. package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
  99. package/dist/protocol/peer-mcp-server.js +14 -1
  100. package/dist/protocol/peer-mcp-server.js.map +1 -1
  101. package/dist/schemas/agent.d.ts +2 -1
  102. package/dist/schemas/agent.d.ts.map +1 -1
  103. package/dist/schemas/agent.js +7 -0
  104. package/dist/schemas/agent.js.map +1 -1
  105. package/dist/schemas/agents.d.ts +26 -0
  106. package/dist/schemas/agents.d.ts.map +1 -1
  107. package/dist/schemas/agents.js +14 -0
  108. package/dist/schemas/agents.js.map +1 -1
  109. package/dist/schemas/automations.d.ts +8 -0
  110. package/dist/schemas/automations.d.ts.map +1 -1
  111. package/dist/schemas/capabilities.d.ts +25 -12
  112. package/dist/schemas/capabilities.d.ts.map +1 -1
  113. package/dist/schemas/capabilities.js +11 -6
  114. package/dist/schemas/capabilities.js.map +1 -1
  115. package/dist/schemas/chat-route.d.ts +49 -0
  116. package/dist/schemas/chat-route.d.ts.map +1 -0
  117. package/dist/schemas/{model-route.js → chat-route.js} +17 -5
  118. package/dist/schemas/chat-route.js.map +1 -0
  119. package/dist/schemas/context-trim.d.ts +9 -0
  120. package/dist/schemas/context-trim.d.ts.map +1 -0
  121. package/dist/schemas/context-trim.js +18 -0
  122. package/dist/schemas/context-trim.js.map +1 -0
  123. package/dist/schemas/devices.d.ts +21 -8
  124. package/dist/schemas/devices.d.ts.map +1 -1
  125. package/dist/schemas/devices.js +8 -9
  126. package/dist/schemas/devices.js.map +1 -1
  127. package/dist/schemas/extension-updates.d.ts +4 -4
  128. package/dist/schemas/extension-updates.js +1 -1
  129. package/dist/schemas/extension-updates.js.map +1 -1
  130. package/dist/schemas/git/git.d.ts +83 -0
  131. package/dist/schemas/git/git.d.ts.map +1 -1
  132. package/dist/schemas/git/git.js +40 -0
  133. package/dist/schemas/git/git.js.map +1 -1
  134. package/dist/schemas/hosts.d.ts +36 -5
  135. package/dist/schemas/hosts.d.ts.map +1 -1
  136. package/dist/schemas/hosts.js +7 -5
  137. package/dist/schemas/hosts.js.map +1 -1
  138. package/dist/schemas/metrics.d.ts +108 -0
  139. package/dist/schemas/metrics.d.ts.map +1 -0
  140. package/dist/schemas/metrics.js +77 -0
  141. package/dist/schemas/metrics.js.map +1 -0
  142. package/dist/schemas/panels.d.ts +2 -2
  143. package/dist/schemas/panels.js +1 -1
  144. package/dist/schemas/panels.js.map +1 -1
  145. package/dist/schemas/personas.d.ts +1 -13
  146. package/dist/schemas/personas.d.ts.map +1 -1
  147. package/dist/schemas/personas.js +3 -20
  148. package/dist/schemas/personas.js.map +1 -1
  149. package/dist/schemas/providers/plan-limits.d.ts +1 -0
  150. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  151. package/dist/schemas/providers/plan-limits.js +6 -0
  152. package/dist/schemas/providers/plan-limits.js.map +1 -1
  153. package/dist/schemas/secrets.d.ts +1 -1
  154. package/dist/schemas/secrets.d.ts.map +1 -1
  155. package/dist/schemas/secrets.js +1 -1
  156. package/dist/schemas/secrets.js.map +1 -1
  157. package/dist/schemas/settings.d.ts +0 -42
  158. package/dist/schemas/settings.d.ts.map +1 -1
  159. package/dist/schemas/settings.js +1 -16
  160. package/dist/schemas/settings.js.map +1 -1
  161. package/dist/schemas/shared.d.ts +2 -2
  162. package/dist/schemas/shared.d.ts.map +1 -1
  163. package/dist/schemas/shared.js +3 -3
  164. package/dist/schemas/shared.js.map +1 -1
  165. package/dist/schemas/system-prompt.d.ts +104 -0
  166. package/dist/schemas/system-prompt.d.ts.map +1 -0
  167. package/dist/schemas/system-prompt.js +25 -0
  168. package/dist/schemas/system-prompt.js.map +1 -0
  169. package/dist/schemas/workflows.d.ts +2 -2
  170. package/dist/schemas/workflows.d.ts.map +1 -1
  171. package/dist/schemas/workflows.js +2 -2
  172. package/dist/schemas/workflows.js.map +1 -1
  173. package/dist/state/definition.d.ts +18 -10
  174. package/dist/state/definition.d.ts.map +1 -1
  175. package/dist/state/fix-attempt-plan.d.ts +4 -0
  176. package/dist/state/fix-attempt-plan.d.ts.map +1 -1
  177. package/dist/state/fix-attempt-plan.js +3 -2
  178. package/dist/state/fix-attempt-plan.js.map +1 -1
  179. package/dist/state/fix-stance.d.ts +1 -0
  180. package/dist/state/fix-stance.d.ts.map +1 -1
  181. package/dist/state/fix-stance.js +11 -0
  182. package/dist/state/fix-stance.js.map +1 -1
  183. package/dist/state/history-state.d.ts.map +1 -1
  184. package/dist/state/history-state.js +8 -1
  185. package/dist/state/history-state.js.map +1 -1
  186. package/dist/state/runtime-state.d.ts +0 -1
  187. package/dist/state/runtime-state.d.ts.map +1 -1
  188. package/dist/state/runtime-state.js +0 -1
  189. package/dist/state/runtime-state.js.map +1 -1
  190. package/dist/state/workspace-state.d.ts +4 -4
  191. package/dist/state/workspace-state.d.ts.map +1 -1
  192. package/dist/state/workspace-state.js +4 -4
  193. package/dist/state/workspace-state.js.map +1 -1
  194. package/dist/text/documents.d.ts +2 -2
  195. package/dist/text/documents.d.ts.map +1 -1
  196. package/dist/text/documents.js.map +1 -1
  197. package/dist/text/mentions.d.ts +1 -0
  198. package/dist/text/mentions.d.ts.map +1 -1
  199. package/dist/text/mentions.js +5 -1
  200. package/dist/text/mentions.js.map +1 -1
  201. package/dist/text/transcript-fold.d.ts +8 -3
  202. package/dist/text/transcript-fold.d.ts.map +1 -1
  203. package/dist/text/transcript-fold.js +83 -28
  204. package/dist/text/transcript-fold.js.map +1 -1
  205. package/package.json +9 -9
  206. package/src/chores/chores.test.ts +1 -1
  207. package/src/chores/digest.test.ts +1 -1
  208. package/src/chores/probes.test.ts +5 -2
  209. package/src/chores/stack.test.ts +1 -1
  210. package/src/chores/verdict.test.ts +6 -5
  211. package/src/contracts/agent.contract.ts +10 -9
  212. package/src/contracts/agents.contract.ts +35 -2
  213. package/src/contracts/capabilities.contract.ts +3 -3
  214. package/src/contracts/{host.contract.ts → device.contract.ts} +8 -6
  215. package/src/contracts/personas.contract.ts +1 -13
  216. package/src/contracts/runner.contract.ts +3 -2
  217. package/src/contracts/secrets.contract.ts +2 -2
  218. package/src/contracts/system.contract.ts +13 -2
  219. package/src/contracts/webext.contract.ts +1 -1
  220. package/src/events/agent-events.ts +37 -14
  221. package/src/events/agent-words.test.ts +68 -0
  222. package/src/events/agent-words.ts +124 -0
  223. package/src/events/{cards.ts → requests.ts} +44 -44
  224. package/src/events/resume.test.ts +8 -1
  225. package/src/events/resume.ts +4 -0
  226. package/src/events/transcript.ts +51 -22
  227. package/src/events/watch-wake.test.ts +3 -2
  228. package/src/events/watch-wake.ts +4 -0
  229. package/src/ids/conversation-ids.test.ts +3 -3
  230. package/src/ids/hostnames.test.ts +1 -1
  231. package/src/ids/share-paths.test.ts +1 -1
  232. package/src/ids/tunnel-ids.test.ts +1 -1
  233. package/src/index.ts +9 -4
  234. package/src/models/agent-catalog.test.ts +1 -1
  235. package/src/models/capability-ledger.test.ts +1 -1
  236. package/src/models/model-offer.test.ts +9 -2
  237. package/src/models/model-offer.ts +10 -7
  238. package/src/models/model-order.test.ts +1 -1
  239. package/src/models/model-pins.test.ts +1 -1
  240. package/src/models/model-roles.test.ts +1 -1
  241. package/src/models/model-roles.ts +6 -12
  242. package/src/models/plan-pools.test.ts +1 -1
  243. package/src/models/provider-specs.test.ts +3 -3
  244. package/src/policy/batch-runs.test.ts +1 -1
  245. package/src/policy/command-classes.test.ts +37 -2
  246. package/src/policy/command-classes.ts +16 -0
  247. package/src/policy/credential-material.test.ts +6 -10
  248. package/src/policy/fence-paths.test.ts +1 -1
  249. package/src/policy/overlay-lint.test.ts +1 -1
  250. package/src/policy/owner-ticket.test.ts +1 -1
  251. package/src/policy/persona-home.test.ts +1 -1
  252. package/src/policy/{card-status.ts → request-status.ts} +6 -6
  253. package/src/policy/safety-policy.test.ts +1 -1
  254. package/src/policy/safety-policy.ts +1 -0
  255. package/src/policy/search-globs.test.ts +1 -1
  256. package/src/policy/turned-away.ts +18 -0
  257. package/src/protocol/container-requirements.test.ts +1 -1
  258. package/src/protocol/ingress-contract.test.ts +8 -2
  259. package/src/protocol/ingress-protocol.test.ts +5 -4
  260. package/src/protocol/peer-dial.test.ts +111 -41
  261. package/src/protocol/peer-dial.ts +39 -4
  262. package/src/protocol/peer-mcp-server.test.ts +37 -7
  263. package/src/protocol/peer-mcp-server.ts +22 -1
  264. package/src/protocol/routes.test.ts +4 -2
  265. package/src/schemas/agent.ts +13 -0
  266. package/src/schemas/agents.ts +25 -0
  267. package/src/schemas/areas.test.ts +2 -8
  268. package/src/schemas/automations.ts +5 -5
  269. package/src/schemas/capabilities.ts +20 -9
  270. package/src/schemas/chat-route.ts +61 -0
  271. package/src/schemas/context-trim.test.ts +38 -0
  272. package/src/schemas/context-trim.ts +29 -0
  273. package/src/schemas/dev-rebuild-log.test.ts +1 -1
  274. package/src/schemas/devices.ts +25 -22
  275. package/src/schemas/extension-updates.ts +1 -1
  276. package/src/schemas/git/git.test.ts +11 -0
  277. package/src/schemas/git/git.ts +66 -0
  278. package/src/schemas/hosts.ts +15 -8
  279. package/src/schemas/issues.ts +1 -1
  280. package/src/schemas/metrics.ts +107 -0
  281. package/src/schemas/panels.ts +2 -2
  282. package/src/schemas/personas.ts +5 -28
  283. package/src/schemas/providers/plan-limits.ts +9 -0
  284. package/src/schemas/secrets.ts +1 -1
  285. package/src/schemas/settings.test.ts +1 -1
  286. package/src/schemas/settings.ts +9 -36
  287. package/src/schemas/shared.ts +6 -6
  288. package/src/schemas/system-prompt.ts +41 -0
  289. package/src/schemas/version-seam.test.ts +1 -1
  290. package/src/schemas/webext.ts +1 -1
  291. package/src/schemas/workflows.ts +4 -4
  292. package/src/state/contract-lock.test.ts +15 -11
  293. package/src/state/fix-attempt-plan.test.ts +10 -1
  294. package/src/state/fix-attempt-plan.ts +6 -2
  295. package/src/state/fix-stance.test.ts +10 -1
  296. package/src/state/fix-stance.ts +13 -0
  297. package/src/state/history-state.ts +17 -1
  298. package/src/state/runtime-state.test.ts +3 -9
  299. package/src/state/runtime-state.ts +0 -4
  300. package/src/state/versions.test.ts +1 -1
  301. package/src/state/workspace-state.test.ts +12 -7
  302. package/src/state/workspace-state.ts +4 -4
  303. package/src/text/archives.test.ts +1 -1
  304. package/src/text/documents.test.ts +1 -1
  305. package/src/text/documents.ts +2 -2
  306. package/src/text/embed.test.ts +17 -6
  307. package/src/text/emoji.test.ts +1 -1
  308. package/src/text/mentions.test.ts +20 -2
  309. package/src/text/mentions.ts +10 -1
  310. package/src/text/path-refs.test.ts +1 -1
  311. package/src/text/title.test.ts +1 -1
  312. package/src/text/transcript-fold.test.ts +183 -1
  313. package/src/text/transcript-fold.ts +146 -39
  314. package/src/text/whisper.test.ts +1 -1
  315. package/src/text/workflow-faults.test.ts +1 -1
  316. package/src/time/zone.test.ts +6 -6
  317. package/dist/contracts/host.contract.d.ts.map +0 -1
  318. package/dist/contracts/host.contract.js +0 -16
  319. package/dist/contracts/host.contract.js.map +0 -1
  320. package/dist/events/cards.d.ts.map +0 -1
  321. package/dist/events/cards.js.map +0 -1
  322. package/dist/policy/card-status.d.ts +0 -5
  323. package/dist/policy/card-status.d.ts.map +0 -1
  324. package/dist/policy/card-status.js.map +0 -1
  325. package/dist/schemas/model-route.d.ts +0 -27
  326. package/dist/schemas/model-route.d.ts.map +0 -1
  327. package/dist/schemas/model-route.js.map +0 -1
  328. package/src/schemas/model-route.ts +0 -43
@@ -1,8 +1,8 @@
1
1
  // What one of the user's own machines is running.
2
2
  import { z } from "zod";
3
- import { hostCardOf, hostEnvironmentOf, type HostFacts, HostFactsSchema, WslEnvironmentSchema } from "./hosts.js";
3
+ import { hostEntryOf, hostEnvironmentOf, type DeviceFacts, DeviceFactsSchema, WSL_SYSTEM_DISTROS, WslEnvironmentSchema } from "./hosts.js";
4
4
  import { DEV_VERSION } from "../state/versions.js";
5
- // Desktop-sync report shape shared by the agent, daemon and browser, produced only by `intentic-machine status --json`.
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
7
7
  // own pairing.
8
8
 
@@ -64,15 +64,20 @@ export const DeviceSandboxOpSchema = z.enum([
64
64
  "reshape",
65
65
  "remove",
66
66
  "logs",
67
- // Only op that redeems a fresh setup code; others recreate a container from its own existing env.
67
+ // The two ops that redeem a fresh setup code; every other one recreates a container from its own existing env.
68
+ // They differ only in which row the claim names — an existing sandbox on this machine, or one that has never run
69
+ // anywhere — which is why each checks the opposite thing about the slug before spending the code.
68
70
  "reconnect",
71
+ "create",
69
72
  "runner-up",
70
73
  "runner-remove",
71
74
  ]);
72
75
  export type DeviceSandboxOp = z.infer<typeof DeviceSandboxOpSchema>;
73
76
  export const DeviceSandboxFlowSchema = z.object({
74
77
  op: DeviceSandboxOpSchema,
75
- // Sandbox slug, or the runner's name for the runner ops (as `/system/runners`/`ic runner list` know it).
78
+ // Sandbox slug, the runner's name for the runner ops (as `/system/runners`/`ic runner list` know it), or for
79
+ // `create` the name the claim is about to produce — the one op where the slug is an expectation rather than a
80
+ // lookup, and the device refuses if anything already answers to it.
76
81
  slug: z.string().min(1),
77
82
  // Approved overlay's sha256, required only by `rebuild`; only content matching it is ever built.
78
83
  hash: z.string().optional(),
@@ -81,7 +86,7 @@ export const DeviceSandboxFlowSchema = z.object({
81
86
  // `runner-up` only, daemon-filled, never by the caller: the browser never holds the pairing credential.
82
87
  parentUrl: z.string().optional(),
83
88
  pair: z.string().optional().meta({ secret: true }),
84
- // Required by `reconnect` only; single-sandbox and short-lived, so a leak only buys a recreate.
89
+ // Required by `reconnect` and `create`; single-sandbox and short-lived, so a leak only buys one recreate.
85
90
  setupCode: z.string().optional().meta({ secret: true }),
86
91
  // `runner-up` only, daemon-filled: `definition` carries no capabilities or secrets; `overlay`/`overlayHash` are
87
92
  // re-verified by hash before build.
@@ -101,9 +106,13 @@ export const DeviceFlowLineSchema = z.discriminatedUnion("kind", [
101
106
  z.object({ kind: z.literal("error"), message: z.string() }),
102
107
  ]);
103
108
  export type DeviceFlowLine = z.infer<typeof DeviceFlowLineSchema>;
104
- // Both ops stop the resident process mid-command; a stream ending with no terminal frame is normal, not a failure, and
109
+ // Every op stops the resident process mid-command; a stream ending with no terminal frame is normal, not a failure, and
105
110
  // confirmation is the version moving on the next poll. `restart` is just a bare `intentic-machine run`.
106
- export const DeviceAgentOpSchema = z.enum(["upgrade", "restart"]);
111
+ // `forget-unreachable` drops the machine's links to sandboxes that have answered nothing for long enough to be gone
112
+ // (peer-dial's LONG_OUTAGE_ATTEMPTS) — never the link carrying the request, which is by definition answering. Never
113
+ // automatic either: a laptop closed for a fortnight must find its sandboxes again, so nothing expires on its own and
114
+ // dropping one is always somebody's decision.
115
+ export const DeviceAgentOpSchema = z.enum(["upgrade", "restart", "forget-unreachable"]);
107
116
  export type DeviceAgentOp = z.infer<typeof DeviceAgentOpSchema>;
108
117
  export const DeviceAgentFlowSchema = z.object({ op: DeviceAgentOpSchema });
109
118
  export type DeviceAgentFlow = z.infer<typeof DeviceAgentFlowSchema>;
@@ -114,7 +123,7 @@ export type DeviceAgentFlowInput = z.infer<typeof DeviceAgentFlowInputSchema>;
114
123
  // command: the same socket carries `run_command`, which would grant a shell on the user's machine.
115
124
  // `sync-unpair` runs the machine's own `sync uninstall --sandbox` (terminates both Mutagen sessions, drops the pairing,
116
125
  // self-revokes enrollment); revoking from the sandbox side is a different route.
117
- // `dev-reload`, `dev-rebuild` and `sync-install` are the three whose argv the daemon fills from what only it knows — the
126
+ // `dev-restart`, `dev-rebuild` and `sync-install` are the three whose argv the daemon fills from what only it knows — the
118
127
  // dev checkout on that machine, a freshly minted pairing token — which is also why none takes a path or a token from the
119
128
  // caller. The two dev commands are also why a machine-side op is the wrong shape for them: this door carries a COMMAND
120
129
  // STRING to a tool every released agent already has, so a sandbox can drive a machine whose agent predates the feature.
@@ -135,7 +144,7 @@ export const DeviceCommandSchema = z.enum([
135
144
  "sync-resume",
136
145
  "sync-unpair",
137
146
  "sync-clean",
138
- "dev-reload",
147
+ "dev-restart",
139
148
  "dev-rebuild",
140
149
  "dev-rebuild-log",
141
150
  "sync-install",
@@ -144,7 +153,7 @@ export type DeviceCommand = z.infer<typeof DeviceCommandSchema>;
144
153
  // The reversible sync switches: the subset a device's own row drives with a pair of buttons, as opposed to the two
145
154
  // commands a card elsewhere issues once. Its own type so those button tables stay total without carrying entries for
146
155
  // commands they can never send.
147
- export const DeviceSyncSwitchSchema = DeviceCommandSchema.exclude(["dev-reload", "dev-rebuild", "dev-rebuild-log", "sync-install", "sync-clean"]);
156
+ export const DeviceSyncSwitchSchema = DeviceCommandSchema.exclude(["dev-restart", "dev-rebuild", "dev-rebuild-log", "sync-install", "sync-clean"]);
148
157
  export type DeviceSyncSwitch = z.infer<typeof DeviceSyncSwitchSchema>;
149
158
 
150
159
  // WHERE A DETACHED REBUILD REPORTS ITSELF. `dev-rebuild` returns the moment the build is under way and nothing streams
@@ -368,7 +377,7 @@ export const reportQuiet = (report: DeviceReport, receivedAt: number): boolean =
368
377
  // distro, `native` for an install on the metal. Connect-time facts are read first, since no scope can withhold them;
369
378
  // facts from an agent too old to carry `hostname` say nothing, and a report without `wsl` is native, as it always
370
379
  // was. Undefined is an absence of evidence, never read as agreement.
371
- export const environmentOf = (facts: Pick<HostFacts, "hostname" | "wsl"> | undefined, report: DeviceReport | undefined): string | undefined => {
380
+ export const environmentOf = (facts: Pick<DeviceFacts, "hostname" | "wsl"> | undefined, report: DeviceReport | undefined): string | undefined => {
372
381
  const wsl = facts?.wsl ?? report?.wsl;
373
382
  if (wsl !== undefined) {
374
383
  return `wsl:${wsl.distro}`;
@@ -398,9 +407,7 @@ export const DeviceGapSchema = z.enum([
398
407
  // Connected, but "Run commands" is off on its capability card, so it won't describe itself: no folders, no
399
408
  // ports, no agent health. Says nothing about its containers, which ride their own switch (`Device.sandboxes`).
400
409
  "scope-off",
401
- // Reachable and asked, but has no `intentic-machine` installed, so nothing knows its folders or ports.
402
- "no-agent",
403
- // Sync-enrolled but has not posted a report yet; unlike `no-agent`, the agent is there and syncing.
410
+ // The agent is there but gave no report: never posted since enrolling, or refused `report` other than by its switch.
404
411
  "unreported",
405
412
  ]);
406
413
  export type DeviceGap = z.infer<typeof DeviceGapSchema>;
@@ -431,7 +438,7 @@ export const DeviceSchema = z.object({
431
438
  // Carried beside the report so a device with no report still has an OS; `platform` is the normalised slug, `facts`
432
439
  // its connect-time self-description.
433
440
  platform: z.string().optional(),
434
- facts: HostFactsSchema.optional(),
441
+ facts: DeviceFactsSchema.optional(),
435
442
  // Announced at connect; matches `report.agent.build` when known, the only version a report-less device has.
436
443
  agentVersion: z.string().optional(),
437
444
  lastSeen: z.number().optional(),
@@ -483,7 +490,7 @@ const hostnameKey = (device: Device): string | undefined => deviceHostname(devic
483
490
 
484
491
  // The card a device's door hangs off: one card is one computer, so two doors naming the same card are one machine
485
492
  // whatever either has said about itself — which is the whole of what an environment that has never connected says.
486
- const cardKey = (device: Device): string | undefined => (device.hostId === undefined ? undefined : hostCardOf(device.hostId));
493
+ const cardKey = (device: Device): string | undefined => (device.hostId === undefined ? undefined : hostEntryOf(device.hostId));
487
494
 
488
495
  // What makes two devices one computer: a shared card, or a shared hostname where a distro answers to it, since WSL
489
496
  // hands a distro the Windows machine's name. Two native installs that merely share a name stay two machines.
@@ -568,10 +575,6 @@ export const hostRunningSandbox = (devices: readonly Device[], slug: string | un
568
575
  // the line naming it would work under.
569
576
  const windowsPath = (path: string): boolean => /^([A-Za-z]:[\\/]|\\\\)/.test(path);
570
577
 
571
- // Docker Desktop's own distros. `wsl -l -q` lists them like any other and neither ever holds a checkout, so counting
572
- // them would make every Docker Desktop PC look ambiguous.
573
- const SYSTEM_DISTROS: ReadonlySet<string> = new Set(["docker-desktop", "docker-desktop-data"]);
574
-
575
578
  // How a line written for a host path reaches the environment that holds it, through one door. `none` carries the
576
579
  // distros that were candidates, since "this PC has two and nothing says which" is a different answer for the reader
577
580
  // than "there is no way in".
@@ -589,11 +592,11 @@ export type PathReach =
589
592
  // an argument it would reject. Two real distros stay a refusal that names them — nothing here knows which one has the
590
593
  // folder, and the distro's own door, once connected, answers directly.
591
594
  // A platform that never said what it is stays a candidate, as every other reading of device evidence does.
592
- export const pathReach = (platform: string | undefined, facts: HostFacts | undefined, path: string): PathReach => {
595
+ export const pathReach = (platform: string | undefined, facts: DeviceFacts | undefined, path: string): PathReach => {
593
596
  if (platform === undefined || (platform === "windows") === windowsPath(path)) {
594
597
  return { kind: "direct" };
595
598
  }
596
- const distros = platform === "windows" ? (facts?.wslDistros ?? []).filter((distro) => !SYSTEM_DISTROS.has(distro)) : [];
599
+ const distros = platform === "windows" ? (facts?.wslDistros ?? []).filter((distro) => !WSL_SYSTEM_DISTROS.has(distro)) : [];
597
600
  const only = distros.length === 1 ? distros[0] : undefined;
598
601
  return only === undefined ? { kind: "none", distros } : { kind: "wsl", distro: only };
599
602
  };
@@ -264,7 +264,7 @@ export const WorkspaceExtensionCreatedSchema = z.object({
264
264
  export const ExtensionRemovalConnectionSchema = z.object({
265
265
  id: z.string().describe("The name the owner gave it, which is also the agent's handle for it."),
266
266
  kind: z.string().describe("Which core kind it is underneath: cli, browser, host or webext."),
267
- card: z.string().describe("The card it was added from, named as the grid names it."),
267
+ entry: z.string().describe("The catalog entry it was added from, named as the grid names it."),
268
268
  secrets: z
269
269
  .array(z.string())
270
270
  .describe("Credential fields stored for it, by name. The values are deleted with the entry and cannot be recovered from here."),
@@ -0,0 +1,11 @@
1
+ import { test, expect } from "bun:test";
2
+ import { isScratch } from "./git.js";
3
+
4
+ test("a scratch directory entry covers everything under it, and a file entry only itself", () => {
5
+ const scratch = [{ path: ".trun/" }, { path: "src/debug.log" }];
6
+
7
+ expect(isScratch(".trun/deep/x.log", scratch)).toBe(true);
8
+ expect(isScratch(".trunk/x", scratch)).toBe(false);
9
+ expect(isScratch("src/debug.log", scratch)).toBe(true);
10
+ expect(isScratch("src/debug.log.txt", scratch)).toBe(false);
11
+ });
@@ -233,6 +233,23 @@ export const GitOperationStateSchema = z.object({
233
233
  ),
234
234
  });
235
235
  export type GitOperationState = z.infer<typeof GitOperationStateSchema>;
236
+ // Judged by shape alone: the names scratch arrives under are made up fresh every time, so no list of names could hold.
237
+ export const ScratchReasonSchema = z
238
+ .enum(["hidden", "byproduct", "checkout", "oversized", "root"])
239
+ .describe(
240
+ "Why it looks like scratch. A new hidden directory that is not one a project keeps on purpose (like `.github`). A log, dump, backup or editor leftover. A git checkout of its own. A new file past the size source code reaches. Or a new dotfile at the top of a workspace whose projects are the repositories inside it.",
241
+ );
242
+ export type ScratchReason = z.infer<typeof ScratchReasonSchema>;
243
+ export const ScratchPathSchema = z.object({
244
+ path: z.string().describe("Relative to its repository. A directory ends in a slash and stands for everything inside it."),
245
+ reason: ScratchReasonSchema,
246
+ files: z.number().optional().describe("How many files it holds. Absent for a checkout of its own, whose contents are not walked."),
247
+ bytes: z.number().optional().describe("Their total size in bytes. Absent exactly when `files` is."),
248
+ });
249
+ export type ScratchPath = z.infer<typeof ScratchPathSchema>;
250
+ // Whether a scratch entry covers `path`: a directory entry everything under it, a file entry only itself.
251
+ export const isScratch = (path: string, scratch: readonly Pick<ScratchPath, "path">[]): boolean =>
252
+ scratch.some((entry) => (entry.path.endsWith("/") ? path.startsWith(entry.path) : path === entry.path));
236
253
  export const RepoChangesSchema = z.object({
237
254
  // The {repo} param the per-repo git routes accept: "root" or a repo id (its root-relative dir).
238
255
  repo: z.string(),
@@ -267,6 +284,14 @@ export const RepoChangesSchema = z.object({
267
284
  .describe(
268
285
  "How many changes were cut from each of the two lists above. A freshly cloned monorepo or a mass delete runs to six figures, which no screen can draw, so past a budget the lists arrive short and this says by how much on each side. Absent means they are complete.",
269
286
  ),
287
+ // A subset of `unstaged`'s untracked rows, restated by the entry that covers them rather than flagged per row, since
288
+ // one scratch directory can stand for thousands of files.
289
+ scratch: z
290
+ .array(ScratchPathSchema)
291
+ .optional()
292
+ .describe(
293
+ "Untracked paths that look like scratch. Staging or committing everything leaves them out, while staging one by its own path takes it like any other file. Absent when there are none.",
294
+ ),
270
295
  // 0 for `ahead`/`behind` with no remote or no upstream.
271
296
  remote: GitRemoteStateSchema.optional().describe("Where this repository stands against its remote."),
272
297
  // Keyed by path, not on each change, since one path can appear on two sides with the same origin. Ids only; look up
@@ -394,9 +419,50 @@ export const AgentChangesSchema = z.object({
394
419
  .describe(
395
420
  "Why the last merge refused, when one did. Carried here as well as in the merge's own answer, because a conflict is found the moment a turn ends and dealt with hours later on this surface, which would otherwise open with nothing to explain what it promised to resolve.",
396
421
  ),
422
+ // On the response, not on a repository's own row: a repository whose branch holds nothing gets no row at all, and
423
+ // that is exactly the case where the conversation did all of its work on the other branch.
424
+ elsewhere: z
425
+ .array(
426
+ z.object({
427
+ repo: z.string().describe("Which repository."),
428
+ branch: z
429
+ .string()
430
+ .optional()
431
+ .describe("The branch its copy is standing on. Absent where it stands on no branch at all, which is a state git allows."),
432
+ }),
433
+ )
434
+ .optional()
435
+ .describe(
436
+ "Repositories whose copy the conversation left standing on a different branch of its own. What is listed for them is this conversation's branch as it was last left, not what its copy holds now, and anything it has written since went to the other branch.",
437
+ ),
438
+ // Read off the live copy, so a retired one has none: its scratch went with it.
439
+ scratch: z
440
+ .array(
441
+ z.object({
442
+ repo: z.string().describe("Which repository."),
443
+ paths: z.array(ScratchPathSchema).describe("What it keeps out there."),
444
+ }),
445
+ )
446
+ .optional()
447
+ .describe(
448
+ "Untracked files the conversation left in its copy that look like scratch: logs, probe scripts, dumps, a checkout of its own. They are not in the list above and no merge carries them. They stay in its copy until they are included or deleted, and go with the copy when it is archived or retired. Absent when there are none.",
449
+ ),
397
450
  });
398
451
  export type AgentChanges = z.infer<typeof AgentChangesSchema>;
399
452
 
453
+ // Names scratch exactly as `AgentChanges.scratch` lists it; anything else is refused, so a stale list cannot reach a
454
+ // file that was never scratch.
455
+ export const AgentScratchSchema = z.object({
456
+ id: z.string().min(1).describe("Which conversation."),
457
+ repo: z.string().min(1).describe("Which repository of its composition."),
458
+ paths: z
459
+ .array(z.string().min(1))
460
+ .min(1)
461
+ .max(MAX_ACTION_PATHS)
462
+ .describe("Paths exactly as the review lists them under scratch, a directory with its trailing slash."),
463
+ });
464
+ export type AgentScratch = z.infer<typeof AgentScratchSchema>;
465
+
400
466
  // Own read (a `git log` per repo), not folded into the review, whose rows are differences against main and these are
401
467
  // not. A commit CARRIES the work without authoring it: a path is attributed to the newest commit that left it there.
402
468
  export const AgentHistoryCommitSchema = z.object({
@@ -11,7 +11,7 @@ export type WslEnvironment = z.infer<typeof WslEnvironmentSchema>;
11
11
 
12
12
  // What a machine reports once at connect (`host.describe`), cached until it reconnects: the skill pack says how to
13
13
  // drive Windows, this says which Windows it is.
14
- export const HostFactsSchema = z.object({
14
+ export const DeviceFactsSchema = z.object({
15
15
  // The OS's own name for itself, e.g. "Windows 11 Pro 24H2".
16
16
  os: z.string(),
17
17
  arch: z.string(),
@@ -31,8 +31,15 @@ export const HostFactsSchema = z.object({
31
31
  wsl: WslEnvironmentSchema.optional(),
32
32
  // Windows only: the distros `wsl -l -q` lists, the names `run_command`'s `in: "wsl:<name>"` accepts.
33
33
  wslDistros: z.array(z.string()).optional(),
34
+ // What this machine's agent is holding links to, and how much of it is answering nothing. COUNTS AND AN AGE ONLY:
35
+ // the rest are other sandboxes' addresses, and a sandbox has no business learning its siblings'. Absent from an
36
+ // agent older than this field, which is why "none unreachable" is not the same value as "did not say".
37
+ links: z.object({ total: z.number(), unreachable: z.number(), unreachableSince: z.number().optional() }).optional(),
34
38
  });
35
- export type HostFacts = z.infer<typeof HostFactsSchema>;
39
+ export type DeviceFacts = z.infer<typeof DeviceFactsSchema>;
40
+
41
+ // Docker Desktop's own distros: `wsl -l -q` lists them like any other, and none ever runs an agent or holds a checkout.
42
+ export const WSL_SYSTEM_DISTROS: ReadonlySet<string> = new Set(["docker-desktop", "docker-desktop-data"]);
36
43
 
37
44
  // One folder, two names. Windows sees a distro's files under a UNC share and a distro sees the Windows drives under
38
45
  // /mnt, so a path handed across the boundary is translated here rather than by hand at every call site.
@@ -54,13 +61,13 @@ const ENVIRONMENT_SEPARATOR = "::";
54
61
  // Which environment a machine is describing when it connects: the distro by the name WSL registered (what `wsl -l -q`
55
62
  // prints and `in: "wsl:<name>"` takes), or the metal. Facts arrive at connect and no scope withholds them, so this is
56
63
  // always answerable — unlike `environmentOf` (devices.ts), which weighs a report too and may hold no evidence at all.
57
- export const environmentKeyOf = (facts: Pick<HostFacts, "wsl">): string =>
64
+ export const environmentKeyOf = (facts: Pick<DeviceFacts, "wsl">): string =>
58
65
  facts.wsl === undefined ? HOST_NATIVE_ENVIRONMENT : `wsl:${facts.wsl.distro}`;
59
66
 
60
- export const hostConnectionKey = (card: string, environment: string): string =>
61
- environment === HOST_NATIVE_ENVIRONMENT ? card : `${card}${ENVIRONMENT_SEPARATOR}${environment}`;
67
+ export const hostConnectionKey = (entry: string, environment: string): string =>
68
+ environment === HOST_NATIVE_ENVIRONMENT ? entry : `${entry}${ENVIRONMENT_SEPARATOR}${environment}`;
62
69
 
63
- export const hostCardOf = (connection: string): string => connection.split(ENVIRONMENT_SEPARATOR)[0] ?? connection;
70
+ export const hostEntryOf = (connection: string): string => connection.split(ENVIRONMENT_SEPARATOR)[0] ?? connection;
64
71
 
65
72
  export const hostEnvironmentOf = (connection: string): string => {
66
73
  const at = connection.indexOf(ENVIRONMENT_SEPARATOR);
@@ -75,7 +82,7 @@ export const HostEnvironmentSchema = z.object({
75
82
  online: z.boolean(),
76
83
  version: z.string().optional(),
77
84
  lastSeen: z.number().optional(),
78
- facts: HostFactsSchema.optional(),
85
+ facts: DeviceFactsSchema.optional(),
79
86
  });
80
87
  export type HostEnvironment = z.infer<typeof HostEnvironmentSchema>;
81
88
 
@@ -92,7 +99,7 @@ export const HostSummarySchema = z.object({
92
99
  version: z.string().optional(),
93
100
  // Epoch ms of the last held socket; absent means not since this daemon booted (liveness resets on restart).
94
101
  lastSeen: z.number().optional(),
95
- facts: HostFactsSchema.optional(),
102
+ facts: DeviceFactsSchema.optional(),
96
103
  });
97
104
  export type HostSummary = z.infer<typeof HostSummarySchema>;
98
105
  export const HostsListSchema = z.object({ hosts: z.array(HostSummarySchema) });
@@ -193,7 +193,7 @@ export const IssueIntakeIdParamSchema = z.object({ automationId: entryId.describ
193
193
 
194
194
  // Constants both the editor and the route need, so a limit is visible to the owner before it is hit.
195
195
 
196
- // Default daily ceiling; higher than the Front Desk's 200 since a report is only sometimes a turn (dedup decides),
196
+ // Default daily ceiling; higher than the Visitor chat's 200 since a report is only sometimes a turn (dedup decides),
197
197
  // unlike a visitor message.
198
198
  export const ISSUES_DAILY_MAX_DEFAULT = 2000;
199
199
 
@@ -0,0 +1,107 @@
1
+ import { z } from "zod";
2
+
3
+ // What the sandbox is using right now, measured only when asked (GET /system/metrics). CPU percentages are of ONE core
4
+ // unless the field says otherwise; memory is resident bytes.
5
+
6
+ // The kinds of process a sandbox runs, as the daemon classifies them by command line.
7
+ export const PROCESS_ROLES = [
8
+ "languageServer",
9
+ "searchEngine",
10
+ "agentRuntime",
11
+ "browser",
12
+ "git",
13
+ "translator",
14
+ "extension",
15
+ "terminal",
16
+ // A build, test, typecheck or lint run and the package manager driving it: the fan-out shape, which is what every
17
+ // memory peak the resource log has recorded was made of.
18
+ "toolchain",
19
+ // An inference server this sandbox runs for itself.
20
+ "localModel",
21
+ // A process of a nested Docker container: charged to this cgroup, visible to nothing else here.
22
+ "container",
23
+ "other",
24
+ ] as const;
25
+ export const ProcessRoleSchema = z.enum(PROCESS_ROLES);
26
+ export type ProcessRole = z.infer<typeof ProcessRoleSchema>;
27
+
28
+ export const ProcessGroupMetricsSchema = z.object({
29
+ processes: z.number().describe("How many processes."),
30
+ rssBytes: z
31
+ .number()
32
+ .describe(
33
+ "Their resident memory added up, in bytes. Memory two processes share is counted in each, so this can exceed what they cost together.",
34
+ ),
35
+ });
36
+ export type ProcessGroupMetrics = z.infer<typeof ProcessGroupMetricsSchema>;
37
+
38
+ export const SessionMetricsSchema = ProcessGroupMetricsSchema.extend({
39
+ cpuPercent: z
40
+ .number()
41
+ .optional()
42
+ .describe(
43
+ "CPU its processes used over the window, as a percentage of one core, so a conversation busy on four cores reads 400. Counts commands that finished inside the window too. Absent on a first reading, which has nothing earlier to measure from.",
44
+ ),
45
+ });
46
+ export type SessionMetrics = z.infer<typeof SessionMetricsSchema>;
47
+
48
+ // Linux pressure stall information: the share of the last ten seconds in which some task waited on the resource.
49
+ export const PressureMetricsSchema = z.object({
50
+ cpu: z.number().describe("Percent of the last ten seconds in which something was waiting for a CPU."),
51
+ memory: z.number().describe("Percent of the last ten seconds in which something was waiting on memory: reclaim, swap-in, or a refault."),
52
+ io: z.number().describe("Percent of the last ten seconds in which something was waiting on disk."),
53
+ });
54
+ export type PressureMetrics = z.infer<typeof PressureMetricsSchema>;
55
+
56
+ export const SandboxUsageSchema = z.object({
57
+ cpuPercent: z
58
+ .number()
59
+ .optional()
60
+ .describe("CPU the whole sandbox used over the window, as a percentage of all it may use (`cores`). Absent on a first reading."),
61
+ cores: z.number().describe("How many cores the sandbox may use: its CPU quota, or every core it is allowed to run on when it has none."),
62
+ memoryBytes: z
63
+ .number()
64
+ .describe("Memory in use, less the file cache the kernel takes back on demand: the figure that runs into the limit."),
65
+ memoryLimitBytes: z.number().describe("The memory limit: the container's own, or the machine's memory when the container has none."),
66
+ swapBytes: z.number().optional().describe("Memory pushed out to swap. Absent where the sandbox cannot see its own swap."),
67
+ diskBytes: z.number().optional().describe("Space used on the volume the workspace lives on. Absent when the volume would not say."),
68
+ diskTotalBytes: z.number().optional().describe("That volume's size. Absent when the volume would not say."),
69
+ loadAverage: z
70
+ .tuple([z.number(), z.number(), z.number()])
71
+ .describe("The load average over 1, 5 and 15 minutes. It is the machine's, so other sandboxes on it count too."),
72
+ processes: z.number().describe("How many processes are running in the sandbox."),
73
+ pressure: PressureMetricsSchema.optional().describe("How much work waited on CPU, memory or disk lately. Absent where the kernel does not report it."),
74
+ });
75
+ export type SandboxUsage = z.infer<typeof SandboxUsageSchema>;
76
+
77
+ export const DaemonUsageSchema = z.object({
78
+ rssBytes: z.number().describe("The daemon's own resident memory."),
79
+ heapUsedBytes: z.number().describe("Of that, JavaScript objects in use."),
80
+ cpuPercent: z.number().optional().describe("CPU the daemon itself used over the window, as a percentage of one core. Absent on a first reading."),
81
+ eventLoopPercent: z
82
+ .number()
83
+ .optional()
84
+ .describe(
85
+ "How much of the window the daemon spent busy rather than waiting. Near 100, every request queues behind whatever it is doing. Absent on a first reading.",
86
+ ),
87
+ });
88
+ export type DaemonUsage = z.infer<typeof DaemonUsageSchema>;
89
+
90
+ export const SandboxMetricsSchema = z.object({
91
+ at: z.number().describe("When this reading was taken, in milliseconds."),
92
+ windowMs: z
93
+ .number()
94
+ .optional()
95
+ .describe("How long the CPU figures were measured over, in milliseconds. Absent on a first reading, and then so is every CPU figure."),
96
+ sandbox: SandboxUsageSchema.describe("The sandbox as a whole."),
97
+ daemon: DaemonUsageSchema.describe("The daemon that runs it, which none of the other figures include."),
98
+ sessions: z
99
+ .record(z.string(), SessionMetricsSchema)
100
+ .describe(
101
+ "What each conversation's processes use, by conversation id: the agent's own process and everything it started. Only conversations with processes running, and only those the caller may see.",
102
+ ),
103
+ roles: z
104
+ .partialRecord(ProcessRoleSchema, ProcessGroupMetricsSchema)
105
+ .describe("Every process in the sandbox but the daemon, by what kind of work it is. A kind with nothing running is absent."),
106
+ });
107
+ export type SandboxMetrics = z.infer<typeof SandboxMetricsSchema>;
@@ -70,14 +70,14 @@ export const PanelSummarySchema = z.object({
70
70
  // desiredState: desired-state.json (present after the first resolve).
71
71
  // directoryUi: .intentic/ui/index.html.
72
72
  // monorepo: pnpm-workspace.yaml + turbo.json.
73
- // vitest: a root vitest.config.ts, or "vitest" in the manifest/workspace catalog.
73
+ // tests: a root `test` script or runner config (vitest.config.ts, bunfig.toml), or "vitest" in the workspace catalog.
74
74
  // userStories: docs/user-stories.
75
75
  // docs: docs/architecture.
76
76
  deployConfig: z.boolean().describe("It declares infrastructure."),
77
77
  desiredState: z.boolean().describe("That declaration has been resolved at least once."),
78
78
  directoryUi: z.boolean().describe("It carries a small interface of its own."),
79
79
  monorepo: z.boolean().describe("It holds several packages."),
80
- vitest: z.boolean().describe("It has tests that can be run."),
80
+ tests: z.boolean().describe("It has tests that can be run."),
81
81
  userStories: z
82
82
  .boolean()
83
83
  .describe("It carries stories an agent could test the running app against. The one fact here that says nothing about the language."),
@@ -65,7 +65,7 @@ export const PersonaContextSchema = z.object({
65
65
  .array(z.string().min(1).max(200))
66
66
  .max(50)
67
67
  .describe(
68
- "Which nested repositories a conversation wearing this card carries, by workspace-relative path. The workspace itself is always carried; empty means the workspace alone.",
68
+ "Which nested repositories a conversation on this persona carries, by workspace-relative path. The workspace itself is always carried; empty means the workspace alone.",
69
69
  ),
70
70
  });
71
71
  export type PersonaContext = z.infer<typeof PersonaContextSchema>;
@@ -177,7 +177,7 @@ export const PersonaBriefingSchema = z.object({
177
177
  .array(TurnBriefingNoteIdSchema)
178
178
  .max(20)
179
179
  .describe(
180
- "Which of the notes the sandbox prepends to each message a conversation wearing this card does NOT get. Everything not named here is sent as usual; the notes that keep a turn inside its own branch or explain a missing account cannot be named at all.",
180
+ "Which of the notes the sandbox prepends to each message a conversation on this persona does NOT get. Everything not named here is sent as usual; the notes that keep a turn inside its own branch or explain a missing account cannot be named at all.",
181
181
  ),
182
182
  });
183
183
  export type PersonaBriefing = z.infer<typeof PersonaBriefingSchema>;
@@ -229,33 +229,10 @@ export type Persona = z.infer<typeof PersonaSchema>;
229
229
  // has no opinion — the caller decides, not "run nothing".
230
230
  export const personaModels = (card: Pick<Persona, "models">, sources: readonly ModelSource[]): readonly ModelPin[] =>
231
231
  readyChain(sources, card.models ?? []);
232
- // Asked once per chat, on the message it was sent with; answers with the one card the message belongs to, or none.
233
- // `folder`/`paths` are the facts a card's `context`/`startIn` can be matched against that words alone can't supply.
234
- export const PersonaRouteAskSchema = z.object({
235
- prompt: z.string().min(1).max(20000).describe("The message a new chat is about to open with."),
236
- folder: z.string().max(200).optional().describe("The workspace folder the chat was opened in, when it was opened in one."),
237
- paths: z
238
- .array(z.string().min(1).max(500))
239
- .max(50)
240
- .default([])
241
- .describe("Workspace paths the message names: uploads, @-mentions, the editor's own file."),
242
- });
243
- export type PersonaRouteAsk = z.infer<typeof PersonaRouteAskSchema>;
244
- export const PersonaRouteSchema = z.object({
245
- persona: entryId.optional().describe("The card this message belongs to, or absent when none does and the chat should stay open to everything."),
246
- reason: z.string().describe("Why, in the one line a chat can show. Present whether or not a card was named."),
247
- // Absent means nothing was spent: matched on the folder, or answered before any model was reached.
248
- model: z
249
- .string()
250
- .optional()
251
- .describe(
252
- "Which model answered, as `provider:model`, so the chat can name what the reading cost. Absent when no model was asked at all, which a folder match and an empty persona list both are.",
253
- ),
254
- });
255
- export type PersonaRoute = z.infer<typeof PersonaRouteSchema>;
232
+ // Which persona a new chat belongs to is asked with what it runs on, in one reading: chat-route.ts.
256
233
  // The one stock persona id; lives here because the daemon and the automations form are separate packages that must
257
- // agree on it exactly, or a Front Desk pins to a card nobody creates.
258
- export const FRONT_DESK_PERSONA = "front-desk";
234
+ // agree on it exactly, or a Visitor chat pins to a card nobody creates.
235
+ export const VISITOR_CHAT_PERSONA = "visitor-chat";
259
236
  // One phrase describing how bounded a card is, shared so the Personas page and the automation picker can't grow
260
237
  // different vocabularies for the same card. "Read-only"/"no shell" are named; everything else collapses to a count.
261
238
  export const personaBounds = (persona: Persona): string => {
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { TRANSLATOR_PROVIDERS, type TranslatorProvider } from "../../models/provider-specs.js";
3
3
  import { AgentHarnessSchema, AgentProviderSchema, EditorContextSchema } from "../agent.js";
4
+ import { MENTION_LIMIT } from "../../text/mentions.js";
4
5
  // Headroom is one shape shared by every provider, not a Claude idea others imitate: a native account and a routed
5
6
  // subscription differ in who holds the credential, never in what a reading is. Every surface that draws a percentage
6
7
  // reads this one type.
@@ -218,6 +219,14 @@ export const SteerSchema = z
218
219
  .max(20)
219
220
  .optional()
220
221
  .describe("Files to send with it, as workspace paths. A screenshot dropped in mid-turn with no words is a legitimate thing to send."),
222
+ // Same split as a turn's: a guessed path is dropped, only a chosen attachment can refuse the steer.
223
+ mentions: z
224
+ .array(z.string().min(1))
225
+ .max(MENTION_LIMIT)
226
+ .optional()
227
+ .describe(
228
+ "Workspace paths the message mentions with `@`. Unlike attachments, one that escapes the workspace or names no file is ignored rather than refused.",
229
+ ),
221
230
  editorContext: EditorContextSchema.optional().describe("What you have open, folded in so that pointing words resolve."),
222
231
  })
223
232
  // An attachment-only steer (a screenshot dropped in mid-turn) is legal; an entirely empty one is not.
@@ -62,7 +62,7 @@ export const CredentialGatesSchema = z.object({
62
62
  });
63
63
  export type CredentialGates = z.infer<typeof CredentialGatesSchema>;
64
64
 
65
- export const CredentialGateSubjectParamSchema = z.object({
65
+ export const CredentialGuardSubjectParamSchema = z.object({
66
66
  subject: z.string().min(1).describe("Which gate, by the secret name or capability id it covers."),
67
67
  });
68
68
 
@@ -1,4 +1,4 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { describe, test, expect } from "bun:test";
2
2
  import { RuleSchema } from "./settings.js";
3
3
 
4
4
  // A rule that saves cleanly and silently does nothing is the failure this schema exists to refuse: an action, and a