@intentic/sandbox-contract 1.308.0 → 1.309.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.
- package/dist/contracts/agent.contract.d.ts +98 -4
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +40 -10
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +285 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/approvals.contract.d.ts +34 -0
- package/dist/contracts/approvals.contract.d.ts.map +1 -1
- package/dist/contracts/approvals.contract.js +30 -1
- package/dist/contracts/approvals.contract.js.map +1 -1
- package/dist/contracts/device.contract.d.ts +1 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +390 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +11 -1
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +117 -107
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +1 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +54 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +210 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +35 -1
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/events/agent-events.d.ts +9 -2
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +5 -1
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/resume.d.ts +1 -0
- package/dist/events/resume.d.ts.map +1 -1
- package/dist/events/resume.js +2 -0
- package/dist/events/resume.js.map +1 -1
- package/dist/events/system-events.d.ts +40 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +14 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +7 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/index.d.ts +1141 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/models/model-label.d.ts +2 -0
- package/dist/models/model-label.d.ts.map +1 -0
- package/dist/models/model-label.js +20 -0
- package/dist/models/model-label.js.map +1 -0
- package/dist/models/model-pins.d.ts +0 -1
- package/dist/models/model-pins.d.ts.map +1 -1
- package/dist/models/model-pins.js +0 -2
- package/dist/models/model-pins.js.map +1 -1
- package/dist/policy/command-classes.d.ts.map +1 -1
- package/dist/policy/command-classes.js +88 -7
- package/dist/policy/command-classes.js.map +1 -1
- package/dist/policy/reserved-servers.d.ts +7 -0
- package/dist/policy/reserved-servers.d.ts.map +1 -0
- package/dist/policy/reserved-servers.js +21 -0
- package/dist/policy/reserved-servers.js.map +1 -0
- package/dist/policy/turned-away.d.ts.map +1 -1
- package/dist/policy/turned-away.js.map +1 -1
- package/dist/protocol/ingress-protocol.d.ts +2 -0
- package/dist/protocol/ingress-protocol.d.ts.map +1 -1
- package/dist/protocol/ingress-protocol.js +4 -1
- package/dist/protocol/ingress-protocol.js.map +1 -1
- package/dist/protocol/peer-mcp-server.d.ts +7 -0
- package/dist/protocol/peer-mcp-server.d.ts.map +1 -1
- package/dist/protocol/peer-mcp-server.js +6 -1
- package/dist/protocol/peer-mcp-server.js.map +1 -1
- package/dist/protocol/tunnel-heartbeat.d.ts +19 -0
- package/dist/protocol/tunnel-heartbeat.d.ts.map +1 -0
- package/dist/protocol/tunnel-heartbeat.js +41 -0
- package/dist/protocol/tunnel-heartbeat.js.map +1 -0
- package/dist/schemas/agent.d.ts +59 -0
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +45 -2
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +60 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +6 -1
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/approvals.d.ts +67 -0
- package/dist/schemas/approvals.d.ts.map +1 -1
- package/dist/schemas/approvals.js +43 -0
- package/dist/schemas/approvals.js.map +1 -1
- package/dist/schemas/automations.d.ts +20 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/devices.d.ts +2 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +3 -2
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +850 -77
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/extension-updates.js +23 -0
- package/dist/schemas/extension-updates.js.map +1 -1
- package/dist/schemas/history.d.ts +1 -0
- package/dist/schemas/history.d.ts.map +1 -1
- package/dist/schemas/history.js +4 -0
- package/dist/schemas/history.js.map +1 -1
- package/dist/schemas/hosts.d.ts +1 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +1 -0
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/metrics.d.ts +258 -0
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +95 -0
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +42 -1
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +48 -1
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/usage.d.ts +2 -0
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +2 -0
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/settings.d.ts +52 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +11 -0
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/turn-break.d.ts +5 -0
- package/dist/schemas/turn-break.d.ts.map +1 -1
- package/dist/schemas/turn-break.js +4 -0
- package/dist/schemas/turn-break.js.map +1 -1
- package/dist/state/definition.d.ts +8 -0
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +3 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +1 -1
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +19 -9
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +16 -5
- package/src/contracts/agent.contract.ts +62 -15
- package/src/contracts/approvals.contract.ts +35 -1
- package/src/contracts/extensions.contract.ts +14 -0
- package/src/contracts/system.contract.ts +42 -1
- package/src/events/agent-events.ts +10 -2
- package/src/events/resume.test.ts +8 -0
- package/src/events/resume.ts +4 -0
- package/src/events/transcript.ts +13 -2
- package/src/index.ts +2 -0
- package/src/models/model-label.test.ts +24 -0
- package/src/models/model-label.ts +29 -0
- package/src/models/model-pins.ts +0 -6
- package/src/policy/command-classes.test.ts +99 -1
- package/src/policy/command-classes.ts +143 -11
- package/src/policy/reserved-servers.test.ts +51 -0
- package/src/policy/reserved-servers.ts +48 -0
- package/src/policy/turned-away.ts +2 -1
- package/src/protocol/ingress-protocol.test.ts +78 -1
- package/src/protocol/ingress-protocol.ts +8 -1
- package/src/protocol/peer-mcp-server.test.ts +10 -3
- package/src/protocol/peer-mcp-server.ts +14 -1
- package/src/protocol/tunnel-heartbeat.test.ts +76 -0
- package/src/protocol/tunnel-heartbeat.ts +67 -0
- package/src/schemas/agent.ts +77 -5
- package/src/schemas/agents.ts +11 -1
- package/src/schemas/approvals.ts +67 -0
- package/src/schemas/context-trim.test.ts +2 -2
- package/src/schemas/devices.ts +4 -2
- package/src/schemas/extension-updates.ts +30 -0
- package/src/schemas/history.ts +8 -1
- package/src/schemas/hosts.ts +4 -0
- package/src/schemas/metrics.ts +148 -0
- package/src/schemas/providers/plan-limits.ts +67 -2
- package/src/schemas/providers/usage.ts +3 -0
- package/src/schemas/settings.ts +16 -0
- package/src/schemas/turn-break.ts +8 -0
- package/src/state/history-state.ts +6 -0
- package/src/state/workspace-state.test.ts +2 -2
- package/src/text/transcript-fold.test.ts +25 -7
- package/src/text/transcript-fold.ts +32 -19
package/src/schemas/metrics.ts
CHANGED
|
@@ -105,3 +105,151 @@ 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
|
+
// Every category a byte on the sandbox's volumes can belong to; STORAGE_CLEANABILITY below says which may be cleaned.
|
|
113
|
+
export const STORAGE_CATEGORIES = [
|
|
114
|
+
// The workspace's own files: repositories, their dependencies, anything the owner or an agent put there.
|
|
115
|
+
"workspace",
|
|
116
|
+
// Transcripts, runtime session stores, attachments and loop memory of every conversation, archived ones included.
|
|
117
|
+
"conversations",
|
|
118
|
+
// Each open conversation's own checkout of the repositories, and the dependencies its turns installed over them.
|
|
119
|
+
"checkouts",
|
|
120
|
+
// The minute-by-minute and per-turn snapshots the History timeline restores from.
|
|
121
|
+
"restorePoints",
|
|
122
|
+
// The repositories' own git data: every commit and branch, pushed or not.
|
|
123
|
+
"repositories",
|
|
124
|
+
// Installed versions of the agent runtimes: the one turns use and the one kept for Revert.
|
|
125
|
+
"engines",
|
|
126
|
+
// Search indexes and caches the sandbox keeps up to date by itself while it runs.
|
|
127
|
+
"indexes",
|
|
128
|
+
// Installed extensions and their scratch.
|
|
129
|
+
"extensions",
|
|
130
|
+
// The sandbox's own Docker: images, containers, volumes.
|
|
131
|
+
"docker",
|
|
132
|
+
// Settings, credentials, sign-ins and the ledgers of what was spent and done.
|
|
133
|
+
"state",
|
|
134
|
+
// Anything no part of the sandbox claims.
|
|
135
|
+
"other",
|
|
136
|
+
// Git data and checkouts of repositories deleted from the workspace.
|
|
137
|
+
"trash",
|
|
138
|
+
// Copies of the sandbox packed for download.
|
|
139
|
+
"exports",
|
|
140
|
+
// Files agents produced: generated images, reports, harness output.
|
|
141
|
+
"artifacts",
|
|
142
|
+
// Screenshots and page snapshots the agent's browser took.
|
|
143
|
+
"browserCaptures",
|
|
144
|
+
// The agent browser's profiles, with the sites it is signed in to.
|
|
145
|
+
"browserProfiles",
|
|
146
|
+
// Weights downloaded for local models.
|
|
147
|
+
"modelWeights",
|
|
148
|
+
// The sandbox's own logs and terminal captures.
|
|
149
|
+
"logs",
|
|
150
|
+
// Scratch space agents and checks leave behind.
|
|
151
|
+
"scratch",
|
|
152
|
+
// Package managers' content stores, which the next install refills.
|
|
153
|
+
"packageStores",
|
|
154
|
+
// Build tools' output caches.
|
|
155
|
+
"buildCaches",
|
|
156
|
+
] as const;
|
|
157
|
+
export const StorageCategoryIdSchema = z.enum(STORAGE_CATEGORIES);
|
|
158
|
+
export type StorageCategoryId = z.infer<typeof StorageCategoryIdSchema>;
|
|
159
|
+
|
|
160
|
+
// none: never removed from here; safe: comes back by itself, removed without asking; confirm: removed only once the
|
|
161
|
+
// owner has read what it costs.
|
|
162
|
+
export const StorageCleanabilitySchema = z.enum(["none", "safe", "confirm"]);
|
|
163
|
+
export type StorageCleanability = z.infer<typeof StorageCleanabilitySchema>;
|
|
164
|
+
|
|
165
|
+
// Which categories may be cleaned, and which ask first: the daemon's cleaner refuses by it, the editor words its
|
|
166
|
+
// confirm by it. State, credentials and live work are `none`; what regenerates is `safe`; the owner's own data asks.
|
|
167
|
+
export const STORAGE_CLEANABILITY = {
|
|
168
|
+
workspace: "none",
|
|
169
|
+
conversations: "none",
|
|
170
|
+
// A live conversation's working copy; archiving the conversation is what frees it.
|
|
171
|
+
checkouts: "none",
|
|
172
|
+
// No compaction is safe to offer: `git gc --auto` already runs after every snapshot, and every object is reachable.
|
|
173
|
+
restorePoints: "none",
|
|
174
|
+
repositories: "none",
|
|
175
|
+
// The Engines card keeps two versions and owns which is which; removing one here would strand its Revert.
|
|
176
|
+
engines: "none",
|
|
177
|
+
// Held open by what maintains them: removing a file frees nothing until they close it.
|
|
178
|
+
indexes: "none",
|
|
179
|
+
extensions: "none",
|
|
180
|
+
docker: "none",
|
|
181
|
+
state: "none",
|
|
182
|
+
other: "none",
|
|
183
|
+
// Nothing reads it again, but it may hold commits never pushed anywhere.
|
|
184
|
+
trash: "confirm",
|
|
185
|
+
exports: "confirm",
|
|
186
|
+
artifacts: "confirm",
|
|
187
|
+
browserCaptures: "confirm",
|
|
188
|
+
browserProfiles: "confirm",
|
|
189
|
+
modelWeights: "confirm",
|
|
190
|
+
logs: "safe",
|
|
191
|
+
scratch: "safe",
|
|
192
|
+
packageStores: "safe",
|
|
193
|
+
buildCaches: "safe",
|
|
194
|
+
} as const satisfies Record<StorageCategoryId, StorageCleanability>;
|
|
195
|
+
|
|
196
|
+
export const StorageItemSchema = z.object({
|
|
197
|
+
path: z.string().describe("Where it is, as an absolute path inside the sandbox."),
|
|
198
|
+
bytes: z.number().describe("Its size in bytes."),
|
|
199
|
+
});
|
|
200
|
+
export type StorageItem = z.infer<typeof StorageItemSchema>;
|
|
201
|
+
|
|
202
|
+
export const StorageCategoryUsageSchema = z.object({
|
|
203
|
+
id: StorageCategoryIdSchema,
|
|
204
|
+
cleanability: StorageCleanabilitySchema.describe("Whether this category can be cleaned from here, and whether cleaning it asks first."),
|
|
205
|
+
bytes: z.number().describe("Its size in bytes."),
|
|
206
|
+
files: z.number().describe("How many files it holds."),
|
|
207
|
+
cleanableBytes: z
|
|
208
|
+
.number()
|
|
209
|
+
.optional()
|
|
210
|
+
.describe(
|
|
211
|
+
"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.",
|
|
212
|
+
),
|
|
213
|
+
items: z.array(StorageItemSchema).describe("Its biggest parts, largest first, at most eight."),
|
|
214
|
+
});
|
|
215
|
+
export type StorageCategoryUsage = z.infer<typeof StorageCategoryUsageSchema>;
|
|
216
|
+
|
|
217
|
+
export const StorageScanSchema = z.object({
|
|
218
|
+
startedAt: z.number().describe("When the scan began, in milliseconds."),
|
|
219
|
+
finishedAt: z.number().describe("When it ended, in milliseconds: the moment these sizes describe."),
|
|
220
|
+
outcome: z
|
|
221
|
+
.enum(["complete", "partial"])
|
|
222
|
+
.describe("`partial` when the scan hit its time limit first, so every size is at least what it says rather than exactly it."),
|
|
223
|
+
disk: z
|
|
224
|
+
.object({
|
|
225
|
+
usedBytes: z.number().describe("Space used on the volume the workspace lives on."),
|
|
226
|
+
totalBytes: z.number().describe("That volume's size."),
|
|
227
|
+
})
|
|
228
|
+
.optional()
|
|
229
|
+
.describe("The volume as a whole. Absent when the volume would not say."),
|
|
230
|
+
categories: z.array(StorageCategoryUsageSchema).describe("Every category that holds anything, largest first."),
|
|
231
|
+
unreadable: z.number().describe("Files and folders the scan could not read, and so did not count."),
|
|
232
|
+
});
|
|
233
|
+
export type StorageScan = z.infer<typeof StorageScanSchema>;
|
|
234
|
+
|
|
235
|
+
export const StorageReportSchema = z.object({
|
|
236
|
+
scan: StorageScanSchema.optional().describe("The last scan that finished. Absent until one has, and again after the daemon restarts."),
|
|
237
|
+
scanning: z.boolean().describe("Whether a scan is running now."),
|
|
238
|
+
});
|
|
239
|
+
export type StorageReport = z.infer<typeof StorageReportSchema>;
|
|
240
|
+
|
|
241
|
+
export const StorageCleanInputSchema = z.object({
|
|
242
|
+
category: StorageCategoryIdSchema.describe("The category to clean; one whose cleanability is `none` is refused."),
|
|
243
|
+
});
|
|
244
|
+
export type StorageCleanInput = z.infer<typeof StorageCleanInputSchema>;
|
|
245
|
+
|
|
246
|
+
export const StorageCleanResultSchema = z.object({
|
|
247
|
+
category: StorageCategoryIdSchema,
|
|
248
|
+
freedBytes: z.number().describe("Space the removals gave back, in bytes. A file that is still linked elsewhere frees nothing and is not counted."),
|
|
249
|
+
removed: z.number().describe("How many items were removed."),
|
|
250
|
+
kept: z
|
|
251
|
+
.number()
|
|
252
|
+
.describe("How many were left in place: changed too recently, in use by a running program, or no longer this category's."),
|
|
253
|
+
failed: z.number().describe("How many removals the filesystem refused."),
|
|
254
|
+
});
|
|
255
|
+
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
|
-
|
|
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
|
package/src/schemas/settings.ts
CHANGED
|
@@ -254,6 +254,21 @@ export const SandboxSettingsSchema = z.object({
|
|
|
254
254
|
.describe(
|
|
255
255
|
"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
256
|
),
|
|
257
|
+
// Measured by the `guidance` experiment against the full set; the full set stays the default until that says otherwise.
|
|
258
|
+
leanGuidance: z
|
|
259
|
+
.boolean()
|
|
260
|
+
.default(false)
|
|
261
|
+
.describe(
|
|
262
|
+
"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.",
|
|
263
|
+
),
|
|
264
|
+
leanGuidanceHoldout: z
|
|
265
|
+
.number()
|
|
266
|
+
.min(0)
|
|
267
|
+
.max(1)
|
|
268
|
+
.default(0)
|
|
269
|
+
.describe(
|
|
270
|
+
"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.",
|
|
271
|
+
),
|
|
257
272
|
iqSearch: z
|
|
258
273
|
.boolean()
|
|
259
274
|
.default(false)
|
|
@@ -578,6 +593,7 @@ export const SavingsReportSchema = z.object({
|
|
|
578
593
|
// Same absence rule as `search`: not measured, never zero.
|
|
579
594
|
map: TurnExperimentSchema.optional(),
|
|
580
595
|
notes: TurnExperimentSchema.optional(),
|
|
596
|
+
guidance: TurnExperimentSchema.optional(),
|
|
581
597
|
});
|
|
582
598
|
export type SavingsReport = z.infer<typeof SavingsReportSchema>;
|
|
583
599
|
|
|
@@ -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>;
|
|
@@ -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
|
];
|
|
@@ -60,8 +60,8 @@ describe(`staleQueryKeys`, () => {
|
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it(`ignores a store's own temp file while it is mid-swap`, () => {
|
|
63
|
-
//
|
|
64
|
-
expect(staleQueryKeys([`.intentic/.settings.json.42.tmp`], [])).toEqual([]);
|
|
63
|
+
// writeTextFile's atomic-rename temp name: `.<name>.<pid>.<n>.tmp` beside the target.
|
|
64
|
+
expect(staleQueryKeys([`.intentic/.settings.json.42.7.tmp`], [])).toEqual([]);
|
|
65
65
|
expect(staleQueryKeys([`.intentic/config/settings.json`], [])).toEqual([`settings`, `manifests`]);
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -186,7 +186,8 @@ describe("foldTurn", () => {
|
|
|
186
186
|
code: "provider-outage",
|
|
187
187
|
message: "Anthropic is down.",
|
|
188
188
|
autoResume: "scheduled",
|
|
189
|
-
outage: { retryAt: 1
|
|
189
|
+
outage: { retryAt: 1 },
|
|
190
|
+
retries: { made: 1, max: 6 },
|
|
190
191
|
},
|
|
191
192
|
];
|
|
192
193
|
// The row states what happened and nothing more: what happens next, and the one control that changes it, are
|
|
@@ -200,6 +201,23 @@ describe("foldTurn", () => {
|
|
|
200
201
|
});
|
|
201
202
|
});
|
|
202
203
|
|
|
204
|
+
// The count lives in the record, so a reader scrolling back sees how many times a stuck turn was sent again.
|
|
205
|
+
it("says on a stopped turn's row which automatic try comes next, or that the ladder stood down", () => {
|
|
206
|
+
const timedOut = "Google turn timed out waiting for OpenCode.";
|
|
207
|
+
const booked: AgentEvent[] = [{ kind: "error", message: timedOut, autoResume: "scheduled", nextAt: 1, retries: { made: 1, max: 3 } }];
|
|
208
|
+
expect(foldOf("hi", booked).at(-1)).toEqual({ role: "notice", text: `${timedOut} Retrying by itself: attempt 2 of 3.` });
|
|
209
|
+
const spent: AgentEvent[] = [{ kind: "error", message: timedOut, autoResume: "available", retries: { made: 3, max: 3 } }];
|
|
210
|
+
expect(foldOf("hi", spent).at(-1)).toEqual({
|
|
211
|
+
role: "notice",
|
|
212
|
+
text: `${timedOut} Retried 3 of 3 times by itself; nothing more is sent automatically.`,
|
|
213
|
+
});
|
|
214
|
+
// An outage nobody armed says so, instead of promising the breaker's retry.
|
|
215
|
+
const unarmed: AgentEvent[] = [
|
|
216
|
+
{ kind: "error", code: "provider-outage", message: "Anthropic is down.", autoResume: "available", outage: { retryAt: 1 }, retries: { made: 0, max: 6 } },
|
|
217
|
+
];
|
|
218
|
+
expect(foldOf("hi", unarmed).at(-1)).toEqual({ role: "notice", text: "Anthropic is down. Nothing is retrying it, so the turn is waiting here." });
|
|
219
|
+
});
|
|
220
|
+
|
|
203
221
|
it("writes the turn's own events down as notices", () => {
|
|
204
222
|
const events: AgentEvent[] = [
|
|
205
223
|
{ kind: "worktree", branch: "agent/x", base: "abc1234", sync: { commits: 2, blocked: [] } },
|
|
@@ -213,7 +231,7 @@ describe("foldTurn", () => {
|
|
|
213
231
|
{ role: "notice", text: "Context compacted to free up space." },
|
|
214
232
|
{
|
|
215
233
|
role: "notice",
|
|
216
|
-
text: "Changes landed in your workspace: review them in the Changes panel. Installing 1
|
|
234
|
+
text: "Changes landed in your workspace: review them in the Changes panel. Installing 1 dependency it added or changed; the project's checks run when that finishes, and the outcome lands in Activity.",
|
|
217
235
|
noticeAction: "depsInstall",
|
|
218
236
|
},
|
|
219
237
|
]);
|
|
@@ -495,8 +513,8 @@ describe("patches", () => {
|
|
|
495
513
|
});
|
|
496
514
|
});
|
|
497
515
|
|
|
498
|
-
// A refusal that ran nothing offers the message back
|
|
499
|
-
// that started itself, where there is no composer and no typed message.
|
|
516
|
+
// A refusal that ran nothing offers the message back for another press — true where somebody typed one, false for a
|
|
517
|
+
// run that started itself, where there is no composer and no typed message.
|
|
500
518
|
describe(`a refusal that ran nothing`, () => {
|
|
501
519
|
const REFUSED = { kind: `error`, code: `context-window-too-small`, message: `This model accepts 16,384 tokens.` } as const satisfies AgentEvent;
|
|
502
520
|
|
|
@@ -551,9 +569,9 @@ describe(`a refusal that ran nothing`, () => {
|
|
|
551
569
|
expect(foldOf(`land it`, [REFUSED]).at(-1)?.noticeAction).toBeUndefined();
|
|
552
570
|
});
|
|
553
571
|
|
|
554
|
-
// The bug this retraction exists for: the
|
|
555
|
-
// here is the SAME message a second time, and a third, once per press against a gate that keeps refusing.
|
|
556
|
-
it(`takes the message back out, leaving the
|
|
572
|
+
// The bug this retraction exists for: the conversation's queue keeps the words for another press, so a bubble left
|
|
573
|
+
// standing here is the SAME message a second time, and a third, once per press against a gate that keeps refusing.
|
|
574
|
+
it(`takes the message back out, leaving the queue's copy as the only one`, () => {
|
|
557
575
|
const fold = new TranscriptFold(openingOf(`land it`));
|
|
558
576
|
const patches = fold.apply(REFUSED);
|
|
559
577
|
|
|
@@ -62,10 +62,11 @@ const dependencyLine = (deps: { missing: number; started: string[]; deferred: bo
|
|
|
62
62
|
if (deps === undefined || deps.missing === 0) {
|
|
63
63
|
return ``;
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
// `missing` counts versions the turn bumped as well as names it added, so the sentence names both.
|
|
66
|
+
const what = `${deps.missing} ${deps.missing === 1 ? `dependency` : `dependencies`} it added or changed`;
|
|
66
67
|
return deps.deferred
|
|
67
|
-
? ` ${what} are queued: installation starts after this turn and any other active agents finish, appears in Work terminals, then its checks and outcome land in Activity.`
|
|
68
|
-
: ` Installing ${what}
|
|
68
|
+
? ` ${what} ${deps.missing === 1 ? `is` : `are`} queued: installation starts after this turn and any other active agents finish, appears in Work terminals, then its checks and outcome land in Activity.`
|
|
69
|
+
: ` Installing ${what}; the project's checks run when that finishes, and the outcome lands in Activity.`;
|
|
69
70
|
};
|
|
70
71
|
|
|
71
72
|
// One line reporting the daemon's rebase of this agent's branch onto the current workspace; `blocked` names repos it
|
|
@@ -106,10 +107,10 @@ const landedRow = (event: Extract<AgentEvent, { kind: "landed" }>): TranscriptRo
|
|
|
106
107
|
};
|
|
107
108
|
};
|
|
108
109
|
|
|
109
|
-
// The clause after a refusal that ran nothing. A turn somebody typed is held in
|
|
110
|
-
// that started itself — an automation, a loop, a watch wake — has no typed message and nobody watching,
|
|
111
|
-
// resend there names a composer the reader is not looking at. Neither version promises a retry: whether
|
|
112
|
-
// the scheduler's decision, not this row's to state.
|
|
110
|
+
// The clause after a refusal that ran nothing. A turn somebody typed is held in its conversation's queue for another
|
|
111
|
+
// press; a turn that started itself — an automation, a loop, a watch wake — has no typed message and nobody watching,
|
|
112
|
+
// so promising a resend there names a composer the reader is not looking at. Neither version promises a retry: whether
|
|
113
|
+
// one comes is the scheduler's decision, not this row's to state.
|
|
113
114
|
const undelivered = (unattended: boolean): string =>
|
|
114
115
|
unattended
|
|
115
116
|
? `Nothing ran, and nothing is held: this run started on its own, so there is no message waiting to be sent again.`
|
|
@@ -152,9 +153,7 @@ const errorRow = (event: Extract<AgentEvent, { kind: "error" }>): TranscriptRow
|
|
|
152
153
|
const { message, code } = event;
|
|
153
154
|
switch (code) {
|
|
154
155
|
case "provider-outage":
|
|
155
|
-
return event
|
|
156
|
-
? { role: "notice", text: `${message} Nothing is retrying it, so the turn is waiting here.` }
|
|
157
|
-
: { role: "notice", text: `${message} Retrying by itself: attempt ${event.outage.attempt} of ${event.outage.maxAttempts}.` };
|
|
156
|
+
return { role: "notice", text: `${message} ${retryClause(event) ?? `Nothing is retrying it, so the turn is waiting here.`}` };
|
|
158
157
|
case "claude-token-refused":
|
|
159
158
|
return event.autoResume === "scheduled"
|
|
160
159
|
? {
|
|
@@ -169,18 +168,31 @@ const errorRow = (event: Extract<AgentEvent, { kind: "error" }>): TranscriptRow
|
|
|
169
168
|
break;
|
|
170
169
|
}
|
|
171
170
|
if (!turnedAwayCode(code)) {
|
|
172
|
-
|
|
171
|
+
const clause = retryClause(event);
|
|
172
|
+
return { role: "notice", text: clause === undefined ? message : `${message} ${clause}` };
|
|
173
173
|
}
|
|
174
174
|
return heldRow(event);
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
177
|
+
// What a laddered wall's automatic re-runs are doing, said on the failure's own row so the count stays in the record.
|
|
178
|
+
const retryClause = (event: Extract<AgentEvent, { kind: "error" }>): string | undefined => {
|
|
179
|
+
const { retries } = event;
|
|
180
|
+
if (retries === undefined) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
if (event.autoResume === "scheduled") {
|
|
184
|
+
return `Retrying by itself: attempt ${retries.made + 1} of ${retries.max}.`;
|
|
185
|
+
}
|
|
186
|
+
return retries.made >= retries.max ? `Retried ${retries.made} of ${retries.max} times by itself; nothing more is sent automatically.` : undefined;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// The turn's opening user row: text, timestamp, attachments, the message's id, and whatever the daemon later stamps onto
|
|
190
|
+
// it (checkpoint, notes).
|
|
191
|
+
export const userRow = (text: string, sentAt: number, attachments: readonly string[], messageId?: string): TranscriptRow => {
|
|
180
192
|
// Drops an attachment already inline as an @-mention, unless it's a generated upload path (its own thumbnail).
|
|
181
193
|
const inline = new Set(mentionedPathTokens(text));
|
|
182
194
|
const chips = attachments.filter((path) => !inline.has(path) || path.includes(`/records/artifacts/attachments/`));
|
|
183
|
-
return { role: "user", text, sentAt, ...(chips.length > 0 ? { attachments: chips } : {}) };
|
|
195
|
+
return { role: "user", text, sentAt, ...(chips.length > 0 ? { attachments: chips } : {}), ...(messageId === undefined ? {} : { messageId }) };
|
|
184
196
|
};
|
|
185
197
|
|
|
186
198
|
export class TranscriptFold {
|
|
@@ -196,7 +208,7 @@ export class TranscriptFold {
|
|
|
196
208
|
private readonly parked = new Map<string, number>();
|
|
197
209
|
// The turn's opening user row, where the checkpoint and daemon notes land; cleared once `retract` takes it back.
|
|
198
210
|
private opener: number | undefined;
|
|
199
|
-
// Set by `retract`: this turn was refused before it ran, and its message went back to the
|
|
211
|
+
// Set by `retract`: this turn was refused before it ran, and its message went back to the conversation's queue.
|
|
200
212
|
private unrun = false;
|
|
201
213
|
|
|
202
214
|
constructor(
|
|
@@ -426,6 +438,7 @@ export class TranscriptFold {
|
|
|
426
438
|
text: event.text,
|
|
427
439
|
sentAt: event.sentAt,
|
|
428
440
|
...(event.attachments === undefined ? {} : { attachments: [...event.attachments] }),
|
|
441
|
+
...(event.messageId === undefined ? {} : { messageId: event.messageId }),
|
|
429
442
|
});
|
|
430
443
|
// Anchors pair by position with the checkpoints only a person's steer reserves.
|
|
431
444
|
if (event.voice === undefined) {
|
|
@@ -461,9 +474,9 @@ export class TranscriptFold {
|
|
|
461
474
|
);
|
|
462
475
|
}
|
|
463
476
|
|
|
464
|
-
// Attended refusals only: the
|
|
465
|
-
// once per press. Splicing is safe because nothing ran — no open bubble, no parked card, no steer above
|
|
466
|
-
// the sandbox kept (`held`) keeps its message and is recorded: no
|
|
477
|
+
// Attended refusals only: the conversation's queue holds those words for another press, so a bubble left standing
|
|
478
|
+
// repeats them once per press. Splicing is safe because nothing ran — no open bubble, no parked card, no steer above
|
|
479
|
+
// it. A turn the sandbox kept (`held`) keeps its message and is recorded: no queue holds a copy of it.
|
|
467
480
|
private retract(event: Extract<AgentEvent, { kind: "error" }>): TranscriptPatch[] {
|
|
468
481
|
if (!this.turnedAway(event) || event.held !== undefined) {
|
|
469
482
|
return [];
|