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:
jsonet
2026-04-30 20:17:38 +08:00
committed by GitHub
parent dac9006b3e
commit 7e7fe90483
14 changed files with 468 additions and 8 deletions
@@ -50,6 +50,7 @@ export interface Session {
outputTokens?: number
endedAt?: number | null
lastActiveAt?: number
workspace?: string | null
}
function uid(): string {
@@ -168,6 +169,7 @@ function mapHermesSession(s: SessionSummary): Session {
messageCount: s.message_count,
endedAt: s.ended_at != null ? Math.round(s.ended_at * 1000) : null,
lastActiveAt: s.last_active != null ? Math.round(s.last_active * 1000) : undefined,
workspace: s.workspace || null,
}
}