@intentic/sandbox-contract 1.308.2 → 1.310.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 (239) hide show
  1. package/dist/contracts/agent.contract.d.ts +98 -4
  2. package/dist/contracts/agent.contract.d.ts.map +1 -1
  3. package/dist/contracts/agent.contract.js +40 -10
  4. package/dist/contracts/agent.contract.js.map +1 -1
  5. package/dist/contracts/agents.contract.d.ts +285 -0
  6. package/dist/contracts/agents.contract.d.ts.map +1 -1
  7. package/dist/contracts/approvals.contract.d.ts +34 -0
  8. package/dist/contracts/approvals.contract.d.ts.map +1 -1
  9. package/dist/contracts/approvals.contract.js +30 -1
  10. package/dist/contracts/approvals.contract.js.map +1 -1
  11. package/dist/contracts/device.contract.d.ts +1 -0
  12. package/dist/contracts/device.contract.d.ts.map +1 -1
  13. package/dist/contracts/extensions.contract.d.ts +390 -0
  14. package/dist/contracts/extensions.contract.d.ts.map +1 -1
  15. package/dist/contracts/extensions.contract.js +11 -1
  16. package/dist/contracts/extensions.contract.js.map +1 -1
  17. package/dist/contracts/runner.contract.d.ts +117 -107
  18. package/dist/contracts/runner.contract.d.ts.map +1 -1
  19. package/dist/contracts/sessions.contract.d.ts +1 -0
  20. package/dist/contracts/sessions.contract.d.ts.map +1 -1
  21. package/dist/contracts/settings.contract.d.ts +57 -15
  22. package/dist/contracts/settings.contract.d.ts.map +1 -1
  23. package/dist/contracts/system.contract.d.ts +210 -0
  24. package/dist/contracts/system.contract.d.ts.map +1 -1
  25. package/dist/contracts/system.contract.js +35 -1
  26. package/dist/contracts/system.contract.js.map +1 -1
  27. package/dist/events/agent-events.d.ts +9 -2
  28. package/dist/events/agent-events.d.ts.map +1 -1
  29. package/dist/events/agent-events.js +5 -1
  30. package/dist/events/agent-events.js.map +1 -1
  31. package/dist/events/resume.d.ts +1 -0
  32. package/dist/events/resume.d.ts.map +1 -1
  33. package/dist/events/resume.js +2 -0
  34. package/dist/events/resume.js.map +1 -1
  35. package/dist/events/system-events.d.ts +40 -0
  36. package/dist/events/system-events.d.ts.map +1 -1
  37. package/dist/events/transcript.d.ts +14 -0
  38. package/dist/events/transcript.d.ts.map +1 -1
  39. package/dist/events/transcript.js +7 -1
  40. package/dist/events/transcript.js.map +1 -1
  41. package/dist/index.d.ts +1114 -84
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +2 -3
  44. package/dist/index.js.map +1 -1
  45. package/dist/models/model-label.d.ts +2 -0
  46. package/dist/models/model-label.d.ts.map +1 -0
  47. package/dist/models/model-label.js +20 -0
  48. package/dist/models/model-label.js.map +1 -0
  49. package/dist/models/model-pins.d.ts +0 -1
  50. package/dist/models/model-pins.d.ts.map +1 -1
  51. package/dist/models/model-pins.js +0 -2
  52. package/dist/models/model-pins.js.map +1 -1
  53. package/dist/policy/command-classes.d.ts.map +1 -1
  54. package/dist/policy/command-classes.js +88 -7
  55. package/dist/policy/command-classes.js.map +1 -1
  56. package/dist/policy/reserved-servers.d.ts +7 -0
  57. package/dist/policy/reserved-servers.d.ts.map +1 -0
  58. package/dist/policy/reserved-servers.js +21 -0
  59. package/dist/policy/reserved-servers.js.map +1 -0
  60. package/dist/policy/turned-away.d.ts.map +1 -1
  61. package/dist/policy/turned-away.js.map +1 -1
  62. package/dist/protocol/ingress-protocol.d.ts +2 -0
  63. package/dist/protocol/ingress-protocol.d.ts.map +1 -1
  64. package/dist/protocol/ingress-protocol.js +4 -1
  65. package/dist/protocol/ingress-protocol.js.map +1 -1
  66. package/dist/protocol/peer-mcp-server.d.ts +7 -0
  67. package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
  68. package/dist/protocol/peer-mcp-server.js +6 -1
  69. package/dist/protocol/peer-mcp-server.js.map +1 -1
  70. package/dist/protocol/tunnel-heartbeat.d.ts +19 -0
  71. package/dist/protocol/tunnel-heartbeat.d.ts.map +1 -0
  72. package/dist/protocol/tunnel-heartbeat.js +41 -0
  73. package/dist/protocol/tunnel-heartbeat.js.map +1 -0
  74. package/dist/protocol/upgrade-relay.d.ts +4 -0
  75. package/dist/protocol/upgrade-relay.d.ts.map +1 -0
  76. package/dist/protocol/upgrade-relay.js +25 -0
  77. package/dist/protocol/upgrade-relay.js.map +1 -0
  78. package/dist/schemas/agent.d.ts +59 -0
  79. package/dist/schemas/agent.d.ts.map +1 -1
  80. package/dist/schemas/agent.js +45 -2
  81. package/dist/schemas/agent.js.map +1 -1
  82. package/dist/schemas/agents.d.ts +60 -0
  83. package/dist/schemas/agents.d.ts.map +1 -1
  84. package/dist/schemas/agents.js +6 -1
  85. package/dist/schemas/agents.js.map +1 -1
  86. package/dist/schemas/approvals.d.ts +67 -0
  87. package/dist/schemas/approvals.d.ts.map +1 -1
  88. package/dist/schemas/approvals.js +43 -0
  89. package/dist/schemas/approvals.js.map +1 -1
  90. package/dist/schemas/automations.d.ts +20 -0
  91. package/dist/schemas/automations.d.ts.map +1 -1
  92. package/dist/schemas/ci.d.ts.map +1 -1
  93. package/dist/schemas/ci.js.map +1 -1
  94. package/dist/schemas/devices.d.ts +2 -0
  95. package/dist/schemas/devices.d.ts.map +1 -1
  96. package/dist/schemas/devices.js +1 -0
  97. package/dist/schemas/devices.js.map +1 -1
  98. package/dist/schemas/extension-updates.d.ts +850 -77
  99. package/dist/schemas/extension-updates.d.ts.map +1 -1
  100. package/dist/schemas/extension-updates.js +23 -0
  101. package/dist/schemas/extension-updates.js.map +1 -1
  102. package/dist/schemas/history.d.ts +1 -0
  103. package/dist/schemas/history.d.ts.map +1 -1
  104. package/dist/schemas/history.js +4 -0
  105. package/dist/schemas/history.js.map +1 -1
  106. package/dist/schemas/metrics.d.ts +257 -0
  107. package/dist/schemas/metrics.d.ts.map +1 -1
  108. package/dist/schemas/metrics.js +73 -3
  109. package/dist/schemas/metrics.js.map +1 -1
  110. package/dist/schemas/providers/plan-limits.d.ts +42 -1
  111. package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
  112. package/dist/schemas/providers/plan-limits.js +48 -1
  113. package/dist/schemas/providers/plan-limits.js.map +1 -1
  114. package/dist/schemas/providers/usage.d.ts +2 -0
  115. package/dist/schemas/providers/usage.d.ts.map +1 -1
  116. package/dist/schemas/providers/usage.js +2 -0
  117. package/dist/schemas/providers/usage.js.map +1 -1
  118. package/dist/schemas/settings.d.ts +61 -29
  119. package/dist/schemas/settings.d.ts.map +1 -1
  120. package/dist/schemas/settings.js +36 -11
  121. package/dist/schemas/settings.js.map +1 -1
  122. package/dist/schemas/turn-break.d.ts +5 -0
  123. package/dist/schemas/turn-break.d.ts.map +1 -1
  124. package/dist/schemas/turn-break.js +4 -0
  125. package/dist/schemas/turn-break.js.map +1 -1
  126. package/dist/state/definition.d.ts +16 -20
  127. package/dist/state/definition.d.ts.map +1 -1
  128. package/dist/state/history-state.d.ts.map +1 -1
  129. package/dist/state/history-state.js +3 -0
  130. package/dist/state/history-state.js.map +1 -1
  131. package/dist/state/workspace-state.d.ts +0 -7
  132. package/dist/state/workspace-state.d.ts.map +1 -1
  133. package/dist/state/workspace-state.js +0 -8
  134. package/dist/state/workspace-state.js.map +1 -1
  135. package/dist/text/transcript-fold.d.ts +1 -1
  136. package/dist/text/transcript-fold.d.ts.map +1 -1
  137. package/dist/text/transcript-fold.js +19 -9
  138. package/dist/text/transcript-fold.js.map +1 -1
  139. package/package.json +27 -5
  140. package/src/chores/chores.test.ts +0 -1
  141. package/src/chores/digest.test.ts +0 -1
  142. package/src/chores/probes.test.ts +0 -1
  143. package/src/chores/stack.test.ts +0 -1
  144. package/src/chores/verdict.test.ts +0 -1
  145. package/src/contracts/agent.contract.ts +62 -15
  146. package/src/contracts/approvals.contract.ts +35 -1
  147. package/src/contracts/extensions.contract.ts +14 -0
  148. package/src/contracts/system.contract.ts +42 -1
  149. package/src/events/agent-events.ts +10 -2
  150. package/src/events/agent-words.test.ts +0 -1
  151. package/src/events/resume.test.ts +8 -1
  152. package/src/events/resume.ts +4 -0
  153. package/src/events/transcript.ts +13 -2
  154. package/src/events/watch-wake.test.ts +0 -1
  155. package/src/ids/conversation-ids.test.ts +1 -2
  156. package/src/ids/hostnames.test.ts +0 -1
  157. package/src/ids/share-paths.test.ts +0 -1
  158. package/src/ids/tunnel-ids.test.ts +0 -1
  159. package/src/index.ts +2 -3
  160. package/src/models/agent-catalog.test.ts +0 -1
  161. package/src/models/capability-ledger.test.ts +0 -1
  162. package/src/models/model-label.test.ts +23 -0
  163. package/src/models/model-label.ts +29 -0
  164. package/src/models/model-offer.test.ts +0 -1
  165. package/src/models/model-order.test.ts +0 -1
  166. package/src/models/model-pins.test.ts +0 -1
  167. package/src/models/model-pins.ts +0 -6
  168. package/src/models/model-roles.test.ts +0 -1
  169. package/src/models/plan-pools.test.ts +0 -1
  170. package/src/models/provider-specs.test.ts +0 -1
  171. package/src/policy/batch-runs.test.ts +0 -1
  172. package/src/policy/command-classes.test.ts +99 -2
  173. package/src/policy/command-classes.ts +143 -11
  174. package/src/policy/credential-material.test.ts +0 -1
  175. package/src/policy/fence-paths.test.ts +0 -1
  176. package/src/policy/overlay-lint.test.ts +0 -1
  177. package/src/policy/owner-ticket.test.ts +0 -1
  178. package/src/policy/persona-home.test.ts +0 -1
  179. package/src/policy/reserved-servers.test.ts +50 -0
  180. package/src/policy/reserved-servers.ts +48 -0
  181. package/src/policy/safety-policy.test.ts +0 -1
  182. package/src/policy/search-globs.test.ts +0 -1
  183. package/src/policy/turned-away.ts +2 -1
  184. package/src/protocol/container-requirements.test.ts +0 -1
  185. package/src/protocol/ingress-contract.test.ts +0 -1
  186. package/src/protocol/ingress-protocol.test.ts +78 -2
  187. package/src/protocol/ingress-protocol.ts +8 -1
  188. package/src/protocol/peer-dial.test.ts +1 -2
  189. package/src/protocol/peer-mcp-server.test.ts +10 -4
  190. package/src/protocol/peer-mcp-server.ts +14 -1
  191. package/src/protocol/routes.test.ts +6 -3
  192. package/src/protocol/tunnel-heartbeat.test.ts +75 -0
  193. package/src/protocol/tunnel-heartbeat.ts +67 -0
  194. package/src/protocol/upgrade-relay.ts +28 -0
  195. package/src/schemas/agent.test.ts +0 -1
  196. package/src/schemas/agent.ts +77 -5
  197. package/src/schemas/agents.ts +11 -1
  198. package/src/schemas/approvals.ts +67 -0
  199. package/src/schemas/areas.test.ts +1 -2
  200. package/src/schemas/ci.ts +3 -9
  201. package/src/schemas/context-trim.test.ts +2 -3
  202. package/src/schemas/dev-rebuild-log.test.ts +0 -1
  203. package/src/schemas/devices.ts +2 -0
  204. package/src/schemas/extension-updates.ts +30 -0
  205. package/src/schemas/git/git.test.ts +0 -1
  206. package/src/schemas/history.ts +8 -1
  207. package/src/schemas/metrics.ts +121 -4
  208. package/src/schemas/providers/plan-limits.ts +67 -2
  209. package/src/schemas/providers/usage.ts +3 -0
  210. package/src/schemas/settings.test.ts +41 -7
  211. package/src/schemas/settings.ts +64 -44
  212. package/src/schemas/turn-break.ts +8 -0
  213. package/src/schemas/version-seam.test.ts +0 -1
  214. package/src/state/contract-lock.test.ts +0 -1
  215. package/src/state/fix-attempt-plan.test.ts +0 -1
  216. package/src/state/fix-stance.test.ts +0 -1
  217. package/src/state/history-state.ts +6 -0
  218. package/src/state/landed-commit.test.ts +0 -1
  219. package/src/state/runtime-state.test.ts +0 -1
  220. package/src/state/versions.test.ts +0 -1
  221. package/src/state/workspace-state.test.ts +2 -5
  222. package/src/state/workspace-state.ts +0 -14
  223. package/src/text/archives.test.ts +0 -1
  224. package/src/text/documents.test.ts +0 -1
  225. package/src/text/embed.test.ts +2 -3
  226. package/src/text/emoji.test.ts +0 -1
  227. package/src/text/mentions.test.ts +0 -1
  228. package/src/text/path-refs.test.ts +0 -1
  229. package/src/text/title.test.ts +0 -1
  230. package/src/text/transcript-fold.test.ts +35 -8
  231. package/src/text/transcript-fold.ts +32 -19
  232. package/src/text/whisper.test.ts +0 -1
  233. package/src/text/workflow-faults.test.ts +0 -1
  234. package/src/time/zone.test.ts +0 -1
  235. package/dist/contracts/prepush.contract.d.ts +0 -32
  236. package/dist/contracts/prepush.contract.d.ts.map +0 -1
  237. package/dist/contracts/prepush.contract.js +0 -41
  238. package/dist/contracts/prepush.contract.js.map +0 -1
  239. package/src/contracts/prepush.contract.ts +0 -58
@@ -25,7 +25,8 @@ export interface SnapshotTurn {
25
25
  }
26
26
  export const SnapshotsListSchema = z.object({ snapshots: z.array(SnapshotSchema).describe("Every point you can go back to, newest first.") });
27
27
  // Restores the workspace to that turn's checkpoint, drops every message after it, and forgets the provider session so
28
- // the next turn opens fresh.
28
+ // the next turn opens fresh. `messageId` is what makes `index` safe to act on: a position is only as current as the
29
+ // transcript it was read from.
29
30
  export const RewindTurnSchema = z.object({
30
31
  conversationId: z.string().min(1).describe("Which conversation to rewind."),
31
32
  index: z
@@ -35,6 +36,12 @@ export const RewindTurnSchema = z.object({
35
36
  .describe(
36
37
  "Which message to go back to, counting from the start. It is also how many messages survive: rewinding to the first keeps none of them and puts the files back to before it ran.",
37
38
  ),
39
+ messageId: z
40
+ .string()
41
+ .min(1)
42
+ .describe(
43
+ "The id of the message at that position, as its row names it. If that position now holds a different message, nothing is rewound: the transcript has moved since you read it.",
44
+ ),
38
45
  });
39
46
  export const RewindResultSchema = z.object({
40
47
  snapshot: z
@@ -59,9 +59,7 @@ export const SandboxUsageSchema = z.object({
59
59
  .optional()
60
60
  .describe("CPU the whole sandbox used over the window, as a percentage of all it may use (`cores`). Absent on a first reading."),
61
61
  cores: z.number().describe("How many cores the sandbox may use: its CPU quota, or every core it is allowed to run on when it has none."),
62
- memoryBytes: z
63
- .number()
64
- .describe("Memory in use, less the file cache the kernel takes back on demand: the figure that runs into the limit."),
62
+ memoryBytes: z.number().describe("Memory in use, less the file cache the kernel takes back on demand: the figure that runs into the limit."),
65
63
  memoryLimitBytes: z.number().describe("The memory limit: the container's own, or the machine's memory when the container has none."),
66
64
  swapBytes: z.number().optional().describe("Memory pushed out to swap. Absent where the sandbox cannot see its own swap."),
67
65
  diskBytes: z.number().optional().describe("Space used on the volume the workspace lives on. Absent when the volume would not say."),
@@ -70,7 +68,9 @@ export const SandboxUsageSchema = z.object({
70
68
  .tuple([z.number(), z.number(), z.number()])
71
69
  .describe("The load average over 1, 5 and 15 minutes. It is the machine's, so other sandboxes on it count too."),
72
70
  processes: z.number().describe("How many processes are running in the sandbox."),
73
- pressure: PressureMetricsSchema.optional().describe("How much work waited on CPU, memory or disk lately. Absent where the kernel does not report it."),
71
+ pressure: PressureMetricsSchema.optional().describe(
72
+ "How much work waited on CPU, memory or disk lately. Absent where the kernel does not report it.",
73
+ ),
74
74
  });
75
75
  export type SandboxUsage = z.infer<typeof SandboxUsageSchema>;
76
76
 
@@ -105,3 +105,120 @@ export const SandboxMetricsSchema = z.object({
105
105
  .describe("Every process in the sandbox but the daemon, by what kind of work it is. A kind with nothing running is absent."),
106
106
  });
107
107
  export type SandboxMetrics = z.infer<typeof SandboxMetricsSchema>;
108
+
109
+ // What is filling the sandbox's disk, by what it is for (GET /system/storage). Sizes are apparent bytes, and a file
110
+ // with several hard links counts once, toward the first place the scan met it.
111
+
112
+ // none: never removed from here; safe: comes back by itself, removed without asking; confirm: removed only once the
113
+ // owner has read what it costs.
114
+ export const StorageCleanabilitySchema = z.enum(["none", "safe", "confirm"]);
115
+ export type StorageCleanability = z.infer<typeof StorageCleanabilitySchema>;
116
+
117
+ // Every category a byte on the volumes can belong to, and its cleanability: the cleaner refuses by it, the editor words its confirm by it.
118
+ export const STORAGE_CLEANABILITY = {
119
+ // The workspace's own files: repositories, their dependencies, anything the owner or an agent put there.
120
+ workspace: "none",
121
+ // Transcripts, runtime session stores, attachments and loop memory of every conversation, archived ones included.
122
+ conversations: "none",
123
+ // Each open conversation's own checkout of the repositories and the dependencies its turns installed; archiving the conversation frees it.
124
+ checkouts: "none",
125
+ // Per-turn and minute-by-minute snapshots History restores from; no compaction to offer: `git gc --auto` runs after each, all is reachable.
126
+ restorePoints: "none",
127
+ // The repositories' own git data: every commit and branch, pushed or not.
128
+ repositories: "none",
129
+ // Installed agent runtimes, the one turns use and the one kept for Revert; the Engines card owns which is which, so removing one strands it.
130
+ engines: "none",
131
+ // Search indexes and caches the sandbox keeps up to date while it runs, held open by it: removing a file frees nothing until it closes.
132
+ indexes: "none",
133
+ // Installed extensions and their scratch.
134
+ extensions: "none",
135
+ // The sandbox's own Docker: images, containers, volumes.
136
+ docker: "none",
137
+ // Settings, credentials, sign-ins and the ledgers of what was spent and done.
138
+ state: "none",
139
+ // Anything no part of the sandbox claims.
140
+ other: "none",
141
+ // Git data and checkouts of repositories deleted from the workspace; nothing reads it again, but it may hold commits never pushed.
142
+ trash: "confirm",
143
+ // Copies of the sandbox packed for download.
144
+ exports: "confirm",
145
+ // Files agents produced: generated images, reports, harness output.
146
+ artifacts: "confirm",
147
+ // Screenshots and page snapshots the agent's browser took.
148
+ browserCaptures: "confirm",
149
+ // The agent browser's profiles, with the sites it is signed in to.
150
+ browserProfiles: "confirm",
151
+ // Weights downloaded for local models.
152
+ modelWeights: "confirm",
153
+ // The sandbox's own logs and terminal captures.
154
+ logs: "safe",
155
+ // Scratch space agents and checks leave behind.
156
+ scratch: "safe",
157
+ // Package managers' content stores, which the next install refills.
158
+ packageStores: "safe",
159
+ // Build tools' output caches.
160
+ buildCaches: "safe",
161
+ } as const satisfies Record<string, StorageCleanability>;
162
+ export type StorageCategoryId = keyof typeof STORAGE_CLEANABILITY;
163
+ export const StorageCategoryIdSchema = z.enum(Object.keys(STORAGE_CLEANABILITY) as StorageCategoryId[]);
164
+
165
+ export const StorageItemSchema = z.object({
166
+ path: z.string().describe("Where it is, as an absolute path inside the sandbox."),
167
+ bytes: z.number().describe("Its size in bytes."),
168
+ });
169
+ export type StorageItem = z.infer<typeof StorageItemSchema>;
170
+
171
+ export const StorageCategoryUsageSchema = z.object({
172
+ id: StorageCategoryIdSchema,
173
+ cleanability: StorageCleanabilitySchema.describe("Whether this category can be cleaned from here, and whether cleaning it asks first."),
174
+ bytes: z.number().describe("Its size in bytes."),
175
+ files: z.number().describe("How many files it holds."),
176
+ cleanableBytes: z
177
+ .number()
178
+ .optional()
179
+ .describe(
180
+ "What cleaning it would free right now: only what is old enough and not in use. Absent where nothing here may be cleaned, and for a package store, whose own tool decides what no project needs.",
181
+ ),
182
+ items: z.array(StorageItemSchema).describe("Its biggest parts, largest first, at most eight."),
183
+ });
184
+ export type StorageCategoryUsage = z.infer<typeof StorageCategoryUsageSchema>;
185
+
186
+ export const StorageScanSchema = z.object({
187
+ startedAt: z.number().describe("When the scan began, in milliseconds."),
188
+ finishedAt: z.number().describe("When it ended, in milliseconds: the moment these sizes describe."),
189
+ outcome: z
190
+ .enum(["complete", "partial"])
191
+ .describe("`partial` when the scan hit its time limit first, so every size is at least what it says rather than exactly it."),
192
+ disk: z
193
+ .object({
194
+ usedBytes: z.number().describe("Space used on the volume the workspace lives on."),
195
+ totalBytes: z.number().describe("That volume's size."),
196
+ })
197
+ .optional()
198
+ .describe("The volume as a whole. Absent when the volume would not say."),
199
+ categories: z.array(StorageCategoryUsageSchema).describe("Every category that holds anything, largest first."),
200
+ unreadable: z.number().describe("Files and folders the scan could not read, and so did not count."),
201
+ });
202
+ export type StorageScan = z.infer<typeof StorageScanSchema>;
203
+
204
+ export const StorageReportSchema = z.object({
205
+ scan: StorageScanSchema.optional().describe("The last scan that finished. Absent until one has, and again after the daemon restarts."),
206
+ scanning: z.boolean().describe("Whether a scan is running now."),
207
+ });
208
+ export type StorageReport = z.infer<typeof StorageReportSchema>;
209
+
210
+ export const StorageCleanInputSchema = z.object({
211
+ category: StorageCategoryIdSchema.describe("The category to clean; one whose cleanability is `none` is refused."),
212
+ });
213
+ export type StorageCleanInput = z.infer<typeof StorageCleanInputSchema>;
214
+
215
+ export const StorageCleanResultSchema = z.object({
216
+ category: StorageCategoryIdSchema,
217
+ freedBytes: z
218
+ .number()
219
+ .describe("Space the removals gave back, in bytes. A file that is still linked elsewhere frees nothing and is not counted."),
220
+ removed: z.number().describe("How many items were removed."),
221
+ kept: z.number().describe("How many were left in place: changed too recently, in use by a running program, or no longer this category's."),
222
+ failed: z.number().describe("How many removals the filesystem refused."),
223
+ });
224
+ export type StorageCleanResult = z.infer<typeof StorageCleanResultSchema>;
@@ -214,6 +214,14 @@ export const SteerSchema = z
214
214
  .object({
215
215
  conversationId: z.string().min(1).describe("Which running conversation to interrupt."),
216
216
  text: z.string().max(20_000).describe("What to say to it. It arrives mid-turn without stopping the turn."),
217
+ messageId: z
218
+ .string()
219
+ .min(1)
220
+ .max(128)
221
+ .optional()
222
+ .describe(
223
+ "Your id for this message. Sending again under an id the sandbox already took is answered with what it did with it the first time, never a second delivery. Leave it out and the sandbox names the message itself.",
224
+ ),
217
225
  attachments: z
218
226
  .array(z.string().min(1))
219
227
  .max(20)
@@ -233,8 +241,42 @@ export const SteerSchema = z
233
241
  .refine((steer) => steer.text.trim().length > 0 || (steer.attachments?.length ?? 0) > 0, {
234
242
  message: "text or attachments required",
235
243
  });
236
- // True cancel, aborted daemon-side; unlike closing the /agent fetch, which sends no cancel frame.
237
- export const StopTurnSchema = z.object({ conversationId: z.string().min(1).describe("Which conversation's running turn to cancel.") });
244
+ // True cancel, aborted daemon-side; unlike closing the /agent fetch, which sends no cancel frame. A stop names the run
245
+ // it means, so one arriving after that run settled cannot cancel whatever the conversation started next.
246
+ export const StopTurnSchema = z.union([
247
+ z.object({
248
+ conversationId: z.string().min(1).describe("Which conversation's running turn to cancel."),
249
+ run: z
250
+ .string()
251
+ .min(1)
252
+ .describe(
253
+ "The run you mean to cancel, as starting or attaching to it named it. If another turn has started since, nothing is cancelled and the answer names the one running instead.",
254
+ ),
255
+ }),
256
+ // A send not yet answered has no run to name, only the id it gave its message.
257
+ z.object({
258
+ conversationId: z.string().min(1).describe("Which conversation's running turn to cancel."),
259
+ messageId: z
260
+ .string()
261
+ .min(1)
262
+ .describe(
263
+ "The message you sent, while its run is not named yet: cancels the turn it is in. If none is, nothing is cancelled: the message has not become a turn, or its turn has already ended.",
264
+ ),
265
+ }),
266
+ // Only for a turn nobody can name: one the sandbox runs with no run to attach to (a scheduled automation's).
267
+ z.object({
268
+ conversationId: z.string().min(1).describe("Which conversation's running turn to cancel."),
269
+ live: z
270
+ .literal(true)
271
+ .describe("Cancel whatever turn is running now, whichever that is. Only for a turn you cannot name: one that has no run to attach to."),
272
+ }),
273
+ ]);
274
+ export type StopTurn = z.infer<typeof StopTurnSchema>;
275
+ export const StopResultSchema = z.object({
276
+ stopped: z.boolean().describe("Whether a turn was cancelled."),
277
+ running: z.string().optional().describe("The run that is live instead of the one you named, left running. Absent when nothing else runs."),
278
+ });
279
+ export type StopResult = z.infer<typeof StopResultSchema>;
238
280
  // The press carries WHO serves the re-run; the daemon keeps WHAT the turn is (prompt, attachments, worktree) from its
239
281
  // own record, since re-deriving from a stripped client transcript would replay a different turn.
240
282
  export const ResumeRoutingSchema = z.object({
@@ -261,3 +303,26 @@ export const ResumeTurnSchema = z.object({
261
303
  "Who serves the re-run, when the conversation has been re-pointed since it was refused. Leave it out to run it on whatever the turn carried.",
262
304
  ),
263
305
  });
306
+ // Names one queued message as it was read, so a change made against an older copy is refused rather than written over
307
+ // somebody else's.
308
+ export const QueuedMessageRefSchema = z.object({
309
+ conversationId: z.string().min(1).describe("Whose queue."),
310
+ id: z.string().min(1).describe("Which waiting message."),
311
+ revision: z
312
+ .number()
313
+ .int()
314
+ .nonnegative()
315
+ .describe("The message's revision as you read it. If it has been changed since, from this window or another, nothing happens."),
316
+ });
317
+ export type QueuedMessageRef = z.infer<typeof QueuedMessageRefSchema>;
318
+ export const QueueEditSchema = QueuedMessageRefSchema.extend({
319
+ text: z.string().describe("What the message should say instead. It keeps its place in the queue."),
320
+ });
321
+ export type QueueEdit = z.infer<typeof QueueEditSchema>;
322
+ export const QueueResumeSchema = z.object({
323
+ conversationId: z.string().min(1).describe("Whose queue to let go."),
324
+ routing: ResumeRoutingSchema.optional().describe(
325
+ "Who serves the turn the waiting messages start, when the conversation has been re-pointed since they were queued: the usual answer to a refusal that held them. Leave it out to send them as they were queued.",
326
+ ),
327
+ });
328
+ export type QueueResume = z.infer<typeof QueueResumeSchema>;
@@ -72,6 +72,9 @@ export const UsageTurnSchema = z.object({
72
72
  // Hash of the brief that was sent, recorded on control turns too. Load-bearing rather than decorative: the file is
73
73
  // rewritten monthly, so a 30-day window holds two different treatments and pooling them would measure neither.
74
74
  notesCohort: z.string().optional(),
75
+ // Arm of the lean-guidance experiment, stable per conversation; true is the short form. The cohort hashes both forms.
76
+ guidanceArm: z.boolean().optional(),
77
+ guidanceCohort: z.string().optional(),
75
78
  // What became of pre-turn retrieval on this turn, and how long it took. Assignment and DELIVERY are different
76
79
  // facts: the first version of this mechanism was assigned to every eligible turn and reached four in five of them,
77
80
  // which is the difference between a null result and a mechanism that never ran. Absent means the flag was off, so
@@ -1,5 +1,4 @@
1
- import { describe, test, expect } from "bun:test";
2
- import { RuleSchema } from "./settings.js";
1
+ import { RepoChecksFileSchema, RuleSchema, SandboxSettingsSchema } from "./settings.js";
3
2
 
4
3
  // A rule that saves cleanly and silently does nothing is the failure this schema exists to refuse: an action, and a
5
4
  // built-in's name, are checked against the moment they are written at.
@@ -12,14 +11,49 @@ describe(`which built-in stands at which moment`, () => {
12
11
  expect(rule(`turn.ending`, { kind: `builtin`, name: `version-landed` }).success).toBe(false);
13
12
  });
14
13
 
15
- test(`the verifiers stand at the turn's end and not at the landing`, () => {
16
- expect(rule(`turn.ending`, { kind: `builtin`, name: `verify-edits` }).success).toBe(true);
17
- expect(rule(`agent.landed`, { kind: `builtin`, name: `verify-edits` }).success).toBe(false);
14
+ test(`the look stands at the turn's end and not at the landing`, () => {
15
+ expect(rule(`turn.ending`, { kind: `builtin`, name: `verify-ui-edits` }).success).toBe(true);
16
+ expect(rule(`agent.landed`, { kind: `builtin`, name: `verify-ui-edits` }).success).toBe(false);
18
17
  });
19
18
 
20
- test(`the landed moment takes no verdict, command or instruction: the work is already in the tree`, () => {
19
+ test(`the landed moment takes no verdict and no command: the work is already in the tree`, () => {
21
20
  expect(rule(`agent.landed`, { kind: `verdict`, verdict: `allow` }).success).toBe(false);
22
21
  expect(rule(`agent.landed`, { kind: `command`, command: `pnpm test` }).success).toBe(false);
23
- expect(rule(`agent.landed`, { kind: `instruct`, text: `well done` }).success).toBe(false);
22
+ });
23
+
24
+ test(`there is no push moment: the repository's own pre-push hook gates a push`, () => {
25
+ expect(rule(`push.starting`, { kind: `command`, command: `pnpm test` }).success).toBe(false);
26
+ });
27
+ });
28
+
29
+ describe(`where a command lives`, () => {
30
+ test(`settings refuse a command rule, since it belongs to the repository it checks`, () => {
31
+ const command = { id: `lint`, label: `lint`, moment: `file.edited`, action: { kind: `command`, command: `oxlint {file}` } };
32
+ expect(SandboxSettingsSchema.safeParse({ rules: [command] }).success).toBe(false);
33
+ const land = { id: `auto-land`, label: `land`, moment: `agent.finished`, action: { kind: `verdict`, verdict: `allow` } };
34
+ expect(SandboxSettingsSchema.safeParse({ rules: [land] }).success).toBe(true);
35
+ });
36
+
37
+ test(`a repository declares edit, turn and land checks, and no push check`, () => {
38
+ const file = (checks: unknown[]) => RepoChecksFileSchema.safeParse({ checks }).success;
39
+ expect(
40
+ file([
41
+ { when: `edit`, run: `lint {file}` },
42
+ { when: `turn`, run: `pnpm verify:turn` },
43
+ { when: `land`, run: `pnpm verify` },
44
+ ]),
45
+ ).toBe(true);
46
+ expect(file([{ when: `push`, run: `pnpm verify:push` }])).toBe(false);
47
+ });
48
+
49
+ test(`a land check is one per repository and narrows by no path`, () => {
50
+ const file = (checks: unknown[]) => RepoChecksFileSchema.safeParse({ checks }).success;
51
+ expect(
52
+ file([
53
+ { when: `land`, run: `pnpm verify` },
54
+ { when: `land`, run: `pnpm test` },
55
+ ]),
56
+ ).toBe(false);
57
+ expect(file([{ when: `land`, run: `pnpm verify`, paths: [`src/**`] }])).toBe(false);
24
58
  });
25
59
  });
@@ -13,39 +13,28 @@ export const SystemPromptModeSchema = z.enum(["intentic", "claude", "custom"]);
13
13
  export type SystemPromptMode = z.infer<typeof SystemPromptModeSchema>;
14
14
  // Excludes "custom": there is nothing to fetch, it's whatever the owner already typed into the settings field.
15
15
  export const BuiltinPromptSchema = z.object({ base: z.enum(["intentic", "claude"]) });
16
- // Rules: "at this moment, if this is true, do this" — one table replacing three settings that were the same idea built
17
- // three ways; a fourth is now a row here, not a release. Moments are named to match `WorkspaceEventKind`, so folding
18
- // chores in later won't rename what users wrote.
19
-
20
- // Five places the daemon already stops to decide something; this names those decisions rather than inventing new ones.
16
+ // Rules: "at this moment, if this is true, do this". The owner's rules decide (land, hold, version) and switch the
17
+ // daemon's built-in reviews; every command a moment runs is a repository's own check (`<repo>/.intentic/checks.json`),
18
+ // compiled into this same table by the daemon, so no command lives in settings.
21
19
  export const RuleMomentSchema = z.enum([
22
20
  // A command here runs on the just-written file (`{file}` is its path); the cheapest moment to catch a defect.
23
21
  "file.edited",
24
22
  // The assistant is about to stop. A rule here can send it back to work, which is the only moment that can.
25
23
  "turn.ending",
26
- // Code is about to leave the machine. A rule here gates the push on its own exit code.
27
- "push.starting",
28
24
  // An agent's turn is over and its delta is sitting on its branch. A rule here decides whether it lands.
29
25
  "agent.finished",
30
26
  // An agent's delta has just reached the main tree. A rule here decides what becomes of it there.
31
27
  "agent.landed",
32
28
  ]);
33
29
  export type RuleMoment = z.infer<typeof RuleMomentSchema>;
34
- // What a rule does; the split is functional since the three settings this table replaces each needed a different shape.
35
- // command: runs a shell command; its exit code is the verdict.
36
- // instruct: says something to the assistant before it finishes.
37
- // verdict: allows or holds what is about to happen (a pass with nothing to run, told which way to go).
38
- // builtin: invokes a named daemon behaviour the table has no business expressing itself.
39
- // Each reads a record only the daemon keeps, which is what makes it a built-in rather than a command.
40
- // verify-edits: what a turn edited, against what it ran.
41
- // verify-removals: what a turn deleted, against the repo's own history (a `git log` question, not a shell one-liner).
30
+ // command: a repository's check; its exit code is the verdict.
31
+ // verdict: allows or holds what is about to happen.
32
+ // builtin: a daemon behaviour that reads a record only the daemon keeps.
42
33
  // verify-ui-edits: rendered surfaces a turn changed, against whether it ever looked at one.
43
- // verify-tests: whether a touched test's assertions got weaker than at HEAD, or would have passed before its own
44
- // change.
45
34
  // version-landed: commits what a land brought, in the main tree, under the subject drafted for it, and the tree's own
46
35
  // remainder before the next isolated turn starts; the only way a person who never commits keeps every agent seeing
47
36
  // the latest tree, since worktrees are cut from HEAD.
48
- export const RuleBuiltinSchema = z.enum(["verify-edits", "verify-removals", "verify-ui-edits", "verify-tests", "version-landed"]);
37
+ export const RuleBuiltinSchema = z.enum(["verify-ui-edits", "version-landed"]);
49
38
  export type RuleBuiltin = z.infer<typeof RuleBuiltinSchema>;
50
39
  export const RuleActionSchema = z.discriminatedUnion("kind", [
51
40
  z.object({
@@ -54,7 +43,6 @@ export const RuleActionSchema = z.discriminatedUnion("kind", [
54
43
  // Past this, the process group is killed and the run is `failed`, never a silent pass.
55
44
  timeoutMs: z.number().min(60_000).max(3_600_000).default(900_000),
56
45
  }),
57
- z.object({ kind: z.literal("instruct"), text: z.string().min(1).max(4000) }),
58
46
  z.object({ kind: z.literal("verdict"), verdict: z.enum(["allow", "hold"]) }),
59
47
  z.object({ kind: z.literal("builtin"), name: RuleBuiltinSchema }),
60
48
  ]);
@@ -63,8 +51,7 @@ export type RuleAction = z.infer<typeof RuleActionSchema>;
63
51
  // defaults to HELD unless a rule explicitly allows it.
64
52
  export const RuleOutcomeSchema = z.enum(["clean", "error", "conflict", "checks-failed"]);
65
53
  export type RuleOutcome = z.infer<typeof RuleOutcomeSchema>;
66
- // Three keys covering "only this repo" and "skip docs-only changes" without a query language; every key absent means
67
- // the rule always matches.
54
+ // Every key absent means the rule always matches.
68
55
  export const RuleConditionSchema = z.object({
69
56
  // A workspace repo id, or "root". Absent ⇒ any.
70
57
  repo: z.string().min(1).optional(),
@@ -80,15 +67,13 @@ export type RuleCondition = z.infer<typeof RuleConditionSchema>;
80
67
  // validated here, not left to the consumer — the alternative is a rule that saves cleanly and silently does nothing.
81
68
  const MOMENT_ACTIONS: Record<RuleMoment, readonly RuleAction["kind"][]> = {
82
69
  "file.edited": ["command"],
83
- "turn.ending": ["builtin", "instruct", "command"],
84
- "push.starting": ["command"],
70
+ "turn.ending": ["builtin", "command"],
85
71
  "agent.finished": ["verdict"],
86
72
  "agent.landed": ["builtin"],
87
73
  };
88
- // A built-in reads a record only one moment keeps, so it stands at that moment alone: the verifiers read a turn's
89
- // ledgers, the versioner reads a landing's claim.
74
+ // A built-in reads a record only one moment keeps, so it stands at that moment alone.
90
75
  const MOMENT_BUILTINS: Partial<Record<RuleMoment, readonly RuleBuiltin[]>> = {
91
- "turn.ending": ["verify-edits", "verify-removals", "verify-ui-edits", "verify-tests"],
76
+ "turn.ending": ["verify-ui-edits"],
92
77
  "agent.landed": ["version-landed"],
93
78
  };
94
79
  export const RuleSchema = z
@@ -254,6 +239,21 @@ export const SandboxSettingsSchema = z.object({
254
239
  .describe(
255
240
  "Your own instructions, used only when the mode above says custom. Then it is the whole of them: both built-in bases go, and so does everything this product would otherwise add, including the guidance the chat's own cards are driven by. That is the price of total control.",
256
241
  ),
242
+ // Measured by the `guidance` experiment against the full set; the full set stays the default until that says otherwise.
243
+ leanGuidance: z
244
+ .boolean()
245
+ .default(false)
246
+ .describe(
247
+ "Send this product's own guidance in its short form: only what the agent cannot find out by looking, instead of a paragraph for every habit it was once caught in. Off by default, because the long form is the one the product was tuned on.",
248
+ ),
249
+ leanGuidanceHoldout: z
250
+ .number()
251
+ .min(0)
252
+ .max(1)
253
+ .default(0)
254
+ .describe(
255
+ "What share of conversations to keep on the long form, so the two can be compared. Whole conversations rather than individual turns, because the guidance sits in the prompt for the whole session.",
256
+ ),
257
257
  iqSearch: z
258
258
  .boolean()
259
259
  .default(false)
@@ -414,14 +414,17 @@ export const SandboxSettingsSchema = z.object({
414
414
  .describe(
415
415
  "Which repositories may run the checks they declare for themselves, and exactly which version of those checks you agreed to. A repository's declaration does nothing until it appears here, the same rule git keeps for hooks, which are never cloned; and a declaration that changes afterwards is held until you look at it again.",
416
416
  ),
417
- // Lives in the owner's own settings, not the workspace: a rule can hold work and gate a push, so it answers to the
418
- // sandbox owner alone. A repository may declare a COMMAND of its own (see `adoptedChecks`), never a verdict.
417
+ // The owner's alone, since a rule can hold work. A command is refused here: it belongs to the repository it checks,
418
+ // in that repository's `.intentic/checks.json`, never in settings.
419
419
  rules: z
420
420
  .array(RuleSchema)
421
421
  .max(50)
422
422
  .default([])
423
+ .refine((rules) => rules.every((rule) => rule.action.kind !== "command"), {
424
+ message: "a command belongs in the repository's own .intentic/checks.json, not in settings",
425
+ })
423
426
  .describe(
424
- "Standing instructions you give the sandbox about its own work: ask for proof before a turn ends, run something before a push, hold or release finished work. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment.",
427
+ "Standing decisions about the sandbox's own work: land or hold finished work, save a version of what landed, ask the assistant to look at an interface it changed. Empty is the default and is exactly the behaviour of a fresh sandbox, because each of those defaults is what no rule matched means at its own moment. A command to run is a repository's own check, declared in its .intentic/checks.json.",
425
428
  ),
426
429
  automationFailureLimit: z
427
430
  .number()
@@ -578,6 +581,7 @@ export const SavingsReportSchema = z.object({
578
581
  // Same absence rule as `search`: not measured, never zero.
579
582
  map: TurnExperimentSchema.optional(),
580
583
  notes: TurnExperimentSchema.optional(),
584
+ guidance: TurnExperimentSchema.optional(),
581
585
  });
582
586
  export type SavingsReport = z.infer<typeof SavingsReportSchema>;
583
587
 
@@ -592,27 +596,23 @@ export type SavingsReport = z.infer<typeof SavingsReportSchema>;
592
596
  // spelling for the daemon that reads it, the screen that names it and the demo that mimics it.
593
597
  export const REPO_CHECKS_FILE = `${STATE_DIR}/checks.json`;
594
598
 
595
- // Named for the occasion as a repository would say it, not for the daemon's wire moment: `edit` is `file.edited`,
596
- // `turn` is `turn.ending` and `push` is `push.starting` (rules/repo-checks.ts maps them). Three, because these are the
597
- // occasions whose command a repository actually owns; a verdict moment has nothing here to express.
598
- //
599
- // `edit` is the cheapest of the three and the only one that reaches the model while it still holds the line it wrote:
600
- // the command runs on that one file (`{file}`), and its output rides back in the edit's own response. A repository
601
- // that declares a whole-tree command here pays it per edit, so the command has to take the file.
602
- export const RepoCheckMomentSchema = z.enum(["edit", "turn", "push"]);
599
+ // Named for the occasion as a repository would say it: `edit` is `file.edited`, `turn` is `turn.ending`, and `land` is
600
+ // the daemon's run over the main tree after an install a land caused (rules/repo-checks.ts maps them). A push is gated by
601
+ // the repository's own git pre-push hook, which every push runs, the app's included.
602
+ // `edit` runs on one file (`{file}`) and its output rides back in the edit's own response, so a command declared there is
603
+ // paid per edit and has to take the file.
604
+ export const RepoCheckMomentSchema = z.enum(["edit", "turn", "land"]);
603
605
  export type RepoCheckMoment = z.infer<typeof RepoCheckMomentSchema>;
604
606
 
605
607
  export const RepoCheckSchema = z.object({
606
608
  when: RepoCheckMomentSchema.describe(
607
- "When to run it: `edit` on each file as it is written (`{file}` is its path), `turn` before the assistant finishes, `push` before code leaves the machine.",
609
+ "When to run it: `edit` on each file as it is written (`{file}` is its path), `turn` before the assistant finishes, `land` on the main tree after finished work lands (absent, the package's own verify or test script runs there).",
608
610
  ),
609
611
  run: z.string().min(1).max(500).describe("The command, run in this repository's own directory, so it reads as it would in a terminal there."),
610
612
  label: z.string().min(1).max(80).optional().describe("What to call it on screen. Absent names it after the command."),
611
- // Same ceiling as a rule's own command; past it the process group is killed and the run is a failure, never a
612
- // silent pass.
613
+ // Same ceiling as a rule's own command; past it the process group is killed and the run is a failure.
613
614
  timeoutMs: z.number().min(60_000).max(3_600_000).optional().describe("How long it may take before it is killed and counted as failed."),
614
- // Repo-relative, as anybody reading this file would write them; the daemon prefixes the repo id before matching,
615
- // since a rule's globs are workspace-relative.
615
+ // Repo-relative; the daemon prefixes the repo id before matching, since a rule's globs are workspace-relative.
616
616
  paths: z
617
617
  .array(z.string().min(1))
618
618
  .max(20)
@@ -622,7 +622,16 @@ export const RepoCheckSchema = z.object({
622
622
  export type RepoCheck = z.infer<typeof RepoCheckSchema>;
623
623
 
624
624
  // The file itself. One key, so a second concern can be added later without breaking a file anyone has written.
625
- export const RepoChecksFileSchema = z.object({ checks: z.array(RepoCheckSchema).max(10).default([]) });
625
+ export const RepoChecksFileSchema = z.object({
626
+ checks: z
627
+ .array(RepoCheckSchema)
628
+ .max(10)
629
+ .default([])
630
+ // One land check per repository: it is THE verdict on the main tree the push hook replays, not one of several.
631
+ .refine((checks) => checks.filter((check) => check.when === "land").length <= 1, { message: "a repository declares at most one land check" })
632
+ // A land checks the whole tree an install settled, not a change, so there is nothing for a glob to narrow.
633
+ .refine((checks) => checks.every((check) => check.when !== "land" || check.paths === undefined), { message: "a land check takes no paths" }),
634
+ });
626
635
  export type RepoChecksFile = z.infer<typeof RepoChecksFileSchema>;
627
636
 
628
637
  // One repository, as a screen reads it: what it declares, and where that stands with the owner.
@@ -630,6 +639,11 @@ export const RepoChecksSummarySchema = z.object({
630
639
  repo: z.string().describe('Which repository, by its workspace id ("root" is the workspace itself).'),
631
640
  path: z.string().describe("Where the declaration lives, relative to the workspace, whether or not the file exists yet."),
632
641
  checks: z.array(RepoCheckSchema).describe("What it declares, in the order the file lists them."),
642
+ fired: z
643
+ .array(z.number().nullable())
644
+ .describe(
645
+ "When each declared check last reported something, in the file's order, as epoch milliseconds; null for one that never has, or for the land check, whose verdict the activity feed records instead.",
646
+ ),
633
647
  adopted: z
634
648
  .boolean()
635
649
  .describe("Whether these are running. False means declared and inert: nothing a repository writes runs until the owner switches it on."),
@@ -639,10 +653,16 @@ export const RepoChecksSummarySchema = z.object({
639
653
  "Whether the declaration changed since it was adopted, which holds it until the owner looks again. True only for a repository that was adopted before.",
640
654
  ),
641
655
  error: z.string().optional().describe("Why the file could not be read, when it exists but does not parse. The checks list is empty in that case."),
656
+ landDefault: z
657
+ .string()
658
+ .optional()
659
+ .describe(
660
+ "What runs on the main tree after a land when the file declares no `land` check: the package's own verify or test script. Absent when the repository has neither.",
661
+ ),
642
662
  });
643
663
  export type RepoChecksSummary = z.infer<typeof RepoChecksSummarySchema>;
644
664
  export const RepoChecksListSchema = z.object({
645
- repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks, plus any the owner has adopted before, sorted by id."),
665
+ repos: z.array(RepoChecksSummarySchema).describe("Every repository that declares checks or has a package check that runs after a land, in id order."),
646
666
  });
647
667
  export type RepoChecksList = z.infer<typeof RepoChecksListSchema>;
648
668
  export const RepoChecksAdoptSchema = z.object({
@@ -47,3 +47,11 @@ export const RETRY_LADDER_TRIES = RETRY_LADDER_MS.length;
47
47
 
48
48
  /** The wait before the next rung, or undefined once the ladder is spent. */
49
49
  export const retryLadderDelay = (triesWithoutProgress: number): number | undefined => RETRY_LADDER_MS[triesWithoutProgress];
50
+
51
+ // Where an automatic re-run ladder stands, whichever wall climbs it (an outage's breaker, a stopped turn's rungs).
52
+ // `made` equal to `max` is a spent ladder: nothing more goes unasked.
53
+ export const RetryLadderSchema = z.object({
54
+ made: z.number().int().min(0).describe("Automatic re-runs already sent for this turn."),
55
+ max: z.number().int().min(1).describe("How many the ladder may send before it stands down."),
56
+ });
57
+ export type RetryLadder = z.infer<typeof RetryLadderSchema>;
@@ -1,4 +1,3 @@
1
- import { test, expect } from "bun:test";
2
1
  import { CapabilitiesListSchema } from "./capabilities.js";
3
2
  import { SandboxSettingsSchema } from "./settings.js";
4
3
  import { SmbNetdiskConfigSchema } from "./netdisk.js";
@@ -1,7 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { packageRoot } from "@intentic/constants/node";
4
- import { test, expect } from "bun:test";
5
4
  import { currentLock } from "./contract-lock.js";
6
5
 
7
6
  // contract.lock.json must match the exported schemas; the git-aware half of this check lives in contract-shrink.mjs.
@@ -1,4 +1,3 @@
1
- import { test, expect } from "bun:test";
2
1
  import { ciFixConversationId, fixAttemptId } from "../ids/conversation-ids.js";
3
2
  import type { AgentSummary } from "../schemas/agents.js";
4
3
  import { planFixAttempt } from "./fix-attempt-plan.js";
@@ -1,4 +1,3 @@
1
- import { test, expect } from "bun:test";
2
1
  import { fixStance } from "./fix-stance.js";
3
2
  import type { AgentSummary } from "../schemas/agents.js";
4
3
 
@@ -113,5 +113,11 @@ export const HISTORY_STATE_FILES: readonly StateFile[] = [
113
113
  { path: "runner-pair-consumed.json", portability: "identity" },
114
114
  // This container's own identity and reconnect token; elsewhere it would dial the parent as this runner.
115
115
  { path: "runner-identity.json", portability: "identity" },
116
+ // The owner's yes to a workspace's hooks, pinned to their bytes: a trust decision the target's owner makes again.
117
+ { path: "hook-approvals.json", portability: "identity", note: "Approve the workspace's Claude Code hooks again from Approvals." },
118
+ // The hook sets waiting for that yes; the next turn that finds them lists them again.
119
+ { path: "hook-requests.json", portability: "derived" },
120
+ // The owner's yes to each workspace extension's declared powers, decided again by the target's owner.
121
+ { path: "extension-approvals.json", portability: "identity", note: "Approve the extensions written in this workspace again from Extensions." },
116
122
  { path: "local-cert/", portability: "identity" },
117
123
  ];
@@ -1,4 +1,3 @@
1
- import { expect, test } from "bun:test";
2
1
  import { declaredTestNote, landedCommitMessage } from "./landed-commit.js";
3
2
 
4
3
  // The push gate reads a weakening's declaration off the commit (assertion-ratchet.mjs), so the trailer must survive
@@ -1,4 +1,3 @@
1
- import { describe, it, expect } from "bun:test";
2
1
  import { RUNTIME_DOMAIN_BINDINGS, staleRuntimeQueryKeys } from "./runtime-state.js";
3
2
 
4
3
  describe(`staleRuntimeQueryKeys`, () => {
@@ -1,4 +1,3 @@
1
- import { test, expect } from "bun:test";
2
1
  import { DEV_VERSION, isBehind, isNewer } from "./versions.js";
3
2
 
4
3
  test("isNewer compares dotted numeric versions", () => {