@intentic/sandbox-contract 1.343.0 → 1.345.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 (325) hide show
  1. package/README.md +10 -0
  2. package/dist/contracts/accounts.contract.d.ts +24 -0
  3. package/dist/contracts/accounts.contract.d.ts.map +1 -1
  4. package/dist/contracts/agent.contract.d.ts +214 -6
  5. package/dist/contracts/agent.contract.d.ts.map +1 -1
  6. package/dist/contracts/agent.contract.js +13 -2
  7. package/dist/contracts/agent.contract.js.map +1 -1
  8. package/dist/contracts/agents.contract.d.ts +656 -0
  9. package/dist/contracts/agents.contract.d.ts.map +1 -1
  10. package/dist/contracts/agents.contract.js +5 -3
  11. package/dist/contracts/agents.contract.js.map +1 -1
  12. package/dist/contracts/device.contract.d.ts +259 -0
  13. package/dist/contracts/device.contract.d.ts.map +1 -1
  14. package/dist/contracts/device.contract.js +5 -1
  15. package/dist/contracts/device.contract.js.map +1 -1
  16. package/dist/contracts/endpoints.contract.d.ts +2 -0
  17. package/dist/contracts/endpoints.contract.d.ts.map +1 -1
  18. package/dist/contracts/endpoints.contract.js +1 -0
  19. package/dist/contracts/endpoints.contract.js.map +1 -1
  20. package/dist/contracts/push.contract.d.ts +11 -0
  21. package/dist/contracts/push.contract.d.ts.map +1 -1
  22. package/dist/contracts/push.contract.js +10 -1
  23. package/dist/contracts/push.contract.js.map +1 -1
  24. package/dist/contracts/runner.contract.d.ts +18 -2
  25. package/dist/contracts/runner.contract.d.ts.map +1 -1
  26. package/dist/contracts/secrets.contract.d.ts +169 -0
  27. package/dist/contracts/secrets.contract.d.ts.map +1 -1
  28. package/dist/contracts/secrets.contract.js +35 -0
  29. package/dist/contracts/secrets.contract.js.map +1 -1
  30. package/dist/contracts/sessions.contract.d.ts +2 -0
  31. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  32. package/dist/contracts/settings.contract.d.ts +37 -2
  33. package/dist/contracts/settings.contract.d.ts.map +1 -1
  34. package/dist/contracts/system.contract.d.ts +185 -35
  35. package/dist/contracts/system.contract.d.ts.map +1 -1
  36. package/dist/contracts/translator.contract.d.ts +24 -0
  37. package/dist/contracts/translator.contract.d.ts.map +1 -1
  38. package/dist/contracts/workspace.contract.d.ts +11 -0
  39. package/dist/contracts/workspace.contract.d.ts.map +1 -1
  40. package/dist/contracts/workspace.contract.js +11 -1
  41. package/dist/contracts/workspace.contract.js.map +1 -1
  42. package/dist/events/agent-events.d.ts +18 -0
  43. package/dist/events/agent-events.d.ts.map +1 -1
  44. package/dist/events/agent-events.js +3 -1
  45. package/dist/events/agent-events.js.map +1 -1
  46. package/dist/events/agent-transcript.d.ts +727 -0
  47. package/dist/events/agent-transcript.d.ts.map +1 -0
  48. package/dist/events/agent-transcript.js +34 -0
  49. package/dist/events/agent-transcript.js.map +1 -0
  50. package/dist/events/agent-words.d.ts +5 -0
  51. package/dist/events/agent-words.d.ts.map +1 -1
  52. package/dist/events/agent-words.js +9 -0
  53. package/dist/events/agent-words.js.map +1 -1
  54. package/dist/events/requests.d.ts +143 -0
  55. package/dist/events/requests.d.ts.map +1 -1
  56. package/dist/events/requests.js +35 -1
  57. package/dist/events/requests.js.map +1 -1
  58. package/dist/events/sandbox-notice.d.ts +7 -0
  59. package/dist/events/sandbox-notice.d.ts.map +1 -1
  60. package/dist/events/sandbox-notice.js +1 -0
  61. package/dist/events/sandbox-notice.js.map +1 -1
  62. package/dist/events/system-events.d.ts +88 -0
  63. package/dist/events/system-events.d.ts.map +1 -1
  64. package/dist/events/transcript.d.ts +20 -720
  65. package/dist/events/transcript.d.ts.map +1 -1
  66. package/dist/events/transcript.js +17 -19
  67. package/dist/events/transcript.js.map +1 -1
  68. package/dist/events/undelivered.d.ts +4 -0
  69. package/dist/events/undelivered.d.ts.map +1 -1
  70. package/dist/events/undelivered.js +13 -0
  71. package/dist/events/undelivered.js.map +1 -1
  72. package/dist/ids/conversation-ids.d.ts +2 -0
  73. package/dist/ids/conversation-ids.d.ts.map +1 -1
  74. package/dist/ids/conversation-ids.js +2 -0
  75. package/dist/ids/conversation-ids.js.map +1 -1
  76. package/dist/ids/row-ids.d.ts +3 -0
  77. package/dist/ids/row-ids.d.ts.map +1 -0
  78. package/dist/ids/row-ids.js +11 -0
  79. package/dist/ids/row-ids.js.map +1 -0
  80. package/dist/index.d.ts +1339 -48
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/models/agent-lanes.d.ts.map +1 -1
  83. package/dist/models/agent-lanes.js +3 -2
  84. package/dist/models/agent-lanes.js.map +1 -1
  85. package/dist/models/agent-standing.d.ts +4 -0
  86. package/dist/models/agent-standing.d.ts.map +1 -1
  87. package/dist/models/agent-standing.js +2 -1
  88. package/dist/models/agent-standing.js.map +1 -1
  89. package/dist/models/plan-pools.d.ts +5 -1
  90. package/dist/models/plan-pools.d.ts.map +1 -1
  91. package/dist/models/plan-pools.js +57 -21
  92. package/dist/models/plan-pools.js.map +1 -1
  93. package/dist/netd/generated/wire.d.ts +5 -0
  94. package/dist/netd/generated/wire.d.ts.map +1 -1
  95. package/dist/policy/command-classes.d.ts +1 -1
  96. package/dist/policy/command-classes.d.ts.map +1 -1
  97. package/dist/policy/command-classes.js +2 -2
  98. package/dist/policy/command-classes.js.map +1 -1
  99. package/dist/policy/keyring-origins.d.ts +27 -0
  100. package/dist/policy/keyring-origins.d.ts.map +1 -0
  101. package/dist/policy/keyring-origins.js +213 -0
  102. package/dist/policy/keyring-origins.js.map +1 -0
  103. package/dist/policy/keyring-seal.d.ts +9 -0
  104. package/dist/policy/keyring-seal.d.ts.map +1 -1
  105. package/dist/policy/keyring-seal.js +18 -6
  106. package/dist/policy/keyring-seal.js.map +1 -1
  107. package/dist/policy/safety-policy.d.ts +1 -1
  108. package/dist/policy/safety-policy.d.ts.map +1 -1
  109. package/dist/policy/safety-policy.js +1 -1
  110. package/dist/policy/safety-policy.js.map +1 -1
  111. package/dist/policy/turned-away.d.ts +1 -0
  112. package/dist/policy/turned-away.d.ts.map +1 -1
  113. package/dist/policy/turned-away.js +11 -0
  114. package/dist/policy/turned-away.js.map +1 -1
  115. package/dist/protocol/doors/host-protocol.d.ts +1 -0
  116. package/dist/protocol/doors/host-protocol.d.ts.map +1 -1
  117. package/dist/protocol/doors/host-protocol.js +1 -0
  118. package/dist/protocol/doors/host-protocol.js.map +1 -1
  119. package/dist/protocol/doors/webext-links.d.ts +1 -0
  120. package/dist/protocol/doors/webext-links.d.ts.map +1 -1
  121. package/dist/protocol/doors/webext-links.js +1 -0
  122. package/dist/protocol/doors/webext-links.js.map +1 -1
  123. package/dist/protocol/peer-dial.d.ts +2 -0
  124. package/dist/protocol/peer-dial.d.ts.map +1 -1
  125. package/dist/protocol/peer-dial.js +62 -29
  126. package/dist/protocol/peer-dial.js.map +1 -1
  127. package/dist/protocol/raw/raw-json-routes.d.ts +193 -0
  128. package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
  129. package/dist/protocol/raw/raw-json-routes.js +5 -0
  130. package/dist/protocol/raw/raw-json-routes.js.map +1 -1
  131. package/dist/protocol/raw/raw-routes.d.ts +31 -0
  132. package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
  133. package/dist/protocol/raw/raw-routes.js +8 -0
  134. package/dist/protocol/raw/raw-routes.js.map +1 -1
  135. package/dist/schemas/agent-place.d.ts +10 -0
  136. package/dist/schemas/agent-place.d.ts.map +1 -0
  137. package/dist/schemas/agent-place.js +15 -0
  138. package/dist/schemas/agent-place.js.map +1 -0
  139. package/dist/schemas/agent.d.ts +116 -83
  140. package/dist/schemas/agent.d.ts.map +1 -1
  141. package/dist/schemas/agent.js +26 -39
  142. package/dist/schemas/agent.js.map +1 -1
  143. package/dist/schemas/agents-list.d.ts +38 -0
  144. package/dist/schemas/agents-list.d.ts.map +1 -1
  145. package/dist/schemas/agents.d.ts +117 -16
  146. package/dist/schemas/agents.d.ts.map +1 -1
  147. package/dist/schemas/agents.js +19 -21
  148. package/dist/schemas/agents.js.map +1 -1
  149. package/dist/schemas/attach.d.ts +8 -0
  150. package/dist/schemas/attach.d.ts.map +1 -0
  151. package/dist/schemas/attach.js +14 -0
  152. package/dist/schemas/attach.js.map +1 -0
  153. package/dist/schemas/command-admission.d.ts +71 -0
  154. package/dist/schemas/command-admission.d.ts.map +1 -0
  155. package/dist/schemas/command-admission.js +27 -0
  156. package/dist/schemas/command-admission.js.map +1 -0
  157. package/dist/schemas/conversation-links.d.ts +44 -0
  158. package/dist/schemas/conversation-links.d.ts.map +1 -0
  159. package/dist/schemas/conversation-links.js +35 -0
  160. package/dist/schemas/conversation-links.js.map +1 -0
  161. package/dist/schemas/data-protection.d.ts +5 -2
  162. package/dist/schemas/data-protection.d.ts.map +1 -1
  163. package/dist/schemas/data-protection.js +17 -1
  164. package/dist/schemas/data-protection.js.map +1 -1
  165. package/dist/schemas/device-folder.d.ts +61 -0
  166. package/dist/schemas/device-folder.d.ts.map +1 -0
  167. package/dist/schemas/device-folder.js +24 -0
  168. package/dist/schemas/device-folder.js.map +1 -0
  169. package/dist/schemas/devices.d.ts +112 -39
  170. package/dist/schemas/devices.d.ts.map +1 -1
  171. package/dist/schemas/devices.js +8 -10
  172. package/dist/schemas/devices.js.map +1 -1
  173. package/dist/schemas/hosts.d.ts +2 -0
  174. package/dist/schemas/hosts.d.ts.map +1 -1
  175. package/dist/schemas/hosts.js +2 -0
  176. package/dist/schemas/hosts.js.map +1 -1
  177. package/dist/schemas/keyring-native.d.ts +137 -0
  178. package/dist/schemas/keyring-native.d.ts.map +1 -0
  179. package/dist/schemas/keyring-native.js +69 -0
  180. package/dist/schemas/keyring-native.js.map +1 -0
  181. package/dist/schemas/keyring.d.ts +413 -0
  182. package/dist/schemas/keyring.d.ts.map +1 -1
  183. package/dist/schemas/keyring.js +139 -4
  184. package/dist/schemas/keyring.js.map +1 -1
  185. package/dist/schemas/metrics.d.ts +11 -0
  186. package/dist/schemas/metrics.d.ts.map +1 -1
  187. package/dist/schemas/metrics.js +17 -0
  188. package/dist/schemas/metrics.js.map +1 -1
  189. package/dist/schemas/phone-passwords.d.ts +186 -0
  190. package/dist/schemas/phone-passwords.d.ts.map +1 -0
  191. package/dist/schemas/phone-passwords.js +25 -0
  192. package/dist/schemas/phone-passwords.js.map +1 -0
  193. package/dist/schemas/providers/plan-limits.d.ts +74 -0
  194. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  195. package/dist/schemas/providers/plan-limits.js +31 -0
  196. package/dist/schemas/providers/plan-limits.js.map +1 -1
  197. package/dist/schemas/providers/provider-oauth.d.ts +24 -0
  198. package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
  199. package/dist/schemas/providers/usage.d.ts +1 -0
  200. package/dist/schemas/providers/usage.d.ts.map +1 -1
  201. package/dist/schemas/providers/usage.js +1 -0
  202. package/dist/schemas/providers/usage.js.map +1 -1
  203. package/dist/schemas/push.d.ts +13 -0
  204. package/dist/schemas/push.d.ts.map +1 -1
  205. package/dist/schemas/push.js +11 -1
  206. package/dist/schemas/push.js.map +1 -1
  207. package/dist/schemas/sandbox-passwords.d.ts +288 -0
  208. package/dist/schemas/sandbox-passwords.d.ts.map +1 -0
  209. package/dist/schemas/sandbox-passwords.js +66 -0
  210. package/dist/schemas/sandbox-passwords.js.map +1 -0
  211. package/dist/schemas/secrets.d.ts +2 -0
  212. package/dist/schemas/secrets.d.ts.map +1 -1
  213. package/dist/schemas/secrets.js +4 -0
  214. package/dist/schemas/secrets.js.map +1 -1
  215. package/dist/schemas/settings-history.d.ts +1 -1
  216. package/dist/schemas/settings-history.d.ts.map +1 -1
  217. package/dist/schemas/settings-history.js +15 -0
  218. package/dist/schemas/settings-history.js.map +1 -1
  219. package/dist/schemas/settings-savings.d.ts +709 -0
  220. package/dist/schemas/settings-savings.d.ts.map +1 -0
  221. package/dist/schemas/settings-savings.js +53 -0
  222. package/dist/schemas/settings-savings.js.map +1 -0
  223. package/dist/schemas/settings.d.ts +7 -673
  224. package/dist/schemas/settings.d.ts.map +1 -1
  225. package/dist/schemas/settings.js +7 -58
  226. package/dist/schemas/settings.js.map +1 -1
  227. package/dist/schemas/terminal.d.ts +5 -0
  228. package/dist/schemas/terminal.d.ts.map +1 -1
  229. package/dist/schemas/terminal.js +4 -0
  230. package/dist/schemas/terminal.js.map +1 -1
  231. package/dist/schemas/work-assurance.d.ts +24 -0
  232. package/dist/schemas/work-assurance.d.ts.map +1 -0
  233. package/dist/schemas/work-assurance.js +23 -0
  234. package/dist/schemas/work-assurance.js.map +1 -0
  235. package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
  236. package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
  237. package/dist/schemas/workspace/workspace-tree.js +20 -0
  238. package/dist/schemas/workspace/workspace-tree.js.map +1 -1
  239. package/dist/state/auth-state.d.ts +4 -0
  240. package/dist/state/auth-state.d.ts.map +1 -0
  241. package/dist/state/auth-state.js +37 -0
  242. package/dist/state/auth-state.js.map +1 -0
  243. package/dist/state/definition.d.ts +12 -4
  244. package/dist/state/definition.d.ts.map +1 -1
  245. package/dist/state/history-state.d.ts.map +1 -1
  246. package/dist/state/history-state.js +17 -1
  247. package/dist/state/history-state.js.map +1 -1
  248. package/dist/state/runtime-state.d.ts.map +1 -1
  249. package/dist/state/runtime-state.js.map +1 -1
  250. package/dist/text/transcript-fold.js +5 -5
  251. package/dist/text/transcript-fold.js.map +1 -1
  252. package/dist/webext/index.d.ts +1 -0
  253. package/dist/webext/index.d.ts.map +1 -1
  254. package/dist/webext/index.js +1 -0
  255. package/dist/webext/index.js.map +1 -1
  256. package/package.json +6 -5
  257. package/src/contracts/agent.contract.ts +13 -1
  258. package/src/contracts/agents.contract.ts +4 -3
  259. package/src/contracts/device.contract.ts +16 -0
  260. package/src/contracts/endpoints.contract.ts +3 -0
  261. package/src/contracts/push.contract.ts +23 -1
  262. package/src/contracts/secrets.contract.ts +39 -0
  263. package/src/contracts/workspace.contract.ts +16 -0
  264. package/src/events/agent-events.ts +6 -1
  265. package/src/events/agent-transcript.ts +48 -0
  266. package/src/events/agent-words.ts +13 -0
  267. package/src/events/requests.ts +41 -3
  268. package/src/events/sandbox-notice.ts +3 -0
  269. package/src/events/transcript.ts +30 -28
  270. package/src/events/undelivered.ts +18 -0
  271. package/src/ids/conversation-ids.ts +7 -0
  272. package/src/ids/row-ids.ts +20 -0
  273. package/src/models/agent-lanes.ts +9 -3
  274. package/src/models/agent-standing.ts +9 -1
  275. package/src/models/plan-pools.test.ts +128 -20
  276. package/src/models/plan-pools.ts +114 -31
  277. package/src/netd/generated/netd-wire.json +25 -0
  278. package/src/netd/generated/wire.ts +1 -1
  279. package/src/policy/command-classes.ts +2 -2
  280. package/src/policy/keyring-origins.test.ts +132 -0
  281. package/src/policy/keyring-origins.ts +287 -0
  282. package/src/policy/keyring-seal.test.ts +37 -2
  283. package/src/policy/keyring-seal.ts +33 -7
  284. package/src/policy/safety-policy.ts +1 -1
  285. package/src/policy/tests/safety-policy.test.ts +1 -1
  286. package/src/policy/turned-away.ts +16 -0
  287. package/src/protocol/doors/host-protocol.ts +3 -0
  288. package/src/protocol/doors/webext-links.ts +3 -0
  289. package/src/protocol/peer-dial.test.ts +90 -4
  290. package/src/protocol/peer-dial.ts +83 -39
  291. package/src/protocol/raw/raw-json-routes.ts +13 -0
  292. package/src/protocol/raw/raw-routes.ts +12 -1
  293. package/src/schemas/agent-place.ts +22 -0
  294. package/src/schemas/agent.ts +42 -78
  295. package/src/schemas/agents.ts +41 -46
  296. package/src/schemas/attach.ts +23 -0
  297. package/src/schemas/command-admission.ts +57 -0
  298. package/src/schemas/conversation-links.ts +62 -0
  299. package/src/schemas/data-protection.ts +36 -10
  300. package/src/schemas/device-folder.ts +59 -0
  301. package/src/schemas/devices.ts +17 -42
  302. package/src/schemas/hosts.ts +4 -0
  303. package/src/schemas/keyring-native.test.ts +13 -0
  304. package/src/schemas/keyring-native.ts +101 -0
  305. package/src/schemas/keyring.ts +202 -4
  306. package/src/schemas/metrics.ts +28 -3
  307. package/src/schemas/phone-passwords.ts +51 -0
  308. package/src/schemas/providers/plan-limits.ts +49 -2
  309. package/src/schemas/providers/usage.ts +2 -0
  310. package/src/schemas/push.ts +19 -3
  311. package/src/schemas/sandbox-passwords.ts +88 -0
  312. package/src/schemas/secrets.ts +4 -0
  313. package/src/schemas/settings-history.ts +20 -0
  314. package/src/schemas/settings-savings.ts +125 -0
  315. package/src/schemas/settings.test.ts +38 -0
  316. package/src/schemas/settings.ts +20 -128
  317. package/src/schemas/terminal.ts +8 -0
  318. package/src/schemas/work-assurance.ts +36 -0
  319. package/src/schemas/workspace/workspace-tree.ts +32 -0
  320. package/src/state/auth-state.ts +89 -0
  321. package/src/state/history-state.ts +44 -1
  322. package/src/state/runtime-state.ts +5 -4
  323. package/src/text/transcript-fold.test.ts +67 -0
  324. package/src/text/transcript-fold.ts +6 -6
  325. package/src/webext/index.ts +2 -0
@@ -0,0 +1,88 @@
1
+ import { z } from "zod";
2
+ import { KeyringOriginSchema, KeyringRememberSchema, KeyringShareModeSchema } from "./keyring.js";
3
+
4
+ // THE SANDBOX'S SIDE OF PASSWORDS (2026-10-19): an agent asking for a credential by where it is to be used (`secrets
5
+ // want`, `type_secret` aimed at a page or a window), and the hosted Passwords view's three reads. The vault and its
6
+ // management stay on the owner's computers; what a sandbox learns of an entry is the pointer its keyring shared here.
7
+
8
+ /** `secrets want`: a credential by the place it is for, asked of the owner's keyrings. */
9
+ export const SecretWantSchema = z
10
+ .object({
11
+ origin: KeyringOriginSchema.describe("Where it is to be used: a site, a program on the owner's computer, an ssh target, a git host."),
12
+ username: z.string().max(256).optional().describe("The account wanted, when the agent knows it."),
13
+ why: z.string().min(1).max(300).describe("What it is for, in words for the person's prompt."),
14
+ // Filled by the CLI itself, from the running turn, so the prompt names the conversation that asked.
15
+ conversationId: z.string().max(200).optional(),
16
+ })
17
+ .strict();
18
+ export type SecretWant = z.infer<typeof SecretWantSchema>;
19
+
20
+ export const SecretWantResultSchema = z.discriminatedUnion("state", [
21
+ z.object({
22
+ state: z.literal("allowed"),
23
+ name: z.string().describe("The name a secret reference uses from now on."),
24
+ machine: z.string().describe("The computer whose keyring shares it."),
25
+ message: z.string(),
26
+ }),
27
+ z.object({ state: z.literal("pending"), id: z.string(), machine: z.string(), message: z.string().describe("Where the owner's answer is awaited.") }),
28
+ z.object({ state: z.literal("denied"), message: z.string() }),
29
+ z.object({ state: z.literal("unknown"), message: z.string().describe("No keyring had it and nobody typed it once, or none could be asked.") }),
30
+ ]);
31
+ export type SecretWantResult = z.infer<typeof SecretWantResultSchema>;
32
+
33
+ /** One need this sandbox raised, as its record and the hosted view's Requests keep it. */
34
+ export const SandboxNeedSchema = z.object({
35
+ id: z.string(),
36
+ origin: KeyringOriginSchema,
37
+ username: z.string().optional(),
38
+ detail: z.string(),
39
+ conversation: z.string().optional(),
40
+ machine: z.string().optional().describe("The computer it was answered on, or waits on."),
41
+ state: z.enum(["pending", "allowed", "denied", "unknown"]),
42
+ name: z.string().optional().describe("The entry it was answered with, once allowed."),
43
+ at: z.number().int(),
44
+ });
45
+ export type SandboxNeed = z.infer<typeof SandboxNeedSchema>;
46
+ export const SandboxNeedsSchema = z.object({ needs: z.array(SandboxNeedSchema) });
47
+
48
+ /** One of the owner's computers as the hosted Passwords view shows its keyring. */
49
+ export const SandboxPasswordsKeyringSchema = z.object({
50
+ machine: z.string(),
51
+ online: z.boolean(),
52
+ setUp: z.boolean().describe("Answering, set up, and trusted by this sandbox: it can be asked."),
53
+ locked: z.boolean().optional(),
54
+ remember: KeyringRememberSchema.optional(),
55
+ });
56
+ export type SandboxPasswordsKeyring = z.infer<typeof SandboxPasswordsKeyringSchema>;
57
+
58
+ /** One password this sandbox may use: a pointer one of the owner's keyrings shares here. Never a value. */
59
+ export const SandboxPasswordSchema = z.object({
60
+ name: z.string(),
61
+ title: z.string(),
62
+ kind: z.enum(["password", "text"]),
63
+ origins: z.array(KeyringOriginSchema).describe("Where it works, from the entry's rules."),
64
+ anyField: z.boolean(),
65
+ machine: z.string().describe("The computer whose keyring keeps it."),
66
+ mode: KeyringShareModeSchema.describe("How this sandbox may use it."),
67
+ online: z.boolean(),
68
+ updatedAt: z.number(),
69
+ lastUsedAt: z.number().optional().describe("This sandbox's last use of it, from its own use ledger."),
70
+ });
71
+ export type SandboxPassword = z.infer<typeof SandboxPasswordSchema>;
72
+
73
+ export const SandboxPasswordsSchema = z.object({
74
+ sandbox: z.string().describe("This sandbox's own address, as the keyrings name it."),
75
+ items: z.array(SandboxPasswordSchema),
76
+ keyrings: z.array(SandboxPasswordsKeyringSchema),
77
+ });
78
+ export type SandboxPasswords = z.infer<typeof SandboxPasswordsSchema>;
79
+
80
+ /** One use of a keyring-held password by this sandbox, from its use ledger. */
81
+ export const SandboxPasswordUseSchema = z.object({
82
+ at: z.number(),
83
+ name: z.string(),
84
+ lane: z.string(),
85
+ detail: z.string().optional(),
86
+ approvedBy: z.string().optional(),
87
+ });
88
+ export const SandboxPasswordsActivitySchema = z.object({ lines: z.array(SandboxPasswordUseSchema) });
@@ -257,6 +257,10 @@ export const SandboxKeyringSchema = z.object({
257
257
  machine: z.string().describe("The computer, by its device card."),
258
258
  online: z.boolean().describe("Whether it answers now."),
259
259
  locked: z.boolean().optional().describe("Whether its keyring is locked, as last heard. Absent while offline."),
260
+ setUp: z
261
+ .boolean()
262
+ .optional()
263
+ .describe("Whether its keyring answers with a vault this sandbox trusts, so a value can be kept there. Absent while offline, and from a daemon older than 2026-10-19."),
260
264
  version: z.string().optional().describe("The keyring's release, as last heard."),
261
265
  pendingInbox: z.number().int().nonnegative().optional().describe("Values sent to be kept there that wait for its next unlock."),
262
266
  shared: z.number().int().nonnegative().describe("How many of its entries are shared with this sandbox."),
@@ -3,6 +3,7 @@ import { at, drop, dropAll, fold, isJsonObject, type JsonObject, mapValue, retyp
3
3
  import { type ModelRole, ModelRoleSchema } from "../models/model-roles.js";
4
4
  import { setupsFromRoleLists } from "../models/model-setups.js";
5
5
  import { ModelPinSchema, type ModelPin } from "./agent.js";
6
+ import { RESTORE_POINT_KEEP_DAYS_DEFAULT } from "./data-protection.js";
6
7
  import type { KeepWarmSettings, RuleSchema } from "./settings.js";
7
8
 
8
9
  // The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
@@ -101,6 +102,21 @@ const roleListsIntoSetups = transform("folds modelRoles and modelDefault into se
101
102
  return { ...rest, modelSetups, roleSetups };
102
103
  });
103
104
 
105
+ const restorePointsKept = fold("carries restorePointDays into restorePointKeepDays, a stored zero taking the 30-day default", {
106
+ from: ["restorePointDays"],
107
+ into: "restorePointKeepDays",
108
+ applies: (retention) => Object.hasOwn(retention, "restorePointDays"),
109
+ convert: ({ restorePointDays }, retention) => {
110
+ // A file that already names the new key (a definition written for both) keeps it.
111
+ const kept = FlatNumber.safeParse(retention["restorePointKeepDays"]);
112
+ if (kept.success) {
113
+ return kept.data;
114
+ }
115
+ const chosen = FlatNumber.safeParse(restorePointDays);
116
+ return chosen.success && chosen.data > 0 ? chosen.data : RESTORE_POINT_KEEP_DAYS_DEFAULT;
117
+ },
118
+ });
119
+
104
120
  export const SETTINGS_HISTORY = [
105
121
  // Every setting a release since 2026-08-10 had and this one does not (read off the contract lock's history):
106
122
  // retired, so passthrough stops carrying them, a definition naming one still applies, and no later setting may
@@ -154,4 +170,8 @@ export const SETTINGS_HISTORY = [
154
170
  ...dropAll(["toolResultClearing", "toolResultClearingHoldout"]),
155
171
  at("modelRoles", refactorsFromChores),
156
172
  roleListsIntoSetups,
173
+ // Restore points kept 30 days by default (2026-10-18). `restorePointDays` held zero both where the owner chose to keep
174
+ // every point and where the settings page had saved the old default, which no reading could tell apart: the key is
175
+ // retired, a positive choice carries over, and a stored zero takes the new default.
176
+ at("retention", restorePointsKept),
157
177
  ] as const;
@@ -0,0 +1,125 @@
1
+ // settings-savings: what each token-reduction mechanism saved and how the turn-level ones are measured, as the
2
+ // settings routes report them. Re-exported by settings.ts, whose routes carry them.
3
+ import { z } from "zod";
4
+
5
+ // What each token-reduction mechanism actually saved. Input-side: both sides of the comparison come off the same
6
+ // command (raw in, emitted out), so the counterfactual is observed, not estimated — exact, per command.
7
+
8
+ // Sequential attribution (sums exactly to raw − emitted, drawable as a stacked bar); not what disabling this stage
9
+ // would save, since downstream would eat some of the same lines. Negative for `footer`, which adds cost back.
10
+ export const SavingsStageSchema = z.object({ id: z.string(), commands: z.number(), savedTokens: z.number() });
11
+ // Aggregated from historyRoot/logs/filter-stats.jsonl (one row per Bash command). Windowed on the ledger's own UTC-day
12
+ // calendar, so the reader's date range and these figures agree.
13
+ export const InputSavingsSchema = z.object({
14
+ // Epoch ms of the ledger's last command, so the card can show its age rather than implying freshness.
15
+ updatedAt: z.number().optional(),
16
+ commands: z.number(),
17
+ rawTokens: z.number(),
18
+ emittedTokens: z.number(),
19
+ savedPct: z.number(),
20
+ // Per-stage attribution, biggest first.
21
+ perCleaner: z.array(SavingsStageSchema),
22
+ // The measured control (raw vs. cleaned); the only real whole-pipeline counterfactual, not an estimate.
23
+ holdout: z.object({ cleaned: z.number(), heldOut: z.number(), measuredSavedPct: z.number().optional() }),
24
+ // Grouped by command text: worth a handler is judged by total tokens across runs, not one outlier's size.
25
+ gaps: z.array(z.object({ command: z.string(), commands: z.number(), tokens: z.number() })),
26
+ });
27
+ export type InputSavings = z.infer<typeof InputSavingsSchema>;
28
+ // One arm of a turn-level experiment; mean is per turn, since the two arms never hold the same count.
29
+ // `mean` is over samples capped at both arms' pooled 95th percentile, so one runaway conversation cannot carry an arm.
30
+ // For a yes/no reading (`openedWithListing`) it is the share of yeses, 0 to 1, and uncapped.
31
+ export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() });
32
+ // One metric's reading of a turn-level experiment (the two arms, plus the arithmetic over them). `metric` says what
33
+ // `mean`/`deltaPct` count:
34
+ // searchCalls: searches a turn ran (the search teaching).
35
+ // openingSearches: same, narrowed to before the turn first touched a file.
36
+ // openedWithListing: whether a turn listed directories to orient itself, as a share of turns (the project map).
37
+ // callsBeforeTarget: how far a turn walked before touching a file it went on to edit.
38
+ // failedCalls: tool calls that ended in error (the field notes, whose largest section is a failure taxonomy).
39
+ // openingPrompt: tokens a turn's first request sent (cache reads plus cache writes), which is what every later call of
40
+ // the turn reads again (tool search, which moves the tool schemas out of it).
41
+ // openingWrite: tokens a follow-up turn's first request wrote to the cache instead of reading them (the stable prompt).
42
+ // Every reading is the main agent's own: a subagent's calls are counted toward the turn and read for nothing.
43
+ // Never cost: each mechanism moves one small part of a turn's work, inside the noise of the rest. The prompt's size is
44
+ // the exception that proves it: it is the part every call pays again, so it is read directly rather than through cost.
45
+ export const TurnMetricReadingSchema = z.object({
46
+ metric: z.enum(["searchCalls", "openingSearches", "openedWithListing", "callsBeforeTarget", "failedCalls", "openingPrompt", "openingWrite"]),
47
+ on: SavingsArmSchema,
48
+ off: SavingsArmSchema,
49
+ // Additional control turns to reach a fixed target resolution, not today's delta (which inherits noise and always
50
+ // promises "next week"). Absent means nothing to wait for: under `minTurns`, already published, or already resolved
51
+ // enough.
52
+ controlTurnsNeeded: z.number().optional(),
53
+ // ± percentage points at 95% (Welch); present once both arms clear `minTurns`, even when `deltaPct` is withheld —
54
+ // "smaller than ±35 points" is still a useful, true answer.
55
+ marginPct: z.number().optional(),
56
+ // Present only once the margin excludes zero — clearing `minTurns` alone isn't enough, or a wide, unresolved swing
57
+ // reads as a real number.
58
+ // deltaPct: change in the metric's mean per turn; negative is a saving.
59
+ // saved: what that delta was worth over the turns that ran with it, in the metric's own unit.
60
+ deltaPct: z.number().optional(),
61
+ saved: z.number().optional(),
62
+ });
63
+ export type TurnMetricReading = z.infer<typeof TurnMetricReadingSchema>;
64
+ // One coin flip, several readings: `metrics` is a list since splitting readings into separate entries would duplicate
65
+ // the arm assignment. Screens read `metrics[0]` as the headline, the rest as supporting lines.
66
+ export const TurnExperimentSchema = z.object({
67
+ // A head and a tail, not a plain array, so "always a headline" is a type fact, not a check every screen repeats.
68
+ // `.nonempty()` doesn't do this in zod 4 (a runtime check, still typed as a plain array).
69
+ metrics: z.tuple([TurnMetricReadingSchema], TurnMetricReadingSchema),
70
+ // Carried on the wire so "measuring…" reflects the daemon's real threshold, not a guess; shared across every
71
+ // reading, since they're the same turns.
72
+ minTurns: z.number(),
73
+ // Turn mechanisms randomize by turn; session-loaded teaching randomizes whole conversations; a once-sent treatment
74
+ // (the map) samples one turn per conversation, not an average, or its effect divides by the conversation's length.
75
+ sampleUnit: z.enum(["turns", "conversations", "opening turns"]).optional(),
76
+ // Content-addressed treatment version; the reader filters to the latest so two instruction revisions don't blur
77
+ // into one experiment.
78
+ cohort: z.string().optional(),
79
+ // The latest earlier cohort that reached an answer, with only its answered readings (a margin, with or without a
80
+ // delta), so restarting a comparison does not wipe a finding off the screen while the new one is early. `from` and
81
+ // `to` are its first and last measured turn inside the window read, epoch ms. Absent when no earlier one answered.
82
+ previous: z
83
+ .object({ from: z.number(), to: z.number(), cohort: z.string().optional(), metrics: z.array(TurnMetricReadingSchema) })
84
+ .optional(),
85
+ // Whether the treatment was taken up at all, which no reading above can tell: the share, 0 to 1, of each group's
86
+ // searches that ran iq, pooled over the turns of this cohort that recorded it. `turns` is the treated group's count;
87
+ // absent until it reaches `minTurns`. The search teaching only.
88
+ uptake: z.object({ on: z.number(), off: z.number(), turns: z.number() }).optional(),
89
+ });
90
+ export type TurnExperiment = z.infer<typeof TurnExperimentSchema>;
91
+ // What the settings row can say about the brief without opening it: whether there is one, how much of it this budget
92
+ // reaches, and whether anything is scheduled to rewrite it. Read off the file and the automation store, never stored.
93
+ export const FieldNotesStatusSchema = z.object({
94
+ // False means the automation has never run (or was never set up) — the ordinary state before the first month.
95
+ present: z.boolean(),
96
+ // Epoch ms the file was last written. Absent when there is no file.
97
+ writtenAt: z.number().optional(),
98
+ // How many ranked sections the current budget reaches, out of how many the file holds. The pair is the point: "5"
99
+ // alone cannot tell a generous budget from a short file.
100
+ ranksSent: z.number().optional(),
101
+ ranksTotal: z.number().optional(),
102
+ // What the brief costs the prompt, in characters, at the current budget.
103
+ chars: z.number().optional(),
104
+ // The monthly rewrite: absent until the owner creates it from the offered template, since an automation names the
105
+ // models it spends and nothing chooses those for them.
106
+ automation: z.enum(["missing", "enabled", "disabled"]),
107
+ // When it next runs, epoch ms; absent when there is nothing scheduled.
108
+ nextRunAt: z.number().optional(),
109
+ // Said out loud rather than shown as "no file": a brief that exists and cannot be read is a broken automation.
110
+ unreadable: z.string().optional(),
111
+ // Blocks of the file its priority table does not rank, so no limit ever sends them. Absent when there are none.
112
+ unlisted: z.array(z.string()).optional(),
113
+ });
114
+ export type FieldNotesStatus = z.infer<typeof FieldNotesStatusSchema>;
115
+ export const SavingsReportSchema = z.object({
116
+ input: InputSavingsSchema,
117
+ search: TurnExperimentSchema.optional(),
118
+ // Same absence rule as `search`: not measured, never zero.
119
+ map: TurnExperimentSchema.optional(),
120
+ notes: TurnExperimentSchema.optional(),
121
+ toolSearch: TurnExperimentSchema.optional(),
122
+ stablePrompt: TurnExperimentSchema.optional(),
123
+ coldTrim: TurnExperimentSchema.optional(),
124
+ });
125
+ export type SavingsReport = z.infer<typeof SavingsReportSchema>;
@@ -207,3 +207,41 @@ describe(`per-job model lists`, () => {
207
207
  expect(convertDocument(SETTINGS_HISTORY, `object`, { modelSetups: [{ id: `low`, pins: [haiku] }], roleSetups: {} }, true).changes).toEqual([]);
208
208
  });
209
209
  });
210
+
211
+ test("disk retention defaults bound checkouts and restore points while preserving explicit zero", () => {
212
+ const defaults = SandboxSettingsSchema.parse({});
213
+ expect(defaults.agentRetentionDays).toBe(3);
214
+ expect(defaults.heldCheckoutDays).toBe(14);
215
+ expect(defaults.retention.restorePointKeepDays).toBe(30);
216
+ const existing = SandboxSettingsSchema.parse({ heldCheckoutDays: 0, retention: { restorePointKeepDays: 0 } });
217
+ expect(existing.heldCheckoutDays).toBe(0);
218
+ expect(existing.retention.restorePointKeepDays).toBe(0);
219
+ });
220
+
221
+ describe(`restore points' retention, renamed so a stored zero is the owner's own (2026-10-18)`, () => {
222
+ const read = (retention: Record<string, unknown>) => {
223
+ const converted = convertDocument(SETTINGS_HISTORY, `object`, { retention: { transcriptDays: 7, ...retention } }, true);
224
+ return { retention: SandboxSettingsSchema.parse(converted.value).retention, changes: converted.changes };
225
+ };
226
+
227
+ test(`an earlier release's stored zero, which the settings page wrote for the old default, takes the 30-day default`, () => {
228
+ const { retention, changes } = read({ restorePointDays: 0 });
229
+ expect(retention.restorePointKeepDays).toBe(30);
230
+ expect(retention.transcriptDays).toBe(7);
231
+ expect(changes.map((change) => change.at)).toEqual([`retention`]);
232
+ });
233
+
234
+ test(`a positive choice carries over`, () => {
235
+ expect(read({ restorePointDays: 90 }).retention.restorePointKeepDays).toBe(90);
236
+ });
237
+
238
+ test(`a zero stored under the new key is kept, and read again changes nothing`, () => {
239
+ const { retention, changes } = read({ restorePointKeepDays: 0 });
240
+ expect(retention.restorePointKeepDays).toBe(0);
241
+ expect(changes).toEqual([]);
242
+ });
243
+
244
+ test(`a file naming both keeps the new one`, () => {
245
+ expect(read({ restorePointDays: 0, restorePointKeepDays: 5 }).retention.restorePointKeepDays).toBe(5);
246
+ });
247
+ });
@@ -6,14 +6,20 @@ import { claudeExperimentSettings, iqSearchSettings } from "./settings-experimen
6
6
  import { CommandJudgeModeSchema, ProjectInstallModeSchema } from "../policy/safety-policy.js";
7
7
  import { ModelRoleSchema } from "../models/model-roles.js";
8
8
  import { MODEL_SETUP_ID, ModelSetupSchema } from "../models/model-setups.js";
9
- import { AdmissionPolicySchema, AdmissionRuleSchema } from "./agent.js";
9
+ import { AdmissionPolicySchema, AdmissionRuleSchema } from "./command-admission.js";
10
10
  import { KeepWarmSettingsSchema } from "./keep-warm.js";
11
11
  import { LimitHandoffSchema } from "./providers/handoff.js";
12
12
  import { SessionsPlacementSchema } from "./offload.js";
13
13
  import { LimitPolicySchema, RetryPolicySchema, RoomPolicySchema } from "./turn-break.js";
14
14
  import { ZoneSchema } from "../time/zone.js";
15
15
  import { VoiceSettingsSchema } from "./voice.js";
16
- import { AiNoticeSchema, AuditSettingsSchema, RetentionSettingsSchema } from "./data-protection.js";
16
+ import {
17
+ AiNoticeSchema,
18
+ AuditSettingsSchema,
19
+ RetentionSettingsSchema,
20
+ HeldCheckoutDaysSchema,
21
+ AgentRetentionDaysSchema,
22
+ } from "./data-protection.js";
17
23
  // Which prompt base the agent runs before this turn composes anything on top: Intentic's own (default), Claude Code's
18
24
  // preset, or the owner's text. Declared out here since both the daemon and the browser branch on it.
19
25
  // Kept in the daemon's auth root, never in SandboxSettings: an agent cannot edit its execution boundary.
@@ -398,14 +404,8 @@ export const SandboxSettingsSchema = z.object({
398
404
  .describe(
399
405
  "Which repositories keep a changelog, and so get a user-facing note written alongside each merge. A list rather than a switch, and empty by default, because the commit writer's standing rule is to copy the house style rather than impose one, and a repository that has never written such a note gives it nothing to copy.",
400
406
  ),
401
- agentRetentionDays: z
402
- .number()
403
- .min(0)
404
- .max(365)
405
- .default(3)
406
- .describe(
407
- "How many days a finished conversation stays on the board before being put away. Zero means never. The one setting here that defaults on, because each card left behind is a real working copy on disk, not just a row.",
408
- ),
407
+ agentRetentionDays: AgentRetentionDaysSchema,
408
+ heldCheckoutDays: HeldCheckoutDaysSchema,
409
409
  // One answer per ending, never a set of switches over the same event: the chat's own question and these rows are
410
410
  // the same question at two scopes. Every ending defaults to `wait`, because a re-run spends the reader's allowance
411
411
  // on a turn they sent once.
@@ -543,10 +543,10 @@ export const SandboxSettingsSchema = z.object({
543
543
  voice: VoiceSettingsSchema.prefault({}).describe(
544
544
  "How the agent's replies are spoken aloud: whether a hands-free conversation is answered out loud, which voice speaks each language, and a speech provider of your own if you connected one.",
545
545
  ),
546
- // What a data protection officer asks about (schemas/data-protection.ts). Every default is the behaviour from before
547
- // these existed; the public-office preset on the data protection page is what turns them on, never a default.
546
+ // What a data protection officer asks about (schemas/data-protection.ts). Restore points default to thirty days;
547
+ // the other policies preserve the earlier behaviour until the owner changes them or applies the public-office preset.
548
548
  retention: RetentionSettingsSchema.prefault({}).describe(
549
- "How long this sandbox keeps each kind of copy it makes of what people and agents wrote: transcripts, restore points, the privacy shield's memory, converted documents and raw command output. Each defaults to forever.",
549
+ "How long this sandbox keeps each kind of copy it makes of what people and agents wrote: transcripts, restore points, the privacy shield's memory, converted documents and raw command output. Restore points default to 30 days; the other copies default to forever.",
550
550
  ),
551
551
  audit: AuditSettingsSchema.prefault({}).describe(
552
552
  "The audit trail: who changed a policy, a member, a gate or a setting, and optionally who read what. How long it is kept, and where a copy is sent.",
@@ -597,6 +597,11 @@ export const SandboxSettingsSchema = z.object({
597
597
  .max(10)
598
598
  .default(3)
599
599
  .describe("How many levels deep the delegation may go, since a subagent can start subagents of its own."),
600
+ // How far off a reset a spawned child's spent turn is booked to re-run by itself (classify-failure.ts); one with fallbacks
601
+ // keeps 90 minutes. 2026-10-18: was a fixed 90 minutes, which ended every child behind a five-hour window.
602
+ childRerunHorizonHours: z.number().int().min(0).max(24).default(6).describe(
603
+ "How long a subagent refused for a spent allowance waits for the provider's reset before it goes again by itself. Past it, or at 0, it stops and its parent hears where else the work can go. One spawned with fallbacks waits at most 90 minutes and then moves to them.",
604
+ ),
600
605
  // Width again, for a team-chat automation rather than a turn: each thread a message lands in is a conversation of
601
606
  // its own, and threads of one automation are worked on side by side up to this many (automations/scheduler.ts in
602
607
  // the daemon); one more waits, told it is queued, until a slot frees. Read per fire, so a change holds from the next
@@ -668,121 +673,8 @@ export type TimezoneState = z.infer<typeof TimezoneStateSchema>;
668
673
  // from, so a fork from an older build reads as a snapshot.
669
674
  export const BuiltinPromptTextSchema = z.object({ text: z.string(), version: z.string() });
670
675
  export type BuiltinPromptText = z.infer<typeof BuiltinPromptTextSchema>;
671
- // What each token-reduction mechanism actually saved. Input-side: both sides of the comparison come off the same
672
- // command (raw in, emitted out), so the counterfactual is observed, not estimated — exact, per command.
673
-
674
- // Sequential attribution (sums exactly to raw − emitted, drawable as a stacked bar); not what disabling this stage
675
- // would save, since downstream would eat some of the same lines. Negative for `footer`, which adds cost back.
676
- export const SavingsStageSchema = z.object({ id: z.string(), commands: z.number(), savedTokens: z.number() });
677
- // Aggregated from historyRoot/logs/filter-stats.jsonl (one row per Bash command). Windowed on the ledger's own UTC-day
678
- // calendar, so the reader's date range and these figures agree.
679
- export const InputSavingsSchema = z.object({
680
- // Epoch ms of the ledger's last command, so the card can show its age rather than implying freshness.
681
- updatedAt: z.number().optional(),
682
- commands: z.number(),
683
- rawTokens: z.number(),
684
- emittedTokens: z.number(),
685
- savedPct: z.number(),
686
- // Per-stage attribution, biggest first.
687
- perCleaner: z.array(SavingsStageSchema),
688
- // The measured control (raw vs. cleaned); the only real whole-pipeline counterfactual, not an estimate.
689
- holdout: z.object({ cleaned: z.number(), heldOut: z.number(), measuredSavedPct: z.number().optional() }),
690
- // Grouped by command text: worth a handler is judged by total tokens across runs, not one outlier's size.
691
- gaps: z.array(z.object({ command: z.string(), commands: z.number(), tokens: z.number() })),
692
- });
693
- export type InputSavings = z.infer<typeof InputSavingsSchema>;
694
- // One arm of a turn-level experiment; mean is per turn, since the two arms never hold the same count.
695
- // `mean` is over samples capped at both arms' pooled 95th percentile, so one runaway conversation cannot carry an arm.
696
- // For a yes/no reading (`openedWithListing`) it is the share of yeses, 0 to 1, and uncapped.
697
- export const SavingsArmSchema = z.object({ turns: z.number(), mean: z.number() });
698
- // One metric's reading of a turn-level experiment (the two arms, plus the arithmetic over them). `metric` says what
699
- // `mean`/`deltaPct` count:
700
- // searchCalls: searches a turn ran (the search teaching).
701
- // openingSearches: same, narrowed to before the turn first touched a file.
702
- // openedWithListing: whether a turn listed directories to orient itself, as a share of turns (the project map).
703
- // callsBeforeTarget: how far a turn walked before touching a file it went on to edit.
704
- // failedCalls: tool calls that ended in error (the field notes, whose largest section is a failure taxonomy).
705
- // openingPrompt: tokens a turn's first request sent (cache reads plus cache writes), which is what every later call of
706
- // the turn reads again (tool search, which moves the tool schemas out of it).
707
- // openingWrite: tokens a follow-up turn's first request wrote to the cache instead of reading them (the stable prompt).
708
- // Every reading is the main agent's own: a subagent's calls are counted toward the turn and read for nothing.
709
- // Never cost: each mechanism moves one small part of a turn's work, inside the noise of the rest. The prompt's size is
710
- // the exception that proves it: it is the part every call pays again, so it is read directly rather than through cost.
711
- export const TurnMetricReadingSchema = z.object({
712
- metric: z.enum(["searchCalls", "openingSearches", "openedWithListing", "callsBeforeTarget", "failedCalls", "openingPrompt", "openingWrite"]),
713
- on: SavingsArmSchema,
714
- off: SavingsArmSchema,
715
- // Additional control turns to reach a fixed target resolution, not today's delta (which inherits noise and always
716
- // promises "next week"). Absent means nothing to wait for: under `minTurns`, already published, or already resolved
717
- // enough.
718
- controlTurnsNeeded: z.number().optional(),
719
- // ± percentage points at 95% (Welch); present once both arms clear `minTurns`, even when `deltaPct` is withheld —
720
- // "smaller than ±35 points" is still a useful, true answer.
721
- marginPct: z.number().optional(),
722
- // Present only once the margin excludes zero — clearing `minTurns` alone isn't enough, or a wide, unresolved swing
723
- // reads as a real number.
724
- // deltaPct: change in the metric's mean per turn; negative is a saving.
725
- // saved: what that delta was worth over the turns that ran with it, in the metric's own unit.
726
- deltaPct: z.number().optional(),
727
- saved: z.number().optional(),
728
- });
729
- export type TurnMetricReading = z.infer<typeof TurnMetricReadingSchema>;
730
- // One coin flip, several readings: `metrics` is a list since splitting readings into separate entries would duplicate
731
- // the arm assignment. Screens read `metrics[0]` as the headline, the rest as supporting lines.
732
- export const TurnExperimentSchema = z.object({
733
- // A head and a tail, not a plain array, so "always a headline" is a type fact, not a check every screen repeats.
734
- // `.nonempty()` doesn't do this in zod 4 (a runtime check, still typed as a plain array).
735
- metrics: z.tuple([TurnMetricReadingSchema], TurnMetricReadingSchema),
736
- // Carried on the wire so "measuring…" reflects the daemon's real threshold, not a guess; shared across every
737
- // reading, since they're the same turns.
738
- minTurns: z.number(),
739
- // Turn mechanisms randomize by turn; session-loaded teaching randomizes whole conversations; a once-sent treatment
740
- // (the map) samples one turn per conversation, not an average, or its effect divides by the conversation's length.
741
- sampleUnit: z.enum(["turns", "conversations", "opening turns"]).optional(),
742
- // Content-addressed treatment version; the reader filters to the latest so two instruction revisions don't blur
743
- // into one experiment.
744
- cohort: z.string().optional(),
745
- // The latest earlier cohort that reached an answer, with only its answered readings (a margin, with or without a
746
- // delta), so restarting a comparison does not wipe a finding off the screen while the new one is early. `from` and
747
- // `to` are its first and last measured turn inside the window read, epoch ms. Absent when no earlier one answered.
748
- previous: z
749
- .object({ from: z.number(), to: z.number(), cohort: z.string().optional(), metrics: z.array(TurnMetricReadingSchema) })
750
- .optional(),
751
- });
752
- export type TurnExperiment = z.infer<typeof TurnExperimentSchema>;
753
- // What the settings row can say about the brief without opening it: whether there is one, how much of it this budget
754
- // reaches, and whether anything is scheduled to rewrite it. Read off the file and the automation store, never stored.
755
- export const FieldNotesStatusSchema = z.object({
756
- // False means the automation has never run (or was never set up) — the ordinary state before the first month.
757
- present: z.boolean(),
758
- // Epoch ms the file was last written. Absent when there is no file.
759
- writtenAt: z.number().optional(),
760
- // How many ranked sections the current budget reaches, out of how many the file holds. The pair is the point: "5"
761
- // alone cannot tell a generous budget from a short file.
762
- ranksSent: z.number().optional(),
763
- ranksTotal: z.number().optional(),
764
- // What the brief costs the prompt, in characters, at the current budget.
765
- chars: z.number().optional(),
766
- // The monthly rewrite: absent until the owner creates it from the offered template, since an automation names the
767
- // models it spends and nothing chooses those for them.
768
- automation: z.enum(["missing", "enabled", "disabled"]),
769
- // When it next runs, epoch ms; absent when there is nothing scheduled.
770
- nextRunAt: z.number().optional(),
771
- // Said out loud rather than shown as "no file": a brief that exists and cannot be read is a broken automation.
772
- unreadable: z.string().optional(),
773
- });
774
- export type FieldNotesStatus = z.infer<typeof FieldNotesStatusSchema>;
775
- export const SavingsReportSchema = z.object({
776
- input: InputSavingsSchema,
777
- search: TurnExperimentSchema.optional(),
778
- // Same absence rule as `search`: not measured, never zero.
779
- map: TurnExperimentSchema.optional(),
780
- notes: TurnExperimentSchema.optional(),
781
- toolSearch: TurnExperimentSchema.optional(),
782
- stablePrompt: TurnExperimentSchema.optional(),
783
- coldTrim: TurnExperimentSchema.optional(),
784
- });
785
- export type SavingsReport = z.infer<typeof SavingsReportSchema>;
676
+ // What each token-reduction mechanism saved, and the comparisons that measure the turn-level ones, in a file of their own.
677
+ export * from "./settings-savings.js";
786
678
 
787
679
  // ---- what a REPOSITORY declares for itself, at `<repo>/.intentic/checks.json` ----
788
680
  // Here rather than in a file of its own because the line between these shapes and the settings above is authority, not
@@ -219,6 +219,14 @@ export const SubagentVerificationSchema = z.object({
219
219
  ),
220
220
  });
221
221
  export type SubagentVerification = z.infer<typeof SubagentVerificationSchema>;
222
+ // Where the Claude runtime keeps an in-process subagent's own record: beside the session it ran in, under the runtime's
223
+ // own id for it. The daemon pairs the two while the subagent runs and writes them onto its delegation's card, so the
224
+ // subagent's whole run reads back from that record after a restart, when nothing in memory still knows where it is.
225
+ export const SubagentSdkRecordSchema = z.object({
226
+ sessionId: z.string().describe("The provider session the subagent ran in."),
227
+ agentId: z.string().describe("The runtime's own id for the subagent, which names its record beside that session."),
228
+ });
229
+ export type SubagentSdkRecord = z.infer<typeof SubagentSdkRecordSchema>;
222
230
  export const SubagentSessionSchema = z.object({
223
231
  id: z
224
232
  .string()
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+
3
+ // WHAT HAS LOOKED AT A CONVERSATION'S WORK, as its card says it: what its last turn checked of its own work, and the
4
+ // latest code review it reported. Both written by a turn's settle from what the turn did, never asked of the model.
5
+ // Re-exported by agents.ts, whose summary carries them.
6
+
7
+ // What the last turn showed of its own work, read off its tool calls and never asked of the model: the record a card
8
+ // badges instead of sending the turn back to prove anything. CI checks what the owner pushes (schemas/ci.ts).
9
+ export const TurnProofSchema = z.object({
10
+ at: z.number().describe("When the turn that left this ended, in milliseconds."),
11
+ verification: z
12
+ .enum(["verified", "unproven", "failing", "no-code"])
13
+ .describe(
14
+ "Verified: a check it ran passed after its last edit to code. Failing: the last one it ran failed. Unproven: it changed code and ran nothing that checked it. No-code: it changed nothing a check could speak to.",
15
+ ),
16
+ check: z.string().optional().describe("The command that spoke, when one did, so a targeted test is never read as the whole suite."),
17
+ unviewed: z
18
+ .number()
19
+ .optional()
20
+ .describe("How many rendered files it changed (pages, components, styles) without looking at the result afterwards. Absent when none."),
21
+ });
22
+ export type TurnProof = z.infer<typeof TurnProofSchema>;
23
+ // The latest code review a conversation reported (events/review.ts), counted rather than carried: the findings stay in
24
+ // its chat, where the card that sends them to be fixed draws them. Kept until a later review replaces it, since a turn
25
+ // that reviews nothing has not undone the one before.
26
+ export const ReviewTallySchema = z.object({
27
+ at: z.number().describe("When the turn that reported it ended, in milliseconds."),
28
+ source: z.enum(["claude", "codex"]).describe("Which harness's reviewer reported it."),
29
+ findings: z.number().int().nonnegative().describe("How many findings it reported."),
30
+ open: z
31
+ .number()
32
+ .int()
33
+ .nonnegative()
34
+ .describe("How many of those no re-report has marked fixed or set aside yet. Equal to `findings` until the review is reported again with its fixes applied."),
35
+ });
36
+ export type ReviewTally = z.infer<typeof ReviewTallySchema>;
@@ -143,6 +143,38 @@ export const WorkspaceMediaTicketSchema = z.object({
143
143
  ticket: z.string().describe("Hand this to the streaming route in the query string. It buys exactly the one file it was minted for."),
144
144
  expiresAt: z.number().describe("When it stops working, in milliseconds, so a player can tell a dead ticket from a dead file."),
145
145
  });
146
+ // The long edges a picture is re-encoded at for drawing (GET /workspace/thumb's `size=e<edge>`), smallest first. A reader
147
+ // asks for the smallest one at least as long as the box the picture fills on its screen, device pixels counted, the
148
+ // way a browser picks from a srcset. Steps of about one and a half, so a pick is never much larger than it needs, and
149
+ // few enough that the pictures of a folder seen at several sizes share their renditions.
150
+ export const PICTURE_EDGES = [384, 512, 768, 1024, 1536, 2048] as const;
151
+ export type PictureEdge = (typeof PICTURE_EDGES)[number];
152
+
153
+ // The box each picture or video is drawn in, asked of a folder's worth at once: a gallery lays its rows out from every
154
+ // picture's shape before any of them has loaded, so a row never reflows under the reader as its pictures arrive.
155
+ export const WORKSPACE_DIMENSIONS_MAX = 500;
156
+ export const WorkspaceDimensionsQuerySchema = WorkspaceScopeSchema.extend({
157
+ paths: z
158
+ .array(z.string().min(1))
159
+ .min(1)
160
+ .max(WORKSPACE_DIMENSIONS_MAX)
161
+ .describe("The pictures and videos to measure, as workspace paths. Anything else, or anything unreadable, is left out of the answer."),
162
+ });
163
+ export const WorkspaceDimensionSchema = z.object({
164
+ path: z.string().describe("The path as it was asked for."),
165
+ width: z.number().describe("Width in pixels, as the picture is shown: a phone photo's or a phone video's rotation is already applied."),
166
+ height: z.number().describe("Height in pixels, as the picture is shown."),
167
+ duration: z.number().optional().describe("A video's running time in seconds, where its container says."),
168
+ });
169
+ export type WorkspaceDimension = z.infer<typeof WorkspaceDimensionSchema>;
170
+ export const WorkspaceDimensionsSchema = z.object({
171
+ items: z
172
+ .array(WorkspaceDimensionSchema)
173
+ .describe(
174
+ "One entry per path that could be measured. A path missing here is not a picture this could read, which is an answer, not an error.",
175
+ ),
176
+ });
177
+ export type WorkspaceDimensions = z.infer<typeof WorkspaceDimensionsSchema>;
146
178
  // Credential for GET /workspace/download, which the browser navigates to so its own download manager streams the archive
147
179
  // to disk. The ticket carries the whole selection, resolved when minted, so however many entries it names the URL stays
148
180
  // short.