fix(chat): isolate concurrent session events and workspace dialog i18n (#351)
* feat: per-session workspace with folder picker, HERMES_HOME support, esbuild fix * fix(chat): isolate concurrent session events and workspace dialog i18n Two user-visible bugs are fixed here: 1. Workspace dialog title showed the raw i18n key 'chat.setWorkspaceTitle' because the key was never added to en.ts / zh.ts. The dialog is opened from ChatPanel.vue but only 'setWorkspace' existed. Add the missing 'setWorkspaceTitle' translation in both locales. 2. With two concurrent runs the assistant text from session A would show up in session B (and vice versa). The /chat-run namespace uses a single shared Socket.IO connection on the client; every startRunViaSocket() call registers its own listeners on the same socket. The server fans events out via 'session:<id>' rooms, but a single socket can be in multiple rooms at once and there was no per-event filtering on the client. Each run's closure captured its own sid and wrote into the wrong session. The server already tags every payload with session_id, so the fix is a guard inside handleEvent() that drops events whose session_id does not match this run's body.session_id. Untagged events are still accepted for backwards compatibility. 3. Also fix a related crash where setting a workspace on a session that had not been persisted yet (no first message sent) threw because the row did not exist. Create the row on demand inside setWorkspace controller. * fix: upgrade esbuild to 0.27+ for vite 8 compatibility --------- Co-authored-by: ekko <fqsy1416@gmail.com>
This commit is contained in:
@@ -131,14 +131,26 @@ export function startRunViaSocket(
|
||||
socket.off('usage.updated', onUsageUpdated)
|
||||
}
|
||||
|
||||
// All event handlers share the same cleanup logic
|
||||
// All event handlers share the same cleanup logic.
|
||||
// IMPORTANT: The Socket.IO connection is shared across all in-flight runs
|
||||
// (single namespace, single socket). When two sessions run concurrently,
|
||||
// every `startRunViaSocket()` call registers its own `message.delta` /
|
||||
// `tool.*` / `run.*` listeners on the SAME socket, so each event would
|
||||
// fan out to every listener and corrupt the wrong session's transcript.
|
||||
// The server tags every payload with `session_id`; we filter here so each
|
||||
// run only sees its own events. We also accept untagged events (for
|
||||
// backwards compatibility) when no session_id was provided in the request.
|
||||
const expectedSid = body.session_id
|
||||
const handleEvent = (event: RunEvent) => {
|
||||
if (closed) return
|
||||
// Filter events by session_id to prevent cross-session contamination
|
||||
if (expectedSid && event.session_id && event.session_id !== expectedSid) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
onEvent(event)
|
||||
} finally {
|
||||
if (event.event === 'run.completed' || event.event === 'run.failed') {
|
||||
console.log('[startRunViaSocket] Run completed/failed, calling cleanup and onDone', event.event)
|
||||
cleanup()
|
||||
onDone()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user