Fix bridge history, profile models, and Windows gateway handling (#845)

* feat: support profile-aware group chat bridge flows

* feat: route cron jobs through hermes cli

* Fix group chat routing and isolate bridge tests

* Add Grok image-to-video media skill

* Default Grok videos to media directory

* Fix bridge profile fallback and cron repeat clearing

* Refine bridge chat and gateway platform handling

* Filter bridge tool-call text deltas

* Preserve structured bridge chat history

* Prepare beta release build artifacts

* Fix Windows run profile resolution

* Fix Windows path compatibility checks

* Fix profile-scoped model page display

* Hide Windows subprocess windows for jobs and updates

* Hide Windows file backend subprocess windows

* Avoid Windows gateway restart lock conflicts

* Treat Windows gateway lock as running on startup

* Force release Windows gateway lock on restart

* Tighten Windows gateway lock cleanup

* Update chat e2e source expectation

* Bump package version to 0.5.30

---------

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
ekko
2026-05-19 16:09:59 +08:00
committed by GitHub
parent 3d74d78698
commit 9a9416c99c
129 changed files with 7017 additions and 1838 deletions
+37 -14
View File
@@ -58,6 +58,7 @@ export interface PendingApproval {
export interface Session {
id: string
profile?: string
title: string
source?: string
messages: Message[]
@@ -232,6 +233,7 @@ function mapHermesMessages(msgs: HermesMessage[]): Message[] {
function mapHermesSession(s: SessionSummary): Session {
return {
id: s.id,
profile: s.profile || 'default',
title: s.title || '',
source: s.source || undefined,
messages: [],
@@ -389,10 +391,19 @@ export const useChatStore = defineStore('chat', () => {
return streamStates.value.has(sessionId) || serverWorking.value.has(sessionId)
}
async function loadSessions() {
function clearActiveSession() {
activeSessionId.value = null
activeSession.value = null
focusMessageId.value = null
setAbortState(null)
setCompressionState(null)
removeItem(storageKey())
}
async function loadSessions(profile?: string | null) {
isLoadingSessions.value = true
try {
const list = await fetchSessions()
const list = await fetchSessions(undefined, undefined, profile || undefined)
const fresh = list.map(mapHermesSession)
// Preserve already-loaded messages for sessions that are still present,
// so we don't blow away the active session's messages on refresh.
@@ -410,6 +421,8 @@ export const useChatStore = defineStore('chat', () => {
: sessions.value[0]?.id
if (targetId) {
await switchSession(targetId)
} else {
clearActiveSession()
}
} catch (err) {
console.error('Failed to load sessions:', err)
@@ -439,14 +452,17 @@ export const useChatStore = defineStore('chat', () => {
}
function createSession(): Session {
function createSession(options: { profile?: string; model?: string; provider?: string } = {}): Session {
const session: Session = {
id: uid(),
profile: options.profile || useProfilesStore().activeProfileName || 'default',
title: '',
source: 'api_server',
source: 'cli',
messages: [],
createdAt: Date.now(),
updatedAt: Date.now(),
model: options.model || undefined,
provider: options.provider || '',
}
sessions.value.unshift(session)
return session
@@ -606,12 +622,13 @@ export const useChatStore = defineStore('chat', () => {
resumeServerWorkingRun(sessionId)
}
function newChat() {
const session = createSession()
// Inherit current global model
function newChat(options: { profile?: string; model?: string; provider?: string } = {}) {
const appStore = useAppStore()
session.model = appStore.selectedModel || undefined
session.provider = appStore.selectedProvider || ''
const session = createSession({
profile: options.profile,
model: options.model || appStore.selectedModel || undefined,
provider: options.provider || appStore.selectedProvider || '',
})
switchSession(session.id)
}
@@ -852,7 +869,10 @@ export const useChatStore = defineStore('chat', () => {
// Capture session ID at send time — all callbacks use this, not activeSessionId
const sid = activeSessionId.value!
const isBridgeSlashCommand = activeSession.value?.source === 'cli' && content.trim().startsWith('/')
const shouldSendInitialSessionConfig = activeSession.value
? activeSession.value.messageCount == null || activeSession.value.messageCount === 0
: false
const isBridgeSlashCommand = content.trim().startsWith('/')
const isBridgeCompressCommand = isBridgeSlashCommand && /^\/compress(?:\s|$)/i.test(content.trim())
const wasLiveBeforeSend = isSessionLive(sid)
const shouldQueue = wasLiveBeforeSend && !isBridgeSlashCommand
@@ -912,19 +932,22 @@ export const useChatStore = defineStore('chat', () => {
const appStore = useAppStore()
await appStore.waitForModelsForRun()
const sessionModel = activeSession.value?.model || appStore.selectedModel
const isBridgeSource = activeSession.value?.source === 'cli'
const sessionProvider = activeSession.value?.provider || appStore.selectedProvider
const runPayload = {
input,
session_id: sid,
model: isBridgeSource ? undefined : sessionModel || undefined,
provider: isBridgeSource ? undefined : sessionProvider || undefined,
profile: shouldSendInitialSessionConfig ? activeSession.value?.profile || undefined : undefined,
model: shouldSendInitialSessionConfig ? sessionModel || undefined : undefined,
provider: shouldSendInitialSessionConfig ? sessionProvider || undefined : undefined,
model_groups: appStore.modelGroups.map(group => ({
provider: group.provider,
models: group.models,
})),
queue_id: userMsg.id,
source: (isBridgeSource ? 'cli' : 'api_server') as 'cli' | 'api_server',
source: 'cli' as const,
}
if (shouldSendInitialSessionConfig && activeSession.value) {
activeSession.value.messageCount = Math.max(activeSession.value.messageCount || 0, 1)
}
if (shouldQueue) {