@intentic/sandbox-contract 1.308.2 → 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 +1 -0
- 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/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 +2 -0
- package/src/schemas/extension-updates.ts +30 -0
- package/src/schemas/history.ts +8 -1
- 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
|
@@ -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 [];
|