@intentic/sandbox-contract 1.310.0 → 1.312.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/README.md +43 -159
- package/dist/chores/chores.js +6 -6
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/fix-deps.d.ts.map +1 -1
- package/dist/chores/fix-deps.js +3 -2
- package/dist/chores/fix-deps.js.map +1 -1
- package/dist/chores/prompt.d.ts.map +1 -1
- package/dist/chores/prompt.js +3 -2
- package/dist/chores/prompt.js.map +1 -1
- package/dist/contracts/accounts.contract.d.ts +15 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +15 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +1228 -32
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +42 -3
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +2 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.d.ts +2 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +2 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/offload.contract.d.ts +107 -0
- package/dist/contracts/offload.contract.d.ts.map +1 -0
- package/dist/contracts/offload.contract.js +53 -0
- package/dist/contracts/offload.contract.js.map +1 -0
- package/dist/contracts/ports.contract.d.ts +5 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +111 -0
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.js +3 -1
- package/dist/contracts/runner.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +16 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +154 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.d.ts +16 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +146 -0
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +40 -3
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/documents/conversions.d.ts +81 -0
- package/dist/documents/conversions.d.ts.map +1 -0
- package/dist/documents/conversions.js +213 -0
- package/dist/documents/conversions.js.map +1 -0
- package/dist/documents/index.d.ts +3 -0
- package/dist/documents/index.d.ts.map +1 -0
- package/dist/documents/index.js +3 -0
- package/dist/documents/index.js.map +1 -0
- package/dist/documents/passthrough.d.ts +13 -0
- package/dist/documents/passthrough.d.ts.map +1 -0
- package/dist/documents/passthrough.js +101 -0
- package/dist/documents/passthrough.js.map +1 -0
- package/dist/events/agent-events.d.ts +20 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +6 -1
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/land-breakage.d.ts +3 -0
- package/dist/events/land-breakage.d.ts.map +1 -1
- package/dist/events/land-breakage.js +3 -0
- package/dist/events/land-breakage.js.map +1 -1
- package/dist/events/land-conflict.d.ts +3 -0
- package/dist/events/land-conflict.d.ts.map +1 -0
- package/dist/events/land-conflict.js +4 -0
- package/dist/events/land-conflict.js.map +1 -0
- package/dist/events/system-events.d.ts +114 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +3 -1
- package/dist/events/system-events.js.map +1 -1
- package/dist/events/transcript.d.ts +5 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +9 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/events/verify-nudge.d.ts.map +1 -1
- package/dist/events/verify-nudge.js.map +1 -1
- package/dist/front/generated/wire.d.ts +137 -0
- package/dist/front/generated/wire.d.ts.map +1 -0
- package/dist/front/generated/wire.js +2 -0
- package/dist/front/generated/wire.js.map +1 -0
- package/dist/front/terminal-frames.d.ts +17 -0
- package/dist/front/terminal-frames.d.ts.map +1 -0
- package/dist/front/terminal-frames.js +79 -0
- package/dist/front/terminal-frames.js.map +1 -0
- package/dist/ids/conversation-ids.d.ts +2 -0
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +2 -0
- package/dist/ids/conversation-ids.js.map +1 -1
- package/dist/index.d.ts +1773 -96
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/policy/command-run.d.ts +1 -1
- package/dist/policy/command-run.d.ts.map +1 -1
- package/dist/policy/command-run.js +7 -4
- package/dist/policy/command-run.js.map +1 -1
- package/dist/protocol/container-requirements.d.ts +0 -1
- package/dist/protocol/container-requirements.d.ts.map +1 -1
- package/dist/protocol/container-requirements.js +1 -2
- package/dist/protocol/container-requirements.js.map +1 -1
- package/dist/protocol/ingress-contract.d.ts +0 -11
- package/dist/protocol/ingress-contract.d.ts.map +1 -1
- package/dist/protocol/raw-routes.d.ts +36 -4
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +18 -13
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/protocol/route-meta.d.ts +2 -0
- package/dist/protocol/route-meta.d.ts.map +1 -1
- package/dist/protocol/route-meta.js.map +1 -1
- package/dist/protocol/runner-protocol.d.ts +34 -0
- package/dist/protocol/runner-protocol.d.ts.map +1 -1
- package/dist/protocol/runner-protocol.js +33 -0
- package/dist/protocol/runner-protocol.js.map +1 -1
- package/dist/protocol/tunnel-lanes.d.ts +10 -0
- package/dist/protocol/tunnel-lanes.d.ts.map +1 -0
- package/dist/protocol/tunnel-lanes.js +16 -0
- package/dist/protocol/tunnel-lanes.js.map +1 -0
- package/dist/schemas/activity.d.ts.map +1 -1
- package/dist/schemas/activity.js.map +1 -1
- package/dist/schemas/agents.d.ts +145 -3
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +43 -9
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +43 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +3 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +4 -0
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/devices.d.ts +34 -6
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +8 -6
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +4 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/git/git.d.ts +16 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js +1 -0
- package/dist/schemas/git/git.js.map +1 -1
- package/dist/schemas/hosts.d.ts +8 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +3 -0
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/keep-warm.d.ts +68 -0
- package/dist/schemas/keep-warm.d.ts.map +1 -0
- package/dist/schemas/keep-warm.js +74 -0
- package/dist/schemas/keep-warm.js.map +1 -0
- package/dist/schemas/metrics.d.ts +7 -0
- package/dist/schemas/metrics.d.ts.map +1 -1
- package/dist/schemas/metrics.js +1 -0
- package/dist/schemas/metrics.js.map +1 -1
- package/dist/schemas/offload.d.ts +62 -0
- package/dist/schemas/offload.d.ts.map +1 -0
- package/dist/schemas/offload.js +23 -0
- package/dist/schemas/offload.js.map +1 -0
- package/dist/schemas/personas.js +3 -3
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/ports.d.ts +10 -0
- package/dist/schemas/ports.d.ts.map +1 -1
- package/dist/schemas/ports.js +8 -0
- package/dist/schemas/ports.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +29 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +7 -0
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +15 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/provider-oauth.js +4 -0
- package/dist/schemas/providers/provider-oauth.js.map +1 -1
- package/dist/schemas/providers/usage.d.ts +6 -0
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +4 -0
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/settings-history.d.ts +28 -0
- package/dist/schemas/settings-history.d.ts.map +1 -0
- package/dist/schemas/settings-history.js +39 -0
- package/dist/schemas/settings-history.js.map +1 -0
- package/dist/schemas/settings.d.ts +8 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +33 -3
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/system.d.ts +24 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +15 -0
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/workspace/mainline.d.ts +371 -0
- package/dist/schemas/workspace/mainline.d.ts.map +1 -0
- package/dist/schemas/workspace/mainline.js +134 -0
- package/dist/schemas/workspace/mainline.js.map +1 -0
- package/dist/schemas/workspace/workspace-tree.d.ts +23 -0
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +34 -0
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/definition.d.ts +36 -4
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/definition.js.map +1 -1
- package/dist/state/fix-stance.d.ts.map +1 -1
- package/dist/state/fix-stance.js +1 -0
- package/dist/state/fix-stance.js.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +5 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/runtime-state.d.ts +6 -0
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +2 -0
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +22 -1
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +27 -1
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +3 -0
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +30 -8
- package/dist/text/transcript-fold.js.map +1 -1
- package/dist/time/zone.d.ts +1 -0
- package/dist/time/zone.d.ts.map +1 -1
- package/dist/time/zone.js +5 -0
- package/dist/time/zone.js.map +1 -1
- package/package.json +34 -22
- package/src/chores/chores.ts +6 -6
- package/src/chores/fix-deps.ts +3 -2
- package/src/chores/prompt.ts +3 -2
- package/src/contracts/agents.contract.ts +50 -3
- package/src/contracts/offload.contract.ts +60 -0
- package/src/contracts/runner.contract.ts +7 -1
- package/src/contracts/workspace.contract.ts +54 -2
- package/src/documents/conversions.ts +402 -0
- package/src/documents/index.ts +5 -0
- package/src/documents/passthrough.ts +139 -0
- package/src/events/agent-events.ts +9 -1
- package/src/events/land-breakage.ts +12 -0
- package/src/events/land-conflict.ts +12 -0
- package/src/events/system-events.ts +9 -3
- package/src/events/transcript.ts +14 -3
- package/src/events/verify-nudge.ts +4 -3
- package/src/front/generated/wire.ts +104 -0
- package/src/front/terminal-frames.fixture.json +55 -0
- package/src/front/terminal-frames.test.ts +63 -0
- package/src/front/terminal-frames.ts +102 -0
- package/src/ids/conversation-ids.ts +7 -0
- package/src/ids/hostnames.fixture.json +20 -0
- package/src/ids/hostnames.test.ts +20 -0
- package/src/index.ts +8 -1
- package/src/policy/command-run.test.ts +35 -0
- package/src/policy/command-run.ts +10 -5
- package/src/protocol/container-requirements.test.ts +2 -3
- package/src/protocol/container-requirements.ts +1 -7
- package/src/protocol/ingress-contract.fixture.json +77 -0
- package/src/protocol/ingress-contract.test.ts +18 -26
- package/src/protocol/ingress-contract.ts +3 -22
- package/src/protocol/raw-routes.ts +21 -14
- package/src/protocol/route-meta.ts +4 -0
- package/src/protocol/runner-protocol.ts +53 -0
- package/src/protocol/tunnel-lanes.json +136 -0
- package/src/protocol/tunnel-lanes.test.ts +30 -0
- package/src/protocol/tunnel-lanes.ts +32 -0
- package/src/schemas/activity.ts +3 -1
- package/src/schemas/agents.ts +66 -10
- package/src/schemas/capabilities.ts +6 -0
- package/src/schemas/devices.ts +16 -9
- package/src/schemas/git/git.ts +5 -0
- package/src/schemas/hosts.ts +8 -0
- package/src/schemas/keep-warm.test.ts +68 -0
- package/src/schemas/keep-warm.ts +124 -0
- package/src/schemas/metrics.ts +2 -0
- package/src/schemas/offload.ts +44 -0
- package/src/schemas/personas.ts +3 -3
- package/src/schemas/ports.ts +10 -0
- package/src/schemas/providers/plan-limits.ts +13 -0
- package/src/schemas/providers/provider-oauth.ts +6 -0
- package/src/schemas/providers/usage.ts +7 -0
- package/src/schemas/settings-history.ts +61 -0
- package/src/schemas/settings.ts +70 -19
- package/src/schemas/system.ts +17 -0
- package/src/schemas/workspace/mainline.ts +201 -0
- package/src/schemas/workspace/workspace-tree.ts +47 -0
- package/src/state/definition.ts +3 -2
- package/src/state/fix-stance.test.ts +14 -0
- package/src/state/fix-stance.ts +2 -0
- package/src/state/history-state.ts +15 -0
- package/src/state/runtime-state.ts +8 -0
- package/src/state/workspace-state.ts +35 -1
- package/src/text/transcript-fold.test.ts +53 -1
- package/src/text/transcript-fold.ts +53 -10
- package/src/time/zone.ts +7 -0
- package/src/webext/index.ts +1 -1
- package/dist/protocol/ingress-protocol.d.ts +0 -30
- package/dist/protocol/ingress-protocol.d.ts.map +0 -1
- package/dist/protocol/ingress-protocol.js +0 -285
- package/dist/protocol/ingress-protocol.js.map +0 -1
- package/dist/protocol/terminal-protocol.d.ts +0 -18
- package/dist/protocol/terminal-protocol.d.ts.map +0 -1
- package/dist/protocol/terminal-protocol.js +0 -2
- package/dist/protocol/terminal-protocol.js.map +0 -1
- package/dist/protocol/tunnel-heartbeat.d.ts +0 -19
- package/dist/protocol/tunnel-heartbeat.d.ts.map +0 -1
- package/dist/protocol/tunnel-heartbeat.js +0 -41
- package/dist/protocol/tunnel-heartbeat.js.map +0 -1
- package/src/protocol/ingress-protocol.test.ts +0 -535
- package/src/protocol/ingress-protocol.ts +0 -448
- package/src/protocol/terminal-protocol.ts +0 -13
- package/src/protocol/tunnel-heartbeat.test.ts +0 -75
- package/src/protocol/tunnel-heartbeat.ts +0 -67
package/src/schemas/agents.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { AgentHarnessSchema, AgentOriginSchema, AgentProviderSchema, ConversationQueueSchema, ForkedFromSchema } from "./agent.js";
|
|
4
4
|
import { LoopStateSchema } from "./loops.js";
|
|
5
5
|
import { LimitPolicySchema, RetryPolicySchema, TurnBreakPolicySchema, TurnBreakSchema } from "./turn-break.js";
|
|
6
|
+
import { KeepWarmSchema } from "./keep-warm.js";
|
|
6
7
|
import { EMOJI_MAX_LENGTH, isSingleEmoji } from "../text/emoji.js";
|
|
7
8
|
// A fleet agent is any conversation with a registry entry, keyed by conversationId. Isolated ones own a git worktree
|
|
8
9
|
// (branch agent/<id>); workspace conversations have none, but both share one status/activity/cost lifecycle.
|
|
@@ -82,10 +83,30 @@ export const UnfinishedWorkSchema = z.object({
|
|
|
82
83
|
})
|
|
83
84
|
.optional()
|
|
84
85
|
.describe("The agent's own checklist where that turn left it. Absent for a conversation that kept no list."),
|
|
85
|
-
// Name of the `turn.ending` check still red when the turn ended
|
|
86
|
-
|
|
86
|
+
// Name of the `turn.ending` check still red when the turn ended. Retired with that check: nothing writes it now, and
|
|
87
|
+
// it stays so entries written before still read.
|
|
88
|
+
check: z
|
|
89
|
+
.string()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("The end-of-turn check that was still failing when the turn ended, by name. No longer written: checks run after work lands."),
|
|
87
92
|
});
|
|
88
93
|
export type UnfinishedWork = z.infer<typeof UnfinishedWorkSchema>;
|
|
94
|
+
// What the last turn showed of its own work, read off its tool calls and never asked of the model: the record a card
|
|
95
|
+
// badges instead of sending the turn back to prove anything. Checks run after the work lands (mainline.ts).
|
|
96
|
+
export const TurnProofSchema = z.object({
|
|
97
|
+
at: z.number().describe("When the turn that left this ended, in milliseconds."),
|
|
98
|
+
verification: z
|
|
99
|
+
.enum(["verified", "unproven", "failing", "no-code"])
|
|
100
|
+
.describe(
|
|
101
|
+
"Verified: a check it ran passed after its last edit to code. Failing: the last one it ran failed. Unproven: it changed code and ran nothing that checked it. No-code: it changed nothing a check could speak to.",
|
|
102
|
+
),
|
|
103
|
+
check: z.string().optional().describe("The command that spoke, when one did, so a targeted test is never read as the whole suite."),
|
|
104
|
+
unviewed: z
|
|
105
|
+
.number()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("How many rendered files it changed (pages, components, styles) without looking at the result afterwards. Absent when none."),
|
|
108
|
+
});
|
|
109
|
+
export type TurnProof = z.infer<typeof TurnProofSchema>;
|
|
89
110
|
// A landing's commit message: subject plus the two trailer sentences a changelog repo gets. One schema, not three
|
|
90
111
|
// duplicated fields, since both the live roster and the review carrier must read the same shape and can't disagree.
|
|
91
112
|
export const LandedMessageSchema = z.object({
|
|
@@ -329,11 +350,24 @@ export const AgentSummarySchema = z.object({
|
|
|
329
350
|
.object({
|
|
330
351
|
at: z.number().describe("When its last request touched the provider's prompt cache, in milliseconds."),
|
|
331
352
|
ttlMs: z.number().describe("How long that entry lives from `at`, in milliseconds."),
|
|
353
|
+
rollsAt: z
|
|
354
|
+
.number()
|
|
355
|
+
.optional()
|
|
356
|
+
.describe(
|
|
357
|
+
"When the date written into the agent's prompt next changes, in milliseconds (midnight where the agent runs). Past it the next turn sends a different prompt, so nothing kept before it is read again.",
|
|
358
|
+
),
|
|
359
|
+
keepable: z
|
|
360
|
+
.boolean()
|
|
361
|
+
.optional()
|
|
362
|
+
.describe("Whether this sandbox can keep this cache warm: it holds the last turn's exact request to replay, on a Claude subscription account."),
|
|
332
363
|
})
|
|
333
364
|
.optional()
|
|
334
365
|
.describe(
|
|
335
366
|
"When this conversation's prompt cache was last kept alive and how long it lasts, which together say when picking the conversation up stops being cheap. Absent when the provider publishes nothing to ground it on.",
|
|
336
367
|
),
|
|
368
|
+
keepWarm: KeepWarmSchema.optional().describe(
|
|
369
|
+
"Whether the sandbox is keeping this conversation's prompt cache warm while it sits idle, how that is going, or why it stopped. Absent when nobody asked for it.",
|
|
370
|
+
),
|
|
337
371
|
activity: AgentActivitySchema.optional().describe("What it is doing at this moment."),
|
|
338
372
|
// Beside `activity` because both answer "where is it", one in a sentence and one as a fraction. Live while a turn
|
|
339
373
|
// runs, and left standing after it, so a settled card still says how far the work got.
|
|
@@ -382,7 +416,11 @@ export const AgentSummarySchema = z.object({
|
|
|
382
416
|
// Beside `attention` since a reader asks both at a glance, but opposite in shape: a turn gone, not one still parked
|
|
383
417
|
// and waiting.
|
|
384
418
|
unfinished: UnfinishedWorkSchema.optional().describe(
|
|
385
|
-
"What its last turn left open: steps it never completed
|
|
419
|
+
"What its last turn left open: steps it never completed. Absent for a turn that finished what it started.",
|
|
420
|
+
),
|
|
421
|
+
// Written by the turn's own settle from its tool calls; nothing is asked of the model to earn or clear it.
|
|
422
|
+
proof: TurnProofSchema.optional().describe(
|
|
423
|
+
"What its last turn showed of its work: whether a check it ran passed after its last edit, and whether it looked at interface files it changed. Absent until a turn that edited or checked anything has ended.",
|
|
386
424
|
),
|
|
387
425
|
// Completed turns and lifetime tool calls, the card's msgs/tools counters.
|
|
388
426
|
turns: z.number().optional().describe("Turns it has finished."),
|
|
@@ -470,6 +508,27 @@ export const AgentSummarySchema = z.object({
|
|
|
470
508
|
startedAt: z.number().describe("When it started, in milliseconds."),
|
|
471
509
|
endedAt: z.number().optional().describe("When the command exited, in milliseconds. Absent while it runs."),
|
|
472
510
|
exitCode: z.number().int().optional().describe("The code it exited with. Absent while it runs, or when its exit left none."),
|
|
511
|
+
// What the turn that left it running decided about it (agent/tools/job-fates.ts): one of these, or none
|
|
512
|
+
// while its turn is still going.
|
|
513
|
+
watch: z
|
|
514
|
+
.string()
|
|
515
|
+
.optional()
|
|
516
|
+
.describe(
|
|
517
|
+
"The watch its exit wakes this conversation through. Present once the turn that left it running has ended and the conversation is waiting on it.",
|
|
518
|
+
),
|
|
519
|
+
handed: z
|
|
520
|
+
.boolean()
|
|
521
|
+
.optional()
|
|
522
|
+
.describe(
|
|
523
|
+
"Left running for the person: the turn's reply gave its address, so it outlives the turn, wakes nothing and is theirs to stop.",
|
|
524
|
+
),
|
|
525
|
+
ports: z.array(z.number().int()).optional().describe("The ports it was listening on when its turn ended."),
|
|
526
|
+
stoppedBy: z
|
|
527
|
+
.enum(["turn", "person", "agent"])
|
|
528
|
+
.optional()
|
|
529
|
+
.describe(
|
|
530
|
+
"Who stopped it: the sandbox, when the turn that used it ended without handing it over; a person; or the agent itself. Present from the moment the stop is asked. Absent for a job that exited by itself.",
|
|
531
|
+
),
|
|
473
532
|
}),
|
|
474
533
|
)
|
|
475
534
|
.optional()
|
|
@@ -500,6 +559,10 @@ export const AgentStopWatchingSchema = AgentIdSchema.extend({
|
|
|
500
559
|
watchId: z.string().optional().describe("Which watch to disarm. Absent disarms every watch this conversation is parked on."),
|
|
501
560
|
});
|
|
502
561
|
|
|
562
|
+
export const AgentStopJobSchema = AgentIdSchema.extend({
|
|
563
|
+
jobId: z.string().min(1).describe("Which of its background jobs, by the id its card and transcript row carry."),
|
|
564
|
+
});
|
|
565
|
+
|
|
503
566
|
// Pages a transcript: a read returns its most recent turns and where they start (`from`); handing that back as `before`
|
|
504
567
|
// asks for the page above. A stale cursor (a rewind, a fork) is clamped, never refused.
|
|
505
568
|
export const AgentTranscriptQuerySchema = AgentIdSchema.extend({
|
|
@@ -517,13 +580,6 @@ export const AgentToolChildrenQuerySchema = AgentIdSchema.extend({
|
|
|
517
580
|
toolId: z.string().min(1).describe("Which tool call, by the id its card carries."),
|
|
518
581
|
});
|
|
519
582
|
// Absent `ids` archives every archivable finished agent (the lane's "Clear"); unarchive always names its own ids.
|
|
520
|
-
export const AgentArchiveSchema = z.object({
|
|
521
|
-
ids: z
|
|
522
|
-
.array(z.string().min(1))
|
|
523
|
-
.max(500)
|
|
524
|
-
.optional()
|
|
525
|
-
.describe("Which conversations to put away. Leave it out for every finished one that can be archived right now."),
|
|
526
|
-
});
|
|
527
583
|
export const AgentIdsSchema = z.object({ ids: z.array(z.string().min(1)).min(1).max(500).describe("Which conversations.") });
|
|
528
584
|
// What actually moved, not the roster afterward: two archives finishing at once would otherwise let a slower snapshot
|
|
529
585
|
// resurrect what a faster one just filed away. Whole summaries, since the receiving surfaces (archive list, detail
|
|
@@ -354,6 +354,12 @@ export const CapabilityStatusSchema = z.object({
|
|
|
354
354
|
state: CapabilityStateSchema.describe("Whether it is live, still coming up, broken, or switched off."),
|
|
355
355
|
detail: z.string().optional().describe("What is wrong, in words a person can act on."),
|
|
356
356
|
code: z.string().optional().describe("A short marker for that reason, for anything deciding what to do about it."),
|
|
357
|
+
settling: z
|
|
358
|
+
.boolean()
|
|
359
|
+
.optional()
|
|
360
|
+
.describe(
|
|
361
|
+
"True while something under way will move this on its own: a start, a download, a pairing code waiting to be typed. Absent when only a person can move it, or a pushed change will say when it moved.",
|
|
362
|
+
),
|
|
357
363
|
});
|
|
358
364
|
export type CapabilityStatus = z.infer<typeof CapabilityStatusSchema>;
|
|
359
365
|
// The list row: manifest entry plus live status, secrets never returned. `secrets` names which config keys hold a
|
package/src/schemas/devices.ts
CHANGED
|
@@ -9,6 +9,14 @@ import { DEV_VERSION } from "../state/versions.js";
|
|
|
9
9
|
// One sandbox's resource share as docker currently enforces it, read off the container by the machine agent.
|
|
10
10
|
// `overlayRuntime` is the environment's locked demand; `hostRuntime` is the owner's addition; `privileged`/`gpu` are
|
|
11
11
|
// docker's enforced truth.
|
|
12
|
+
// Reshape request, turned into `ic sandbox reshape` flags: absent means leave it, `null` on the two caps means back to
|
|
13
|
+
// the default; at least one key must be set.
|
|
14
|
+
export const SandboxResourcesAskFieldsSchema = z.object({
|
|
15
|
+
memoryGib: z.int().positive().nullable().optional(),
|
|
16
|
+
cpus: z.int().positive().nullable().optional(),
|
|
17
|
+
privileged: z.boolean().optional(),
|
|
18
|
+
gpu: z.boolean().optional(),
|
|
19
|
+
});
|
|
12
20
|
export const SandboxResourcesSchema = z.object({
|
|
13
21
|
// The cgroup memory ceiling in bytes; absent when docker imposes none (the hosted shape).
|
|
14
22
|
memoryBytes: z.number().optional(),
|
|
@@ -18,17 +26,12 @@ export const SandboxResourcesSchema = z.object({
|
|
|
18
26
|
gpu: z.boolean(),
|
|
19
27
|
hostRuntime: z.array(z.string()),
|
|
20
28
|
overlayRuntime: z.array(z.string()),
|
|
29
|
+
// A reshape saved for the sandbox's next restart (`ic sandbox reshape --later`) and not yet in force: the next
|
|
30
|
+
// update, rollback, rebuild, reshape or Restart applies it. Absent when nothing is saved.
|
|
31
|
+
saved: SandboxResourcesAskFieldsSchema.optional(),
|
|
21
32
|
});
|
|
22
33
|
export type SandboxResources = z.infer<typeof SandboxResourcesSchema>;
|
|
23
34
|
|
|
24
|
-
// Reshape request, turned into `ic sandbox reshape` flags: absent means leave it, `null` on the two caps means back to
|
|
25
|
-
// the default; at least one key must be set.
|
|
26
|
-
export const SandboxResourcesAskFieldsSchema = z.object({
|
|
27
|
-
memoryGib: z.int().positive().nullable().optional(),
|
|
28
|
-
cpus: z.int().positive().nullable().optional(),
|
|
29
|
-
privileged: z.boolean().optional(),
|
|
30
|
-
gpu: z.boolean().optional(),
|
|
31
|
-
});
|
|
32
35
|
// Fields also exported separately for callers that compose them and enforce the at-least-one rule themselves.
|
|
33
36
|
export const SandboxResourcesAskSchema = SandboxResourcesAskFieldsSchema.refine((ask) => Object.values(ask).some((value) => value !== undefined), {
|
|
34
37
|
message: "a reshape must change at least one thing",
|
|
@@ -81,8 +84,12 @@ export const DeviceSandboxFlowSchema = z.object({
|
|
|
81
84
|
slug: z.string().min(1),
|
|
82
85
|
// Approved overlay's sha256, required only by `rebuild`; only content matching it is ever built.
|
|
83
86
|
hash: z.string().optional(),
|
|
84
|
-
// What `reshape` should change,
|
|
87
|
+
// What `reshape` should change, meaningless to the rest. Absent on a reshape applies what is saved for the next
|
|
88
|
+
// restart now; absent with `later` forgets what is saved.
|
|
85
89
|
resources: SandboxResourcesAskSchema.optional(),
|
|
90
|
+
// `reshape` only: save `resources` for the sandbox's next restart instead of restarting it now, replacing
|
|
91
|
+
// whatever was saved before (`ic sandbox reshape --later`).
|
|
92
|
+
later: z.boolean().optional(),
|
|
86
93
|
// `runner-up` only, daemon-filled, never by the caller: the browser never holds the pairing credential.
|
|
87
94
|
parentUrl: z.string().optional(),
|
|
88
95
|
pair: z.string().optional().meta({ secret: true }),
|
package/src/schemas/git/git.ts
CHANGED
|
@@ -450,6 +450,11 @@ export const AgentChangesSchema = z.object({
|
|
|
450
450
|
});
|
|
451
451
|
export type AgentChanges = z.infer<typeof AgentChangesSchema>;
|
|
452
452
|
|
|
453
|
+
// The review's `conflicts` alone, re-derived the same way. The whole review also takes a code-only line count per
|
|
454
|
+
// file, and on a branch of hundreds of files that count, not the conflict check, is what takes seconds.
|
|
455
|
+
export const AgentConflictsSchema = AgentChangesSchema.pick({ conflicts: true });
|
|
456
|
+
export type AgentConflicts = z.infer<typeof AgentConflictsSchema>;
|
|
457
|
+
|
|
453
458
|
// Names scratch exactly as `AgentChanges.scratch` lists it; anything else is refused, so a stale list cannot reach a
|
|
454
459
|
// file that was never scratch.
|
|
455
460
|
export const AgentScratchSchema = z.object({
|
package/src/schemas/hosts.ts
CHANGED
|
@@ -35,9 +35,17 @@ export const DeviceFactsSchema = z.object({
|
|
|
35
35
|
// the rest are other sandboxes' addresses, and a sandbox has no business learning its siblings'. Absent from an
|
|
36
36
|
// agent older than this field, which is why "none unreachable" is not the same value as "did not say".
|
|
37
37
|
links: z.object({ total: z.number(), unreachable: z.number(), unreachableSince: z.number().optional() }).optional(),
|
|
38
|
+
// What this agent understands beyond the ops every agent has, so a caller can refuse up front rather than send a
|
|
39
|
+
// field an older agent drops without a word. Absent from an agent older than this field: it supports none of them.
|
|
40
|
+
features: z.array(z.string()).optional(),
|
|
38
41
|
});
|
|
39
42
|
export type DeviceFacts = z.infer<typeof DeviceFactsSchema>;
|
|
40
43
|
|
|
44
|
+
// `reshape` with `later`: save the change for the next restart. An agent without it strips `later` from the request and
|
|
45
|
+
// reshapes NOW, restarting the sandbox and interrupting everyone in it, which is why a later-reshape is never sent to one.
|
|
46
|
+
export const DEVICE_FEATURE_RESHAPE_LATER = "reshape-later";
|
|
47
|
+
export const deviceSupports = (facts: Pick<DeviceFacts, "features"> | undefined, feature: string): boolean => facts?.features?.includes(feature) === true;
|
|
48
|
+
|
|
41
49
|
// Docker Desktop's own distros: `wsl -l -q` lists them like any other, and none ever runs an agent or holds a checkout.
|
|
42
50
|
export const WSL_SYSTEM_DISTROS: ReadonlySet<string> = new Set(["docker-desktop", "docker-desktop-data"]);
|
|
43
51
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { TranscriptFold } from "../text/transcript-fold.js";
|
|
2
|
+
import {
|
|
3
|
+
changedParts,
|
|
4
|
+
keepWarmCap,
|
|
5
|
+
keepWarmDueAt,
|
|
6
|
+
keepWarmHorizon,
|
|
7
|
+
keepWarmLeadMs,
|
|
8
|
+
keepWarmMaxRefreshes,
|
|
9
|
+
keepWarmRefreshes,
|
|
10
|
+
keptWarmLine,
|
|
11
|
+
} from "./keep-warm.js";
|
|
12
|
+
|
|
13
|
+
const MINUTE = 60_000;
|
|
14
|
+
const HOUR = 60 * MINUTE;
|
|
15
|
+
|
|
16
|
+
// The arithmetic both the daemon and every press read, so the price shown before a press is the price the daemon keeps.
|
|
17
|
+
describe("keep-warm economics", () => {
|
|
18
|
+
test("refreshes a fifth of an entry's life early, never under a minute or over ten", () => {
|
|
19
|
+
expect(keepWarmLeadMs(HOUR)).toBe(10 * MINUTE);
|
|
20
|
+
expect(keepWarmLeadMs(5 * MINUTE)).toBe(MINUTE);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("spends at most half of what a cold resume would rewrite", () => {
|
|
24
|
+
// A 1h rewrite costs 2x base input and a read 0.1x: nineteen reads break even, so nine.
|
|
25
|
+
expect(keepWarmMaxRefreshes(HOUR)).toBe(9);
|
|
26
|
+
// A 5m rewrite costs 1.25x: eleven reads break even, so five.
|
|
27
|
+
expect(keepWarmMaxRefreshes(5 * MINUTE)).toBe(5);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("counts the refreshes that keep an entry alive through the deadline", () => {
|
|
31
|
+
const cache = { at: 0, ttlMs: HOUR };
|
|
32
|
+
expect(keepWarmRefreshes(cache, HOUR)).toBe(0);
|
|
33
|
+
expect(keepWarmRefreshes(cache, 4 * HOUR)).toBe(4);
|
|
34
|
+
expect(keepWarmDueAt(cache)).toBe(50 * MINUTE);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("reaches no further than the horizon, or the date change when that comes first", () => {
|
|
38
|
+
const cache = { at: 0, ttlMs: HOUR };
|
|
39
|
+
expect(keepWarmHorizon(cache)).toBe(HOUR + 9 * 50 * MINUTE);
|
|
40
|
+
expect(keepWarmCap(cache, 3 * HOUR)).toBe(3 * HOUR);
|
|
41
|
+
expect(keepWarmCap({ at: 0, ttlMs: 5 * MINUTE }, undefined)).toBe(5 * MINUTE + 5 * 4 * MINUTE);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("names the parts two fingerprints disagree on", () => {
|
|
45
|
+
const before = { hash: "a", parts: { version: "1", model: "m", day: "d1" } };
|
|
46
|
+
expect(changedParts(before, { hash: "b", parts: { version: "2", model: "m", day: "d2" } })).toEqual(["day", "version"]);
|
|
47
|
+
expect(changedParts(before, before)).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("the receipt a kept conversation's turn leaves", () => {
|
|
52
|
+
test("says it picked up warm, and for how long it was kept", () => {
|
|
53
|
+
expect(keptWarmLine({ readTokens: 251_000, writtenTokens: 3_000, kept: { forMs: 3 * HOUR + 12 * MINUTE, refreshes: 4 } })).toBe(
|
|
54
|
+
"Picked up warm: 251k tokens read from the cache, kept warm for 3h 12m with 4 refreshes.",
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("says so plainly when the turn went cold anyway", () => {
|
|
59
|
+
expect(keptWarmLine({ readTokens: 0, writtenTokens: 250_000, kept: { forMs: 40 * MINUTE, refreshes: 1 } })).toContain("Picked up cold");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("is a notice row in the transcript, and nothing for a turn nobody kept", () => {
|
|
63
|
+
const fold = new TranscriptFold([]);
|
|
64
|
+
expect(fold.apply({ kind: "prompt_cache", readTokens: 10, writtenTokens: 200_000 })).toEqual([]);
|
|
65
|
+
fold.apply({ kind: "prompt_cache", readTokens: 251_000, writtenTokens: 3_000, kept: { forMs: HOUR, refreshes: 1 } });
|
|
66
|
+
expect(fold.rows.at(-1)).toMatchObject({ role: "notice", text: expect.stringContaining("Picked up warm") });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// keep-warm: holding an idle conversation's prompt cache open by re-reading it shortly before it expires.
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
const HOUR_MS = 3_600_000;
|
|
6
|
+
|
|
7
|
+
// Anthropic's price multipliers on base input: a read costs 0.1x, a write 2x under the 1h TTL and 1.25x under 5m.
|
|
8
|
+
const READ_COST = 0.1;
|
|
9
|
+
const writeCost = (ttlMs: number): number => (ttlMs >= HOUR_MS ? 2 : 1.25);
|
|
10
|
+
|
|
11
|
+
// A cache clock as the daemon publishes it: the last request that touched the entry, and the entry's lifetime.
|
|
12
|
+
export interface CacheClock {
|
|
13
|
+
readonly at: number;
|
|
14
|
+
readonly ttlMs: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** How long before expiry a refresh starts: a fifth of the entry's life, never under a minute or over ten. */
|
|
18
|
+
export const keepWarmLeadMs = (ttlMs: number): number => Math.min(10 * 60_000, Math.max(60_000, Math.round(ttlMs / 5)));
|
|
19
|
+
|
|
20
|
+
/** Refreshes one hold may spend: half of what a cold resume rewrites, the other half left for each refresh's own tail. */
|
|
21
|
+
export const keepWarmMaxRefreshes = (ttlMs: number): number => Math.floor((writeCost(ttlMs) - READ_COST) / READ_COST / 2);
|
|
22
|
+
|
|
23
|
+
/** How many refreshes keep an entry alive through `until`. */
|
|
24
|
+
export const keepWarmRefreshes = (cache: CacheClock, until: number): number => {
|
|
25
|
+
const beyond = until - (cache.at + cache.ttlMs);
|
|
26
|
+
return beyond <= 0 ? 0 : Math.ceil(beyond / (cache.ttlMs - keepWarmLeadMs(cache.ttlMs)));
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The latest instant a hold may reach, given the refreshes it already spent: past it, refreshing costs more than the cold resume it saves. */
|
|
30
|
+
export const keepWarmHorizon = (cache: CacheClock, spent = 0): number =>
|
|
31
|
+
cache.at + cache.ttlMs + Math.max(0, keepWarmMaxRefreshes(cache.ttlMs) - spent) * (cache.ttlMs - keepWarmLeadMs(cache.ttlMs));
|
|
32
|
+
|
|
33
|
+
/** When the next refresh is due for an entry last touched at `cache.at`. */
|
|
34
|
+
export const keepWarmDueAt = (cache: CacheClock): number => cache.at + cache.ttlMs - keepWarmLeadMs(cache.ttlMs);
|
|
35
|
+
|
|
36
|
+
/** The furthest `until` a hold can honestly promise: the economic horizon, or the date change, whichever comes first. */
|
|
37
|
+
export const keepWarmCap = (cache: CacheClock, rollsAt: number | undefined, spent = 0): number =>
|
|
38
|
+
rollsAt === undefined ? keepWarmHorizon(cache, spent) : Math.min(keepWarmHorizon(cache, spent), rollsAt);
|
|
39
|
+
|
|
40
|
+
// Why a hold ended before anyone picked the conversation up. `elapsed` is the one ending that is not a problem.
|
|
41
|
+
export const KeepWarmEndSchema = z.enum(["elapsed", "allowance", "limited", "changed", "rewrote", "cold", "failed", "midnight", "moved"]);
|
|
42
|
+
export type KeepWarmEnd = z.infer<typeof KeepWarmEndSchema>;
|
|
43
|
+
|
|
44
|
+
export const KeepWarmSchema = z.object({
|
|
45
|
+
since: z.number().describe("When keeping it warm started, in milliseconds."),
|
|
46
|
+
until: z
|
|
47
|
+
.number()
|
|
48
|
+
.describe(
|
|
49
|
+
"When it stops by itself, in milliseconds: the time asked for, shortened to what the sandbox can honestly keep, which is never past the point where refreshing costs more than re-reading, nor past the date change that rewrites the prompt.",
|
|
50
|
+
),
|
|
51
|
+
auto: z.boolean().optional().describe("Started by the sandbox-wide setting after a turn, rather than by a press on this conversation."),
|
|
52
|
+
refreshes: z.number().int().min(0).describe("Refreshes sent so far."),
|
|
53
|
+
readTokens: z
|
|
54
|
+
.number()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("How much the last refresh read back from the provider's cache, in tokens: the proof the cache was still there."),
|
|
57
|
+
ended: z
|
|
58
|
+
.object({
|
|
59
|
+
at: z.number().describe("When it stopped, in milliseconds."),
|
|
60
|
+
reason: KeepWarmEndSchema.describe(
|
|
61
|
+
"Why: `elapsed` the time asked for ran out; `allowance` the account reached the reserve kept for real work; `limited` the provider refused a refresh; `changed` what the next turn would send no longer matches the cache; `rewrote` a refresh found the cache already gone; `cold` it expired before a refresh could run; `failed` a refresh failed; `midnight` the date in the prompt changed; `moved` the conversation's session or account changed.",
|
|
62
|
+
),
|
|
63
|
+
detail: z.string().optional().describe("The specifics, when there are any: which parts of the prompt changed, or the failure's own words."),
|
|
64
|
+
})
|
|
65
|
+
.optional()
|
|
66
|
+
.describe("Why keeping it warm stopped before anyone picked the conversation up. Absent while it is still being kept."),
|
|
67
|
+
});
|
|
68
|
+
export type KeepWarm = z.infer<typeof KeepWarmSchema>;
|
|
69
|
+
|
|
70
|
+
export const AgentKeepWarmSchema = z.object({
|
|
71
|
+
id: z.string().min(1).describe("Which conversation."),
|
|
72
|
+
until: z
|
|
73
|
+
.number()
|
|
74
|
+
.nullable()
|
|
75
|
+
.describe(
|
|
76
|
+
"Keep its prompt cache warm until this instant, in milliseconds; shortened to what the sandbox can honestly keep. Null stops keeping it warm.",
|
|
77
|
+
),
|
|
78
|
+
});
|
|
79
|
+
export type AgentKeepWarm = z.infer<typeof AgentKeepWarmSchema>;
|
|
80
|
+
|
|
81
|
+
// What a turn's first request found in the cache, and how long it had been kept warm for it when it was.
|
|
82
|
+
export const PromptCacheOpeningSchema = z.object({
|
|
83
|
+
readTokens: z.number().describe("Tokens the turn's first request read from the provider's cache."),
|
|
84
|
+
writtenTokens: z.number().describe("Tokens it wrote to the cache, which is what it paid full price for."),
|
|
85
|
+
kept: z
|
|
86
|
+
.object({
|
|
87
|
+
forMs: z.number().describe("How long the cache had been kept warm for this turn, in milliseconds."),
|
|
88
|
+
refreshes: z.number().int().min(0).describe("How many refreshes that took."),
|
|
89
|
+
})
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Present when this turn picked up a conversation the sandbox had been keeping warm."),
|
|
92
|
+
});
|
|
93
|
+
export type PromptCacheOpening = z.infer<typeof PromptCacheOpeningSchema>;
|
|
94
|
+
|
|
95
|
+
const tokensLabel = (tokens: number): string => (tokens >= 1_000 ? `${Math.round(tokens / 1_000)}k` : String(tokens));
|
|
96
|
+
|
|
97
|
+
const spanLabel = (ms: number): string => {
|
|
98
|
+
const minutes = Math.max(1, Math.round(ms / 60_000));
|
|
99
|
+
const hours = Math.floor(minutes / 60);
|
|
100
|
+
return hours === 0 ? `${minutes}m` : `${hours}h ${minutes % 60}m`;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** The transcript's line for a turn that picked up a conversation kept warm for it; undefined for any other turn. */
|
|
104
|
+
export const keptWarmLine = (opening: PromptCacheOpening): string | undefined => {
|
|
105
|
+
const { kept } = opening;
|
|
106
|
+
if (kept === undefined) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const held = `kept warm for ${spanLabel(kept.forMs)} with ${kept.refreshes} ${kept.refreshes === 1 ? "refresh" : "refreshes"}`;
|
|
110
|
+
return opening.readTokens >= opening.writtenTokens
|
|
111
|
+
? `Picked up warm: ${tokensLabel(opening.readTokens)} tokens read from the cache, ${held}.`
|
|
112
|
+
: `Picked up cold despite being ${held}: ${tokensLabel(opening.writtenTokens)} tokens sent again, because this turn's prompt no longer matched the cache.`;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// A turn's prefix, part by part, so two turns that should share a cache can say which part stopped matching.
|
|
116
|
+
export const PromptFingerprintSchema = z.object({
|
|
117
|
+
hash: z.string().describe("One short hash over every part."),
|
|
118
|
+
parts: z.record(z.string(), z.string()).describe("Each part's own short hash or value, by name."),
|
|
119
|
+
});
|
|
120
|
+
export type PromptFingerprint = z.infer<typeof PromptFingerprintSchema>;
|
|
121
|
+
|
|
122
|
+
/** Which parts differ between two fingerprints, by name; empty when they match. */
|
|
123
|
+
export const changedParts = (before: PromptFingerprint, after: PromptFingerprint): string[] =>
|
|
124
|
+
[...new Set([...Object.keys(before.parts), ...Object.keys(after.parts)])].filter((name) => before.parts[name] !== after.parts[name]).toSorted();
|
package/src/schemas/metrics.ts
CHANGED
|
@@ -140,6 +140,8 @@ export const STORAGE_CLEANABILITY = {
|
|
|
140
140
|
other: "none",
|
|
141
141
|
// Git data and checkouts of repositories deleted from the workspace; nothing reads it again, but it may hold commits never pushed.
|
|
142
142
|
trash: "confirm",
|
|
143
|
+
// Transcripts as they were before the sandbox converted them to its current format; nothing reads them again.
|
|
144
|
+
backups: "confirm",
|
|
143
145
|
// Copies of the sandbox packed for download.
|
|
144
146
|
exports: "confirm",
|
|
145
147
|
// Files agents produced: generated images, reports, harness output.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { RunnerCommandFrameSchema, RunnerCommandSchema } from "../protocol/runner-protocol.js";
|
|
3
|
+
|
|
4
|
+
// OFFLOADED WORK, as the in-sandbox `offload-run` command and the editor see it (settings `offload`): a heavy line this
|
|
5
|
+
// sandbox hands a runner on one of the owner's machines instead of running it itself.
|
|
6
|
+
|
|
7
|
+
// Whether a runner can take a line right now, asked before the tree is snapshotted, so a machine that is asleep costs
|
|
8
|
+
// nothing. `name` is the machine the runner sits on, the word the owner picked it by.
|
|
9
|
+
export const OffloadTargetSchema = z.object({
|
|
10
|
+
runner: z.string().min(1),
|
|
11
|
+
name: z.string().min(1),
|
|
12
|
+
ready: z.boolean(),
|
|
13
|
+
// Why not, in the words the command prints before it runs the line here instead.
|
|
14
|
+
why: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
export type OffloadTarget = z.infer<typeof OffloadTargetSchema>;
|
|
17
|
+
|
|
18
|
+
export const OffloadRunSchema = RunnerCommandSchema.extend({ runner: z.string().min(1) });
|
|
19
|
+
export type OffloadRun = z.infer<typeof OffloadRunSchema>;
|
|
20
|
+
|
|
21
|
+
// What a run streams back: the runner's own frames, or one refusal when the runner could not take it after all (it went
|
|
22
|
+
// offline between the question and the run, or it predates offloading), which sends the line back here.
|
|
23
|
+
export const OffloadFrameSchema = z.union([...RunnerCommandFrameSchema.options, z.object({ kind: z.literal("refused"), why: z.string() })]);
|
|
24
|
+
export type OffloadFrame = z.infer<typeof OffloadFrameSchema>;
|
|
25
|
+
|
|
26
|
+
// One offloaded run as the runner card lists it: the recent ones, kept in memory, newest first.
|
|
27
|
+
export const OffloadRecordSchema = z.object({
|
|
28
|
+
runId: z.string(),
|
|
29
|
+
runner: z.string(),
|
|
30
|
+
name: z.string(),
|
|
31
|
+
label: z.string(),
|
|
32
|
+
// The line, cut to a readable length.
|
|
33
|
+
command: z.string(),
|
|
34
|
+
startedAt: z.number(),
|
|
35
|
+
endedAt: z.number().optional(),
|
|
36
|
+
code: z.number().int().optional(),
|
|
37
|
+
failure: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
export type OffloadRecord = z.infer<typeof OffloadRecordSchema>;
|
|
40
|
+
|
|
41
|
+
// One kind of heavy work the owner can send elsewhere: a heavy-command rule this sandbox queues (its id, as settings
|
|
42
|
+
// `offload.commands` keys it) and the pattern that sorts a line into it.
|
|
43
|
+
export const OffloadKindSchema = z.object({ id: z.string().min(1), pattern: z.string() });
|
|
44
|
+
export type OffloadKind = z.infer<typeof OffloadKindSchema>;
|
package/src/schemas/personas.ts
CHANGED
|
@@ -125,9 +125,9 @@ export const TURN_BRIEFING_NOTES: readonly TurnBriefingNote[] = [
|
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
127
|
id: "checks",
|
|
128
|
-
label: "
|
|
129
|
-
when: "First message
|
|
130
|
-
cost: "
|
|
128
|
+
label: "Checks after landing",
|
|
129
|
+
when: "First message and after a compaction, and again whenever the main tree's own check turns red or back to green: what runs after its work lands, and which failures the main tree already has.",
|
|
130
|
+
cost: "The agent does not know which failures are the main tree's own and not its doing, so it may spend a turn chasing one somebody else is already fixing.",
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
id: "dependencies",
|
package/src/schemas/ports.ts
CHANGED
|
@@ -45,6 +45,16 @@ export const PortSummarySchema = z.object({
|
|
|
45
45
|
.describe(
|
|
46
46
|
'The terminal it came from, to watch it in or stop it from. Absent when nothing in its ancestry is one, which is the honest "you cannot reach this from here".',
|
|
47
47
|
),
|
|
48
|
+
// A job an agent's turn left running for the person (AgentSummary.jobs[].handed); ports listed anywhere else stay
|
|
49
|
+
// unclaimed by any conversation.
|
|
50
|
+
job: z
|
|
51
|
+
.object({
|
|
52
|
+
conversationId: z.string().describe("The conversation whose turn left it running."),
|
|
53
|
+
jobId: z.string().describe("The job, as that conversation's card names it; stopping it stops this port."),
|
|
54
|
+
label: z.string().describe("What the job is, in the agent's own words when it gave any."),
|
|
55
|
+
})
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("The background job an agent left running for you on this port, when that is what answers here."),
|
|
48
58
|
forwarded: z.boolean().describe("Whether it is currently reachable from outside."),
|
|
49
59
|
// `https://port-<slot>-<sandboxId>.<zone>`.
|
|
50
60
|
previewUrl: z.string().optional().describe("Where to open it. Present only while forwarded, and only on a sandbox that has an outside address."),
|
|
@@ -24,11 +24,24 @@ export const UsageWindowSchema = z.object({
|
|
|
24
24
|
gates: WindowGatesSchema,
|
|
25
25
|
});
|
|
26
26
|
export type UsageWindow = z.infer<typeof UsageWindowSchema>;
|
|
27
|
+
// A failed re-read, whatever the cause: a 429 park, a 403 asking for verification, a 5xx, a timeout. Recorded as the fact
|
|
28
|
+
// "the last attempt failed", never inferred from a list of known failure kinds, so a surface that dates readings by
|
|
29
|
+
// age can leave out every reading that cannot move, including one whose failure nobody has seen before.
|
|
30
|
+
export const UsageUnreadSchema = z.object({
|
|
31
|
+
since: z.number().describe("When re-reading this account first failed, in milliseconds. It has failed on every attempt since."),
|
|
32
|
+
reason: z
|
|
33
|
+
.string()
|
|
34
|
+
.describe("Why, in the provider's own words where it gave some (\"Verify your account to continue.\"). Short enough to print; never a pasted response body."),
|
|
35
|
+
});
|
|
36
|
+
export type UsageUnread = z.infer<typeof UsageUnreadSchema>;
|
|
27
37
|
// Every window kept, not just the binding one, since which pool binds changes between turns. Utilization only climbs
|
|
28
38
|
// within a window, so a stale reading is still a valid floor. `measuredAt` is epoch ms — windows are seconds.
|
|
29
39
|
export const AccountUsageSchema = z.object({
|
|
30
40
|
windows: z.array(UsageWindowSchema),
|
|
31
41
|
measuredAt: z.number(),
|
|
42
|
+
unread: UsageUnreadSchema.optional().describe(
|
|
43
|
+
"Present while re-reading this account keeps failing: these windows are the last reading that succeeded, and `measuredAt` will not move until a read succeeds again.",
|
|
44
|
+
),
|
|
32
45
|
});
|
|
33
46
|
export type AccountUsage = z.infer<typeof AccountUsageSchema>;
|
|
34
47
|
// Anthropic's once-a-week reset of the session window only; the weekly allowance is untouched. The answer is entirely
|
|
@@ -22,6 +22,12 @@ export const OauthAccountSchema = z.object({
|
|
|
22
22
|
.optional()
|
|
23
23
|
.describe("Its stored credential can no longer be renewed and somebody has to sign in again. Absent means healthy, or not checked yet."),
|
|
24
24
|
detail: z.string().optional().describe("Why, in words a person can act on."),
|
|
25
|
+
seatRefusal: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe(
|
|
29
|
+
"Its organisation has switched it off for this harness (no seat): it still signs in and its plan limits may still read, but no turn can run on it until an admin gives access back. The provider's own sentence; cleared by the next turn that runs on it. Absent means no refusal is on file.",
|
|
30
|
+
),
|
|
25
31
|
usage: AccountUsageSchema.optional().describe(
|
|
26
32
|
"How full its plan limits were when last measured, so a picker can show what is left before committing work to it. Absent until a reading exists, which reads as unknown rather than as nothing left.",
|
|
27
33
|
),
|
|
@@ -103,6 +103,13 @@ export const UsageTurnSchema = z.object({
|
|
|
103
103
|
compactions: z.number().optional(),
|
|
104
104
|
contextTokens: z.number().optional(),
|
|
105
105
|
contextWindow: z.number().optional(),
|
|
106
|
+
// Absent is a turn; `keep-warm` is a cache refresh sent while the conversation sat idle, billed but not a turn.
|
|
107
|
+
purpose: z.enum(["keep-warm"]).optional().describe("What the row is when it is not a turn: `keep-warm` is a cache refresh sent while the conversation sat idle."),
|
|
108
|
+
// The first request alone: a warm resume reads nearly the whole context here, a cold one writes it.
|
|
109
|
+
openingCacheReadTokens: z.number().optional().describe("Tokens the turn's first request read from the provider's cache."),
|
|
110
|
+
openingCacheCreationTokens: z.number().optional().describe("Tokens the turn's first request wrote to the provider's cache."),
|
|
111
|
+
// Equal on two turns means the prefix both sent was assembled from the same parts (PromptFingerprintSchema).
|
|
112
|
+
promptFingerprint: z.string().optional().describe("A short hash over the parts of the prompt a cache is keyed on; a change between two turns names why the second could not reuse the first's cache."),
|
|
106
113
|
// This turn's model was chosen by the Auto judge reading the conversation's opening message, not picked by hand.
|
|
107
114
|
// Marked on that one turn only; a later row of the same conversation naming a different model is the user
|
|
108
115
|
// overruling it, which is the escalation rate this feature has to be able to answer for.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import { dropAll, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
3
|
+
import type { RuleSchema } from "./settings.js";
|
|
4
|
+
|
|
5
|
+
// The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
|
|
6
|
+
// SandboxSettingsSchema. Two documents carry this shape, the daemon's settings.json and a sandbox.toml's [settings]
|
|
7
|
+
// table, and both read through this one list; it sits beside the schema so that neither reader has to reach into the
|
|
8
|
+
// other for it. A name retired here stays retired: no later setting may take it to mean something else.
|
|
9
|
+
|
|
10
|
+
// Rules at a moment or with an action a later version withdrew (the `push.starting` moment, `instruct` actions, three
|
|
11
|
+
// built-in checks) can never run, and one of them made the whole file unreadable, every setting in it with it.
|
|
12
|
+
const RETIRED_MOMENTS: ReadonlySet<unknown> = new Set(["push.starting"]);
|
|
13
|
+
const RETIRED_ACTIONS: ReadonlySet<unknown> = new Set(["instruct"]);
|
|
14
|
+
const RETIRED_BUILTINS: ReadonlySet<unknown> = new Set(["verify-edits", "verify-removals", "verify-tests"]);
|
|
15
|
+
|
|
16
|
+
const isRetiredRule = (rule: unknown): boolean => {
|
|
17
|
+
if (!isJsonObject(rule)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const action = isJsonObject(rule["action"]) ? rule["action"] : {};
|
|
21
|
+
return RETIRED_MOMENTS.has(rule["moment"]) || RETIRED_ACTIONS.has(action["kind"]) || (action["kind"] === "builtin" && RETIRED_BUILTINS.has(action["name"]));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const holdsRetiredRule = (value: unknown): value is readonly unknown[] => Array.isArray(value) && value.some(isRetiredRule);
|
|
25
|
+
|
|
26
|
+
export const SETTINGS_HISTORY = [
|
|
27
|
+
// Every setting a release since 2026-08-10 had and this one does not (read off the contract lock's history):
|
|
28
|
+
// retired, so passthrough stops carrying them, a definition naming one still applies, and no later setting may
|
|
29
|
+
// take one of these names to mean something else.
|
|
30
|
+
...dropAll([
|
|
31
|
+
"agentRunEffort",
|
|
32
|
+
"agentRunModel",
|
|
33
|
+
"agentRunModels",
|
|
34
|
+
"autoFastModels",
|
|
35
|
+
"autoTier",
|
|
36
|
+
"autoTierEagerness",
|
|
37
|
+
"commandJudgeModels",
|
|
38
|
+
"commandRules",
|
|
39
|
+
"contextShelf",
|
|
40
|
+
"dependencyFreshness",
|
|
41
|
+
"explainCommands",
|
|
42
|
+
"iqContext",
|
|
43
|
+
"iqContextHoldout",
|
|
44
|
+
"moveAfterLimit",
|
|
45
|
+
"quickModel",
|
|
46
|
+
"resumeAfterLimit",
|
|
47
|
+
"resumeAfterOutage",
|
|
48
|
+
"terseHoldout",
|
|
49
|
+
"terseOutput",
|
|
50
|
+
"testFaultDetection",
|
|
51
|
+
]),
|
|
52
|
+
retype(
|
|
53
|
+
"rules",
|
|
54
|
+
holdsRetiredRule,
|
|
55
|
+
// What survives the filter is every rule this version can read; the parse after the chain says so.
|
|
56
|
+
(rules) => rules.filter((rule) => !isRetiredRule(rule)) as z.input<typeof RuleSchema>[],
|
|
57
|
+
"drops rules at a withdrawn moment or with a withdrawn action (push.starting, instruct, three built-in checks)",
|
|
58
|
+
),
|
|
59
|
+
// suggest, the old default, becomes the new default: routing on.
|
|
60
|
+
mapValue("personaRouting", { off: false, suggest: true, auto: true }),
|
|
61
|
+
] as const;
|