Skip to content

Sync fork to upstream v2.0.6 (35 commits), preserve fork customizations - #5

Merged
LukaTheHero merged 36 commits into
mainfrom
chore/sync-upstream-v2.0.6
Jun 30, 2026
Merged

Sync fork to upstream v2.0.6 (35 commits), preserve fork customizations#5
LukaTheHero merged 36 commits into
mainfrom
chore/sync-upstream-v2.0.6

Conversation

@LukaTheHero

Copy link
Copy Markdown
Owner

Why

Keeps the fork current with Pasta-Devs upstream. After the v2.0.5 sync (#4), upstream released v2.0.6 (35 commits of stabilization). This brings the fork up to v2.0.6 while keeping every fork customization intact.

What

Merges upstream v2.0.6 (Pasta-Devs/main, 49164fec) into the fork's main (a5deecb2). The merge-base is the v2.0.5 release, and none of the 35 new commits touch the fork's five customized files, so the merge is conflict-free — the customizations carry over byte-for-byte:

  • Claude (Subscription) model list (shared/constants/model-lists.ts) — curated [1m] list + legacy labels + Fable 5
  • [1m] 1M-context handling (server/.../claude-subscription.provider.ts)
  • Multi-open persistent group folders (PersonasPanel.tsx, CharactersPanel.tsx, ui.store.ts)

The 35 upstream commits are v2.0.6 stabilization (chat/conversation/summary fixes, Professor Mari streaming, memory-recall fixes, issue-sweep UI, theme accent pulse, settings polish).

The merge commit preserves both parents (a5deecb2 fork main + 49164fec upstream v2.0.6). pnpm check (typecheck + lint + build) passes.

Note for updating installs

v2.0.6 adds no new dependencies, so updating an install to this revision is a git pull + rebuild.

SpicyMarinara and others added 30 commits June 23, 2026 20:17
…mari-fixes

[codex] Prepare v2.0.4 hotfixes
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
…uto-summary-hide

fix(summary): restore auto-hide for roleplay rolling summaries
…ose, 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>
Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
…ls (Pasta-Devs#2856)

Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
…s#2858)

Co-authored-by: Spicy Marinara <mgrabower97@gmail.com>
* Prepare v2.0.5 stabilization

* Address v2.0.5 review feedback
…ter, author notes, drag handle, group selfie, react-gate, summary tail, streaming lag) (Pasta-Devs#2872)

* fix(chat): only show swipe counter when a message has multiple swipes (Pasta-Devs#2867)

* fix(chat): /continue creates a new reply when the last message is from the user (Pasta-Devs#2866)

* fix(panels): show touch drag handle on touch devices regardless of width (Pasta-Devs#2869)

* fix(chat): keep Author's Notes panel open when the mobile keyboard appears (Pasta-Devs#2868)

* fix(chat): include lastMessageRole in conversation slash-context deps (Pasta-Devs#2866)

* fix(conversation): attribute merged group-chat commands to their speaker (Pasta-Devs#2870)

In Conversation-mode group chats the default merged generation produces one
response containing every character's turn; all parsed commands (including
[selfie]) were attributed to the first character, so a selfie taken by another
character rendered the first character's appearance. Split the response by the
same Name: prefixes the client uses for display and attribute each command to
its segment's character, falling back to the generation's character.

* fix(conversation): type merged command attribution without union narrowing (Pasta-Devs#2870)

* fix(chat): gate Author's Notes focus guard to mobile to keep desktop outside-click (Pasta-Devs#2868)

* fix(chat): keep toolbar overflow menu open when the mobile keyboard resizes (Pasta-Devs#2868)

The overflow menu (ChatToolbarMenu) closed on every window resize while on a
mobile viewport. The on-screen keyboard opening fires a resize, so opening the
keyboard unmounted the menu and, with it, any open child panel rendered through
it — the Author's Notes panel and the manual Summary editor. Only close the
menu when the viewport width actually changes (orientation/window resize), not
when the keyboard merely shrinks the height.

* fix(summary): reclaim a drifted summary tail instead of stranding it (Pasta-Devs#2879)

The rolling summary selected its batch with `visible.slice(-contextSize)`,
and that same set bounds each entry's frozen `hiddenMessageIds` (the batch
minus the protected tail). Once a previous summary's protected tail drifted
beyond the last `contextSize` visible messages, no later entry ever claimed
it, so it stayed visible forever and accumulated as stale context.

Add `selectRollingSummaryMessages`: it keeps the plain last-`contextSize`
window for the first summary and whenever the visible set fits the window,
but extends the window back to the previous summary's hidden boundary when
visible messages have piled up past it — pulling the drifted tail back into
the batch so it is summarized and hidden on this run. Wired into the
automatic summary and the manual "summarize last N" path; the explicit
manual range path is unchanged.

* fix(conversation): only advertise emoji reactions when commands are enabled (Pasta-Devs#2877)

The `[react: emoji="…"]` capability was appended to the conversation system
prompt unconditionally, but the tag is parsed, stripped, and applied inside
the command pipeline that only runs when Character Commands are enabled
(`characterCommands !== false`). With commands off the model was still told
it could react, so it emitted the tag and the server left it in the visible
message as raw text with no reaction badge.

Gate the react instruction on the same `conversationCommandsEnabled`
condition as its handler, so the syntax is only advertised when it will
actually be processed.

* fix(summary): key the summary boundary to summary-hidden ids, not the manual Hide-from-AI flag (Pasta-Devs#2879)

selectRollingSummaryMessages found the previous summary's boundary by
scanning for the most recent message with `hiddenFromAI` set. That flag is
also written by the user's manual "Hide from AI" toggle, with no
distinguishing marker, so a single manually hidden early message was misread
as a summary boundary and ballooned the selection from `contextSize` to
nearly the whole chat — inflating the summary-LLM input and, with
`hideSummarisedMessages` enabled, mass-hiding the backlog in one run.

Key the boundary scan to ids actually recorded in a live summary entry's
`hiddenMessageIds` (passed via the new `summaryEntries` argument). A manual
hide never appears there, so it can no longer expand the window; with no
summary-hidden message before the window the plain last-`contextSize` window
is used. Threaded the entries through both the automatic and manual
non-range call sites.

* fix(summary): anchor the boundary on messageIds too, covering pre-hiddenMessageIds entries (Pasta-Devs#2879)

The boundary scan keyed only on a live entry's hiddenMessageIds. Entries
auto-created before that field existed recorded messageIds and actually hid
messages but have no hiddenMessageIds, so they anchored no boundary and the
drifted tail was not reclaimed for those chats (no worse than baseline, but
not fixed). Also include messageIds in the summary-owned set and require the
boundary message to be hidden, so the protected tail (also in messageIds,
but visible) is never chosen. Mirrors the hiddenMessageIds ?? messageIds
fallback the unhide-on-delete path already uses.

* perf(chat): throttle live stream rendering to one parse per frame (Pasta-Devs#2878)

While a response streams, the live message re-rendered on every token and the
markdown renderer re-parsed the entire accumulated buffer from scratch each
time, so per-token work grew with the message length (~O(n²)). On fast streams
— local models running on the same machine as the server, the reported worst
case — that saturates the main thread and the UI stutters.

Add a small requestAnimationFrame coalescer (`rafThrottle`) and a
`useThrottledStreamBuffer` hook, and have the conversation and roleplay
live-stream renderers subscribe through it, so the expensive markdown re-parse
runs at most once per animation frame.

The store's `streamBuffer` is still written on every token, so token-exact
consumers such as ChatArea's auto-scroll subscriber are unaffected; only the
markdown-rendering consumers throttle. The committed message is rendered from
the query cache once streaming ends, so a live frame being up to ~16ms behind
is never visible. On slow streams (<=60 tok/s) tokens already arrive slower
than the frame budget, so this is a no-op there.

* fix(chat): deliver stream-buffer resets synchronously to keep the throttled live message coherent (Pasta-Devs#2878)

Throttling the live stream buffer (commit 2e1e595) re-rendered the streaming
message at most once per animation frame, but `streamingCharacterId` and
`thinkingBuffer` are still read un-throttled. At a group-turn boundary the
generation loop does `setStreamBuffer("")` and then `setStreamingCharacterId(next)`
back to back, so the speaker id flipped to the new character a frame before the
throttled buffer cleared. The new speaker's row then rendered with the previous
speaker's text — and in the bubble layout the streaming-preview's
monotonic-growth guard (it only accepts a longer string) pinned the previous
speaker's message under the new speaker for the rest of the turn, not just one
frame.

Throttle only ongoing growth — the buffer getting longer by appended tokens,
which is the per-token re-parse cost Pasta-Devs#2878 targets. Deliver every other
transition synchronously: the clear at a turn boundary, the first token after a
clear, and any shrink/rewrite. Delivering the clear in the same render as the
id flip restores the pre-throttle coherence, so the new speaker never inherits
the previous speaker's text, while the per-token storm stays throttled.

Extracted the growth check as `isOngoingStreamGrowth` for unit testing.

* fix(chat): require an appended prefix for throttled stream growth (Pasta-Devs#2878)

`isOngoingStreamGrowth` classified any longer buffer as ongoing growth, but the
hook's `lastSeen` closure persists across active-chat switches (the conversation
surface is not keyed by chat id). When two chats stream at once, switching into
a chat whose live buffer is longer than the previously-viewed chat's last-seen
text was misclassified as growth and held for a frame, so the new chat's stream
row briefly showed the previous chat's text.

A per-token stream always appends (`next === lastSeen + token`), so additionally
require `next.startsWith(lastSeen)` to count as growth. A cross-chat swap (or any
non-append change) fails the prefix check and is delivered synchronously, so the
new chat never renders the previous chat's buffer, while genuine per-token
growth is still throttled.

---------

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

fix: preserve knowledge router prompt injections
…y-menu-clicks

fix: keep memory recall modal interactive
…-sweep

[codex] Prepare v2.0.6 issue sweep
Includes Gemini embedding support, Professor Mari restart ordering, impersonation prompt filtering, and the CodeRabbit-requested Gemini batch embedding follow-up.
SpicyMarinara and others added 6 commits June 28, 2026 17:26
Brings the fork up to upstream release v2.0.6. None of the 35 new upstream
commits touch the fork's five customized files, so the merge is conflict-free
and all fork customizations carry over unchanged (Claude Subscription model
list + Fable 5 + [1m] 1M-context handling; multi-open persistent group folders
in the personas and characters panels).
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

✅ Bunny Review Completed

Tip

Review posted. The specimen has left the observation table.

  • Commit: 7f2d77b - Merge upstream (Pasta-Devs/main v2.0.6, 35 commits) into fork main

@LukaTheHero
LukaTheHero merged commit 2ba9169 into main Jun 30, 2026
5 of 6 checks passed
@github-actions

Copy link
Copy Markdown

🐰 Bunny Review

Bunny Merge Signal: Ready

Tip

READY
No actionable findings were isolated for this head. Expected CI controls were observed passing.

Findings Nitpicks Controls Reviewed Head Mode
0 0 1 pass 7f2d77b4 full

Note

Mode: full
Head: 7f2d77b - Merge upstream (Pasta-Devs/main v2.0.6, 35 commits) into fork main
Base: origin/main - Merge pull request #4 from LukaTheHero/chore/sync-upstream-v2.0.5

🧭 Specimen Summary

  • The reviewer could not run because OPENAI_API_KEY is absent from this workflow run. Repository-secret withholding leaves the specimen unexamined.

🔎 Isolated Defects

Tip

No actionable defects isolated.

🧹 Nitpicks

  • None recorded.

✅ Control Checks

Status Type Check Detail
✅ PASS CI Timing CI Status Expected CI controls completed without a reported failure.

🧪 Observations

  • No model pass ran; the specimen remained unexamined.

🧰 CI Status

  • pass: pnpm-validate
  • pass: container-build-test
  • note: Bunny gates only required job conclusions from ci-checks.json; advisory reports such as continue-on-error dependency checks are not included in this status.

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.

5 participants