Conversation
Rebased multichat feature from zane/multichat-demo onto main. Key changes: - Add activeSessions state to track multiple chat sessions - Add SessionStatusContext for tracking session streaming states - Add ChatSessionsContainer to render all active sessions (hidden when not active) - Add SessionIndicators component for showing streaming/unread status in sidebar - Modify AppLayout to accept activeSessions prop - Modify BaseChat to support isActiveSession prop and auto-focus - Modify PairRouteWrapper to manage active sessions instead of rendering Pair directly - Add optimized-spinner component for better performance - Update sidebar to show session status indicators Preserves main branch features: - Working directory support per session - Extension overrides support - Analytics tracking
…ased * 'main' of github.com:block/goose: feat: add hotkey to toggle full tool output display (#6067)
| COUNT(m.id) as message_count | ||
| FROM sessions s | ||
| INNER JOIN messages m ON s.id = m.session_id | ||
| LEFT JOIN messages m ON s.id = m.session_id |
There was a problem hiding this comment.
Left sidebar needs to show sessions without messages for "New Chat"
There was a problem hiding this comment.
hmm, yeah, but we can't do this like this. we create sessions without messages too often and it will just overwhelm all the overviews that we have.
alternatives: make this a flag to this method? probably end up with a convenience method for the current behavior.
or do this at the client. the client knows about the started sessions that don't have messages yet.
There was a problem hiding this comment.
ok good call, changed back and now tracked on the client instead
| // Store current scroll position to prevent jump | ||
| const scrollTop = element.scrollTop; | ||
|
|
||
| // Temporarily set to auto to measure natural height, but use minHeight to prevent collapse |
There was a problem hiding this comment.
Fixed a pre-existing bug where the chat input placeholder was disappearing/jumping when starting a new chat
…ased * 'main' of github.com:block/goose: chore: break up process agent response (#6348) More 3.7 removal (#6414) CLI show extension errors (#6398) fix[desktop]: Improve UX for ExtensionItem component (#6443) update[doc]: Add tip for GitHub Copilot Provider (#6441) Avoid using cliclack.confirm in non-interactive session (#6412) docs: claude prompt caching note (#6429) Restore task completion notification (#6427) docs: stream-json and auth-token cli options (#6426)
DOsinga
left a comment
There was a problem hiding this comment.
I think we need to think more about how the data flows work. maybe set up a chat? the query parameter passing, the useRefs introduced, the context manager, and the event posting, it all feels icky.
| COUNT(m.id) as message_count | ||
| FROM sessions s | ||
| INNER JOIN messages m ON s.id = m.session_id | ||
| LEFT JOIN messages m ON s.id = m.session_id |
There was a problem hiding this comment.
hmm, yeah, but we can't do this like this. we create sessions without messages too often and it will just overwhelm all the overviews that we have.
alternatives: make this a flag to this method? probably end up with a convenience method for the current behavior.
or do this at the client. the client knows about the started sessions that don't have messages yet.
| // Build URL with search params if resumeSessionId is provided | ||
| const searchParams = new URLSearchParams(); | ||
| if (options?.resumeSessionId) { | ||
| searchParams.set('resumeSessionId', options.resumeSessionId); |
There was a problem hiding this comment.
this seems unrelated to the current change and makes me feel uneasy; we play fast and loose with routing here already. maybe we can let this one slide if we pinkie promise to clean this up once and for all?
There was a problem hiding this comment.
Its actually needed so the sidebar can look up the current session and for the refresh case. Open to suggestions butnot really seeing another option here its pretty standard to use a query param for navigation related ids and refresh. Added comments to make it a bit clearer why we do it.
| .reverse(); | ||
| }, [messages]); | ||
|
|
||
| // Auto-submit initial message for new sessions or forked sessions |
There was a problem hiding this comment.
I don't think we can do it this way. this has the different scenarios, the multi facetted if statements that try to mimic a specific situation the app might be useRef global variables that we used to have in the recipe manager with all the hard to read code and hard to main aspects of it. we need to rethink what we're doing here.
There was a problem hiding this comment.
I pulled this into a hook to be more manageable for now
|
|
||
| useEffect(() => { | ||
| chatStateRef.current = chatState; | ||
| }, [chatState]); |
There was a problem hiding this comment.
yeah this is getting out of hand. we now have 4 refs for stale state? we have to refactor this. maybe useReducer
There was a problem hiding this comment.
good call changed to use a reducer for all these states
…ased * 'main' of github.com:block/goose: (23 commits) Use Intl.NumberFormat for token formatting in SessionsInsights (#6466) feat(ui): format large and small token counts for readability (#6449) fix: apply subrecipes when using slash commands (#6460) Fix: exclude platform_schedule_tool in CLI (#6442) Fix: Small update in how ML-based prompt injection determines final result (#6439) docs: remove SSE transport and rename to Streamable HTTP (#6319) fix: correct Cloudinary extension command and env variable (#6453) fix: add gap between buttons in MacDesktopInstallButtons.js (#6452) refactor: include hidden dotfiles folders in file picker search (#6315) upgraded safe npm packages (#6450) chore(deps): bump react-router and react-router-dom in /ui/desktop (#6408) chore(deps): bump lru from 0.12.5 to 0.16.3 (#6379) chore(deps-dev): bump @modelcontextprotocol/sdk from 1.24.0 to 1.25.2 in /ui/desktop (#6375) fix: inconsistent API url requirement between desktop and CLI versions (#6419) feat(vertexai): Add streaming support (#6409) fix deeplink recipe launch cold start (#6210) Spell check setting (#6446) File bug directly (#6413) fix(cli): incorrect bin name in shell completions (#6444) Use crunchy from crates instead of git fork (#6415) ...
|
@DOsinga thanks for the feedback! Agreed on a lot of those points and refactored, pls take a second look. Ultimately we need to add a global state library to reduce our components and get rid of the window events and conditionals and refs everywhere. I will follow up with that next to make all this a lot more manageable but I think this is good enough for now with the current architecture to get the feature live. |
…ased * 'main' of github.com:block/goose: fix(code_execution): serialize record_result output as JSON (#6495) perf(google): avoid accumulating thoughtSignatures across conversation history (#6462) fix(openai): make tool_call arguments optional and fix silent stream termination (#6309) fix: Improve error messages for invalid tool calls (#6483) fix: require auth when running goose on non loopback address (#6478) chore(deps): bump hono from 4.11.3 to 4.11.4 in /ui/desktop (#6485) feat(cli): graceful fallback for keyring failures (#5808) fix: support global .gooseignore and negation patterns (#6157) docs: manual config for jetbrains (#6490) fix: Recipe slash command doesn't work with single optional parameter (#6235) fix(openrouter): Handle Gemini thoughtSignature for tool calls (#6370) docs: fix extensions page (#6484) Allow customizing the new line keybinding in the CLI (#5956) Ask for permission in the CLI (#6475) docs: add Ralph Loop tutorial for multi-model iterative development (#6455) Remove gitignore fallback from gooseignore docs (#6480) fix: clean up result recording for code mode (#6343) fix(code_execution): handle model quirks with tool calls (#6352) feat(ui): support prefersBorder option for MCP Apps (#6465) fixed line breaks (#6459)
| showPopularTopics?: boolean; | ||
| suppressEmptyState: boolean; | ||
| sessionId: string; | ||
| isActiveSession?: boolean; |
There was a problem hiding this comment.
this is a pattern the AIs like very much - optional booleans that then get a default assigned. let's watch out for it! in general, no optional booleans unless undefined has a particular meaning (in which case we'd probably want to ask ourselves, do we need an enum instead?)
| // LRU eviction: keep only the last MAX_ACTIVE_SESSIONS | ||
| if (updated.length > MAX_ACTIVE_SESSIONS) { | ||
| updated = updated.slice(updated.length - MAX_ACTIVE_SESSIONS); | ||
| } |
There was a problem hiding this comment.
we can move this in the if - if we don't append there's no reason to slice. also, if we move the existing session, we drop the initialMessage on the floor. is that intended?
| AppEvents.CLEAR_INITIAL_MESSAGE, | ||
| handleClearInitialMessage as unknown as (event: Event) => void | ||
| ); | ||
| }; |
There was a problem hiding this comment.
these type cast strike me as rather ugly; wouldn't you say that doing the casting inside the event handlers would be better? or use a type map.
| }); | ||
| setTimeout(() => { | ||
| isProcessingRef.current = false; | ||
| }, 1000); |
There was a problem hiding this comment.
wow. timeout and a useRef? that's a tad smelly. now that we have multi-chat, can we not just send the message to the chat and then we get to pair it will already be there?
There was a problem hiding this comment.
yeah this is hacky, I tried using the existing system and it would load multiple chat sessions so its to get around the quick launcher sending multiple events from the other electron window.. will come back to this also
| setSessions((prevSessions) => | ||
| prevSessions.map((s) => (s.id === sessionId ? { ...s, name: newDescription } : s)) | ||
| ); | ||
| // Notify sidebar of the rename |
There was a problem hiding this comment.
are you? we're just telling anybody, no? in fact we should update the title of the current conversation too - there's an issue for that
There was a problem hiding this comment.
ok removed comment, it does update the title also, I merged in that issue/fix from main already and verified its working
|
|
||
| const sessionWithDefaultName = apiSessions.find((s) => shouldShowNewChatTitle(s)); | ||
|
|
||
| const shouldContinue = pollCount < maxPolls && (sessionWithDefaultName || pollCount < 5); |
There was a problem hiding this comment.
this is confusing to me, why keep polling if the name has changed?
There was a problem hiding this comment.
the name gets updated again as the session progresses but yeah agreed its confusing, I'll come back to this in another pr with a refactor to move to streaming and remove the polling like you mentioned in the other comment
| const maxPolls = maxPollDurationMs / pollIntervalMs; | ||
| let pollCount = 0; | ||
|
|
||
| const pollForUpdates = async () => { |
There was a problem hiding this comment.
so this whole polling buseiness is always suspicious. I get why we do this, since the backend changes the name out of the critical path. we should be able to solve this with a message in the stream though. can be done in a follow up. :pinky_swear:
| return { | ||
| hasAutoSubmitted: hasAutoSubmittedRef.current, | ||
| }; | ||
| } |
There was a problem hiding this comment.
I don't know man, I feel like we might be making this whole pending message business way too complicated and react is just getting in our way here.
what if we just had something super simple like a global(!) variable that is a map from sessionId to pending message and then when you reach BaseChat, we check that, send that message out and clear the entry in the map?
There was a problem hiding this comment.
Good point but we might be able to handle this better with a global state library so I will follow up with that also
| tokenState: state.tokenState, | ||
| notifications: notificationsMap, | ||
| onMessageUpdate, | ||
| }; |
There was a problem hiding this comment.
I'm still not wild about this. I think we can turn this in a true multi chat state component that just listens to multiple conversations, but I guess that is out of scope for this.
There was a problem hiding this comment.
noted, will consider in the upcoming refactor to global state
| responseStyleChanged: CustomEvent; | ||
| 'session-created': CustomEvent<{ session?: import('./api').Session }>; | ||
| 'session-deleted': CustomEvent<{ sessionId: string }>; | ||
| 'session-renamed': CustomEvent<{ sessionId: string; newName: string }>; |
There was a problem hiding this comment.
huh. if we have a window event map, why the casting on top?
alexhancock
left a comment
There was a problem hiding this comment.
Nice! This is a big lift.
I don't really understand the dataloading and state management at the top level. I have a hard time working out where the activeSessions are populated from. I do like the reducer for useChatState.tsx as that seems like a nice way to manage the state modifications for each chat, but I wonder if an evolution of this can involve a rust integration that supports the data model with multiple chats more natively?
| if (chatState === ChatState.LoadingConversation) { | ||
| streamState = 'loading'; | ||
| } else if ( | ||
| chatState === ChatState.Streaming || |
There was a problem hiding this comment.
should we just define a new attribute on ChatState?
There was a problem hiding this comment.
ChatState doesn't quite fit because ChatState is an enum for UI state transitions, not a data container but good point about simplifying the data flow. I'll look at this with the global state update next. Also good suggestion about better multi chat rust integration, I'll think about what that looks like and get a plan together.
|
Sounds good will merge asap after some final testing. The goose logo was an upstream change that added it to the sidebar so I moved it to the top right above chat in my latest commit. Not seeing the only one session issue will try to repro that real quick. |
…ovider * 'main' of github.com:block/goose: increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492) docs: max tokens config (#6596) User configurable templates (#6420) docs: http proxy environment variables (#6594) feat: exclude subagent tool from code_execution filtering (#6531)
* main: increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492) docs: max tokens config (#6596) User configurable templates (#6420) docs: http proxy environment variables (#6594) feat: exclude subagent tool from code_execution filtering (#6531) Fix path for global agent skills (#6591) recipes: add mcp server (#6552) feat(gcp-vertex): add model list with org policy filtering (#6393) chore: encourage extension searching (#6582) blog: mobile apps consolidation and roadmap (#6580) chore: remove unused dependencies in cargo.toml (#6561) resolved all the extensions to load in cli (#6464)
* main: increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492)
* main: increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492)
* main: (41 commits) chore: tweak release docs (#6571) fix(goose): propagate session_id across providers and MCP (#6584) increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492) docs: max tokens config (#6596) User configurable templates (#6420) docs: http proxy environment variables (#6594) feat: exclude subagent tool from code_execution filtering (#6531) Fix path for global agent skills (#6591) recipes: add mcp server (#6552) feat(gcp-vertex): add model list with org policy filtering (#6393) chore: encourage extension searching (#6582) blog: mobile apps consolidation and roadmap (#6580) chore: remove unused dependencies in cargo.toml (#6561) ...
* main: (68 commits) fix(docs): use dynamic import for globby ESM module (#6636) chore: trigger CI Document tab completion (#6635) Install goose-mcp crate dependencies (#6632) feat(goose): standardize agent-session-id for session correlation (#6626) chore: tweak release docs (#6571) fix(goose): propagate session_id across providers and MCP (#6584) increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492) docs: max tokens config (#6596) User configurable templates (#6420) docs: http proxy environment variables (#6594) feat: exclude subagent tool from code_execution filtering (#6531) Fix path for global agent skills (#6591) ...
* main: (68 commits) fix(docs): use dynamic import for globby ESM module (#6636) chore: trigger CI Document tab completion (#6635) Install goose-mcp crate dependencies (#6632) feat(goose): standardize agent-session-id for session correlation (#6626) chore: tweak release docs (#6571) fix(goose): propagate session_id across providers and MCP (#6584) increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492) docs: max tokens config (#6596) User configurable templates (#6420) docs: http proxy environment variables (#6594) feat: exclude subagent tool from code_execution filtering (#6531) Fix path for global agent skills (#6591) ...
* main: (68 commits) fix(docs): use dynamic import for globby ESM module (#6636) chore: trigger CI Document tab completion (#6635) Install goose-mcp crate dependencies (#6632) feat(goose): standardize agent-session-id for session correlation (#6626) chore: tweak release docs (#6571) fix(goose): propagate session_id across providers and MCP (#6584) increase worker threads for ci (#6614) docs: todo tutorial update (#6613) Added goose doc map md file for goose agent to find relevant doc easily. (#6598) add back goose branding to home (#6617) fix: actually set the working dir for extensions from session (#6612) Multi chat (#6428) Lifei/fixed accumulated token count (#6587) Dont show MCP UI/Apps until tool is approved (#6492) docs: max tokens config (#6596) User configurable templates (#6420) docs: http proxy environment variables (#6594) feat: exclude subagent tool from code_execution filtering (#6531) Fix path for global agent skills (#6591) ...
Signed-off-by: fbalicchia <fbalicchia@cuebiq.com>

Summary
Multi chat in one window working with existing sidebar navigation.
closes #4437
closes #6406
Kapture.2026-01-09.at.15.05.21.mp4