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
@@ -1,3 +1,4 @@
import { join } from 'path'
import { getActiveProfileDir } from '../../services/hermes/hermes-profile'
import type {
ConversationDetail,
@@ -62,7 +63,7 @@ interface ConversationSessionRow {
}
function conversationDbPath(): string {
return `${getActiveProfileDir()}/state.db`
return join(getActiveProfileDir(), 'state.db')
}
function normalizeNumber(value: unknown, fallback = 0): number {
+9
View File
@@ -118,6 +118,7 @@ export const GC_ROOMS_SCHEMA: Record<string, string> = {
maxHistoryTokens: 'INTEGER NOT NULL DEFAULT 32000',
tailMessageCount: 'INTEGER NOT NULL DEFAULT 10',
totalTokens: 'INTEGER NOT NULL DEFAULT 0',
sessionSeed: "TEXT NOT NULL DEFAULT '0'",
}
export const GC_MESSAGES_TABLE = 'gc_messages'
@@ -129,6 +130,14 @@ export const GC_MESSAGES_SCHEMA: Record<string, string> = {
senderName: 'TEXT NOT NULL',
content: 'TEXT NOT NULL',
timestamp: 'INTEGER NOT NULL',
role: "TEXT NOT NULL DEFAULT 'user'",
tool_call_id: 'TEXT',
tool_calls: 'TEXT',
tool_name: 'TEXT',
finish_reason: 'TEXT',
reasoning: 'TEXT',
reasoning_details: 'TEXT',
reasoning_content: 'TEXT',
}
export const GC_ROOM_AGENTS_TABLE = 'gc_room_agents'
@@ -219,9 +219,10 @@ export function renameSession(id: string, title: string): boolean {
return result.changes > 0
}
export function listSessions(profile: string, source?: string, limit = 2000): HermesSessionRow[] {
export function listSessions(profile?: string, source?: string, limit = 2000): HermesSessionRow[] {
if (!isSqliteAvailable()) return []
const db = getDb()!
const profileFilter = profile?.trim()
// Use a subquery to generate preview from first user message if not set
const sql = `
@@ -239,13 +240,17 @@ export function listSessions(profile: string, source?: string, limit = 2000): He
''
) AS preview
FROM ${SESSIONS_TABLE} s
WHERE s.profile = ?
WHERE 1 = 1
${profileFilter ? 'AND s.profile = ?' : ''}
${source ? 'AND s.source = ?' : ''}
ORDER BY s.last_active DESC
LIMIT ?
`
const params: any[] = [profile]
const params: any[] = []
if (profileFilter) {
params.push(profileFilter)
}
if (source) {
params.push(source)
}
+7 -6
View File
@@ -1,4 +1,5 @@
import { getActiveProfileDir, getProfileDir } from '../../services/hermes/hermes-profile'
import { join } from 'path'
import type { LocalUsageStats } from './usage-store'
const SQLITE_AVAILABLE = (() => {
@@ -66,7 +67,7 @@ interface HermesSessionInternalRow extends HermesSessionRow {
}
function sessionDbPath(): string {
return `${getActiveProfileDir()}/state.db`
return join(getActiveProfileDir(), 'state.db')
}
function normalizeNumber(value: unknown, fallback = 0): number {
@@ -643,7 +644,7 @@ export async function getSessionDetailFromDb(sessionId: string): Promise<HermesS
export async function getSessionDetailFromDbWithProfile(sessionId: string, profile: string): Promise<HermesSessionDetailRow | null> {
const { DatabaseSync } = await import('node:sqlite')
const dbPath = `${getProfileDir(profile)}/state.db`
const dbPath = join(getProfileDir(profile), 'state.db')
const db = new DatabaseSync(dbPath, { open: true, readOnly: true })
try {
const idx = loadAllSessions(db)
@@ -670,7 +671,7 @@ export async function getSessionDetailFromDbWithProfile(sessionId: string, profi
export async function getExactSessionDetailFromDbWithProfile(sessionId: string, profile: string): Promise<HermesSessionDetailRow | null> {
const { DatabaseSync } = await import('node:sqlite')
const dbPath = `${getProfileDir(profile)}/state.db`
const dbPath = join(getProfileDir(profile), 'state.db')
const db = new DatabaseSync(dbPath, { open: true, readOnly: true })
try {
const idx = loadAllSessions(db)
@@ -702,7 +703,7 @@ export async function findLatestExactSessionIdWithProfile(
if (!trimmed) return null
const { DatabaseSync } = await import('node:sqlite')
const dbPath = `${getProfileDir(profile)}/state.db`
const dbPath = join(getProfileDir(profile), 'state.db')
const db = new DatabaseSync(dbPath, { open: true, readOnly: true })
const loweredQuery = trimmed.toLowerCase()
const likePattern = buildLikePattern(loweredQuery)
@@ -1212,7 +1213,7 @@ export async function listSessionSummaries(source?: string, limit = 2000, profil
}
const { DatabaseSync } = await import('node:sqlite')
const dbPath = profile ? `${getProfileDir(profile)}/state.db` : sessionDbPath()
const dbPath = profile ? join(getProfileDir(profile), 'state.db') : sessionDbPath()
const db = new DatabaseSync(dbPath, { open: true, readOnly: true })
try {
@@ -1259,7 +1260,7 @@ export async function searchSessionSummariesWithProfile(
if (!trimmed) return []
const { DatabaseSync } = await import('node:sqlite')
const dbPath = `${getProfileDir(profile)}/state.db`
const dbPath = join(getProfileDir(profile), 'state.db')
const db = new DatabaseSync(dbPath, { open: true, readOnly: true })
const normalized = sanitizeFtsQuery(trimmed)
const prefixQuery = toPrefixQuery(normalized)