Commit Graph

7 Commits

Author SHA1 Message Date
Severian 7569fb8e82 Diagnose setup by what is certain, and deliver errors the way the client asked
Review of the branch found the persona diagnosis was mostly truncation
wearing a name tag. The <.> marker sits at the start of the system
message and the persona marker after the card, so a card Janitor cut
short arrives with the prompt present and the persona gone — and was
told its persona was wrong, with truncation as an afterthought. The
original message was only ever about context size, which is the best
evidence there is about what actually happens.

diagnoseSetup() now says only what the markers make certain. A missing
<.> is a missing prompt, since truncation cannot remove the first three
characters. A persona marker that is present but not "." is a wrong
description, quoted back so there is nothing to guess. A persona marker
that is gone, or cut off mid-tag, leads with truncation and mentions the
persona second.

The error path decided "streaming?" from the Accept header while the
success path used body.stream, so a client that sent stream:true without
that header got JSON back for every error and never displayed it. The
flag is hoisted above the try; the header is only a fallback for when
the body could not be read.

Token search is restricted to after the greeting, in order. A card built
from a sucker-proxied chat could carry an expired token in its text, and
a lookup on it would fail forever. Conversation order also means the
token a chat was started with always wins over one pasted later. The
system fallback now takes a role-less first message only; a request with
roles but no system turn is reported as such rather than misdiagnosed as
a missing prompt. The echo feature reads the parser's lastUser instead of
its own case-sensitive lookup. "Nothing was saved" is dropped from the
unknown-error message, since it is not guaranteed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
2026-09-11 06:46:52 +08:00
Severian a8b3669c17 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
2026-09-11 06:06:36 +08:00
Severian bf74a85fec 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
2026-09-11 06:00:23 +08:00
Severian 94d43dbb9d Say what actually went wrong
Every failure produced the same reply: read the directions, and check your
context size. For a card Janitor truncated 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 sent them back to
directions they had already followed.

Failures that can be explained now explain themselves: no card in the
request, no greeting in the chat, a card that arrived truncated (which
keeps the context-size advice, since that is where it belongs), and a
request with nothing in it. Anything else is a bug nobody anticipated, so
the reply says as much, says it is not the user's fault, and asks them to
DM @lyseverian on Discord — an unexplained failure nobody reports never
gets fixed. Internals are never echoed back.

SuckerError is recognised by name as well as by identity, because Next can
bundle a module into more than one chunk and a mismatched class would
quietly send every explained error down the unknown-bug path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
2026-09-11 05:15:01 +08:00
Severian b479460804 Harden the parsing, and mark unread changelog entries
Stress-testing the beta with request shapes we had not tried turned up two
more ways to hit the same class of bug. Roles were compared exactly, so a
client sending "Assistant" instead of "assistant" fell straight into the
dingus error. And content was assumed to be a string, so OpenAI's multipart
array form did the same. Roles are now compared case-insensitively and
content is flattened to text first; messages with no usable text are
skipped rather than mistaken for a slot. The slots are plain strings now,
so no caller can reach past them to a raw field again.

Separately: the Changelog button carries a dot when the current release
has not been opened yet, on the desktop button, the mobile menu item, and
the hamburger that hides it. The marker is the version in localStorage, so
every release brings it back on its own and reading it once clears it. The
version is a single constant now rather than being written in two places.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
2026-09-11 05:07:19 +08:00
Severian e7c9adbcc5 Keep multi-turn conversations linked to their card
The first pass read the name from the last user turn and the token from
the last assistant turn. Both are wrong once a conversation continues.

Only sucker's *first* reply carries a conversation token — later ones say
"Character updated" or "unchanged" with no token in them — so reading the
last assistant turn lost the link three turns in and would have created a
duplicate card on every message. tokenCandidates() now searches the whole
conversation, still preferring whatever the user just typed, since pasting
a token is deliberate.

The name is the first user turn after the greeting, not the last one. That
is what the old code read at messages[3], and unlike the last turn it stays
put as messages are appended.

Tests cover a three-turn conversation for both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
2026-09-11 04:18:26 +08:00
Severian 11c244fdd2 Read the request by role, not by position
Some users' JanitorAI sends a "." user turn between the card and the
greeting; others' does not. Sucker read fixed indices, so on the shorter
shape every slot was off by one: first_mes was set to the name line, and
the name slot did not exist at all. That TypeError was caught by the
handler and reported as the "you dingus, read the directions" message,
which blamed the user for a shape they never chose.

src/lib/messages.ts now resolves the slots by role instead — the card is
the system turn, the greeting is the first assistant turn, the user's
latest input is the last user turn, and sucker's own previous reply is the
last assistant turn. All four hold in both shapes and in a continued
conversation, so the conversation token is found in either as well.

Extraction now throws a description of what is missing rather than a
TypeError, and tests/messages.test.ts pins both shapes. The tests need no
framework: `npm test` runs node's built-in runner.

Existing cards are unaffected: the update path already discards the
extracted name and keeps the original.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
2026-09-11 04:14:04 +08:00