@intentic/sandbox-contract 1.238.1 → 1.240.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 +49 -30
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +1 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +474 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +40 -30
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +474 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +474 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events.d.ts +2546 -123
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +122 -61
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +1607 -165
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/engines.d.ts +157 -0
- package/dist/schemas/engines.d.ts.map +1 -0
- package/dist/schemas/engines.js +70 -0
- package/dist/schemas/engines.js.map +1 -0
- package/dist/schemas/plan-limits.d.ts +19 -0
- package/dist/schemas/plan-limits.d.ts.map +1 -1
- package/dist/schemas/plan-limits.js +8 -1
- package/dist/schemas/plan-limits.js.map +1 -1
- package/dist/workspace-state.d.ts +5 -0
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +1 -0
- package/dist/workspace-state.js.map +1 -1
- package/package.json +4 -4
- package/src/contracts/agent.contract.ts +1 -1
- package/src/events.test.ts +14 -0
- package/src/events.ts +250 -104
- package/src/index.ts +1 -0
- package/src/schemas/engines.ts +129 -0
- package/src/schemas/plan-limits.ts +31 -12
- package/src/workspace-state.test.ts +5 -0
- package/src/workspace-state.ts +12 -0
package/src/events.ts
CHANGED
|
@@ -286,6 +286,168 @@ export const ToolCallContentSchema = z.discriminatedUnion("type", [
|
|
|
286
286
|
]);
|
|
287
287
|
export type ToolCallContent = z.infer<typeof ToolCallContentSchema>;
|
|
288
288
|
|
|
289
|
+
/* WHAT A PARKED CARD IS ABOUT: the document the turn wrote and is now asking a question against.
|
|
290
|
+
*
|
|
291
|
+
* A card asks for a decision; until this it carried no SUBJECT. The commonest shape of a real decision is "I
|
|
292
|
+
* analysed this and wrote it up, now choose", and the write-up went into a file whose card had already folded
|
|
293
|
+
* itself into `Write · +135 −0` twenty tool calls back. So the reader was asked to choose between options
|
|
294
|
+
* describing a document the chat had never shown them.
|
|
295
|
+
*
|
|
296
|
+
* Carried BY VALUE rather than as a path, for the same reason the diff on a tool call is: the bytes are already
|
|
297
|
+
* in hand when the card is raised, a path would make the card's meaning depend on a file that keeps changing
|
|
298
|
+
* under it, and a restored or published transcript has no workspace to go read. The path rides along anyway, so
|
|
299
|
+
* a document past the wire cap still has somewhere to send the reader.
|
|
300
|
+
*
|
|
301
|
+
* Nothing is asked of the MODEL for this. It calls `ask` exactly as before; the daemon knows what the turn
|
|
302
|
+
* wrote, because every write came past it as a frame (documents.ts decides which of them is a document). A
|
|
303
|
+
* harness that can see the answer must not spend prompt on asking the model to repeat it. */
|
|
304
|
+
export const CardDocumentSchema = z.object({
|
|
305
|
+
path: z.string().describe("Where it lives, as a workspace path."),
|
|
306
|
+
title: z.string().describe("What it is called: its opening heading, or its file name."),
|
|
307
|
+
markdown: z.string().describe("The document itself."),
|
|
308
|
+
truncated: z.boolean().optional().describe("It was clipped at the wire cap; the file on disk has more."),
|
|
309
|
+
plan: z.boolean().optional().describe("It is one of the CLI's plan files, written to be approved rather than merely read."),
|
|
310
|
+
});
|
|
311
|
+
export type CardDocument = z.infer<typeof CardDocumentSchema>;
|
|
312
|
+
|
|
313
|
+
/* ONE CARD'S OWN FIELDS, spelled once. Three readers carry the same card and must agree on what it is: the
|
|
314
|
+
* frame that raises it (AgentEventSchema below), the journal entry that keeps a parked one across a restart
|
|
315
|
+
* (ParkedCardSchema), and the record row that keeps it for good (RestoredMessageSchema's card fields). A shape
|
|
316
|
+
* declared inline in each was three shapes with one name. */
|
|
317
|
+
const REQUEST_ID = z.string().describe("What to send back when you answer.");
|
|
318
|
+
const planCard = {
|
|
319
|
+
requestId: REQUEST_ID,
|
|
320
|
+
text: z.string().describe("The plan itself."),
|
|
321
|
+
// Present when the adjacent plan prose POINTS at a document instead of being one: the model wrote the real
|
|
322
|
+
// plan to a file and summarised it there. Absent when the text already is the whole plan.
|
|
323
|
+
document: CardDocumentSchema.optional().describe("The write-up this plan refers to, when the plan itself is a pointer to one."),
|
|
324
|
+
};
|
|
325
|
+
const questionCard = {
|
|
326
|
+
requestId: REQUEST_ID,
|
|
327
|
+
questions: z.array(AskQuestionSchema).describe("What it wants to know."),
|
|
328
|
+
document: CardDocumentSchema.optional().describe("The document this turn wrote and is asking about, so the choice can be read beside it."),
|
|
329
|
+
};
|
|
330
|
+
const permissionCard = { requestId: REQUEST_ID };
|
|
331
|
+
// The agent's browser needs a person: it parked mid-sign-in on something it cannot clear itself (a captcha,
|
|
332
|
+
// a password it does not hold, a phone check). `session` names the browser session on /browsers, the card's
|
|
333
|
+
// one action is going THERE, where the live stage and Take control already are; the Browsers banner and this
|
|
334
|
+
// card resolve the same requestId. `account` is the capability the sign-in is for, so the card can say whose
|
|
335
|
+
// login is stuck even after the browser has navigated somewhere unrecognizable.
|
|
336
|
+
const browserHelpCard = {
|
|
337
|
+
requestId: z.string(),
|
|
338
|
+
session: z.string(),
|
|
339
|
+
account: z.string(),
|
|
340
|
+
message: z.string(),
|
|
341
|
+
};
|
|
342
|
+
// The agent's TERMINAL needs a person: a command it started is sitting at a prompt it cannot answer (a
|
|
343
|
+
// one-time password, a security-key touch, a confirm). `session` names the tmux session on the terminal
|
|
344
|
+
// panel, the card's one action is going THERE, where the live pane and its prompt already are, which is
|
|
345
|
+
// the same division of labour the browser card has with /browsers.
|
|
346
|
+
const terminalHelpCard = {
|
|
347
|
+
requestId: z.string(),
|
|
348
|
+
session: z.string(),
|
|
349
|
+
message: z.string(),
|
|
350
|
+
};
|
|
351
|
+
const serviceOfferCard = { requestId: z.string(), offer: ServiceOfferSchema };
|
|
352
|
+
const capabilityOfferCard = { requestId: z.string(), offer: CapabilityOfferSchema };
|
|
353
|
+
const paymentOfferCard = { requestId: z.string(), offer: PaymentOfferSchema };
|
|
354
|
+
|
|
355
|
+
/* HOW AN OFFER'S ACCEPTED HALF ENDED, the follow-up that lands on the card after the click. Each is the body of
|
|
356
|
+
* the frame that reports it (`service_receipt`, `capability_outcome`, `payment_receipt`) and the field the
|
|
357
|
+
* record keeps it in, one shape for both, so a receipt reopened tomorrow says exactly what the live card said. */
|
|
358
|
+
export const ServiceReceiptSchema = z.object({
|
|
359
|
+
outcome: z.enum(["ok", "refunded", "refused"]),
|
|
360
|
+
credits: z.number(),
|
|
361
|
+
remaining: z.number().optional(),
|
|
362
|
+
});
|
|
363
|
+
export type ServiceReceipt = z.infer<typeof ServiceReceiptSchema>;
|
|
364
|
+
export const CapabilityOutcomeSchema = z.object({
|
|
365
|
+
outcome: z.enum(["connected", "unfinished"]),
|
|
366
|
+
id: z.string().optional(),
|
|
367
|
+
});
|
|
368
|
+
export type CapabilityOutcome = z.infer<typeof CapabilityOutcomeSchema>;
|
|
369
|
+
export const PaymentReceiptSchema = z.object({
|
|
370
|
+
outcome: z.enum(["paid", "failed"]),
|
|
371
|
+
amountUsd: z.string(),
|
|
372
|
+
transaction: z.string().optional(),
|
|
373
|
+
network: z.string().optional(),
|
|
374
|
+
});
|
|
375
|
+
export type PaymentReceipt = z.infer<typeof PaymentReceiptSchema>;
|
|
376
|
+
|
|
377
|
+
/* THE THREE RESTORABLE CARDS, named so the turn journal can hold them verbatim: a parked turn's raised cards
|
|
378
|
+
* are written down beside its prompt (sandbox turn-journal.ts), and a daemon death under the park restores the
|
|
379
|
+
* very same frames instead of ending the turn `interrupted`, the card the user was about to answer survives
|
|
380
|
+
* the restart that killed the process holding it. The two handover cards are deliberately not among them:
|
|
381
|
+
* `browser_help`'s Chromium and `terminal_help`'s waiting command both die with the container, so those parks
|
|
382
|
+
* cannot be restored, only reported. */
|
|
383
|
+
const PlanCardSchema = z.object({
|
|
384
|
+
kind: z.literal("plan").describe("The agent has written a plan and is waiting for a yes."),
|
|
385
|
+
...planCard,
|
|
386
|
+
});
|
|
387
|
+
const QuestionCardSchema = z.object({
|
|
388
|
+
kind: z.literal("question").describe("The agent has asked you something and is waiting."),
|
|
389
|
+
...questionCard,
|
|
390
|
+
});
|
|
391
|
+
const PermissionCardSchema = PermissionAskSchema.extend({
|
|
392
|
+
kind: z.literal("permission").describe("The agent wants to use a tool it needs permission for."),
|
|
393
|
+
...permissionCard,
|
|
394
|
+
});
|
|
395
|
+
export const ParkedCardSchema = z.discriminatedUnion("kind", [PlanCardSchema, QuestionCardSchema, PermissionCardSchema]);
|
|
396
|
+
export type ParkedCard = z.infer<typeof ParkedCardSchema>;
|
|
397
|
+
|
|
398
|
+
// ---- restored cards ----
|
|
399
|
+
/* THE CARDS A TURN PARKED ON, as the record keeps them: the card exactly as it was raised, and the reply that
|
|
400
|
+
* released it exactly as the client sent it (the `resolved` frame's own payload), plus whatever landed on the
|
|
401
|
+
* card afterwards (a permission's late explanation, an offer's stream and receipt).
|
|
402
|
+
*
|
|
403
|
+
* They exist because the record used to keep NONE of this. A question the user answered was on screen for as
|
|
404
|
+
* long as the turn's frame log lived (minutes) and in the browser's local mirror for as long as that survived
|
|
405
|
+
* (until the next web build), and then the record repainted the conversation without it: the card, the
|
|
406
|
+
* questions, and the user's own picks, gone from the only durable copy. The rule the record follows is that a
|
|
407
|
+
* reopened chat redraws what was on screen, and a decision the user made is the part of a conversation they
|
|
408
|
+
* come back to re-read.
|
|
409
|
+
*
|
|
410
|
+
* The REPLY rides verbatim rather than as a derived status, for the same reason the `resolved` frame carries
|
|
411
|
+
* it that way: the client already turns a reply into the card's frozen state for the live stream, and a
|
|
412
|
+
* restored card goes through that one function too, so the two can never disagree about what "answered"
|
|
413
|
+
* looks like. Absent, nobody answered (the turn was stopped, or died under the card), which is not a decision
|
|
414
|
+
* and must not replay as one. */
|
|
415
|
+
const settled = {
|
|
416
|
+
reply: AgentReplySchema.optional().describe(
|
|
417
|
+
"How it was answered, exactly as the client sent it. Absent when nobody did: the turn was stopped or died under the card, which is not a decision and does not read back as one.",
|
|
418
|
+
),
|
|
419
|
+
};
|
|
420
|
+
export const RestoredPlanSchema = z.object({ ...planCard, ...settled });
|
|
421
|
+
export type RestoredPlan = z.infer<typeof RestoredPlanSchema>;
|
|
422
|
+
export const RestoredQuestionSchema = z.object({ ...questionCard, ...settled });
|
|
423
|
+
export type RestoredQuestion = z.infer<typeof RestoredQuestionSchema>;
|
|
424
|
+
// `explain`, the quick model's late sentence (the `permission_note` frame), lands here through PermissionAskSchema.
|
|
425
|
+
export const RestoredPermissionSchema = PermissionAskSchema.extend({ ...permissionCard, ...settled });
|
|
426
|
+
export type RestoredPermission = z.infer<typeof RestoredPermissionSchema>;
|
|
427
|
+
export const RestoredBrowserHelpSchema = z.object({ ...browserHelpCard, ...settled });
|
|
428
|
+
export type RestoredBrowserHelp = z.infer<typeof RestoredBrowserHelpSchema>;
|
|
429
|
+
export const RestoredTerminalHelpSchema = z.object({ ...terminalHelpCard, ...settled });
|
|
430
|
+
export type RestoredTerminalHelp = z.infer<typeof RestoredTerminalHelpSchema>;
|
|
431
|
+
export const RestoredServiceOfferSchema = z.object({
|
|
432
|
+
...serviceOfferCard,
|
|
433
|
+
...settled,
|
|
434
|
+
events: z.array(ServiceStreamEventSchema).optional().describe("The approved run's stream, in order (the service_event frames)."),
|
|
435
|
+
receipt: ServiceReceiptSchema.optional().describe("How the approved run ended (the service_receipt frame)."),
|
|
436
|
+
});
|
|
437
|
+
export type RestoredServiceOffer = z.infer<typeof RestoredServiceOfferSchema>;
|
|
438
|
+
export const RestoredCapabilityOfferSchema = z.object({
|
|
439
|
+
...capabilityOfferCard,
|
|
440
|
+
...settled,
|
|
441
|
+
outcome: CapabilityOutcomeSchema.optional().describe("How an accepted ask's setup ended (the capability_outcome frame)."),
|
|
442
|
+
});
|
|
443
|
+
export type RestoredCapabilityOffer = z.infer<typeof RestoredCapabilityOfferSchema>;
|
|
444
|
+
export const RestoredPaymentOfferSchema = z.object({
|
|
445
|
+
...paymentOfferCard,
|
|
446
|
+
...settled,
|
|
447
|
+
receipt: PaymentReceiptSchema.optional().describe("How the approved payment ended (the payment_receipt frame)."),
|
|
448
|
+
});
|
|
449
|
+
export type RestoredPaymentOffer = z.infer<typeof RestoredPaymentOfferSchema>;
|
|
450
|
+
|
|
289
451
|
// ---- restored transcripts ----
|
|
290
452
|
// What /sessions/{id} replays into a reopened tab, and what the daemon's own conversation record stores. It has
|
|
291
453
|
// to REDRAW the transcript the user was looking at rather than merely paraphrase it, so it keeps the assistant's
|
|
@@ -427,9 +589,42 @@ export const RestoredMessageSchema = z.object({
|
|
|
427
589
|
.enum(["tierHold"])
|
|
428
590
|
.optional()
|
|
429
591
|
.describe("A one-press follow-up this recorded notice offers, by name. The chat decides what it does and whether it still applies."),
|
|
592
|
+
/* THE CARD THIS BUBBLE PARKED ON (assistant rows only), at most one: a card closes the bubble it lands in,
|
|
593
|
+
* live (turnReducer nulls the turn's bubble) and in the fold alike (sessions/turn-transcript.ts), so the
|
|
594
|
+
* next thing the agent says opens a fresh row beneath it. One field per kind rather than one union field,
|
|
595
|
+
* because that is the shape the live ChatMessage has and a restored row is meant to be indistinguishable
|
|
596
|
+
* from the one it replaces. See the restored-cards section above for why these exist at all. */
|
|
597
|
+
plan: RestoredPlanSchema.optional().describe("The plan this row asked approval for, and the answer."),
|
|
598
|
+
question: RestoredQuestionSchema.optional().describe("The questions this row asked, and the picks that answered them."),
|
|
599
|
+
permission: RestoredPermissionSchema.optional().describe("The tool this row asked permission for, and the decision."),
|
|
600
|
+
browserHelp: RestoredBrowserHelpSchema.optional().describe("The browser hand-over this row asked for, and how it ended."),
|
|
601
|
+
terminalHelp: RestoredTerminalHelpSchema.optional().describe("The terminal hand-over this row asked for, and how it ended."),
|
|
602
|
+
serviceOffer: RestoredServiceOfferSchema.optional().describe("The priced service run this row offered, the decision, and the receipt."),
|
|
603
|
+
capabilityOffer: RestoredCapabilityOfferSchema.optional().describe("The capability setup this row asked for, the decision, and the outcome."),
|
|
604
|
+
paymentOffer: RestoredPaymentOfferSchema.optional().describe("The payment this row asked for, the decision, and the receipt."),
|
|
430
605
|
});
|
|
431
606
|
export type RestoredMessage = z.infer<typeof RestoredMessageSchema>;
|
|
432
607
|
|
|
608
|
+
/* THE CARD FIELDS A ROW CAN CARRY, as one list, for every reader that has to ask "does this row hold a card":
|
|
609
|
+
* the fold that counts a card-only bubble as a row (sessions/turn-transcript.ts), the client's own row count
|
|
610
|
+
* (recordedRows), which must agree with it to the row or a branch is cut in the wrong place, and the client's
|
|
611
|
+
* restore, which turns each into its live card. The live ChatMessage names its cards exactly this way, so the
|
|
612
|
+
* list is the same list on both sides rather than two that have to be kept in step. */
|
|
613
|
+
export const RESTORED_CARD_FIELDS = [
|
|
614
|
+
"plan",
|
|
615
|
+
"question",
|
|
616
|
+
"permission",
|
|
617
|
+
"browserHelp",
|
|
618
|
+
"terminalHelp",
|
|
619
|
+
"serviceOffer",
|
|
620
|
+
"capabilityOffer",
|
|
621
|
+
"paymentOffer",
|
|
622
|
+
] as const;
|
|
623
|
+
export type RestoredCardField = (typeof RESTORED_CARD_FIELDS)[number];
|
|
624
|
+
export type RestoredCards = Pick<RestoredMessage, RestoredCardField>;
|
|
625
|
+
// Whether a row holds a card at all, the question the row counts on both sides ask.
|
|
626
|
+
export const holdsCard = (message: RestoredCards): boolean => RESTORED_CARD_FIELDS.some((field) => message[field] !== undefined);
|
|
627
|
+
|
|
433
628
|
export const SessionTranscriptSchema = z.object({
|
|
434
629
|
messages: z
|
|
435
630
|
.array(RestoredMessageSchema)
|
|
@@ -477,57 +672,6 @@ export const SharePayloadSchema = z.object({
|
|
|
477
672
|
});
|
|
478
673
|
export type SharePayload = z.infer<typeof SharePayloadSchema>;
|
|
479
674
|
|
|
480
|
-
/* WHAT A PARKED CARD IS ABOUT: the document the turn wrote and is now asking a question against.
|
|
481
|
-
*
|
|
482
|
-
* A card asks for a decision; until this it carried no SUBJECT. The commonest shape of a real decision is "I
|
|
483
|
-
* analysed this and wrote it up, now choose", and the write-up went into a file whose card had already folded
|
|
484
|
-
* itself into `Write · +135 −0` twenty tool calls back. So the reader was asked to choose between options
|
|
485
|
-
* describing a document the chat had never shown them.
|
|
486
|
-
*
|
|
487
|
-
* Carried BY VALUE rather than as a path, for the same reason the diff on a tool call is: the bytes are already
|
|
488
|
-
* in hand when the card is raised, a path would make the card's meaning depend on a file that keeps changing
|
|
489
|
-
* under it, and a restored or published transcript has no workspace to go read. The path rides along anyway, so
|
|
490
|
-
* a document past the wire cap still has somewhere to send the reader.
|
|
491
|
-
*
|
|
492
|
-
* Nothing is asked of the MODEL for this. It calls `ask` exactly as before; the daemon knows what the turn
|
|
493
|
-
* wrote, because every write came past it as a frame (documents.ts decides which of them is a document). A
|
|
494
|
-
* harness that can see the answer must not spend prompt on asking the model to repeat it. */
|
|
495
|
-
export const CardDocumentSchema = z.object({
|
|
496
|
-
path: z.string().describe("Where it lives, as a workspace path."),
|
|
497
|
-
title: z.string().describe("What it is called: its opening heading, or its file name."),
|
|
498
|
-
markdown: z.string().describe("The document itself."),
|
|
499
|
-
truncated: z.boolean().optional().describe("It was clipped at the wire cap; the file on disk has more."),
|
|
500
|
-
plan: z.boolean().optional().describe("It is one of the CLI's plan files, written to be approved rather than merely read."),
|
|
501
|
-
});
|
|
502
|
-
export type CardDocument = z.infer<typeof CardDocumentSchema>;
|
|
503
|
-
|
|
504
|
-
/* THE THREE RESTORABLE CARDS, named so the turn journal can hold them verbatim: a parked turn's raised cards
|
|
505
|
-
* are written down beside its prompt (sandbox turn-journal.ts), and a daemon death under the park restores the
|
|
506
|
-
* very same frames instead of ending the turn `interrupted`, the card the user was about to answer survives
|
|
507
|
-
* the restart that killed the process holding it. The two handover cards are deliberately not among them:
|
|
508
|
-
* `browser_help`'s Chromium and `terminal_help`'s waiting command both die with the container, so those parks
|
|
509
|
-
* cannot be restored, only reported. */
|
|
510
|
-
const PlanCardSchema = z.object({
|
|
511
|
-
kind: z.literal("plan").describe("The agent has written a plan and is waiting for a yes."),
|
|
512
|
-
requestId: z.string().describe("What to send back when you answer."),
|
|
513
|
-
text: z.string().describe("The plan itself."),
|
|
514
|
-
// Present when the adjacent plan prose POINTS at a document instead of being one: the model wrote the real
|
|
515
|
-
// plan to a file and summarised it there. Absent when the text already is the whole plan.
|
|
516
|
-
document: CardDocumentSchema.optional().describe("The write-up this plan refers to, when the plan itself is a pointer to one."),
|
|
517
|
-
});
|
|
518
|
-
const QuestionCardSchema = z.object({
|
|
519
|
-
kind: z.literal("question").describe("The agent has asked you something and is waiting."),
|
|
520
|
-
requestId: z.string().describe("What to send back when you answer."),
|
|
521
|
-
questions: z.array(AskQuestionSchema).describe("What it wants to know."),
|
|
522
|
-
document: CardDocumentSchema.optional().describe("The document this turn wrote and is asking about, so the choice can be read beside it."),
|
|
523
|
-
});
|
|
524
|
-
const PermissionCardSchema = PermissionAskSchema.extend({
|
|
525
|
-
kind: z.literal("permission").describe("The agent wants to use a tool it needs permission for."),
|
|
526
|
-
requestId: z.string().describe("What to send back when you answer."),
|
|
527
|
-
});
|
|
528
|
-
export const ParkedCardSchema = z.discriminatedUnion("kind", [PlanCardSchema, QuestionCardSchema, PermissionCardSchema]);
|
|
529
|
-
export type ParkedCard = z.infer<typeof ParkedCardSchema>;
|
|
530
|
-
|
|
531
675
|
// One frame from an agent turn, relayed to the UI. `kind`-discriminated. The daemon normalizes the SDK's
|
|
532
676
|
// ~40 SDKMessage types down to this union: high-value block types get a dedicated frame
|
|
533
677
|
// (delta/thinking/tool_call/tool_call_update/todos/usage/rate_limit_info/account_usage/context_usage/init/compact); any SDK message
|
|
@@ -843,35 +987,18 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
843
987
|
PlanCardSchema,
|
|
844
988
|
QuestionCardSchema,
|
|
845
989
|
PermissionCardSchema,
|
|
846
|
-
// The agent's browser needs a person
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
//
|
|
850
|
-
// login is stuck even after the browser has navigated somewhere unrecognizable.
|
|
851
|
-
z.object({
|
|
852
|
-
kind: z.literal("browser_help"),
|
|
853
|
-
requestId: z.string(),
|
|
854
|
-
session: z.string(),
|
|
855
|
-
account: z.string(),
|
|
856
|
-
message: z.string(),
|
|
857
|
-
}),
|
|
858
|
-
// The agent's TERMINAL needs a person: a command it started is sitting at a prompt it cannot answer (a
|
|
859
|
-
// one-time password, a security-key touch, a confirm). `session` names the tmux session on the terminal
|
|
860
|
-
// panel, the card's one action is going THERE, where the live pane and its prompt already are, which is
|
|
861
|
-
// the same division of labour the browser card has with /browsers. Not journalled for restore, and for the
|
|
990
|
+
// The agent's browser needs a person (see browserHelpCard for what the card carries). Not journalled for
|
|
991
|
+
// restore: the Chromium holding the page dies with the container.
|
|
992
|
+
z.object({ kind: z.literal("browser_help"), ...browserHelpCard }),
|
|
993
|
+
// The agent's TERMINAL needs a person (see terminalHelpCard). Not journalled for restore, and for the
|
|
862
994
|
// browser card's reason one door along: the pane holding the prompt belongs to a process the restart kills.
|
|
863
|
-
z.object({
|
|
864
|
-
kind: z.literal("terminal_help"),
|
|
865
|
-
requestId: z.string(),
|
|
866
|
-
session: z.string(),
|
|
867
|
-
message: z.string(),
|
|
868
|
-
}),
|
|
995
|
+
z.object({ kind: z.literal("terminal_help"), ...terminalHelpCard }),
|
|
869
996
|
/* A premium service run awaiting the owner's click. Raised OUTSIDE the turn generator, the daemon's
|
|
870
997
|
* services route parks the agent's own `services run` call and pushes this frame into the live run
|
|
871
998
|
* (platform/service-offer.ts), so unlike the four cards above it is not journalled for restore: its
|
|
872
999
|
* waiter is the CLI's held connection, which dies with the daemon, and a restored card would offer
|
|
873
1000
|
* buttons nothing is waiting behind. Settles through the same `POST /agent/reply` as every other card. */
|
|
874
|
-
z.object({ kind: z.literal("service_offer"),
|
|
1001
|
+
z.object({ kind: z.literal("service_offer"), ...serviceOfferCard }),
|
|
875
1002
|
/* One event off an approved run's stream, pushed as the provider emits it so the settled card shows the
|
|
876
1003
|
* run living rather than a spinner of unknowable length. Today that is `status` lines; `result` stays off
|
|
877
1004
|
* the transcript on purpose (it is the agent's answer to act on, not the card's to duplicate), the frame
|
|
@@ -881,49 +1008,31 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
881
1008
|
* rather than a promise: `ok` served and charged, `refunded` failed to answer and charged nothing,
|
|
882
1009
|
* `refused` the platform said no after the click (a raced-out allowance). `remaining` is the meter after,
|
|
883
1010
|
* when the platform stated one. Skip needs no receipt, nothing happened, and `resolved` already says so. */
|
|
884
|
-
z.
|
|
885
|
-
kind: z.literal("service_receipt"),
|
|
886
|
-
requestId: z.string(),
|
|
887
|
-
outcome: z.enum(["ok", "refunded", "refused"]),
|
|
888
|
-
credits: z.number(),
|
|
889
|
-
remaining: z.number().optional(),
|
|
890
|
-
}),
|
|
1011
|
+
ServiceReceiptSchema.extend({ kind: z.literal("service_receipt"), requestId: z.string() }),
|
|
891
1012
|
/* A missing capability asking for the owner's setup, the agent hit something this sandbox is not
|
|
892
1013
|
* connected to and raised the card instead of describing manual steps. Raised OUTSIDE the turn generator
|
|
893
1014
|
* exactly like the service offer above (the daemon's ask route parks the agent's `capabilities request`
|
|
894
1015
|
* call and pushes this frame into the live run; capabilities/capability-offer.ts), so it is not
|
|
895
1016
|
* journalled for restore either: its waiter is the CLI's held connection, which dies with the daemon.
|
|
896
1017
|
* Settles through the same `POST /agent/reply` as every other card. */
|
|
897
|
-
z.object({ kind: z.literal("capability_offer"),
|
|
1018
|
+
z.object({ kind: z.literal("capability_offer"), ...capabilityOfferCard }),
|
|
898
1019
|
/* How an accepted ask ended, pushed once the daemon stops watching for the connection: `connected`, the
|
|
899
1020
|
* capability came live while the agent waited (`id` is the connected instance, the agent's handle for it)
|
|
900
1021
|
*, or `unfinished`, the setup did not complete while anyone was waiting (the deadline passed, or the
|
|
901
1022
|
* asking command died). A skip needs no outcome frame, nothing was set up, and `resolved` already says
|
|
902
1023
|
* so. It is what settles the card's "waiting for you to finish setup" state on every surface. */
|
|
903
|
-
z.
|
|
904
|
-
kind: z.literal("capability_outcome"),
|
|
905
|
-
requestId: z.string(),
|
|
906
|
-
outcome: z.enum(["connected", "unfinished"]),
|
|
907
|
-
id: z.string().optional(),
|
|
908
|
-
}),
|
|
1024
|
+
CapabilityOutcomeSchema.extend({ kind: z.literal("capability_outcome"), requestId: z.string() }),
|
|
909
1025
|
/* A USDC payment awaiting the owner's click. Raised OUTSIDE the turn generator exactly like the service
|
|
910
1026
|
* offer above (the daemon's wallet route parks the agent's `wallet fetch` call and pushes this frame into
|
|
911
1027
|
* the live run; wallet/payment-offer.ts), so it is not journalled for restore either: its waiter is the
|
|
912
1028
|
* CLI's held connection, which dies with the daemon. Settles through the same `POST /agent/reply`. */
|
|
913
|
-
z.object({ kind: z.literal("payment_offer"),
|
|
1029
|
+
z.object({ kind: z.literal("payment_offer"), ...paymentOfferCard }),
|
|
914
1030
|
/* How an approved (or auto-approved) payment ended, pushed after the endpoint answered so the card can
|
|
915
1031
|
* settle as a receipt rather than a promise: `paid`, the endpoint confirmed settlement (`transaction` is
|
|
916
1032
|
* the onchain hash when it stated one); `failed`, the payment was refused or settlement failed, in which
|
|
917
1033
|
* case the signed authorization expires unused and NOTHING left the wallet. A skip needs no receipt,
|
|
918
1034
|
* nothing moved, and `resolved` already says so. */
|
|
919
|
-
z.
|
|
920
|
-
kind: z.literal("payment_receipt"),
|
|
921
|
-
requestId: z.string(),
|
|
922
|
-
outcome: z.enum(["paid", "failed"]),
|
|
923
|
-
amountUsd: z.string(),
|
|
924
|
-
transaction: z.string().optional(),
|
|
925
|
-
network: z.string().optional(),
|
|
926
|
-
}),
|
|
1035
|
+
PaymentReceiptSchema.extend({ kind: z.literal("payment_receipt"), requestId: z.string() }),
|
|
927
1036
|
// The card above named by `requestId` is released, the user answered (or dismissed it, or the turn was
|
|
928
1037
|
// stopped out from under it), so the turn is executing again. Emitted by whoever parked, the moment its
|
|
929
1038
|
// waiter settles, because the park's END is otherwise invisible on this stream: nothing else here says
|
|
@@ -1048,8 +1157,31 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
1048
1157
|
* is a real classification all the same: it says the failure came from the LOOP rather than
|
|
1049
1158
|
* from the provider, the credential or the request, which rules out every recovery next door. */
|
|
1050
1159
|
"harness-incomplete",
|
|
1160
|
+
/* THE ENGINE IS TOO OLD FOR THE MODEL, and the provider says so in the same breath as the
|
|
1161
|
+
* version that would work ("Claude Code 2.1.233 does not support this model; version 2.1.251
|
|
1162
|
+
* or newer is required"). Its own code because the fix is unlike every neighbour's: nothing is
|
|
1163
|
+
* disconnected, nothing is spent, no retry of any length helps, and the thing that has to
|
|
1164
|
+
* change is not the request but the PROGRAM running it (schemas/engines.ts).
|
|
1165
|
+
*
|
|
1166
|
+
* It used to be unfixable from inside a sandbox at all — the engine came with the image, so a
|
|
1167
|
+
* whole fleet failed every turn on this model until a new image reached it. Now the daemon can
|
|
1168
|
+
* install the version the provider named, which is why this frame carries the numbers rather
|
|
1169
|
+
* than only the sentence: `engine` is what the card's Update button acts on. The install is
|
|
1170
|
+
* still a person's decision, because the version that satisfies a floor is by definition one
|
|
1171
|
+
* nobody has blessed yet. */
|
|
1172
|
+
"engine-version-floor",
|
|
1051
1173
|
])
|
|
1052
1174
|
.optional(),
|
|
1175
|
+
/* engine-version-floor only: which engine is too old, what it is running, and the floor the provider
|
|
1176
|
+
* demanded. On the wire because the recovery is a specific, offerable action — install at or above
|
|
1177
|
+
* `floor` — and a client that had only the sentence would have to parse prose to offer it. */
|
|
1178
|
+
engine: z
|
|
1179
|
+
.object({
|
|
1180
|
+
id: z.string().describe("Which engine (e.g. claude)."),
|
|
1181
|
+
running: z.string().optional().describe("The version that was refused, when the provider named it."),
|
|
1182
|
+
floor: z.string().describe("The lowest version the provider will accept."),
|
|
1183
|
+
})
|
|
1184
|
+
.optional(),
|
|
1053
1185
|
// rate_limit only: when the exhausted window reopens (epoch seconds, from the stream's own
|
|
1054
1186
|
// rate_limit_event or the account's persisted usage windows). Absent when the reset instant is unknown
|
|
1055
1187
|
// (nothing to schedule against).
|
|
@@ -1147,11 +1279,18 @@ export const RESUME_NOTES = {
|
|
|
1147
1279
|
auth: `The Claude credential that interrupted this conversation has been renewed, and this turn resumed automatically. ${REPEATED}`,
|
|
1148
1280
|
outage: `The model provider was briefly unavailable and interrupted this conversation; this turn resumed automatically. ${REPEATED}`,
|
|
1149
1281
|
restart: `The sandbox restarted while this turn was running, which stopped it, and this turn resumed automatically once it came back. ${REPEATED}`,
|
|
1150
|
-
/* A SPENT ALLOWANCE STRANDS A TURN IN
|
|
1282
|
+
/* A SPENT ALLOWANCE STRANDS A TURN IN THREE SHAPES, and they must not share a note.
|
|
1151
1283
|
*
|
|
1152
1284
|
* `limit` is the mid-turn one and reads like its three neighbours above: the session holds real work, and
|
|
1153
1285
|
* carrying on from it is exactly right.
|
|
1154
1286
|
*
|
|
1287
|
+
* `switched` is that same mid-turn stranding picked back up on a DIFFERENT account (or provider, or
|
|
1288
|
+
* harness), which is what the composer's account switcher does between the refusal and the press. A session
|
|
1289
|
+
* belongs to the credential that minted it, so this one cannot resume: it opens a fresh session seeded from
|
|
1290
|
+
* the daemon's record. REPEATED's "already completed in this session" is therefore false where it counts —
|
|
1291
|
+
* the work is in the carried-across conversation, not in this session's own history — and a model told to
|
|
1292
|
+
* look for it there finds nothing and starts over silently.
|
|
1293
|
+
*
|
|
1155
1294
|
* `refused` is the turn the provider turned away at the door, before the model read one word of it, and it
|
|
1156
1295
|
* is the COMMONER of the two, because an allowance that is already spent refuses the first request it is
|
|
1157
1296
|
* asked. REPEATED is actively wrong for it: "part of it was already completed in this session, continue from
|
|
@@ -1162,6 +1301,8 @@ export const RESUME_NOTES = {
|
|
|
1162
1301
|
* allowance is the user's own budget and stays their call to spend (turn-resume.ts), so what re-ran this
|
|
1163
1302
|
* turn was a person pressing Continue. */
|
|
1164
1303
|
limit: `The model provider's usage allowance ran out while this turn was running, which stopped it, and it has been sent again. ${REPEATED}`,
|
|
1304
|
+
switched:
|
|
1305
|
+
"The model provider's usage allowance ran out while this turn was running, which stopped it, and it has been sent again on a different account, which starts a fresh session. The conversation so far has been carried across above, including the part of the request that was already completed: continue from that point instead of starting over.",
|
|
1165
1306
|
refused:
|
|
1166
1307
|
"The model provider refused the previous attempt at this request outright, because its usage allowance was spent: no part of the request below was read or acted on, and nothing has been done towards it. It has been sent again, and starts from the beginning.",
|
|
1167
1308
|
// A turn that was PARKED on the user when the daemon died: nothing re-runs at boot, the card is restored
|
|
@@ -1209,13 +1350,18 @@ const RESUME_DISCLOSURES: Record<ResumeReason, ResumeDisclosure> = {
|
|
|
1209
1350
|
auth: { kind: "notice", text: "Claude sign-in renewed, this turn picked up where it left off." },
|
|
1210
1351
|
outage: { kind: "notice", text: "The model provider came back, this turn picked up where it left off." },
|
|
1211
1352
|
restart: { kind: "notice", text: "The sandbox came back, this turn picked up where it left off." },
|
|
1212
|
-
/* THE
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1353
|
+
/* THE THREE NOBODY AUTOMATED, said in the passive voice the other three earn honestly and these do not: a
|
|
1354
|
+
* person pressed Continue. Which is the whole reason these rows exist at all. A press used to append the word
|
|
1355
|
+
* "Continue" as a message of its own, so a chat that bounced off a spent allowance four times read back as
|
|
1356
|
+
* the user saying "Continue" four times to an agent that had answered none of them, and the provider session
|
|
1357
|
+
* the model actually reads accumulated all four (plus a synthetic "No response requested." per press). One
|
|
1358
|
+
* row for one press was never the problem; a row that claims the user said something new is.
|
|
1359
|
+
*
|
|
1360
|
+
* `switched` names the account because that is the fact the reader needs: they pressed the same button they
|
|
1361
|
+
* pressed a minute ago, and the difference between the press that bounced and the press that worked is who
|
|
1362
|
+
* served it. The line is also the only place a retired session is accounted for. */
|
|
1218
1363
|
limit: { kind: "notice", text: "Sent again after the allowance ran out mid-turn, picking up where it left off." },
|
|
1364
|
+
switched: { kind: "notice", text: "Sent again on the switched account after the allowance ran out mid-turn, in a fresh session." },
|
|
1219
1365
|
refused: { kind: "notice", text: "Sent again after the allowance refused it: nothing had run." },
|
|
1220
1366
|
answered: { kind: "note", note: { title: "Picked back up after a sandbox restart", text: RESUME_NOTES.answered } },
|
|
1221
1367
|
};
|
package/src/index.ts
CHANGED
|
@@ -144,6 +144,7 @@ export * from "./schemas/claude-gate.js";
|
|
|
144
144
|
export * from "./schemas/codebase-health.js";
|
|
145
145
|
export * from "./schemas/computers.js";
|
|
146
146
|
export * from "./schemas/drafts.js";
|
|
147
|
+
export * from "./schemas/engines.js";
|
|
147
148
|
export * from "./schemas/environment.js";
|
|
148
149
|
export * from "./schemas/exit.js";
|
|
149
150
|
export * from "./schemas/extension-updates.js";
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// engines: the upstream agent programs this sandbox spawns, and which version of each it runs
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/* AN ENGINE IS THE PROGRAM A RUNTIME RIDES ON, which is a different noun from `runtime` and has to stay one.
|
|
5
|
+
*
|
|
6
|
+
* `runtime` (agent-catalog.ts) is the LOOP: claude-code, codex, opencode, acp, pi, cursor — what shape a turn
|
|
7
|
+
* has. An engine is the installed PROGRAM that loop spawns or imports: the `claude` binary and its SDK, the
|
|
8
|
+
* `codex` wrapper, `@cursor/sdk`, the `opencode` binary, the `cli-proxy-api` translator. One engine can back
|
|
9
|
+
* several runtimes (the translator serves Gemini, Kimi and ChatGPT; Kimi runs under the Claude Code loop), so
|
|
10
|
+
* the two lists are neither the same set nor the same question.
|
|
11
|
+
*
|
|
12
|
+
* The distinction earns its keep because engines have a lifecycle runtimes do not: they are published upstream,
|
|
13
|
+
* on somebody else's schedule, and until this existed the only way to move one was to ship a sandbox image.
|
|
14
|
+
* That made an ordinary upstream event — Anthropic raising the version floor a model requires — into a support
|
|
15
|
+
* problem: every sandbox in the fleet failed every Claude turn until a new image reached it.
|
|
16
|
+
*
|
|
17
|
+
* So an engine version now comes from a STORE on the daemon volume, and the image's copy is the floor beneath
|
|
18
|
+
* it. The blessed list says which version this project has actually run its suite against; an owner who wants
|
|
19
|
+
* upstream's newest without waiting for that says so per engine. */
|
|
20
|
+
|
|
21
|
+
export const ENGINE_IDS = ["claude", "codex", "cursor", "opencode", "translator"] as const;
|
|
22
|
+
export type EngineId = (typeof ENGINE_IDS)[number];
|
|
23
|
+
export const EngineIdSchema = z.enum(ENGINE_IDS);
|
|
24
|
+
|
|
25
|
+
/* THE OWNER'S STANDING ANSWER for one engine, and the whole of the version policy.
|
|
26
|
+
*
|
|
27
|
+
* blessed , the default: whatever the blessed list names, which is a version this project's CI has run
|
|
28
|
+
* against. Data, not an image, so blessing a version reaches a running sandbox in seconds.
|
|
29
|
+
* latest , upstream's newest published version, taken without waiting for anyone to bless it. The opt-in
|
|
30
|
+
* for an owner who would rather have the fix than the assurance.
|
|
31
|
+
* pinned , exactly this version, until they say otherwise. What a revert leaves behind, and what somebody
|
|
32
|
+
* debugging a regression wants.
|
|
33
|
+
* image , do not use the store at all: run what the image bakes. The way back to a stock sandbox. */
|
|
34
|
+
export const EngineChannelSchema = z.object({
|
|
35
|
+
kind: z.enum(["blessed", "latest", "pinned", "image"]).describe("Where this engine's version comes from."),
|
|
36
|
+
version: z.string().optional().describe("Which version, when it is pinned to one."),
|
|
37
|
+
});
|
|
38
|
+
export type EngineChannel = z.infer<typeof EngineChannelSchema>;
|
|
39
|
+
|
|
40
|
+
// A version the store installed and then refused, with the reason it was refused. Kept per engine so a bad
|
|
41
|
+
// publish is not retried on a timer forever, and so the card can say why the sandbox is back on the image's
|
|
42
|
+
// copy rather than leaving that as an unexplained downgrade.
|
|
43
|
+
export const EngineQuarantineSchema = z.object({
|
|
44
|
+
version: z.string().describe("Which version was refused."),
|
|
45
|
+
reason: z.string().describe("What was wrong with it: it would not launch, or it did not export what the daemon calls."),
|
|
46
|
+
at: z.string().describe("When it was refused."),
|
|
47
|
+
});
|
|
48
|
+
export type EngineQuarantine = z.infer<typeof EngineQuarantineSchema>;
|
|
49
|
+
|
|
50
|
+
// One engine as the Environment card draws it: what is running, what is on offer, and what the ways out are.
|
|
51
|
+
export const EngineRowSchema = z.object({
|
|
52
|
+
id: EngineIdSchema.describe("Which engine."),
|
|
53
|
+
label: z.string().describe("What it is called on screen."),
|
|
54
|
+
running: z
|
|
55
|
+
.object({
|
|
56
|
+
// Absent means this sandbox has no copy of the engine at all: a core image bakes no provider packs,
|
|
57
|
+
// and until the store installs one, a turn on that provider cannot start. Different from "running
|
|
58
|
+
// the image's copy", and the card says so rather than drawing a blank version.
|
|
59
|
+
version: z.string().optional().describe("The version a turn would use right now. Absent means there is no copy of this engine here yet."),
|
|
60
|
+
source: z
|
|
61
|
+
.enum(["image", "store"])
|
|
62
|
+
.describe("Whether that version is the one baked into the sandbox image or one the store installed over it."),
|
|
63
|
+
})
|
|
64
|
+
.describe("What a turn started now would actually run."),
|
|
65
|
+
baked: z
|
|
66
|
+
.string()
|
|
67
|
+
.optional()
|
|
68
|
+
.describe("The version the image bakes, which is the floor everything else falls back to. Absent on an image that carries no copy of it."),
|
|
69
|
+
channel: EngineChannelSchema.describe("The owner's standing answer for this engine."),
|
|
70
|
+
// What the channel would move to, absent when the running version is already it. Carries whether the
|
|
71
|
+
// blessed list names it, because on `latest` the answer is routinely no and the row has to say so.
|
|
72
|
+
offered: z
|
|
73
|
+
.object({
|
|
74
|
+
version: z.string().describe("The version this engine would move to."),
|
|
75
|
+
blessed: z.boolean().describe("Whether the blessed list names this version, which on the latest channel is routinely no."),
|
|
76
|
+
})
|
|
77
|
+
.optional()
|
|
78
|
+
.describe("A newer version waiting, absent when the running one is already what the channel asks for."),
|
|
79
|
+
blessed: z.string().optional().describe("What the blessed list names for this engine, when the list has been read."),
|
|
80
|
+
// The store keeps one version back so a revert is a pointer move rather than a download. Absent on an
|
|
81
|
+
// engine that has only ever run the image's copy.
|
|
82
|
+
previous: z.string().optional().describe("The version kept one step back, which is what going back means."),
|
|
83
|
+
quarantined: z.array(EngineQuarantineSchema).describe("Versions the store installed and then refused, with the reason."),
|
|
84
|
+
diskBytes: z.number().int().nonnegative().describe("What this engine's kept versions cost on the daemon's volume."),
|
|
85
|
+
});
|
|
86
|
+
export type EngineRow = z.infer<typeof EngineRowSchema>;
|
|
87
|
+
|
|
88
|
+
export const EnginesViewSchema = z.object({
|
|
89
|
+
engines: z.array(EngineRowSchema).describe("Every engine this sandbox can run, whether or not the store holds anything for it."),
|
|
90
|
+
checkedAt: z.string().optional().describe("When upstream was last asked what it publishes. Absent until the first check has run."),
|
|
91
|
+
listSource: z.string().describe("Where the blessed list is read from, so a self-hosted sandbox can show its own."),
|
|
92
|
+
listReadAt: z.string().optional().describe("When that list was last read. Absent means it has never been reachable from here."),
|
|
93
|
+
});
|
|
94
|
+
export type EnginesView = z.infer<typeof EnginesViewSchema>;
|
|
95
|
+
|
|
96
|
+
export const EngineChannelInputSchema = z.object({
|
|
97
|
+
id: EngineIdSchema.describe("Which engine."),
|
|
98
|
+
kind: z.enum(["blessed", "latest", "pinned", "image"]).describe("Where its version should come from."),
|
|
99
|
+
version: z.string().optional().describe("Which version, required when pinning and ignored otherwise."),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/* Install and activate a version now. `version` absent means what the channel offers, which is the button on
|
|
103
|
+
* the row. Naming one explicitly is the "update anyway" path: it installs a version the blessed list does not
|
|
104
|
+
* name, which is how a sandbox gets past an upstream version floor that the list has not caught up with. */
|
|
105
|
+
export const EngineUpdateInputSchema = z.object({
|
|
106
|
+
id: EngineIdSchema.describe("Which engine."),
|
|
107
|
+
version: z
|
|
108
|
+
.string()
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Which version. Leave it out for whatever the channel offers; naming one takes a version nobody has blessed, deliberately."),
|
|
111
|
+
/* The other half of the update-anyway path: a turn that died on an upstream version floor knows the floor
|
|
112
|
+
* and not which published version satisfies it. Sending the floor lets the daemon answer that — it takes
|
|
113
|
+
* the LOWEST version at or above it, the smallest step that works — rather than making a browser walk a
|
|
114
|
+
* registry to fill in a version number. */
|
|
115
|
+
floor: z
|
|
116
|
+
.string()
|
|
117
|
+
.optional()
|
|
118
|
+
.describe("Install the lowest published version at or above this one. What a turn refused for being too old sends back."),
|
|
119
|
+
});
|
|
120
|
+
export const EngineRevertInputSchema = z.object({ id: EngineIdSchema.describe("Which engine.") });
|
|
121
|
+
|
|
122
|
+
export const EngineAppliedSchema = z.object({
|
|
123
|
+
ok: z.literal(true).describe("It went through."),
|
|
124
|
+
version: z.string().describe("Which version is now active."),
|
|
125
|
+
source: z.enum(["image", "store"]).describe("Whether that is the image's copy or the store's."),
|
|
126
|
+
// An engine loaded in-process (the Claude SDK's JavaScript half, @cursor/sdk) is picked up by the NEXT
|
|
127
|
+
// turn rather than by the one that pressed the button: a turn already running holds the module it loaded.
|
|
128
|
+
fromNextTurn: z.boolean().describe("Whether the change reaches turns already in flight, or only the next one."),
|
|
129
|
+
});
|