mirror of
https://github.com/severian-dev/sucker.severian.dev.git
synced 2026-10-18 01:20:29 +00:00
Stop giving advice for situations nobody can cause
The previous commit split failures into explained and unexplained, but the split that matters is fixable and not. A request with no card, no greeting, or nothing in it cannot come out of Janitor. Telling someone to "open the chat until the bot's first message shows" is the dingus message wearing a new coat: instructions for something they did not do and cannot undo. Only the truncated card is a user's to fix, so only it is shown verbatim, with the context-size advice. The rest are ImpossibleRequest now: the reply says plainly that it is not their fault and there is nothing for them to fix, and asks them to report it, carrying a short tag so the report says which assumption broke rather than expecting the user to explain it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
This commit is contained in:
+55
-34
@@ -3,19 +3,26 @@
|
||||
*
|
||||
* Every failure used to produce one message: "You dingus, read the directions
|
||||
* on sucker before trying again", with a note about context size. For a
|
||||
* truncated card that was fair. For a request shape the user never chose, or
|
||||
* for a bug in sucker, it blamed them for something they could not have done
|
||||
* anything about — and told them to check directions they had already followed.
|
||||
* truncated card that was fair. For anything else it blamed the user for
|
||||
* something they could not have done anything about.
|
||||
*
|
||||
* So: anything thrown as a SuckerError is written for the person who hit it
|
||||
* and is shown to them verbatim. Anything else is a bug nobody anticipated,
|
||||
* and the reply says so and asks them to report it, because an unexplained
|
||||
* failure that nobody hears about never gets fixed.
|
||||
* The split that matters is not explained-vs-unexplained, it is
|
||||
* **fixable-vs-not**:
|
||||
*
|
||||
* - A truncated card is the one failure a user can act on: raise the
|
||||
* context size. That message is shown verbatim.
|
||||
* - Everything else — a request with no card in it, no greeting, nothing
|
||||
* at all — cannot happen through Janitor, and if it does the user has no
|
||||
* way to cause or fix it. Telling them to "open the chat properly" would
|
||||
* be the old message wearing a new coat. Those say plainly that it is not
|
||||
* their fault and ask them to report it, carrying a short tag so the
|
||||
* report identifies which assumption broke.
|
||||
*/
|
||||
|
||||
/** Where to send someone when sucker is at fault. */
|
||||
export const CONTACT = "DM @lyseverian on Discord";
|
||||
|
||||
/** A failure the person who hit it can actually do something about. */
|
||||
export class SuckerError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
@@ -24,29 +31,52 @@ export class SuckerError extends Error {
|
||||
}
|
||||
|
||||
/**
|
||||
* `instanceof` is checked by name as well: Next can bundle the same module
|
||||
* into more than one chunk, and a class identity that differs between them
|
||||
* would quietly send every explained error down the "unknown bug" path.
|
||||
* A request shaped in a way Janitor does not produce. Not the user's fault
|
||||
* and not theirs to fix; `detail` exists so their report says which
|
||||
* assumption broke, not so they can act on it.
|
||||
*/
|
||||
export class ImpossibleRequest extends Error {
|
||||
readonly detail: string;
|
||||
constructor(detail: string) {
|
||||
super(`impossible request: ${detail}`);
|
||||
this.name = "ImpossibleRequest";
|
||||
this.detail = detail;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Both are checked by name as well as by identity: Next can bundle a module
|
||||
* into more than one chunk, and a mismatched class would quietly send every
|
||||
* classified error down the wrong path.
|
||||
*/
|
||||
const named = (error: unknown, name: string): boolean =>
|
||||
typeof error === "object" &&
|
||||
error !== null &&
|
||||
(error as { name?: unknown }).name === name;
|
||||
|
||||
export function isSuckerError(error: unknown): error is SuckerError {
|
||||
return (
|
||||
error instanceof SuckerError ||
|
||||
(typeof error === "object" &&
|
||||
error !== null &&
|
||||
(error as { name?: unknown }).name === "SuckerError")
|
||||
);
|
||||
return error instanceof SuckerError || named(error, "SuckerError");
|
||||
}
|
||||
|
||||
export function isImpossibleRequest(error: unknown): error is ImpossibleRequest {
|
||||
return error instanceof ImpossibleRequest || named(error, "ImpossibleRequest");
|
||||
}
|
||||
|
||||
export function userFacingMessage(error: unknown): string {
|
||||
if (isSuckerError(error)) return error.message;
|
||||
const detail = isImpossibleRequest(error) ? ` (${error.detail})` : "";
|
||||
return (
|
||||
"Something went wrong that sucker has no explanation for, and nothing was " +
|
||||
`saved. This one is not your fault — please ${CONTACT} and say what you ` +
|
||||
"were pulling, because unreported ones do not get fixed."
|
||||
`saved. This one is not your fault and there is nothing for you to fix — ` +
|
||||
`please ${CONTACT} and say what you were pulling, because unreported ones ` +
|
||||
`do not get fixed.${detail}`
|
||||
);
|
||||
}
|
||||
|
||||
/** The card arrived without the markers sucker needs to read it. */
|
||||
/**
|
||||
* The one failure a user can act on: Janitor cut the card short because the
|
||||
* bot is bigger than their context size, so the markers sucker reads are gone.
|
||||
*/
|
||||
export const TRUNCATED_CARD = new SuckerError(
|
||||
"The card came through incomplete — the markers sucker looks for are not in " +
|
||||
"it. That usually means the bot is larger than your context size and " +
|
||||
@@ -54,18 +84,9 @@ export const TRUNCATED_CARD = new SuckerError(
|
||||
`try again. If it is already generous, re-read the directions on sucker, and ${CONTACT} if it still will not go.`
|
||||
);
|
||||
|
||||
export const NO_CARD = new SuckerError(
|
||||
"There is no character card in that request — sucker could not find a " +
|
||||
"system message in it. Point sucker at a chat with a bot in it, not an " +
|
||||
"empty one, and try again."
|
||||
);
|
||||
|
||||
export const NO_GREETING = new SuckerError(
|
||||
"That chat has no message from the bot yet, so there is nothing to pull. " +
|
||||
"Open the chat until the bot's first message shows, then send again."
|
||||
);
|
||||
|
||||
export const TOO_FEW_MESSAGES = new SuckerError(
|
||||
"That request barely had anything in it — sucker needs a chat with a bot " +
|
||||
"and at least one message in it. Open the chat properly and try again."
|
||||
);
|
||||
// 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
|
||||
// which assumption broke.
|
||||
export const NO_CARD = new ImpossibleRequest("no system message in the request");
|
||||
export const NO_GREETING = new ImpossibleRequest("no greeting in the request");
|
||||
export const TOO_FEW_MESSAGES = new ImpossibleRequest("fewer than two messages in the request");
|
||||
|
||||
Reference in New Issue
Block a user