mirror of
https://github.com/severian-dev/sucker.severian.dev.git
synced 2026-10-18 01:20:29 +00:00
Name the setting that did not come through
The instructions mark exactly two steps REQUIRED, and the two markers sucker looks for are those two steps. So a failure can say which one is missing — the custom prompt, the persona, or both — instead of sending someone back to re-read a page they have already followed. Truncation causes the same symptom, so it is still named, but second: it only applies once the settings really are set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
|||||||
NO_CARD,
|
NO_CARD,
|
||||||
NO_GREETING,
|
NO_GREETING,
|
||||||
TOO_FEW_MESSAGES,
|
TOO_FEW_MESSAGES,
|
||||||
TRUNCATED_CARD,
|
missingSetup,
|
||||||
userFacingMessage,
|
userFacingMessage,
|
||||||
} from "@/lib/errors";
|
} from "@/lib/errors";
|
||||||
|
|
||||||
@@ -165,11 +165,12 @@ function extractCardData(messages: Message[]): ExtractedCard {
|
|||||||
`Name extraction: user="${nameFromUser}", inferred="${inferredName}", tracking="${cleanTrackingName}", display="${displayName}"`
|
`Name extraction: user="${nameFromUser}", inferred="${inferredName}", tracking="${cleanTrackingName}", display="${displayName}"`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
// Each marker is one of the two REQUIRED steps in the instructions, so the
|
||||||
!content.includes("<.>") ||
|
// error can name the one that did not arrive.
|
||||||
!content.includes("<UserPersona>.</UserPersona>")
|
const hasPrompt = content.includes("<.>");
|
||||||
) {
|
const hasPersona = content.includes("<UserPersona>.</UserPersona>");
|
||||||
throw TRUNCATED_CARD;
|
if (!hasPrompt || !hasPersona) {
|
||||||
|
throw missingSetup(hasPrompt, hasPersona);
|
||||||
}
|
}
|
||||||
|
|
||||||
content = content.replace("<.>", "");
|
content = content.replace("<.>", "");
|
||||||
|
|||||||
+29
-8
@@ -74,15 +74,36 @@ export function userFacingMessage(error: unknown): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The one failure a user can act on: Janitor cut the card short because the
|
* The two markers sucker reads map one-to-one onto the two steps the
|
||||||
* bot is bigger than their context size, so the markers sucker reads are gone.
|
* instructions mark REQUIRED, so a missing one names the setting that is not
|
||||||
|
* coming through rather than sending people back to re-read everything.
|
||||||
|
*
|
||||||
|
* Truncation produces the same symptom — Janitor cuts a card bigger than the
|
||||||
|
* context size and takes the markers with it — so it is named too, second,
|
||||||
|
* because it only applies once the settings really are right.
|
||||||
*/
|
*/
|
||||||
export const TRUNCATED_CARD = new SuckerError(
|
export function missingSetup(hasPrompt: boolean, hasPersona: boolean): SuckerError {
|
||||||
"The card came through incomplete — the markers sucker looks for are not in " +
|
const PROMPT = "your custom prompt is not coming through as <.> — set it in your API settings";
|
||||||
"it. That usually means the bot is larger than your context size and " +
|
const PERSONA =
|
||||||
"Janitor cut it short, so raise Context Size in Generation Settings and " +
|
"your persona is not coming through — it needs the name {{user}} and a description of just a dot";
|
||||||
`try again. If it is already generous, re-read the directions on sucker, and ${CONTACT} if it still will not go.`
|
|
||||||
);
|
let what: string;
|
||||||
|
if (!hasPrompt && !hasPersona) {
|
||||||
|
what =
|
||||||
|
"neither of the two required settings came through: the custom prompt " +
|
||||||
|
"has to be <.>, and your persona needs the name {{user}} with a " +
|
||||||
|
"description of just a dot";
|
||||||
|
} else {
|
||||||
|
what = hasPrompt ? PERSONA : PROMPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SuckerError(
|
||||||
|
`Sucker could not read that card: ${what}. Save it, then refresh Janitor — ` +
|
||||||
|
"the settings page, not sucker. If it is already set that way, the bot is " +
|
||||||
|
"probably bigger than your context size and Janitor cut the card short, " +
|
||||||
|
`so raise Context Size in Generation Settings. Still stuck: ${CONTACT}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// None of these can happen through Janitor. They exist so that if one ever
|
// None of these can happen through Janitor. They exist so that if one ever
|
||||||
// does, the reply is honest about whose problem it is and the report says
|
// does, the reply is honest about whose problem it is and the report says
|
||||||
|
|||||||
+22
-6
@@ -10,16 +10,32 @@ import {
|
|||||||
NO_GREETING,
|
NO_GREETING,
|
||||||
SuckerError,
|
SuckerError,
|
||||||
TOO_FEW_MESSAGES,
|
TOO_FEW_MESSAGES,
|
||||||
TRUNCATED_CARD,
|
missingSetup,
|
||||||
isImpossibleRequest,
|
isImpossibleRequest,
|
||||||
isSuckerError,
|
isSuckerError,
|
||||||
userFacingMessage,
|
userFacingMessage,
|
||||||
} from "../src/lib/errors.ts";
|
} from "../src/lib/errors.ts";
|
||||||
|
|
||||||
test("the one fixable failure is shown as written, with the advice that fixes it", () => {
|
test("a missing setting is named, not left to the reader to find", () => {
|
||||||
assert.equal(userFacingMessage(TRUNCATED_CARD), TRUNCATED_CARD.message);
|
const noPrompt = missingSetup(false, true).message;
|
||||||
assert.match(TRUNCATED_CARD.message, /Context Size/);
|
assert.match(noPrompt, /custom prompt is not coming through as <\.>/);
|
||||||
assert.ok(TRUNCATED_CARD.message.includes(CONTACT));
|
assert.ok(!/persona needs the name/.test(noPrompt), "must not blame the setting that is fine");
|
||||||
|
|
||||||
|
const noPersona = missingSetup(true, false).message;
|
||||||
|
assert.match(noPersona, /persona is not coming through/);
|
||||||
|
assert.ok(!/custom prompt is not coming through/.test(noPersona));
|
||||||
|
|
||||||
|
const neither = missingSetup(false, false).message;
|
||||||
|
assert.match(neither, /neither of the two required settings/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("every setup message still offers truncation as the second cause", () => {
|
||||||
|
for (const m of [missingSetup(false, true), missingSetup(true, false), missingSetup(false, false)]) {
|
||||||
|
assert.match(m.message, /Context Size/);
|
||||||
|
assert.ok(m.message.includes(CONTACT));
|
||||||
|
// Shown verbatim: this is the one class of failure a user can act on.
|
||||||
|
assert.equal(userFacingMessage(m), m.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shapes Janitor cannot produce never tell the user to fix them", () => {
|
test("shapes Janitor cannot produce never tell the user to fix them", () => {
|
||||||
@@ -47,7 +63,7 @@ test("a genuine bug reports itself without leaking internals", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("nobody is called a dingus", () => {
|
test("nobody is called a dingus", () => {
|
||||||
for (const e of [TRUNCATED_CARD, NO_CARD, NO_GREETING, TOO_FEW_MESSAGES, new Error("x")]) {
|
for (const e of [missingSetup(false, false), NO_CARD, NO_GREETING, TOO_FEW_MESSAGES, new Error("x")]) {
|
||||||
assert.ok(!/dingus/i.test(userFacingMessage(e)));
|
assert.ok(!/dingus/i.test(userFacingMessage(e)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user