Answer budget within the context window; enforced reviews of completed todos
Context window - Requests asked for the model's full max_output, but providers count input + output against the window: long chats with large-output models (DeepSeek V4.1 Flash, 384k) failed with "input too long" before automatic compaction (85 %) kicked in. max_tokens is now what the window leaves (estimated input = last usage + messages since), the chat is compacted when less than 32k would remain for an answer, and a context-overflow error compacts and retries once. - ModelConfig.max_input (catalogue limit.input, editable in the provider settings) lowers the compaction threshold for models with an input limit. Enforced reviews (modes with refine.rounds > 0, refine.after_todos default on) - Every todo marked completed is reviewed against the work since the last review; with findings it is reopened and the feedback goes to the agent (max. rounds per todo). Passed reviews leave a visible note. - Ending the turn with open todos sends the agent back to work while it makes progress (max. 10 reminders in a row without progress); such modes may take up to 2,000 steps per turn. Reaching the step limit is reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ca14ba3338
commit
bd896794fd
14 files changed
+386
-27
No files matched your search
@@ -115,7 +115,36 @@ function BackgroundResult({ text }: { text: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
const REFINE_RE = /^<refine-feedback round="([^"]*)" reviewer="([^"]*)">\n?([\s\S]*?)\n?<\/refine-feedback>/;
|
||||
const REFINE_RE = /^<refine-feedback round="([^"]*)" reviewer="([^"]*)"(?: todos="\d+")?>\n?([\s\S]*?)\n?<\/refine-feedback>/;
|
||||
/** Enforced reviews: a completed todo passed, or a reminder about open todos. */
|
||||
const NOTE_RE = /^<(refine-approved|todo-reminder)(?: reviewer="([^"]*)")?(?: open="\d+")?>\n?([\s\S]*?)\n?<\/(?:refine-approved|todo-reminder)>/;
|
||||
/** Messages from the harness shown as cards instead of user bubbles. */
|
||||
const isHarnessNote = (text: string) => BG_RE.test(text) || REFINE_RE.test(text) || NOTE_RE.test(text);
|
||||
|
||||
/** A todo passed its review, or the agent was sent back to its open todos. */
|
||||
function HarnessNote({ text }: { text: string }) {
|
||||
const t = useT();
|
||||
const m = text.match(NOTE_RE);
|
||||
if (!m) return null;
|
||||
const [, kind, reviewer, body] = m;
|
||||
const items = body.split("\n").map((l) => l.replace(/^- /, "").trim()).filter(Boolean);
|
||||
const approved = kind === "refine-approved";
|
||||
return (
|
||||
<div className={`harness-note ${approved ? "ok" : ""}`}>
|
||||
<Icon name={approved ? "check" : "refresh"} />
|
||||
<span className="grow">
|
||||
<b>{t(approved ? "chat.reviewPassed" : "chat.todoReminder", { n: items.length })}</b> {items.join(" · ")}
|
||||
</span>
|
||||
{reviewer && <span className="chip">{reviewer}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function HarnessMessage({ text }: { text: string }) {
|
||||
if (REFINE_RE.test(text)) return <RefineFeedback text={text} />;
|
||||
if (NOTE_RE.test(text)) return <HarnessNote text={text} />;
|
||||
return <BackgroundResult text={text} />;
|
||||
}
|
||||
|
||||
/** Findings of the review loop, sent back to the agent. */
|
||||
function RefineFeedback({ text }: { text: string }) {
|
||||
@@ -469,14 +498,14 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
);
|
||||
}
|
||||
const allTexts = m.parts.filter((p) => p.type === "text") as { text: string }[];
|
||||
const background = allTexts.filter((p) => BG_RE.test(p.text) || REFINE_RE.test(p.text));
|
||||
const texts = allTexts.filter((p) => !BG_RE.test(p.text) && !REFINE_RE.test(p.text));
|
||||
const background = allTexts.filter((p) => isHarnessNote(p.text));
|
||||
const texts = allTexts.filter((p) => !isHarnessNote(p.text));
|
||||
const imgs = m.parts.filter((p) => p.type === "image") as { image: ImageData }[];
|
||||
if (texts.length === 0 && imgs.length === 0) {
|
||||
return background.length ? (
|
||||
<div key={m.id} className="msg">
|
||||
{background.map((p, i) => (
|
||||
REFINE_RE.test(p.text) ? <RefineFeedback key={i} text={p.text} /> : <BackgroundResult key={i} text={p.text} />
|
||||
<HarnessMessage key={i} text={p.text} />
|
||||
))}
|
||||
</div>
|
||||
) : null;
|
||||
@@ -484,7 +513,7 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
return (
|
||||
<div key={m.id} className="msg">
|
||||
{background.map((p, i) => (
|
||||
REFINE_RE.test(p.text) ? <RefineFeedback key={i} text={p.text} /> : <BackgroundResult key={i} text={p.text} />
|
||||
<HarnessMessage key={i} text={p.text} />
|
||||
))}
|
||||
<div className="msg-user">
|
||||
{!busy && !readOnly && (
|
||||
|
||||
@@ -376,6 +376,7 @@ interface ModelRow {
|
||||
name?: string;
|
||||
context?: number;
|
||||
max_output?: number;
|
||||
max_input?: number;
|
||||
reasoning?: boolean;
|
||||
vision?: boolean;
|
||||
effort?: string;
|
||||
@@ -454,6 +455,7 @@ function ProviderEditor({ name, p, reload, status }: { name: string; p: Json; re
|
||||
price_multipliers: f.price_multipliers ?? m.price_multipliers,
|
||||
context: m.context ?? f.context,
|
||||
max_output: m.max_output ?? f.max_output,
|
||||
max_input: m.max_input ?? f.max_input ?? undefined,
|
||||
vision: m.vision || !!f.vision,
|
||||
reasoning: m.reasoning || !!f.reasoning,
|
||||
};
|
||||
@@ -616,6 +618,7 @@ function ProviderEditor({ name, p, reload, status }: { name: string; p: Json; re
|
||||
<th>ID</th>
|
||||
<th>{t("settings.context")}</th>
|
||||
<th>{t("settings.maxOutput")}</th>
|
||||
<th title={t("settings.maxInputHint")}>{t("settings.maxInput")}</th>
|
||||
<th title={t("settings.reasoning")}><Icon name="brain" /></th>
|
||||
<th title={t("settings.vision")}><Icon name="eye" /></th>
|
||||
<th>{t("settings.effort")}</th>
|
||||
@@ -640,6 +643,9 @@ function ProviderEditor({ name, p, reload, status }: { name: string; p: Json; re
|
||||
<td>
|
||||
<input type="number" value={m.max_output ?? ""} onChange={(e) => setModel(i, { max_output: num(e.target.value) })} />
|
||||
</td>
|
||||
<td>
|
||||
<input type="number" value={m.max_input ?? ""} placeholder="–" onChange={(e) => setModel(i, { max_input: num(e.target.value) })} />
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" checked={!!m.reasoning} onChange={(e) => setModel(i, { reasoning: e.target.checked })} />
|
||||
</td>
|
||||
|
||||
@@ -61,6 +61,7 @@ function AgentRow({ a, raw, tools, onSaved }: { a: AgentInfo; raw: any; tools: T
|
||||
refine_rounds: String(raw?.refine?.rounds ?? a.refine?.rounds ?? 0),
|
||||
refine_model: raw?.refine?.model ?? "",
|
||||
refine_criteria: raw?.refine?.criteria ?? "",
|
||||
refine_after_todos: (raw?.refine?.after_todos ?? a.refine?.after_todos ?? true) as boolean,
|
||||
});
|
||||
const builtin = a.source === "builtin" || (a.source === "config" && !raw?.prompt && ["build", "plan", "craft", "chat", "general", "explore"].includes(a.name));
|
||||
|
||||
@@ -74,7 +75,7 @@ function AgentRow({ a, raw, tools, onSaved }: { a: AgentInfo; raw: any; tools: T
|
||||
const rounds = Math.max(0, Math.min(10, Number(form.refine_rounds) || 0));
|
||||
patches.push({
|
||||
path: [...base, "refine"],
|
||||
value: rounds || form.refine_model || form.refine_criteria ? { rounds, model: form.refine_model || null, criteria: form.refine_criteria || null } : null,
|
||||
value: rounds || form.refine_model || form.refine_criteria ? { rounds, model: form.refine_model || null, criteria: form.refine_criteria || null, after_todos: form.refine_after_todos } : null,
|
||||
});
|
||||
}
|
||||
if (!builtin || form.description) patches.push({ path: [...base, "description"], value: form.description || null });
|
||||
@@ -158,6 +159,11 @@ function AgentRow({ a, raw, tools, onSaved }: { a: AgentInfo; raw: any; tools: T
|
||||
<label>{t("agents.refineCriteria")}</label>
|
||||
<textarea rows={3} value={form.refine_criteria} onChange={(e) => setForm({ ...form, refine_criteria: e.target.value })} />
|
||||
<div className="hint">{t("agents.refineCriteriaHint")}</div>
|
||||
<label className="check">
|
||||
<input type="checkbox" checked={form.refine_after_todos} onChange={(e) => setForm({ ...form, refine_after_todos: e.target.checked })} />
|
||||
{t("agents.refineAfterTodos")}
|
||||
</label>
|
||||
<div className="hint">{t("agents.refineAfterTodosHint")}</div>
|
||||
</>
|
||||
)}
|
||||
<label>{t("agents.description")}</label>
|
||||
|
||||
@@ -334,6 +334,10 @@ export const de: Messages = {
|
||||
"wizard.idea.cadBox": "Eine Box mit Schnappdeckel, Innenmaß 80 × 50 × 30 mm",
|
||||
"wizard.idea.cadBracket": "Eine Wandhalterung für ein 25-mm-Rohr mit zwei M4-Schraublöchern",
|
||||
"activity.reviewing": "Ergebnis wird geprüft (Runde {round})…",
|
||||
"chat.reviewPassed": "Review bestanden:",
|
||||
"chat.todoReminder": "Weiterarbeiten – {n} offene Todos:",
|
||||
"agents.refineAfterTodos": "Jedes erledigte Todo prüfen und bis zum Ende der Liste arbeiten",
|
||||
"agents.refineAfterTodosHint": "Jedes Todo, das der Agent als erledigt markiert, wird geprüft; bei Befunden wird es wieder geöffnet. Beendet er den Turn mit offenen Todos, arbeitet er weiter (für unbeaufsichtigte Läufe, z. B. über Nacht). Aus: nur ein Review am Ende des Turns.",
|
||||
"chat.reviewRound": "Review {round}: Änderungen nötig",
|
||||
"agents.refine": "Review-Schleife",
|
||||
"agents.refineRounds": "Review-Runden pro Aufgabe",
|
||||
@@ -757,6 +761,8 @@ export const de: Messages = {
|
||||
"settings.pricesHint": "Preise pro 1 Mio. Tokens in der eingestellten Währung. Cache-Preise fallen auf den Input-Preis zurück.",
|
||||
"settings.modelsEmptyHint": "Keine Modelle eingetragen: Sie werden automatisch beim Provider abgefragt.",
|
||||
"settings.context": "Kontext",
|
||||
"settings.maxInput": "Max. Eingabe",
|
||||
"settings.maxInputHint": "Eingabe-Limit, falls der Anbieter eines unterhalb des Kontextfensters setzt (leer: das Kontextfenster). Der Chat wird zusammengefasst, bevor die Eingabe es erreicht.",
|
||||
"settings.maxOutput": "Max. Ausgabe",
|
||||
"settings.reasoning": "Reasoning",
|
||||
"settings.vision": "Bilder",
|
||||
|
||||
@@ -333,6 +333,10 @@ export const en = {
|
||||
"wizard.idea.cadBox": "A box with a snap-on lid, inner size 80 × 50 × 30 mm",
|
||||
"wizard.idea.cadBracket": "A wall bracket for a 25 mm pipe with two M4 screw holes",
|
||||
"activity.reviewing": "Reviewing the result (round {round})…",
|
||||
"chat.reviewPassed": "Review passed:",
|
||||
"chat.todoReminder": "Back to work – {n} open todos:",
|
||||
"agents.refineAfterTodos": "Review every completed todo and work until the list is done",
|
||||
"agents.refineAfterTodosHint": "Each todo the agent marks completed is reviewed; with findings it is reopened. Ending the turn with open todos sends the agent back to work (for unattended runs, e.g. overnight). Off: only one review at the end of the turn.",
|
||||
"chat.reviewRound": "Review {round}: changes requested",
|
||||
"agents.refine": "Review loop",
|
||||
"agents.refineRounds": "Review rounds per task",
|
||||
@@ -756,6 +760,8 @@ export const en = {
|
||||
"settings.pricesHint": "Prices per 1M tokens in the configured currency. Cache prices default to the input price.",
|
||||
"settings.modelsEmptyHint": "No models listed: they are discovered automatically from the provider.",
|
||||
"settings.context": "Context",
|
||||
"settings.maxInput": "Max input",
|
||||
"settings.maxInputHint": "Input limit, if the provider sets one below the context window (empty: the context window). The chat is compacted before the input reaches it.",
|
||||
"settings.maxOutput": "Max output",
|
||||
"settings.reasoning": "Reasoning",
|
||||
"settings.vision": "Vision",
|
||||
|
||||
@@ -1068,6 +1068,13 @@ button.session-group:hover {
|
||||
.form label {
|
||||
color: var(--fg2);
|
||||
}
|
||||
.form label.check {
|
||||
grid-column: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--fg1);
|
||||
}
|
||||
.form .hint {
|
||||
grid-column: 2;
|
||||
font-size: 12px;
|
||||
@@ -3164,6 +3171,28 @@ button.danger-solid {
|
||||
.refine-card summary .icon {
|
||||
color: var(--purple);
|
||||
}
|
||||
.harness-note {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin: 6px 0;
|
||||
padding: 6px 10px;
|
||||
border-left: 3px solid var(--blue);
|
||||
border-radius: 4px;
|
||||
background: var(--bg1);
|
||||
font-size: 12.5px;
|
||||
color: var(--fg2);
|
||||
}
|
||||
.harness-note .icon {
|
||||
color: var(--blue);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.harness-note.ok {
|
||||
border-left-color: var(--green);
|
||||
}
|
||||
.harness-note.ok .icon {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
/* --- automations ---------------------------------------------------------------- */
|
||||
.job.off {
|
||||
|
||||
+2
-1
@@ -141,6 +141,7 @@ export interface ModelInfo {
|
||||
name: string;
|
||||
context?: number;
|
||||
max_output?: number | null;
|
||||
max_input?: number | null;
|
||||
reasoning: boolean;
|
||||
vision: boolean;
|
||||
effort?: string | null;
|
||||
@@ -166,7 +167,7 @@ export interface AgentInfo {
|
||||
tools?: string[] | null;
|
||||
source: "builtin" | "config" | "file" | "personal";
|
||||
max_parallel?: number | null;
|
||||
refine?: { rounds: number; model?: string | null; criteria?: string | null } | null;
|
||||
refine?: { rounds: number; model?: string | null; criteria?: string | null; after_todos?: boolean } | null;
|
||||
/** Source file of Markdown agents. */
|
||||
path?: string | null;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
* under "unreleased"; scripts/release.sh renames the key to the release version.
|
||||
*/
|
||||
export const WHATS_NEW: Record<string, { en: string[]; de: string[] }> = {
|
||||
unreleased: {
|
||||
en: [
|
||||
"Modes with a review: every completed todo is reviewed, and the agent keeps working until the todo list is done – for overnight runs",
|
||||
"Long chats are compacted in time instead of failing with “input too long”",
|
||||
],
|
||||
de: [
|
||||
"Modi mit Review: jedes erledigte Todo wird geprüft, und der Agent arbeitet weiter, bis die Liste abgehakt ist – für Läufe über Nacht",
|
||||
"Lange Chats werden rechtzeitig zusammengefasst, statt mit „Eingabe zu lang“ abzubrechen",
|
||||
],
|
||||
},
|
||||
"202611-00017": {
|
||||
en: ["Your messages are shown as Markdown; the eye button next to the text field previews it before sending"],
|
||||
de: ["Deine Nachrichten werden als Markdown dargestellt; das Augen-Symbol neben dem Textfeld zeigt vor dem Senden eine Vorschau"],
|
||||
|
||||
Reference in new issue
Block a user