@intentic/sandbox-contract 1.322.0 → 1.324.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 (213) hide show
  1. package/README.md +7 -0
  2. package/dist/contracts/agent.contract.d.ts +109 -6
  3. package/dist/contracts/agent.contract.d.ts.map +1 -1
  4. package/dist/contracts/agent.contract.js +13 -3
  5. package/dist/contracts/agent.contract.js.map +1 -1
  6. package/dist/contracts/agents.contract.d.ts +124 -0
  7. package/dist/contracts/agents.contract.d.ts.map +1 -1
  8. package/dist/contracts/approvals.contract.d.ts +31 -0
  9. package/dist/contracts/approvals.contract.d.ts.map +1 -1
  10. package/dist/contracts/approvals.contract.js +2 -2
  11. package/dist/contracts/approvals.contract.js.map +1 -1
  12. package/dist/contracts/capabilities.contract.d.ts +2 -0
  13. package/dist/contracts/capabilities.contract.d.ts.map +1 -1
  14. package/dist/contracts/loops.contract.d.ts +2 -0
  15. package/dist/contracts/loops.contract.d.ts.map +1 -1
  16. package/dist/contracts/needs.contract.d.ts +4 -0
  17. package/dist/contracts/needs.contract.d.ts.map +1 -1
  18. package/dist/contracts/privacy.contract.d.ts +187 -0
  19. package/dist/contracts/privacy.contract.d.ts.map +1 -0
  20. package/dist/contracts/privacy.contract.js +76 -0
  21. package/dist/contracts/privacy.contract.js.map +1 -0
  22. package/dist/contracts/runner.contract.d.ts +167 -122
  23. package/dist/contracts/runner.contract.d.ts.map +1 -1
  24. package/dist/contracts/sessions.contract.d.ts +11 -0
  25. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  26. package/dist/contracts/settings.contract.d.ts +74 -0
  27. package/dist/contracts/settings.contract.d.ts.map +1 -1
  28. package/dist/contracts/system.contract.d.ts +90 -32
  29. package/dist/contracts/system.contract.d.ts.map +1 -1
  30. package/dist/contracts/system.contract.js +11 -1
  31. package/dist/contracts/system.contract.js.map +1 -1
  32. package/dist/contracts/usage.contract.d.ts +1 -0
  33. package/dist/contracts/usage.contract.d.ts.map +1 -1
  34. package/dist/contracts/workflows.contract.d.ts +3 -0
  35. package/dist/contracts/workflows.contract.d.ts.map +1 -1
  36. package/dist/events/agent-events.d.ts +53 -1
  37. package/dist/events/agent-events.d.ts.map +1 -1
  38. package/dist/events/agent-events.js +6 -0
  39. package/dist/events/agent-events.js.map +1 -1
  40. package/dist/events/agent-ui.d.ts +33 -0
  41. package/dist/events/agent-ui.d.ts.map +1 -0
  42. package/dist/events/agent-ui.js +24 -0
  43. package/dist/events/agent-ui.js.map +1 -0
  44. package/dist/events/requests.d.ts +3 -0
  45. package/dist/events/requests.d.ts.map +1 -1
  46. package/dist/events/requests.js +4 -0
  47. package/dist/events/requests.js.map +1 -1
  48. package/dist/events/resume.d.ts +1 -0
  49. package/dist/events/resume.d.ts.map +1 -1
  50. package/dist/events/resume.js +7 -1
  51. package/dist/events/resume.js.map +1 -1
  52. package/dist/events/sandbox-notice.d.ts +2 -0
  53. package/dist/events/sandbox-notice.d.ts.map +1 -1
  54. package/dist/events/sandbox-notice.js +1 -1
  55. package/dist/events/sandbox-notice.js.map +1 -1
  56. package/dist/events/system-events.d.ts +15 -0
  57. package/dist/events/system-events.d.ts.map +1 -1
  58. package/dist/events/system-events.js +1 -0
  59. package/dist/events/system-events.js.map +1 -1
  60. package/dist/events/transcript.d.ts +70 -0
  61. package/dist/events/transcript.d.ts.map +1 -1
  62. package/dist/events/transcript.js +44 -14
  63. package/dist/events/transcript.js.map +1 -1
  64. package/dist/front/generated/browser-wire.d.ts +4 -0
  65. package/dist/front/generated/browser-wire.d.ts.map +1 -1
  66. package/dist/index.d.ts +640 -38
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +6 -0
  69. package/dist/index.js.map +1 -1
  70. package/dist/models/agent-runtimes.d.ts +1 -0
  71. package/dist/models/agent-runtimes.d.ts.map +1 -1
  72. package/dist/models/agent-runtimes.js +6 -0
  73. package/dist/models/agent-runtimes.js.map +1 -1
  74. package/dist/policy/request-status.d.ts.map +1 -1
  75. package/dist/policy/request-status.js +16 -7
  76. package/dist/policy/request-status.js.map +1 -1
  77. package/dist/policy/turned-away.d.ts.map +1 -1
  78. package/dist/policy/turned-away.js +1 -0
  79. package/dist/policy/turned-away.js.map +1 -1
  80. package/dist/protocol/raw-routes.d.ts +12 -0
  81. package/dist/protocol/raw-routes.d.ts.map +1 -1
  82. package/dist/protocol/raw-routes.js +3 -0
  83. package/dist/protocol/raw-routes.js.map +1 -1
  84. package/dist/schemas/agent.d.ts +97 -0
  85. package/dist/schemas/agent.d.ts.map +1 -1
  86. package/dist/schemas/agent.js +20 -2
  87. package/dist/schemas/agent.js.map +1 -1
  88. package/dist/schemas/agents.d.ts +18 -0
  89. package/dist/schemas/agents.d.ts.map +1 -1
  90. package/dist/schemas/agents.js +9 -2
  91. package/dist/schemas/agents.js.map +1 -1
  92. package/dist/schemas/approvals.d.ts +102 -0
  93. package/dist/schemas/approvals.d.ts.map +1 -1
  94. package/dist/schemas/approvals.js +45 -4
  95. package/dist/schemas/approvals.js.map +1 -1
  96. package/dist/schemas/automations.d.ts +6 -0
  97. package/dist/schemas/automations.d.ts.map +1 -1
  98. package/dist/schemas/capabilities.d.ts +2 -0
  99. package/dist/schemas/capabilities.d.ts.map +1 -1
  100. package/dist/schemas/capabilities.js +10 -1
  101. package/dist/schemas/capabilities.js.map +1 -1
  102. package/dist/schemas/git/git.d.ts.map +1 -1
  103. package/dist/schemas/git/git.js.map +1 -1
  104. package/dist/schemas/logs.d.ts +1 -0
  105. package/dist/schemas/logs.d.ts.map +1 -1
  106. package/dist/schemas/logs.js +4 -0
  107. package/dist/schemas/logs.js.map +1 -1
  108. package/dist/schemas/loops.d.ts +3 -0
  109. package/dist/schemas/loops.d.ts.map +1 -1
  110. package/dist/schemas/loops.js +1 -1
  111. package/dist/schemas/loops.js.map +1 -1
  112. package/dist/schemas/needs.d.ts +6 -0
  113. package/dist/schemas/needs.d.ts.map +1 -1
  114. package/dist/schemas/needs.js +13 -3
  115. package/dist/schemas/needs.js.map +1 -1
  116. package/dist/schemas/platform-link.d.ts +50 -0
  117. package/dist/schemas/platform-link.d.ts.map +1 -0
  118. package/dist/schemas/platform-link.js +19 -0
  119. package/dist/schemas/platform-link.js.map +1 -0
  120. package/dist/schemas/privacy.d.ts +277 -0
  121. package/dist/schemas/privacy.d.ts.map +1 -0
  122. package/dist/schemas/privacy.js +127 -0
  123. package/dist/schemas/privacy.js.map +1 -0
  124. package/dist/schemas/providers/plan-limits.d.ts +8 -0
  125. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  126. package/dist/schemas/providers/plan-limits.js +25 -4
  127. package/dist/schemas/providers/plan-limits.js.map +1 -1
  128. package/dist/schemas/providers/usage.d.ts +4 -0
  129. package/dist/schemas/providers/usage.d.ts.map +1 -1
  130. package/dist/schemas/providers/usage.js +5 -2
  131. package/dist/schemas/providers/usage.js.map +1 -1
  132. package/dist/schemas/push.d.ts +1 -0
  133. package/dist/schemas/push.d.ts.map +1 -1
  134. package/dist/schemas/push.js +4 -0
  135. package/dist/schemas/push.js.map +1 -1
  136. package/dist/schemas/settings.d.ts +80 -0
  137. package/dist/schemas/settings.d.ts.map +1 -1
  138. package/dist/schemas/settings.js +14 -3
  139. package/dist/schemas/settings.js.map +1 -1
  140. package/dist/schemas/system.d.ts +17 -0
  141. package/dist/schemas/system.d.ts.map +1 -1
  142. package/dist/schemas/system.js +2 -1
  143. package/dist/schemas/system.js.map +1 -1
  144. package/dist/schemas/updates.d.ts +30 -0
  145. package/dist/schemas/updates.d.ts.map +1 -1
  146. package/dist/schemas/updates.js +37 -0
  147. package/dist/schemas/updates.js.map +1 -1
  148. package/dist/schemas/workflows.d.ts +5 -0
  149. package/dist/schemas/workflows.d.ts.map +1 -1
  150. package/dist/state/definition.d.ts +10 -0
  151. package/dist/state/definition.d.ts.map +1 -1
  152. package/dist/state/history-state.d.ts.map +1 -1
  153. package/dist/state/history-state.js +1 -0
  154. package/dist/state/history-state.js.map +1 -1
  155. package/dist/state/runtime-state.d.ts +3 -0
  156. package/dist/state/runtime-state.d.ts.map +1 -1
  157. package/dist/state/runtime-state.js +1 -0
  158. package/dist/state/runtime-state.js.map +1 -1
  159. package/dist/state/workspace-state.d.ts +17 -1
  160. package/dist/state/workspace-state.d.ts.map +1 -1
  161. package/dist/state/workspace-state.js +20 -1
  162. package/dist/state/workspace-state.js.map +1 -1
  163. package/dist/text/transcript-fold.d.ts.map +1 -1
  164. package/dist/text/transcript-fold.js +9 -0
  165. package/dist/text/transcript-fold.js.map +1 -1
  166. package/package.json +5 -5
  167. package/src/contracts/agent.contract.ts +16 -3
  168. package/src/contracts/approvals.contract.ts +2 -2
  169. package/src/contracts/privacy.contract.ts +95 -0
  170. package/src/contracts/system.contract.ts +14 -1
  171. package/src/events/agent-events.ts +11 -0
  172. package/src/events/agent-ui.ts +40 -0
  173. package/src/events/requests.ts +6 -0
  174. package/src/events/resume.test.ts +13 -0
  175. package/src/events/resume.ts +11 -2
  176. package/src/events/sandbox-notice.ts +1 -1
  177. package/src/events/system-events.ts +2 -0
  178. package/src/events/transcript.ts +84 -20
  179. package/src/front/generated/browser-wire.json +27 -0
  180. package/src/front/generated/browser-wire.ts +1 -1
  181. package/src/index.ts +6 -0
  182. package/src/models/agent-catalog.test.ts +1 -0
  183. package/src/models/agent-runtimes.ts +17 -0
  184. package/src/models/capability-ledger.test.ts +3 -0
  185. package/src/policy/request-status.ts +19 -9
  186. package/src/policy/turned-away.ts +2 -0
  187. package/src/protocol/raw-routes.ts +9 -0
  188. package/src/schemas/agent.ts +36 -4
  189. package/src/schemas/agents.ts +17 -4
  190. package/src/schemas/approvals.ts +59 -4
  191. package/src/schemas/automations.ts +4 -4
  192. package/src/schemas/capabilities.ts +15 -2
  193. package/src/schemas/ci.ts +1 -1
  194. package/src/schemas/git/git.ts +1 -0
  195. package/src/schemas/logs.ts +6 -0
  196. package/src/schemas/loops.ts +2 -1
  197. package/src/schemas/needs.ts +18 -3
  198. package/src/schemas/platform-link.ts +42 -0
  199. package/src/schemas/privacy.ts +204 -0
  200. package/src/schemas/providers/plan-limits.ts +38 -5
  201. package/src/schemas/providers/usage.ts +13 -2
  202. package/src/schemas/push.ts +7 -0
  203. package/src/schemas/settings-history.ts +3 -3
  204. package/src/schemas/settings.test.ts +5 -5
  205. package/src/schemas/settings.ts +47 -19
  206. package/src/schemas/system.ts +6 -1
  207. package/src/schemas/updates.ts +71 -0
  208. package/src/state/history-state.ts +3 -0
  209. package/src/state/runtime-state.ts +4 -0
  210. package/src/state/workspace-state.test.ts +8 -2
  211. package/src/state/workspace-state.ts +32 -2
  212. package/src/text/transcript-fold.test.ts +15 -0
  213. package/src/text/transcript-fold.ts +10 -0
@@ -133,8 +133,11 @@ const hookDigest = z.string().regex(/^[0-9a-f]{64}$/);
133
133
 
134
134
  export const SettingsHookSchema = z.object({
135
135
  source: z
136
- .enum(["user", "project"])
137
- .describe("Whose configuration declares it: the sandbox's own (~/.claude) or the workspace's (.claude/ in the project)."),
136
+ .enum(["user", "project", "plugin"])
137
+ .describe(
138
+ "Whose configuration declares it: the sandbox's own (~/.claude), the workspace's (.claude/ in the project), or a Claude Code plugin the turn loads.",
139
+ ),
140
+ plugin: z.string().optional().describe("The plugin that declares it, by the name the request's plugin list gives it. Present only for a plugin's hook."),
138
141
  declaredIn: z
139
142
  .string()
140
143
  .optional()
@@ -143,7 +146,11 @@ export const SettingsHookSchema = z.object({
143
146
  ),
144
147
  event: z.string().describe("When it runs, in Claude Code's own words: before a tool, after one, when a prompt is sent, when a session starts."),
145
148
  matcher: z.string().optional().describe("Which tools it is limited to, when it is limited at all."),
146
- type: z.string().describe("What kind of hook it is: a shell command, an address it calls, a prompt it asks a model."),
149
+ type: z
150
+ .string()
151
+ .describe(
152
+ "What kind of hook it is: a shell command, an address it calls, a prompt it asks a model, or a module: a plugin's code that runs inside Claude Code itself.",
153
+ ),
147
154
  run: z.string().describe("Exactly what it runs: the command line, the address, the prompt."),
148
155
  });
149
156
  export type SettingsHook = z.infer<typeof SettingsHookSchema>;
@@ -162,6 +169,46 @@ export const HookScriptSchema = z.object({
162
169
  });
163
170
  export type HookScript = z.infer<typeof HookScriptSchema>;
164
171
 
172
+ export const HookModuleSchema = z.object({
173
+ path: z.string().describe("The plugin's hooks module, as its hooks.json names it."),
174
+ hooks: z
175
+ .array(z.string())
176
+ .describe("The moments it acts on, in Claude Code's own words: a tool call, a prompt being sent, the system prompt being put together. Empty when it could not be read."),
177
+ calls: z
178
+ .array(z.string())
179
+ .describe(
180
+ "What it reaches for while it runs, as Claude Code names its methods: starting programs, network requests, reading and writing files. Empty when it could not be read, or when it reaches for nothing.",
181
+ ),
182
+ unreadable: z
183
+ .string()
184
+ .optional()
185
+ .describe("Why the sandbox could not tell what the module does. The approval still covers its code byte for byte. Present only when that is the case."),
186
+ });
187
+ export type HookModule = z.infer<typeof HookModuleSchema>;
188
+
189
+ export const HookPluginSchema = z.object({
190
+ name: z.string().describe("The plugin, as the sandbox lists it: the connection, extension or persona that brought it, or the name settings enable it under."),
191
+ from: z
192
+ .enum(["plugin", "extension", "persona", "skills", "settings"])
193
+ .describe(
194
+ "What brought it into the turn: a plugin connection, an extension, a persona's kit, a plugin folder among Claude Code's skills, or Claude Code's own settings enabling it.",
195
+ ),
196
+ source: z
197
+ .enum(["user", "project"])
198
+ .optional()
199
+ .describe("Which settings enable it or hold it, the sandbox's own or the workspace's. Present only for a plugin settings or a skills folder bring in."),
200
+ dir: z.string().optional().describe("Where its files were read. Absent for one settings enable that this sandbox has not installed."),
201
+ module: HookModuleSchema.optional().describe("Its hooks module, code that runs inside Claude Code with everything the agent's session holds. Absent when it has none."),
202
+ });
203
+ export type HookPlugin = z.infer<typeof HookPluginSchema>;
204
+
205
+ export const HookMarketplaceSchema = z.object({
206
+ source: z.enum(["user", "project"]).describe("Whose settings add it: the sandbox's own or the workspace's."),
207
+ name: z.string().describe("The marketplace's name in those settings."),
208
+ location: z.string().describe("Where Claude Code fetches it from, as the settings spell it."),
209
+ });
210
+ export type HookMarketplace = z.infer<typeof HookMarketplaceSchema>;
211
+
165
212
  export const HookRequestSchema = z.object({
166
213
  digest: hookDigest.describe(
167
214
  "The set's fingerprint: every hook as declared plus the bytes of every file they run. Approving it approves exactly this, and nothing that differs from it by a character.",
@@ -171,7 +218,15 @@ export const HookRequestSchema = z.object({
171
218
  hooks: z
172
219
  .array(SettingsHookSchema)
173
220
  .describe("Every hook in the set. Until it is approved, turns run with all of them off, and so with every other hook the agent would load."),
174
- scripts: z.array(HookScriptSchema).describe("The files those hooks run by name, which the approval pins byte for byte."),
221
+ scripts: z.array(HookScriptSchema).describe("The files those hooks run by name, and a plugin module's code, which the approval pins byte for byte."),
222
+ plugins: z
223
+ .array(HookPluginSchema)
224
+ .optional()
225
+ .describe("The plugins whose hooks or modules are in the set, and those Claude Code's settings enable. Absent when none is."),
226
+ marketplaces: z
227
+ .array(HookMarketplaceSchema)
228
+ .optional()
229
+ .describe("Plugin marketplaces Claude Code's settings add, which it may install enabled plugins from. Absent when none is."),
175
230
  dismissed: z
176
231
  .boolean()
177
232
  .optional()
@@ -9,10 +9,10 @@ import { ZoneSchema } from "../time/zone.js";
9
9
  // (non-zero exit skips the wake), then runs one turn with the prompt. The manifest is user config; run history is
10
10
  // daemon-recorded.
11
11
 
12
- // schedule: fires on its cron
13
- // once: fires a single time at `at`, then retires itself (the daemon switches it off as it fires). Unlike a cron, a
14
- // fire missed while the sandbox was down still lands, late, on the next poll: a recurring wake that skips a beat has
15
- // another one coming, a one-time wake has nothing behind it
12
+ // schedule: fires on its cron. Moments missed while the sandbox was down fire once, late, as the daemon starts, however
13
+ // many there were: a recurring wake is owed its work, not a backlog
14
+ // once: fires a single time at `at`, then retires itself (the daemon switches it off as it fires). A fire missed while
15
+ // the sandbox was down still lands, late, on the next poll
16
16
  // event: fires when an external system POSTs /automations/{id}/fire; its auth token lives outside the manifest
17
17
  // (.intentic/secrets/doors.json), never in this versioned, widely-readable file
18
18
  // listener: fires from a realtime source's own connection (an extension's gateway), no cron, no token, never reachable
@@ -57,8 +57,12 @@ export const CliConfigSchema = z
57
57
  // internals. `path` is a subdirectory for a plugin inside a larger checkout; `token` is https auth for a private repo.
58
58
  export const PluginConfigSchema = z.object({
59
59
  url: z.url().describe("The repository to take the plugin from."),
60
- // Branch / tag / commit sha to pin; absent = the default branch's HEAD.
61
- ref: z.string().min(1).optional().describe("A branch, tag or commit to pin to. Leave it out to follow the default branch."),
60
+ // Branch / tag / commit sha to install from; absent = the default branch's HEAD. Either way `commit` pins it.
61
+ ref: z
62
+ .string()
63
+ .min(1)
64
+ .optional()
65
+ .describe("A branch, tag or commit to install from. Leave it out for the default branch. The install pins the commit it names at that moment."),
62
66
  path: z
63
67
  .string()
64
68
  .min(1)
@@ -66,6 +70,15 @@ export const PluginConfigSchema = z.object({
66
70
  .optional()
67
71
  .describe("Where inside the repository the plugin lives, for one that sits in a larger checkout."),
68
72
  token: z.string().min(1).optional().describe("A credential for a private repository. Stored, never echoed back."),
73
+ // Set by the sandbox at install, never by the form: re-applying a stored entry (a rotated token, a migration)
74
+ // checks this out again, and only adding the plugin anew moves it.
75
+ commit: z
76
+ .string()
77
+ .regex(/^[0-9a-f]{40}$/, "commit must be a full 40-character commit sha")
78
+ .optional()
79
+ .describe(
80
+ "The exact commit installed, in full, resolved from the branch, tag or commit when the plugin was added. Its hooks run inside the agent's session, so it stays at this commit until it is added again.",
81
+ ),
69
82
  });
70
83
  // An intentic extension from a git repo (UI bundle + agent contributions + processes). Unlike `plugin`, `ref` must be a
71
84
  // full commit sha, since extension code runs trusted in the owner's browser and every update is a deliberate re-add at
package/src/schemas/ci.ts CHANGED
@@ -113,7 +113,7 @@ export type CiRepo = z.infer<typeof CiRepoSchema>;
113
113
  export const CI_POLL_INTERVAL_MS = 2 * 60_000;
114
114
  // What was decided about a main-line branch while its CI fails, in the order it was decided (ci/main-fixer.ts).
115
115
  export const MainFailureDecisionKindSchema = z.enum([
116
- // Its one fix agent was put on it: by the sandbox at the first job that failed, or by a person's Fix press, which
116
+ // Its one fix agent was put on it: by the sandbox with the first run that failed, or by a person's Fix press, which
117
117
  // gives the agent its turns back.
118
118
  "fix-up",
119
119
  // Nobody was sent: repairs are switched off, or nothing could take it.
@@ -379,6 +379,7 @@ export type WorkspaceModules = z.infer<typeof WorkspaceModulesSchema>;
379
379
  // One file an agent touched that still differs from main, plus whether main's working tree already holds it:
380
380
  // in main's history: accepted, no longer a difference — no row (see `absorbed`).
381
381
  // in main's tree, uncommitted: `landed: true`.
382
+ // taken by the last land and edited in main since (committed or not): `landed: true`, since no land has anything to apply.
382
383
  // neither (never landed, or landed then discarded): `landed: false` — what "Land now" applies to.
383
384
  // A landed row stays until committed; that is the only act that retires it.
384
385
  export const AgentChangeSchema = GitChangeSchema.extend({
@@ -65,6 +65,12 @@ export const ClientDiagnosticsAcceptedSchema = z.object({
65
65
  export const PresenceReportSchema = z.object({
66
66
  clientId: z.string().describe("This connection's own id, the same one it gave the event stream."),
67
67
  idle: z.boolean().describe("Whether the person has stopped doing anything."),
68
+ // Narrower than `idle`, which is the window being off screen: a window left on screen with nobody touching it for
69
+ // minutes. What an automatic update reads to tell a person at the editor from an editor left open overnight.
70
+ away: z
71
+ .boolean()
72
+ .optional()
73
+ .describe("Whether the window is on screen but nobody has touched it for several minutes. Absent from a page older than it, which reads as not away."),
68
74
  view: z.string().optional().describe("Which view they are on."),
69
75
  sessionId: z.string().optional().describe("Which conversation they have open."),
70
76
  path: z
@@ -186,7 +186,8 @@ export type LoopIteration = z.infer<typeof LoopIterationSchema>;
186
186
  // overspent: maxSpendUsd reached.
187
187
  // stopped: the user pressed Stop.
188
188
  // error: the loop itself failed, not a turn inside it (see `LoopIteration.outcome`).
189
- export const LoopStateSchema = z.enum(["running", "done", "exhausted", "stalled", "overspent", "stopped", "error"]);
189
+ // unpriced: a spend ceiling was set but the runtime does not report cost, so the ceiling cannot be enforced.
190
+ export const LoopStateSchema = z.enum(["running", "done", "exhausted", "stalled", "overspent", "stopped", "error", "unpriced"]);
190
191
  export type LoopState = z.infer<typeof LoopStateSchema>;
191
192
  export const LoopRecordSchema = LoopSchema.extend({
192
193
  state: LoopStateSchema.describe(
@@ -262,6 +262,17 @@ export const ConversationGrantSchema = z.object({
262
262
  .boolean()
263
263
  .default(false)
264
264
  .describe("Whether its own dependency installs run without asking, where the owner's setting would otherwise ask first."),
265
+ // Answered on a host guard's card, "Allow <secret> anywhere in this conversation", read live by the host guard.
266
+ secrets: z
267
+ .array(z.string())
268
+ .default([])
269
+ .describe("Secrets whose host guard it may send past without asking, by registry name."),
270
+ // Answered on any permission card's Allow menu, read live by the card registry: every later request a person could
271
+ // have allowed once settles as allowed. A request that always asks still asks.
272
+ everything: z
273
+ .boolean()
274
+ .default(false)
275
+ .describe("Whether every request an allow-once could settle is allowed without asking, until somebody takes it back."),
265
276
  updatedAt: z.number().describe("When it last changed, in milliseconds."),
266
277
  by: z.string().optional().describe("Who last allowed something here."),
267
278
  });
@@ -277,6 +288,8 @@ export const StandingGrantsSchema = z.object({
277
288
  folders: z.array(z.string()).describe("Workspace folders its file tools may touch beyond its fence."),
278
289
  shelves: z.array(GrantShelfSchema).describe("Shelves of tools opened for it."),
279
290
  installs: z.boolean().default(false).describe("Whether its own dependency installs run without asking."),
291
+ secrets: z.array(z.string()).default([]).describe("Secrets it may send past their host guard without asking."),
292
+ everything: z.boolean().default(false).describe("Whether every request an allow-once could settle is allowed without asking."),
280
293
  by: z.string().optional().describe("Who last allowed one of those."),
281
294
  updatedAt: z.number().optional().describe("When one of those last changed, in milliseconds."),
282
295
  releases: z
@@ -290,9 +303,11 @@ export type StandingGrants = z.infer<typeof StandingGrantsSchema>;
290
303
  export const GrantRevokeSchema = z.object({
291
304
  conversationId: z.string(),
292
305
  kind: z
293
- .enum(["capability", "folder", "shelf", "release", "install"])
294
- .describe("Which kind of yes: a grant past the persona or area, a credential's release, or letting its installs run unasked."),
295
- what: z.string().describe("The capability id, folder, shelf or released credential it named; empty for installs."),
306
+ .enum(["capability", "folder", "shelf", "release", "install", "secret", "everything"])
307
+ .describe(
308
+ "Which kind of yes: a grant past the persona or area, a credential's release, letting its installs run unasked, a secret sent past its host guard, or allowing everything.",
309
+ ),
310
+ what: z.string().describe("The capability id, folder, shelf, released credential or secret it named; empty for installs and everything."),
296
311
  });
297
312
  export type GrantRevoke = z.infer<typeof GrantRevokeSchema>;
298
313
 
@@ -0,0 +1,42 @@
1
+ import { z } from "zod";
2
+
3
+ // THE DAEMON'S LINK WITH THE PLATFORM, as /health reports it (`announce`) and as the owner's Reconnect renews it (POST
4
+ // /platform/relink). The one link nothing outside the container can probe: whether this daemon's registration was
5
+ // taken, and, when it was refused, whether the platform has no record of the sandbox (`unknown`, which a platform that
6
+ // forgot it says too) or deleted it (`deleted`). `ic sandbox doctor`, the setup wizard and the editor's recovery screen
7
+ // read it.
8
+ export const AnnounceStateSchema = z.object({
9
+ // off: nothing to register with; pending: the first attempt is in flight; registered: the platform took it;
10
+ // rejected: it answered no; unreachable: it could not be reached.
11
+ state: z.enum(["off", "pending", "registered", "rejected", "unreachable"]),
12
+ // Why, for the failing states, already in the owner's terms.
13
+ detail: z.string().optional(),
14
+ // On a failing state: false only once the platform said the sandbox was deleted; every other failure is retried.
15
+ retrying: z.boolean().optional(),
16
+ // Which no: `unknown` the platform has no record of this sandbox, and its owner's Reconnect can have it adopted;
17
+ // `deleted` it holds a deletion record, which ends the retrying.
18
+ reason: z.enum(["unknown", "deleted"]).optional(),
19
+ // The identity of the database that took the registration (the platform's GET /api/identity), when it said.
20
+ identity: z.string().optional(),
21
+ // When this state was last confirmed, ms since epoch.
22
+ at: z.number().optional(),
23
+ });
24
+ export type AnnounceState = z.infer<typeof AnnounceStateSchema>;
25
+
26
+ // The owner's Reconnect: registers now, and with a ticket (the platform's adoption ticket, carried opaque), first asks a
27
+ // platform that has no record of the sandbox to make one (its POST /sandbox/adopt). `name` and `image` are what the
28
+ // owner's browser remembers of it, since only the platform held them.
29
+ export const RelinkRequestSchema = z.object({
30
+ ticket: z.string().min(1).optional(),
31
+ name: z.string().max(60).optional(),
32
+ image: z.string().max(150_000).optional(),
33
+ });
34
+ export type RelinkRequest = z.infer<typeof RelinkRequestSchema>;
35
+
36
+ export const RelinkAnswerSchema = z.object({
37
+ announce: AnnounceStateSchema,
38
+ // What the platform said to the adoption, when one was asked for and needed: its HTTP status (0 when it could not
39
+ // be reached) and its reason, in the owner's terms.
40
+ adoption: z.object({ status: z.number(), detail: z.string() }).optional(),
41
+ });
42
+ export type RelinkAnswer = z.infer<typeof RelinkAnswerSchema>;
@@ -0,0 +1,204 @@
1
+ // The privacy shield: personal data (names, national ids, document numbers, accounts…) kept away from model providers
2
+ // the owner does not trust. The daemon puts a gateway between every runtime it can point at one and the provider, which
3
+ // replaces what it finds with stable tokens on the way out and puts the real values back on the way in, so the agent
4
+ // works on real data while an untrusted provider only ever reads tokens. Off unless the owner turns it on.
5
+
6
+ import { z } from "zod";
7
+
8
+ // What the shield looks for, one switch each. `known` values (a dataset the owner taught it) carry the class they were
9
+ // taught under, so there is no class of their own.
10
+ export const PERSONAL_DATA_CLASSES = [
11
+ "person-name",
12
+ "national-id",
13
+ "tax-id",
14
+ "identity-document",
15
+ "bank-account",
16
+ "payment-card",
17
+ "email",
18
+ "phone",
19
+ "address",
20
+ ] as const;
21
+ export const PersonalDataClassSchema = z.enum(PERSONAL_DATA_CLASSES);
22
+ export type PersonalDataClass = z.infer<typeof PersonalDataClassSchema>;
23
+
24
+ // off: nothing is routed or read. watch: every shieldable turn goes through the gateway, which reads and records what it
25
+ // would have masked and sends everything unchanged. on: untrusted providers get tokens, and a turn that cannot be
26
+ // shielded (a runtime the gateway cannot sit in front of) is refused unless its provider is trusted.
27
+ export const PrivacyShieldModeSchema = z.enum(["off", "watch", "on"]);
28
+ export type PrivacyShieldMode = z.infer<typeof PrivacyShieldModeSchema>;
29
+
30
+ // What an image bound for an untrusted provider becomes. mask: it is sent, with every stretch of its text the shield
31
+ // reads on this machine (OCR) and finds to be personal data painted over with that value's token; one that cannot be
32
+ // read is held back with a note, never sent unchecked. allow: sent as it is. (Before 2026-10, `withhold` sent a note
33
+ // in place of every image and `read` sent its masked text instead of the picture; both read as `mask` now.)
34
+ export const PrivacyImagesSchema = z.enum(["mask", "allow"]);
35
+ export type PrivacyImages = z.infer<typeof PrivacyImagesSchema>;
36
+
37
+ // How names are found. dictionary: the first-name and surname lists, with titles and inflection. model: those plus a
38
+ // local named-entity model, where one is installed; without it, the dictionary alone.
39
+ export const PrivacyNamesSchema = z.enum(["dictionary", "model"]);
40
+ export type PrivacyNames = z.infer<typeof PrivacyNamesSchema>;
41
+
42
+ export const PRIVACY_TRUSTED_MAX = 200;
43
+ export const PRIVACY_ALLOW_MAX = 1000;
44
+ export const PRIVACY_CONVERSATIONS_MAX = 200;
45
+
46
+ // One conversation the owner let an untrusted provider read as it is, granted from that conversation's own composer:
47
+ // narrower than the trusted list, and the way on for a provider whose runtime the gateway cannot sit in front of
48
+ // (Cursor's own wire, an ACP agent, Pi), which the shield otherwise refuses outright.
49
+ export const PrivacyConversationTrustSchema = z.object({
50
+ conversationId: z.string().min(1).max(200),
51
+ provider: z.string().min(1).max(200).describe("Provider id, as the trusted list names it."),
52
+ });
53
+ export type PrivacyConversationTrust = z.infer<typeof PrivacyConversationTrustSchema>;
54
+
55
+ export const PrivacyShieldPolicySchema = z.object({
56
+ mode: PrivacyShieldModeSchema.default("off").describe("Whether the shield is off, only watching, or masking."),
57
+ trusted: z
58
+ .array(z.string().min(1).max(200))
59
+ .max(PRIVACY_TRUSTED_MAX)
60
+ .default([])
61
+ .describe(
62
+ "Providers that may read personal data as it is, by provider id (`claude`, `codex`, `endpoint/<id>`). A local model is always trusted.",
63
+ ),
64
+ classes: z
65
+ .array(PersonalDataClassSchema)
66
+ .default([...PERSONAL_DATA_CLASSES])
67
+ .describe("Which kinds of personal data are looked for."),
68
+ images: PrivacyImagesSchema.default("mask").describe("What an image bound for an untrusted provider becomes."),
69
+ names: PrivacyNamesSchema.default("dictionary").describe("How names are found."),
70
+ allow: z
71
+ .array(z.string().min(1).max(200))
72
+ .max(PRIVACY_ALLOW_MAX)
73
+ .default([])
74
+ .describe("Values never masked: your own company, a public figure, a word the detector keeps mistaking for a name."),
75
+ conversations: z
76
+ .array(PrivacyConversationTrustSchema)
77
+ .max(PRIVACY_CONVERSATIONS_MAX)
78
+ .default([])
79
+ .describe("Providers that may read one conversation's personal data as it is, each granted from that conversation; oldest first."),
80
+ });
81
+ export type PrivacyShieldPolicy = z.infer<typeof PrivacyShieldPolicySchema>;
82
+
83
+ // Whether the owner let this provider read this conversation as it is. A turn outside any conversation (a helper job)
84
+ // never is.
85
+ export const trustedInConversation = (
86
+ policy: Pick<PrivacyShieldPolicy, "conversations">,
87
+ provider: string,
88
+ conversationId: string | undefined,
89
+ ): boolean => conversationId !== undefined && policy.conversations.some((entry) => entry.conversationId === conversationId && entry.provider === provider);
90
+
91
+ // The policy with that grant made or taken back. A full list drops its oldest grant to make room, since the newest is
92
+ // the one somebody is waiting on.
93
+ export const withConversationTrust = (policy: PrivacyShieldPolicy, conversationId: string, provider: string, on: boolean): PrivacyShieldPolicy => {
94
+ const others = policy.conversations.filter((entry) => !(entry.conversationId === conversationId && entry.provider === provider));
95
+ return { ...policy, conversations: on ? [...others, { conversationId, provider }].slice(-PRIVACY_CONVERSATIONS_MAX) : others };
96
+ };
97
+
98
+ // The policy a sandbox that never wrote one runs on.
99
+ export const DEFAULT_PRIVACY_SHIELD: PrivacyShieldPolicy = PrivacyShieldPolicySchema.parse({});
100
+
101
+ // How a provider stands against the gateway, for the trusted list: whether its traffic can be shielded at all, and
102
+ // whether it never leaves this machine.
103
+ export const PrivacyProviderSchema = z.object({
104
+ id: z.string().describe("Provider id, as the trusted list names it."),
105
+ label: z.string(),
106
+ shieldable: z.boolean().describe("Its runtime can be put behind the gateway; one that cannot is refused while the shield is on, unless trusted."),
107
+ local: z.boolean().describe("It runs on this machine, so it is trusted whatever the list says."),
108
+ });
109
+ export type PrivacyProvider = z.infer<typeof PrivacyProviderSchema>;
110
+
111
+ export const PrivacyShieldStatusSchema = z.object({
112
+ policy: PrivacyShieldPolicySchema,
113
+ known: z.number().int().describe("Values taught from your datasets, matched exactly wherever they appear."),
114
+ tokens: z.number().int().describe("Values the shield has given a token so far."),
115
+ readers: z.object({
116
+ ocr: z.boolean().describe("The local text reader (PaddleOCR) that finds personal data in images is installed."),
117
+ model: z.boolean().describe("A local named-entity model for names is installed."),
118
+ }),
119
+ providers: z.array(PrivacyProviderSchema),
120
+ });
121
+ export type PrivacyShieldStatus = z.infer<typeof PrivacyShieldStatusSchema>;
122
+
123
+ // One request the gateway handled, as the log keeps it: counts and kinds, never a value.
124
+ export const PrivacyLedgerActionSchema = z.enum(["masked", "watched", "passed", "refused"]);
125
+ export type PrivacyLedgerAction = z.infer<typeof PrivacyLedgerActionSchema>;
126
+
127
+ export const PrivacyLedgerEntrySchema = z.object({
128
+ at: z.string().describe("When, as an ISO timestamp."),
129
+ conversationId: z.string().optional(),
130
+ provider: z.string(),
131
+ trusted: z.boolean(),
132
+ action: PrivacyLedgerActionSchema,
133
+ counts: z.partialRecord(PersonalDataClassSchema, z.number().int()).describe("How many of each kind were found in what this request added."),
134
+ images: z.number().int().describe("Images the shield changed: personal data painted over, or held back when they could not be read."),
135
+ documents: z.number().int().describe("Documents replaced by their masked text."),
136
+ protocol: z.string().describe("Which wire format the request spoke."),
137
+ detail: z.string().optional().describe("Why it was refused, when it was."),
138
+ });
139
+ export type PrivacyLedgerEntry = z.infer<typeof PrivacyLedgerEntrySchema>;
140
+
141
+ // A dataset taught to the shield: each value is matched exactly from then on, in every form it is written.
142
+ export const PRIVACY_KNOWN_BATCH_MAX = 50_000;
143
+
144
+ export const PrivacyKnownValueSchema = z.object({
145
+ value: z.string().min(2).max(500),
146
+ class: PersonalDataClassSchema,
147
+ });
148
+ export type PrivacyKnownValue = z.infer<typeof PrivacyKnownValueSchema>;
149
+
150
+ export const PrivacyKnownSourceSchema = z.object({
151
+ source: z.string().describe("Where the values came from, as whoever taught them named it."),
152
+ count: z.number().int(),
153
+ at: z.string().describe("When they were last taught."),
154
+ });
155
+ export type PrivacyKnownSource = z.infer<typeof PrivacyKnownSourceSchema>;
156
+
157
+ // The name lists the shield finds names by when no model is asked for (and alongside one when it is): what each list
158
+ // holds, how many, and where it came from.
159
+ export const PrivacyNameListSchema = z.object({
160
+ id: z.string().describe("Stable id of the list."),
161
+ kind: z.enum(["first-name", "surname", "ambiguous", "title", "never"]).describe("What a word on it says about a name."),
162
+ languages: z.array(z.enum(["pl", "en"])).describe("The languages its words come from."),
163
+ count: z.number().int().describe("How many words it holds."),
164
+ matching: z
165
+ .enum(["inflected", "as-written"])
166
+ .describe("inflected: matched in every grammatical form of a listed word; as-written: matched only exactly as listed."),
167
+ source: z.string().describe("Where the words come from: the register or dataset, or that they were written by hand."),
168
+ url: z.string().optional().describe("The source's page, where it has one."),
169
+ license: z.string().optional(),
170
+ });
171
+ export type PrivacyNameList = z.infer<typeof PrivacyNameListSchema>;
172
+
173
+ // What the lists say of one word.
174
+ export const PrivacyNameWordSchema = z.object({
175
+ word: z.string(),
176
+ firstName: z.boolean().describe("A listed first name, in this form or as an inflection of one."),
177
+ surname: z.boolean().describe("A listed surname, in this form or as an inflection of one."),
178
+ surnameForm: z.boolean().describe("Shaped like a Polish surname (-ski, -cki, -wicz…), listed or not."),
179
+ ambiguous: z.boolean().describe("Also an ordinary word, so found only beside other evidence (a surname, a title)."),
180
+ never: z.boolean().describe("Never taken as part of a name (a title, an institution, a function word)."),
181
+ });
182
+ export type PrivacyNameWord = z.infer<typeof PrivacyNameWordSchema>;
183
+
184
+ // What the dictionary makes of a word or a full name, for checking whether it would be found.
185
+ export const PrivacyNameLookupSchema = z.object({
186
+ text: z.string().describe("The query as a name is written: each word capitalized."),
187
+ found: z.boolean().describe("Whether the dictionary alone masks it as a name, written so on its own."),
188
+ words: z.array(PrivacyNameWordSchema),
189
+ });
190
+ export type PrivacyNameLookup = z.infer<typeof PrivacyNameLookupSchema>;
191
+
192
+ export const PRIVACY_DICTIONARY_SAMPLE_MAX = 200;
193
+
194
+ export const PrivacyDictionarySchema = z.object({
195
+ lists: z.array(PrivacyNameListSchema),
196
+ totals: z
197
+ .object({ firstNames: z.number().int(), surnames: z.number().int() })
198
+ .describe("Distinct words across the first-name lists, and across the surname lists."),
199
+ // Words of the lists that start with a one-word query, for browsing; empty without one.
200
+ matches: z.array(z.object({ word: z.string(), lists: z.array(z.string()).describe("Ids of the lists holding it.") })),
201
+ // The query read as a name, when there is one.
202
+ lookup: PrivacyNameLookupSchema.optional(),
203
+ });
204
+ export type PrivacyDictionary = z.infer<typeof PrivacyDictionarySchema>;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { ChildRunSchema } from "../../events/requests.js";
3
3
  import { TRANSLATOR_PROVIDERS, type TranslatorProvider } from "../../models/provider-specs.js";
4
- import { AgentHarnessSchema, AgentProviderSchema, EditorContextSchema } from "../agent.js";
4
+ import { AgentHarnessSchema, AgentProviderSchema, ConversationIdSchema, EditorContextSchema } from "../agent.js";
5
5
  import { MENTION_LIMIT } from "../../text/mentions.js";
6
6
  // Headroom is one shape shared by every provider, not a Claude idea others imitate: a native account and a routed
7
7
  // subscription differ in who holds the credential, never in what a reading is. Every surface that draws a percentage
@@ -32,7 +32,9 @@ export const UsageUnreadSchema = z.object({
32
32
  since: z.number().describe("When re-reading this account first failed, in milliseconds. It has failed on every attempt since."),
33
33
  reason: z
34
34
  .string()
35
- .describe("Why, in the provider's own words where it gave some (\"Verify your account to continue.\"). Short enough to print; never a pasted response body."),
35
+ .describe(
36
+ 'Why, in the provider\'s own words where it gave some ("Verify your account to continue."). Short enough to print; never a pasted response body.',
37
+ ),
36
38
  });
37
39
  export type UsageUnread = z.infer<typeof UsageUnreadSchema>;
38
40
  // Every window kept, not just the binding one, since which pool binds changes between turns. Utilization only climbs
@@ -202,18 +204,27 @@ export const AgentReplySchema = z.discriminatedUnion("kind", [
202
204
  .record(z.string(), z.array(z.string()))
203
205
  .optional()
204
206
  .describe("What you chose, keyed by the question, with the chosen labels or your own words."),
207
+ attachments: z
208
+ .record(z.string(), z.array(z.string()))
209
+ .optional()
210
+ .describe(
211
+ "Files that go with your own-words answer, keyed by the question, as workspace-relative paths of files already uploaded (a screenshot, a mock-up).",
212
+ ),
205
213
  cancelled: z
206
214
  .boolean()
207
215
  .optional()
208
216
  .describe("Dismissing it instead, which tells the agent to carry on using sensible defaults rather than leaving it waiting."),
209
217
  }),
210
- // "always" may be narrowed further by the SDK's own suggestions.
218
+ // "always" may be narrowed further by the SDK's own suggestions. "everything" is the conversation-wide yes the card's
219
+ // Allow menu offers: this call, and every later request in the conversation a person could have allowed once.
211
220
  z.object({
212
221
  kind: z.literal("permission").describe("Answering a request to use a tool."),
213
222
  requestId: z.string().min(1).describe("Which card you are answering."),
214
223
  decision: z
215
- .enum(["once", "always", "deny"])
216
- .describe("Once allows this call alone; always allows that whole tool for the rest of the conversation; no blocks it."),
224
+ .enum(["once", "always", "everything", "deny"])
225
+ .describe(
226
+ "Once allows this call alone; always allows what the request's always-label names (a tool, a rule, a secret) for the rest of the conversation; everything allows this call and every later request in this conversation that an allow-once could settle, until it is taken back on Grants; no blocks it. A request that always asks (alwaysAsks) reads everything as once.",
227
+ ),
217
228
  feedback: z.string().optional().describe("Why not, which goes back to the model as the reason."),
218
229
  // Whole, not a patch: effort or an account named for one model means nothing on another.
219
230
  child: ChildRunSchema.optional().describe(
@@ -401,6 +412,28 @@ export const QueueResumeSchema = z.object({
401
412
  ),
402
413
  });
403
414
  export type QueueResume = z.infer<typeof QueueResumeSchema>;
415
+ // Re-times what a conversation's queue holds: the same messages, booked for another instant or for after another
416
+ // conversation's work lands. One of the two, never both, since a hold waits on exactly one thing.
417
+ export const QueueScheduleSchema = z
418
+ .object({
419
+ conversationId: z.string().min(1).describe("Whose queue."),
420
+ sendAt: z
421
+ .number()
422
+ .int()
423
+ .positive()
424
+ .optional()
425
+ .describe("Send what waits at this instant (epoch milliseconds) instead. At most a month ahead; an instant already past sends it now."),
426
+ sendAfter: ConversationIdSchema.optional().describe(
427
+ "Send what waits once that conversation has finished and all of its work has landed in the workspace, instead. Sent now when it has nothing running and nothing left to land.",
428
+ ),
429
+ })
430
+ .refine((input) => (input.sendAt === undefined) !== (input.sendAfter === undefined), {
431
+ message: "name exactly one of sendAt and sendAfter",
432
+ })
433
+ .refine((input) => input.sendAfter !== input.conversationId, {
434
+ message: "a conversation cannot wait for its own work to land",
435
+ });
436
+ export type QueueSchedule = z.infer<typeof QueueScheduleSchema>;
404
437
  // Moves a conversation to another account of the provider it runs on: the one command that changes who pays, so the
405
438
  // account a conversation runs on is always the daemon's record and never a window's guess.
406
439
  export const SwitchAccountSchema = z.object({
@@ -42,11 +42,16 @@ export const UsageTurnSchema = z.object({
42
42
  turns: z
43
43
  .number()
44
44
  .describe("The provider's own count for the request, since one exchange can be several under the hood. One when it reported none."),
45
- inputTokens: z.number().describe("Tokens sent."),
45
+ inputTokens: z.number().describe("Uncached input tokens, excluding cache reads and cache writes."),
46
46
  outputTokens: z.number().describe("Tokens received."),
47
47
  cacheReadTokens: z.number().describe("Tokens served from cache, which cost less."),
48
48
  cacheCreationTokens: z.number().describe("Tokens written to cache, which cost more up front and less afterwards."),
49
49
  costUsd: z.number().describe("What it cost, in dollars."),
50
+ // Whether `costUsd` was reported by the vendor (true or absent) or is zero only because the vendor does not price
51
+ // its calls (false). Old rows before this field was added carry no marker, which readers treat as known: they
52
+ // already recorded a real number. Runtimes like Codex and Cursor report tokens but no cost, so their turns land
53
+ // with `costUsd: 0, costKnown: false` rather than silently claiming they were free.
54
+ costKnown: z.boolean().optional().describe("False when the vendor did not report a cost and the recorded zero is a placeholder, not a price. Absent or true means the cost is real."),
50
55
  durationMs: z.number().describe("How long it took, in milliseconds."),
51
56
  // Arm of the search-teaching experiment; stable per conversation. Absent means unmeasured.
52
57
  iqSearchArm: z.boolean().optional(),
@@ -75,6 +80,9 @@ export const UsageTurnSchema = z.object({
75
80
  // Arm of the lean-guidance experiment, stable per conversation; true is the short form. The cohort hashes both forms.
76
81
  guidanceArm: z.boolean().optional(),
77
82
  guidanceCohort: z.string().optional(),
83
+ // Arm of the tool-result clearing experiment, stable per conversation; true is a conversation whose old tool results
84
+ // the gateway drops. Native Claude turns only: no other runtime's requests pass a gateway that clears.
85
+ clearingArm: z.boolean().optional(),
78
86
  // What became of pre-turn retrieval on this turn, and how long it took. Assignment and DELIVERY are different
79
87
  // facts: the first version of this mechanism was assigned to every eligible turn and reached four in five of them,
80
88
  // which is the difference between a null result and a mechanism that never ran. Absent means the flag was off, so
@@ -126,11 +134,14 @@ export const UsageRollupRowSchema = z.object({
126
134
  harness: z.string().describe("Which agentic loop."),
127
135
  conversationId: z.string().optional().describe("Which conversation."),
128
136
  turns: z.number().describe("Turns in this group."),
129
- inputTokens: z.number().describe("Tokens sent."),
137
+ inputTokens: z.number().describe("Uncached input tokens, excluding cache reads and cache writes."),
130
138
  outputTokens: z.number().describe("Tokens received."),
131
139
  cacheReadTokens: z.number().describe("Tokens served from cache."),
132
140
  cacheCreationTokens: z.number().describe("Tokens written to cache."),
133
141
  costUsd: z.number().describe("What the group cost, in dollars."),
142
+ // True when every turn in the group reported its cost; false when at least one did not, making `costUsd` a
143
+ // lower bound rather than the true total. Absent on rollups from before this field existed.
144
+ costKnown: z.boolean().optional().describe("False when the cost includes unpriced turns and is a lower bound rather than the true total."),
134
145
  durationMs: z.number().describe("Time spent, in milliseconds."),
135
146
  });
136
147
  export type UsageRollupRow = z.infer<typeof UsageRollupRowSchema>;
@@ -52,6 +52,13 @@ export const PushNotificationSchema = z.object({
52
52
  .describe(
53
53
  "Keep it on screen until it is dismissed. Used when the agent is waiting for you, where one that fades away is a question that went unanswered in silence.",
54
54
  ),
55
+ // Optional so an older service worker or relay, which ignores it, still shows the replacement under the same tag.
56
+ silent: z
57
+ .boolean()
58
+ .optional()
59
+ .describe(
60
+ "Arrive without sound or vibration. Used for the replacement that says an ask stopped waiting: it takes the waiting one's place under the same tag, and is news nobody has to act on.",
61
+ ),
55
62
  });
56
63
  export type PushNotification = z.infer<typeof PushNotificationSchema>;
57
64
  export const PushConfigSchema = z.object({
@@ -23,9 +23,9 @@ const isRetiredRule = (rule: unknown): boolean => {
23
23
 
24
24
  const holdsRetiredRule = (value: unknown): value is readonly unknown[] => Array.isArray(value) && value.some(isRetiredRule);
25
25
 
26
- // A rule at the `turn.ending` moment reads and runs nothing since nothing checks inside a turn (its moment and the
27
- // `verify-ui-edits` built-in stay in the read schema so a file written before reads); dropped, so it stops showing as a
28
- // rule the owner has and stops travelling with the settings.
26
+ // An owner's rule at the `turn.ending` moment reads and runs nothing, since only the checks a repository declares run
27
+ // there (its moment and the `verify-ui-edits` built-in stay in the read schema so a file written before reads); dropped,
28
+ // so it stops showing as a rule the owner has and stops travelling with the settings.
29
29
  const isInertRule = (rule: unknown): boolean =>
30
30
  isJsonObject(rule) &&
31
31
  (rule["moment"] === "turn.ending" || (isJsonObject(rule["action"]) && rule["action"]["kind"] === "builtin" && rule["action"]["name"] === "verify-ui-edits"));