Skip to content

Sync fork to upstream v2.0.5 (603 commits), preserve fork customizations - #4

Merged
LukaTheHero merged 604 commits into
mainfrom
chore/sync-upstream-v2.0.5
Jun 27, 2026
Merged

Sync fork to upstream v2.0.5 (603 commits), preserve fork customizations#4
LukaTheHero merged 604 commits into
mainfrom
chore/sync-upstream-v2.0.5

Conversation

@LukaTheHero

Copy link
Copy Markdown
Owner

Why

This fork had drifted to 6 commits ahead / 603 behind Pasta-Devs upstream. This brings it up to upstream release v2.0.5, pulling in 603 commits of upstream fixes and features while keeping every fork-specific customization intact.

What

Merges upstream v2.0.5 (Pasta-Devs/main, c391711b) into the fork's main (e32a18b3). They diverged at c49f1d95, so conflicts were limited to the five files the fork had customized. Resolution:

  • Claude (Subscription) model list (shared/constants/model-lists.ts): kept the fork's curated CLAUDE_SUBSCRIPTION_MODELS — current models first then (Legacy), with a base (200k) entry plus a separate [1m] (1M-context) entry per 1M-capable model, mirroring the Claude Code menu — and added Fable 5 (base + [1m]).
  • [1m] 1M-context handling (server/.../claude-subscription.provider.ts): re-applied onto upstream's refactored chat(). The [1m] suffix isn't a real model id, so it is stripped to a plain id and the SDK context-1m-2025-08-07 beta is enabled; the suffixed id is kept only for context-fit sizing so the 1M window is used.
  • Multi-open persistent group folders (PersonasPanel.tsx, CharactersPanel.tsx, ui.store.ts): re-applied on top of upstream's rewritten panels. Folder open/closed state now lives in the UI store (expandedPersonaGroupIds / expandedCharacterGroupIds — multiple folders open at once, synced + persisted) instead of upstream's single-open local state, while keeping upstream's folder-filter auto-expand.
  • "Hide grouped outside folders", synthetic "Ungrouped" folder removal, comment-on-member-row: upstream had independently reimplemented these, so the fork now takes upstream's newer version rather than its own earlier implementation.

The merge commit preserves both parents. pnpm check (typecheck + lint + build) passes; the server builds and boots after a dependency install.

Note for updating installs

Upstream v2.0.5 adds new @earendil-works/* dependencies, so any install updated to this revision needs a pnpm install before building.

Gunterlie and others added 30 commits June 20, 2026 23:33
minP (Min-P sampling) was a first-class generation parameter — defined in the
schema, shipped in the default preset, and parsed/salvaged from stored params —
but it was never placed on ChatOptions, so it silently did nothing on every
provider.

- Add `minP` to ChatOptions.
- Thread it through the generate route: preset baseline, connection/chat
  overrides, and the game-mode reset, then into all three ChatOptions sites.
- Map it in the OpenAI provider as `min_p`, gated by the same shouldSendTopK()
  check as top_k (sent only to backends known to accept the non-standard
  sampler — the bundled local model — with customParameters as the escape hatch
  for other backends). minP=0 means "disabled" and is omitted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…only keys

Follow-up to the Tier-1 fixes, from the CodeRabbit review on Pasta-Devs#2669:

- Filter out empty/whitespace-only stop sequences (the schema allows them) and
  trim each entry before dispatch, so an empty stop sequence can't 400 Anthropic
  or abort OpenAI generation.
- Trim the API key before the auth-header guard in all three providers, so a
  whitespace-only key is treated as blank (no header) instead of emitting a
  malformed `Bearer   ` / `x-api-key:   `.

Skipped the suggested ENCRYPTION_KEY set-but-empty change: getEncryptionKeyOverride()
already routes through normalizeEnvValue(), which collapses empty/whitespace to null,
so a set-but-empty value is treated as unset — consistent with every other env override.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Security: remove autonomousIntent from the generate schema so
arbitrary client text can no longer be injected into system prompts;
the server now derives the intent hint from the validated
autonomousIntentKey via getIntentHint().

Correctness fixes:
- Daily budget key now uses local date instead of UTC, aligning with
  schedule day boundaries on non-UTC deployments
- /busy-delay route was passing full chat.metadata to
  parseConversationStatusOverrides instead of the nested field
- mergeConversationStatusOverrides now merges objects instead of
  replacing them, so sibling character overrides survive partial patches
- Malformed expiresAt values (NaN) are now treated as expired/null
- Midnight-crossing schedule blocks are now correctly identified as
  both previous and next in getAdjacentBlocks
- good_night intent hint now requires the offline block to start within
  90 minutes, preventing premature triggering
- clearGenerationInProgress called on all scheduler error paths
- Scheduler re-reads status and returns early if character is offline
- Schedule generation invalidates chatKeys.list() in addition to detail
- saveOverride returns boolean; UI resets only on success and clears
  pendingStatuses after a successful refetch
- triggerAutonomousGeneration passes skipPresenceDelay=true after the
  busy-delay has already been waited out client-side
- Background autonomous hook passes skipPresenceDelay=true to /generate
- /status slash command fallback removed that mapped display names to
  fake character IDs
- AutonomousCheckResult.reason union extended with missing literals
- DelayedCharacterStatus aliased to shared ConversationPresenceStatus

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…end (Pasta-Devs#2673)

appendGenerationTailMessages trimmed the prefill only for its presence guard but
pushed the untrimmed value as the final assistant message. Anthropic's Messages
API rejects a trailing assistant turn that ends in whitespace (HTTP 400), which
users see as a content block/refusal — and prefills ending in "\n" or a space are
common, so presets that work in SillyTavern fail here.

Push assistantPrefill.trimEnd() (strip only the trailing edge Anthropic rejects;
leading whitespace is preserved; the user-facing prefill rendering is separate).
Mirror the same in the dry-run preview so it matches what is sent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the blocking `await new Promise(setTimeout)` with a per-chat
timer (scheduleDelayedGeneration). evaluateChat now returns immediately
after scheduling the delay, allowing the poll loop to proceed to the
next chat. The runningChats slot is held by the timer callback and
released in its finally block, preserving the existing re-entry guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cter record

conversationStatus/conversationActivity were written to the shared
character extensions via chars.update(), causing one chat's manual
presence override (e.g. dnd in chat A) to bleed into the sidebar and
input area of unrelated chats.

Replace the per-character write with a single patchMetadata call that
stores {status, activity} per character in a new chat-scoped
conversationCharacterStatuses field. The sidebar now reads per-chat
status from each chat row's own metadata, and ChatArea overlays it into
the characterMap so input-area dots also reflect the correct chat.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o-grouped agents (Pasta-Devs#2680)

executeAgent returned the tool path with `return executeAgentWithTools(...)`
and no `await`, so a rethrow from the tool loop (executeToolCall throwing)
escaped this function's own try/catch and surfaced as a rejected promise
instead of a failed AgentResult.

In the pipeline, executeGroup joins batch + tool agents for the same
provider+model group with Promise.all. One tool agent's rejection rejected
that Promise.all, and executePhase then overwrote EVERY agent in the group
— including co-grouped batch agents (tracker, expression) and sibling tool
agents that already succeeded — with success:false error results.

Adding `await` lets the existing catch convert the rethrow into a failed
AgentResult for only the failing agent, preserving per-agent isolation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n at serialization (Pasta-Devs#2679)

The prefill-only fix (Pasta-Devs#2673 / Pasta-Devs#2674) trims in the prefill helper, so it
covers only the case where a prefill is configured. With no prefill, the
trailing assistant message can still end in whitespace and reach Anthropic
untrimmed → HTTP 400 ("final assistant content must not end with trailing
whitespace"), surfacing as a refusal/block. The trailing turn can be a
depth-injected role:assistant section, or — under markdown/none wrap — the
last chat-history assistant message.

Centralize the trim at the point of serialization in the Anthropic provider:
a new trimTrailingAssistantWhitespace() strips the trailing edge of the last
assistant message, applied in chat() (after mergeConsecutiveMessages) and in
chatComplete() (before formatAnthropicPayloadMessages). This covers every
trailing-assistant surface — prefill, depth-injected, merged, history — in
one place, generalizing the prefill fix. Leading whitespace and non-trailing
turns are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The seat row rendered seats in fixed seating order, so it never
reflected the play direction and a Reverse left the row unchanged.
Reorder the chips into the actual turn queue — current player first,
then each following seat in the live direction — so a Reverse visibly
flips the queue and the human persona sits in its real turn slot. Mark
the on-deck seat with a small "next" cue. Derived client-side from the
direction + currentSeatId already in publicView; no engine change.
Characters can now start a game of UNO themselves instead of the user
relying on a narrow client-side trigger phrase or manually running /uno.

- New `[uno]` conversation command (the agreement gate): advertised to
  characters in conversation mode when UNO is available, no game is
  active, and another player is present. A character includes `[uno]`
  only when the user proposes a game and they're willing; declining is
  simply not emitting it. On emit, the server deals a game (human +
  every non-offline character; the agreeing character is always seated)
  and, if the opening card lands the turn on a bot, advances bot seats
  so it resolves to the human's turn. The board hydrates over the
  existing uno_state_patch SSE — no client change.
- Scene exclusion: the `[scene]` guidance now explicitly tells
  characters NOT to start a roleplay scene for UNO / cards / board
  games, so "let's play uno" no longer fires a scene by mistake.
- Schedule integration: characters seated at an active turn-game are
  treated as present at the table, bypassing the schedule offline-skip
  and dnd/idle typing delay for that chat so a seated player never goes
  silent or replies minutes late mid-game.

The new command defaults on and is toggleable like the other
conversation commands.
An active UNO game drives generation through its bot-turn requests, which
take the chat's single generation lock. The autonomous-message scheduler
had no turn-game awareness, so every poll it could fire a normal
conversation generation for the chat — seizing that lock and making the
next bot-turn request 409 ("a generation is already in progress"). Since
the client's bot-turn fire never retries, one collision left the game
stuck on a bot's turn.

Gate /autonomous/check on getActiveTurnGame (mirroring the existing
scene-active guard) so autonomous returns shouldTrigger:false while a
game is running. This covers both the server scheduler and any client
autonomous trigger, since both consult /autonomous/check first.
…-agnostic bot runner, generic board SSE)

- UnoBoard: submit plays/jump-ins by the clicked card's stable id instead of
  by (color, value) face, and auto-declare UNO on the play that takes the human
  from two cards to one. Previously the human was instantly catchable in the gap
  before they could click "Call UNO!" while the bot loop fired; bots already
  auto-declare for themselves, so this just makes the human symmetric.
- turn-game-bot-runner: stop filtering tool calls against the UNO-specific
  UNO_TOOL_NAMES list. Try engine.parseToolCall on each tool call and take the
  first the engine recognizes, so any future engine's tools work without editing
  the runner. Drops the now-unused shared import.
- Generalize the board SSE event from uno_state_patch to turn_game_state_patch
  (server emitters + client handlers) so a second game reuses the same channel
  rather than needing a new event. Each game still ships its own client board.
…ensive parse, real rewind)

- Move/state routes no longer trust a client-supplied seatId. The acting seat
  and the view's perspective are always the chat's inferred human seat, so a
  crafted request can neither drive a bot/other seat nor request another seat's
  perspective and reveal its hidden hand. Bot seats stay driven by the
  server-authoritative bot loop. (The service keeps its seatId param for the bot
  runner.) Client stops sending the now-ignored seatId.

- The [uno] command parses chat characterIds defensively (try/catch + string
  filter, mirroring resolveSeats), so malformed metadata can't throw and
  silently abort starting a game.

- Per-message engine snapshots are now actually used for selection: loadGame
  resolves the visible message anchor and selects via getForGeneration, so a
  message edit / branch / regeneration rewinds the authoritative game state
  (previously it always read the latest snapshot). Human moves anchor their
  snapshot to the latest visible assistant message so a fresh move isn't masked
  by an older one, and regenerating the following turn resumes from the
  post-move state. getActiveTurnGame stays on the latest snapshot (rewind is
  irrelevant there) to keep the autonomous/bot-loop hot paths cheap.
…ne parity

Addresses the third Copilot review pass on the turn-game (UNO) PR:

- 7-0 jump-in: stop enumerating (and now explicitly reject server-side) a
  non-winning 7 played via jump-in. A jump-in can't carry a swap target, so
  such a move would otherwise be advertised as legal yet rejected on apply with
  a misleading "choose a player to swap" error. It stays legal only when the 7
  is the winning (last) card, which needs no swap.
- Move schema: add a refine so `play` / `jump_in` payloads must carry either
  `cardId` or a `card` face, matching the documented move contract instead of
  accepting `{ type: "play" }` with neither.
- jump_in tool: restrict the value enum to non-wild faces (the engine always
  rejects wild jump-ins), so the model can't propose moves that can never be legal.
- Swipe deletion: prune and shift `game_engine_state` snapshots alongside
  `game_state_snapshots` when a message swipe is removed, mirroring the existing
  RPG-snapshot cleanup. Without this, turn-game snapshots orphaned and their
  swipe indices drifted, breaking the visible-anchor rewind and growing the
  table unbounded. Removes the now-redundant unused deleteForMessageSwipes helper.
…tion; fix stale SSE comment

Copilot review (PR Pasta-Devs#2721, 4th pass):

- turn-games.routes.ts: /move and /start could mutate game_engine_state
  while /api/generate is running the bot-turn loop for the same chat.
  Both paths read-modify-write the latest snapshot across awaits, so
  interleaving races the runner and loses updates (worst for out-of-turn
  moves like jump_in/call_out). Reject with 409 when app.activeGenerations
  holds an entry for the chat, mirroring the existing guard in
  generate.routes.ts and the autonomous scheduler. /resign is left ungated
  on purpose — blocking a quit mid-turn is worse UX; the proper fix there
  (abort-then-resign) is out of scope for this change.

- UnoBoard.tsx: header comment named the SSE event uno_state_patch, but
  the server emits and the store listens for turn_game_state_patch. Fixed
  the stale comment so it matches the implementation.
…election

Copilot review (PR Pasta-Devs#2721, 5th pass):

- ConversationView.tsx: the UnoSetup modal was mounted unkeyed, so its
  internal selection / house-rule useState persisted across chat switches.
  Stale selected ids from a previous chat inflated botCount and could deal
  an empty botCharacterIds list. Key it by chatId so it remounts and resets
  on a chat switch, matching the ConversationInput pattern just below.

- UnoSetup.tsx: toggleSelected's setState callback ignored its `current`
  argument and closed over the render-time selectedIds, so batched toggles
  could clobber each other. Derive `next` from the updater's `current`
  state, falling back to the chat's character ids only before the user's
  first selection.
…-facing draw_penalty

Copilot review (PR Pasta-Devs#2721, 6th pass):

- turn-game-bot-runner.service.ts: the move-selection and narration system
  prompts hard-coded UNO concepts ("friendly game of UNO", "Wild Draw Four",
  "declared_color", "playing UNO", bans on naming colors/values), which
  would mis-instruct bots for any future engine and undercut the
  game-agnostic design. Base the prompts on engine.label and defer
  game-specific requirements to the tool descriptions and the engine's own
  describeForModel(...).instructions. The wild/declared_color requirement
  already lives in the play_card tool schema, so nothing is lost. Narration
  guidance is now generic: don't reveal your hand/hidden info, don't recite
  the board, don't explain rules.

- uno/engine.ts: describeForModel(...).legalMoves advertised
  { type: "draw_penalty" }, but no model-facing tool produces that move —
  the bot resolves a pending +2/+4 with draw_card (-> { type: "draw" },
  which handleDraw already routes to the penalty). Map draw_penalty to draw
  in the model-facing legal-move list so it matches the available tools and
  the instruction text, while keeping the distinct draw_penalty action for
  the UI via publicView's yourActions.canDrawPenalty and the engine's
  internal apply/fallback paths.
…NO command toggle

Copilot review (PR Pasta-Devs#2721, 7th pass):

- game-engine-state.storage.ts: create() only deduped when input.messageId
  was truthy, so repeated writes with messageId === "" (the live anchor —
  e.g. the bot-turn persistence-failure fallback) accumulated unbounded
  rows for (chatId, "", swipeIndex). Delete the prior (chatId, messageId,
  swipeIndex) row unconditionally; the tuple still isolates real-message
  snapshots and distinct swipes, so single-live-row semantics are kept.

- UnoBoard.tsx: icon-only controls lacked accessible names (relied on
  title, which screen readers don't reliably announce). Added aria-labels
  to the End-game button, the wild/7-swap close button, and the color-swatch
  picker buttons, plus a descriptive aria-label on each hand-card button via
  a new cardName() helper (e.g. "Red 7, playable", "Wild Draw Four").

- ChatSettingsDrawer.tsx / ChatSetupWizard.tsx: "uno" is in
  CONVERSATION_COMMAND_KEYS but both command-toggle option arrays omitted
  it, so users couldn't enable/disable UNO from the UI. Added a "UNO" entry
  to both (the only two surfaces that enumerate command toggles).
The bot-turn announce emit used sendSseEvent, which writes directly to
the SSE stream and throws if the client has disconnected — aborting the
bot-turn loop mid-game. The marker is non-critical, so use the swallowing
trySendSseEvent variant, matching the other emits in this file.
…cap observable

startTurnGame now rejects unless chat.mode === "conversation". The whole
feature (the [uno] command, bot-turn generate gating, board UI) is
conversation-only, so a start in another mode would create a game that can't
be advanced and would leave stale game_engine_state rows behind.

The bot-turn loop also no longer stalls silently when it exhausts MAX_BOT_TURNS:
a non-advancing engine now breaks early with a warning (a fallback move should
always change the board), and reaching the cap with a bot still on seat logs a
warning. We deliberately do not auto-retrigger another bot generate there — that
would reintroduce the unbounded cross-request loop the cap exists to bound; the
human resumes the game with any move, which re-fires the loop via the normal path.
…legal draw

pickFallback's out-of-turn branch returned { type: "draw" } as its final
fallback, but draw is never legal when it isn't the seat's turn — only
call_out, declare_uno, and jump_in are. That violated the pickFallbackMove
contract (return a deterministic legal move) and could spin/fail a bot loop if
a future caller ever requested a fallback for an interruptible seat. Now return
the first legal enumerated interrupt (preferring the passive catch/declare, then
jump-in); the empty case is a documented defensive sentinel that production —
which only requests a fallback for the seat on turn — never reaches.
…draw play; 404 for moves with no active game

After a voluntary draw the only legal play is the drawn card, but the board
summary only listed the full hand, so the model could not tell which card it
drew (especially with duplicate faces) and would propose an illegal move and
fall back. The board now names the drawn card and the instructions point at it.
The engine also resolves a post-draw play against the drawn card's id directly,
so a hand with duplicate faces no longer rejects a correct play; an arg-less
play_card after drawing now plays that card.

Separately, POST /:chatId/move returned 409 for every non-ok outcome, including
'No active game in this chat.' — a missing resource. It now returns 404 for that
case (matching GET /state) and keeps 409 only for an illegal move against a live
game, which still carries the legal moves to retry with.
Sulphuratum and others added 20 commits June 23, 2026 11:43
Local models should get the tools block
…s-chat

Refine chat sidebar presence rendering
Add help output and additional update-entry and delete-entry helper functions
…licts

# Conflicts:
#	packages/client/src/components/panels/SettingsPanel.tsx
…le-picker

fix(client): resolve silent theme and extension import failures on Gtk/Firefox
Merge v2.0.3 stabilization fixes, docs, and release metadata into staging.
…revent premature cleanup on Gtk/Firefox (Pasta-Devs#2837)

Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
Merge staging into main for the v2.0.3 release.
* Prepare v2.0.4 hotfixes

* Serialize Professor Mari connection saves

* Flush held thinking stream before rewrite cache

* Fix slash continue targeting

* fix(summary): restore auto-hide for roleplay rolling summaries

The auto-summary hide path added in Pasta-Devs#2821 was inadvertently reverted by
f955a64 ("Local models should get the tools block"). That commit landed
the tools-block feature, but its branch carried an older copy of
generate.routes.ts that overwrote the auto-hide orchestration, so
automatic rolling summaries stopped hiding the messages they covered.
Manual summaries and unhide-on-delete were untouched and still work; only
the automatic path regressed.

Re-apply the dropped block onto current staging, keeping the tools-block
change intact:
- re-add the computeSummaryHideIds / resolveRoleplaySummaryTail imports
- recompute autoHideIds (gated on hideSummarisedMessages, tail-protected)
- persist hiddenMessageIds on the summary entry so deletion restores
  exactly the hidden set
- call bulkSetHiddenFromAI and echo hiddenMessageIds in the chat_summary
  SSE payload

The helpers, the storage method, and the manual-hide path all survived the
revert, so this is a faithful restoration with no new logic.

Refs Pasta-Devs#2820

* Prepare v2.0.4 stabilization polish (Pasta-Devs#2841)

---------

Co-authored-by: Romuromylus <233513852+Romuromylus@users.noreply.github.com>
* Prepare v2.0.4 hotfixes

* Serialize Professor Mari connection saves

* Flush held thinking stream before rewrite cache

* Fix slash continue targeting

* fix(summary): restore auto-hide for roleplay rolling summaries

The auto-summary hide path added in Pasta-Devs#2821 was inadvertently reverted by
f955a64 ("Local models should get the tools block"). That commit landed
the tools-block feature, but its branch carried an older copy of
generate.routes.ts that overwrote the auto-hide orchestration, so
automatic rolling summaries stopped hiding the messages they covered.
Manual summaries and unhide-on-delete were untouched and still work; only
the automatic path regressed.

Re-apply the dropped block onto current staging, keeping the tools-block
change intact:
- re-add the computeSummaryHideIds / resolveRoleplaySummaryTail imports
- recompute autoHideIds (gated on hideSummarisedMessages, tail-protected)
- persist hiddenMessageIds on the summary entry so deletion restores
  exactly the hidden set
- call bulkSetHiddenFromAI and echo hiddenMessageIds in the chat_summary
  SSE payload

The helpers, the storage method, and the manual-hide path all survived the
revert, so this is a faithful restoration with no new logic.

Refs Pasta-Devs#2820

* Prepare v2.0.4 stabilization polish (Pasta-Devs#2841)

* fix(chat): tracker edit targeting, prefill debounce, prompt-editor close, per-chat lorebook disable, convo card info (Pasta-Devs#2847)

* fix(tracker): target tracker character edits by unique id; debounce Assistant Prefill input

Two roleplay-mode fixes that share no code but ship together per maintainer request.

Character tracker manual edits (and removes/avatar uploads) resolved the target
character with findIndex by characterId. Present-character ids come from the LLM
character-tracker agent and are not guaranteed unique (the prompt allows "ID or
name"), so when several present characters shared an id, findIndex always matched
the first one and every edit collapsed onto the first character. Resolve the
target via a unique-id match that falls back to the rendered index, mirroring the
findUniqueNamedIndex rule already used by the other tracker list mutations. Apply
it consistently to edit, remove, and avatar upload.

The Assistant Prefill field committed every keystroke straight to a per-chat
metadata mutation, so the controlled textarea only advanced after the round-trip
and invalidation completed — text appeared one character at a time and got worse
as chat metadata grew. Buffer the field locally and commit on blur (and on
unmount, so closing the drawer mid-edit keeps the text) via a new DraftTextarea
that mirrors the existing DraftNumberInput pattern.

* fix(tracker): drop edits whose character id vanished; guard prefill draft re-seed while focused

Addresses two regressions surfaced by adversarial review of the original fix.

resolveCharacterTargetIndex fell back to the rendered index when a provided
characterId was absent from live state, which could write or delete onto a
different character if the tracker agent rewrote present characters between
render and commit. Restore the previous drop-on-absent behavior: return -1 when
the id is provided but not found, and fall back to the rendered index only for
the duplicate-id (ambiguous) case.

DraftTextarea re-seeded its draft from `value` on every external change, so a
background metadata write (or a chat switch) could clobber keystrokes the user
was mid-typing in the Assistant Prefill field. Guard the re-seed on focus,
matching the sibling ThinkingTagsInput / CustomParametersInput.

* fix(chat): keep the expanded prompt editor open when clicked inside a chat drawer

The conversation-mode Prompt Preset "Edit Prompt" window (and the sibling
Game/Scene expanded editors) is a full-screen overlay portalled to <body>,
rendered above the chat settings drawer. The drawer closes itself on any
capture-phase pointerdown that lands outside its panel or outside a
[data-chat-floating-panel] element. Because the editor lives in a separate
portal, the very first click inside it — including on the Collapse button —
was treated as "outside", so the drawer closed and unmounted the editor
before the edit could commit. Mouse users could neither type nor save.

Mark the editor overlay with data-chat-floating-panel so the drawer's
outside-click handler treats clicks inside it as inside. Closing now only
happens via Collapse/Escape, which route through the editor's onClose and
persist the change.

* feat(lorebook): let users disable auto-activated lorebooks per chat

A lorebook bound to a character (or a global / active-persona book) is
force-activated in every non-game chat where it applies, with no way to turn
it off from the chat UI. With several characters carrying overlapping books,
entries pile up and overflow the token budget, and the only escape was to
unbind the book globally.

The exclusion plumbing already existed but was only wired to the Game
Lorebook Keeper: filterRelevantLorebooks() and listActiveEntries() both honor
an excludedLorebookIds filter, but generate/scan/dry-run/prompt paths only
ever passed the game-keeper exclusions, and there was no UI or metadata field
to set a per-chat exclusion.

- shared: add ChatMetadata.excludedLorebookIds.
- server: rename resolveGameLorebookScopeExclusions -> resolveLorebookScopeExclusions
  and fold per-chat excludedLorebookIds into the result for every mode, so all
  callers (generation, lorebook scan, dry-run, prompt preview) drop excluded
  books before injection. Validate the field on the metadata PATCH route.
- client: surface a "disable in this chat" (eye-off) control on auto-activated
  books in the chat Lorebooks panel, render disabled books greyed with a
  re-enable control, and write excludedLorebookIds via the metadata mutation.
  Disabling does not unbind the book — it only suppresses it for this chat.
  The redundant pin-an-already-active-book control is replaced by disable;
  inactive books are still added via the picker.

* fix(conversation): include character/persona card info when a prompt preset is selected

In Conversation mode the model received only the character and user names —
no description, personality, scenario, or persona info — whenever a prompt
preset was selected (e.g. the default universal preset). The model would
report it had no information about the character it was supposed to play.

Conversation mode is deliberately excluded from the preset assembler, so in
that mode a preset only supplies the conversation prompt text; it never
injects character/persona card fields the way the roleplay assembler does.
The identity fallback that would supply them was gated off whenever a preset
was present (chatMode !== "game" && !presetId), which is correct for roleplay
(the assembler covers it) but left conversation mode with nothing.

Always run the identity fallback for conversation mode, regardless of preset.
The injector already skips any character/persona whose profile is already
present in the prompt, so custom conversation prompts that embed {{description}}
et al. are not duplicated, and conversation-without-a-preset behavior is
unchanged.

---------

Co-authored-by: Romuromylus <233513852+Romuromylus@users.noreply.github.com>

* Add streaming to professor maris chat (Pasta-Devs#2852)

Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>

* Don't turn off streaming by default if tool calling is enabled (Pasta-Devs#2854)

Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>

* Don't send tool available in system prompt if model supports body.tools (Pasta-Devs#2856)

Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>

* Don't limit mari chat message amount and length by default (Pasta-Devs#2858)

Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>

* Prepare v2.0.5 stabilization (Pasta-Devs#2859)

* Prepare v2.0.5 stabilization

* Address v2.0.5 review feedback

---------

Co-authored-by: Romuromylus <233513852+Romuromylus@users.noreply.github.com>
Co-authored-by: Romu <jeromehbonaparte@gmail.com>
Co-authored-by: Sulphuratum <ristauerik@gmail.com>
Brings the fork up to upstream release v2.0.5 while preserving all of this
fork's customizations. Conflicts were limited to the five files this fork had
customized; resolved as follows:

- CharactersPanel.tsx / PersonasPanel.tsx: took upstream's rewritten panels —
  which independently reimplement "show only ungrouped outside folders"
  (foldered-id filtering, synthetic "Ungrouped" folder removed, drag-to-folder
  assignment, and the persona comment shown on member rows) — then re-applied
  this fork's multi-open persistent-folder feature on top. Folder open/closed
  state now lives in the UI store (multi-open, synced + persisted across
  reloads/storage-clears/devices) instead of upstream's single-open local
  state, while keeping upstream's folder-filter auto-expand behavior.

- ui.store.ts: took upstream, re-added expandedPersonaGroupIds /
  expandedCharacterGroupIds state, the two toggle actions, and registered both
  in the synced (pickSyncedSettings) and persisted (partialize) subsets.

- model-lists.ts: took upstream, restored this fork's CLAUDE_SUBSCRIPTION_MODELS
  list (Opus/Sonnet/Haiku + [1m] 1M-context entries and legacy labels) so the
  picker mirrors the Claude Code menu, and added Fable 5 (base + [1m] variant).

- claude-subscription.provider.ts: took upstream, re-applied the [1m]-suffix
  handling (strip the suffix to a real model id, enable the
  context-1m-2025-08-07 beta) onto upstream's refactored chat().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants