Skip to content

feat: Parallel sessions + multi-agent, with per-profile avatars#657

Merged
fathah merged 8 commits into
mainfrom
feat-multi-sessions
Jun 13, 2026
Merged

feat: Parallel sessions + multi-agent, with per-profile avatars#657
fathah merged 8 commits into
mainfrom
feat-multi-sessions

Conversation

@fathah

@fathah fathah commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Parallel sessions + multi-agent, with per-profile avatars

Adds true multi-session / multi-agent support to the desktop, plus profile
avatars & colors and a handful of related UX cleanups.

Highlights

Parallel / background sessions (multi-agent)

  • Starting a new chat — or switching to another profile/agent — no longer aborts
    the in-flight one. Conversations keep running in the background, across
    profiles, and you can jump back to watch them stream live.
  • New Active-sessions bar above the chat: one tab per open conversation
    (agent avatar + title, spinner while generating), with a close (×) button.
    Click a tab to switch (and follow that agent); closing stops the run if it's
    still working and never leaves the view empty.
  • Each conversation is identified by a renderer-minted runId threaded through
    the main process; every streaming IPC event is tagged with it so concurrent
    runs never bleed into each other's transcript.

Profile avatars & colors

  • Each profile gets an avatar (uploaded image, center-cropped/downscaled to
    128px) and an accent color from a flat palette; unset profiles get a
    stable, name-derived default color.
  • Shared ProfileAvatar replaces the generic icons in the nav switcher, the
    active-sessions bar, and the Agents/Profiles page.
  • Manage page: a hover Edit button opens a modal to set avatar/color, with a
    Danger zone (delete profile + clear warning). Active profile is shown with
    an animated green outline instead of a badge.

UX cleanups

  • Removed the chat header (session id + token badge + new/delete buttons) to
    reclaim vertical space; new-chat/delete live in the sidebar / sessions page.
  • Clicking a profile card now only switches the active agent (and starts its
    gateway) — a conversation starts only via the Chat button.
  • Recent Sessions list force-refreshes on profile switch (it's per-profile).

Implementation notes

Main / preload

  • src/main/index.ts — replaced the single currentChatAbort with a
    Map<runId, abort>; send-message takes a runId and tags every event;
    abort-chat targets one run; shutdown aborts all.
  • src/main/profile-meta.ts (new) — profile-meta.json per profile dir
    ({ color, avatar }) + setters; profiles.ts returns resolved color/avatar.
  • src/shared/profileColors.ts (new) — flat palette + defaultColorForName,
    shared by main and renderer.
  • src/preload/* — thread runId through send/abort/listeners; add profile
    color/avatar IPC.

Renderer

  • Chat.tsx is now per-run and self-contained (owns its messages); useChatIPC
    filters events by runId; useChatActions threads runId.
  • Layout.tsx holds the global runs[] + activeRunId and all run lifecycle
    (new/switch/resume/close); chatRuns.ts (new) holds the pure run helpers.
  • New components: ActiveSessionsBar.tsx, common/ProfileAvatar.tsx;
    utils/imageResize.ts for avatar downscaling.
  • ChatHeader.tsx removed.

Tests

  • tests/chat-runs.test.ts — run-registry helpers + the runId event filter.
  • tests/profile-colors.test.ts — palette membership + deterministic defaults.
  • Updated Agents.test.tsx for the new edit-modal delete flow.
  • npm run typecheck (web + node) and npm run lint pass; pre-existing
    unrelated test failures (ipc-handlers, reconcile-streamed-with-db,
    tool-activity-group-title) are untouched by this branch.

Not yet verified

  • A full end-to-end run of two concurrent / cross-profile streaming sessions in
    the live app has not been done here — logic is covered by typecheck/tests and
    review. Worth a manual pass before merge.

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces true parallel multi-session support and per-profile avatars. Conversations no longer abort when a new one starts; each run is tracked by a renderer-minted runId threaded through every IPC event so concurrent streams never cross-contaminate transcripts. Profile appearance (color + avatar image) is stored in a per-profile profile-meta.json and surfaced via a new ProfileAvatar component used uniformly across the nav, sessions bar, and manage page.

  • Parallel sessions / multi-agent: activeRuns: Map<runId, abort> replaces the single currentChatAbort; useChatIPC filters all events by runId; Layout mounts one <Chat key={runId}> per open run and shows/hides via display: none.
  • ActiveSessionsBar: New tab strip above the chat; hidden when there is only one idle session; close button aborts and drops the run, always keeping at least one tab open.
  • Profile avatars & colors: Uploaded images are center-cropped to 128 px PNG client-side before an IPC write; hex color and data-URL are validated in the main process; defaultColorForName provides stable palette defaults shared by both processes.

Confidence Score: 5/5

The change is safe to merge; the core multi-run routing is well-isolated and the tricky concurrency edges (double-click resume, renderer-gone abort, cross-run event bleed) all have explicit guards.

The per-runId event filtering in useChatIPC and the activeRuns map in the main process are the heart of the change, and both are implemented correctly. The resumingRef deduplication prevents the double-mount race on session resume. handleClear properly resets hermesSessionId after clearing. The two observations flagged are about an avatar preview lag and a post-mount memory redundancy — neither affects correctness of the streaming or session-switching logic.

src/renderer/src/screens/Agents/Agents.tsx for the avatar optimistic-update gap; src/renderer/src/screens/Layout/Layout.tsx for the retained seed array.

Important Files Changed

Filename Overview
src/main/index.ts Replaces single currentChatAbort with a per-runId Map; abort, cleanup, and event routing all updated cleanly.
src/main/profile-meta.ts New file; validates hex color and image data URL before writing; size cap and error propagation look correct.
src/renderer/src/screens/Layout/Layout.tsx Core orchestration for multi-run lifecycle; double-click resume guard via resumingRef is well-handled; run.seed held in state after mount is a minor memory redundancy.
src/renderer/src/screens/Chat/Chat.tsx Refactored to be per-run and self-contained; active prop correctly gates all keyboard and context-menu effects to avoid duplicate handlers from background instances.
src/renderer/src/screens/Chat/hooks/useChatIPC.ts All IPC listeners now filter by eventRunId === ownRunId; prevents background-session events from leaking into the active transcript.
src/renderer/src/screens/Agents/Agents.tsx New appearance modal with color picker and avatar upload; avatar upload lacks optimistic preview update after the IPC call resolves.
src/renderer/src/screens/Layout/chatRuns.ts Clean pure helpers for run lifecycle; mintRun UUID fallback handles environments without crypto.randomUUID.
src/renderer/src/screens/Layout/ActiveSessionsBar.tsx New component; correctly hides itself when only one idle session exists; ARIA roles applied.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant Layout
    participant Chat as Chat (per run)
    participant IPC as Preload/IPC
    participant Main as Main Process

    U->>Layout: handleNewChat / handleChatWithProfile
    Layout->>Layout: "mintRun(profile) -> new ChatRun"
    Layout->>Chat: "mount Chat runId=X"

    U->>Chat: send message
    Chat->>IPC: "sendMessage(runId=X)"
    IPC->>Main: "invoke send-message with runId=X"
    Main->>Main: activeRuns.set(X, abort)

    loop Streaming
        Main-->>IPC: send chat-chunk X chunk
        IPC-->>Chat: onChatChunk(runId, chunk)
        Note over Chat: filter eventRunId === X
        Chat->>Chat: setMessages(append chunk)
    end

    Main-->>IPC: send chat-done X sessionId
    IPC-->>Chat: onChatDone(runId, sessionId)
    Chat->>Layout: onSessionIdChange(X, sessionId)
    Chat->>Layout: onLoadingChange(X, false)
    Main->>Main: activeRuns.delete(X)

    U->>Layout: handleCloseRun(X)
    Layout->>Main: abortChat(X)
    Layout->>Layout: setRuns filter X out
    Layout->>Chat: unmount key removed
Loading

Reviews (2): Last reviewed commit: "Address PR #657 review: surface profile-..." | Re-trigger Greptile

Comment thread src/renderer/src/screens/Agents/Agents.tsx
Comment thread src/renderer/src/screens/Agents/Agents.tsx
Comment thread src/renderer/src/screens/Agents/Agents.tsx Outdated
Comment thread src/renderer/src/screens/Layout/Layout.tsx
…sume

- Agents: handlePickColor / handleRemoveAvatar now surface IPC failures
  instead of silently leaving an optimistic, possibly-wrong UI.
- Agents: avatar upload errors use a dedicated message (uploadImageFailed)
  rather than the profile-creation string.
- Layout: guard handleResumeSession with an in-flight Set so a rapid
  double-click can't mount two tabs for the same session (the live-run
  check straddles an await).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@fathah fathah merged commit a936131 into main Jun 13, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant