Update CLI chat session bridge (#697)
* feat: add CLI chat sessions with Python agent bridge Introduce a new CLI chat mode that connects Web UI directly to Hermes Agent's AIAgent via a Python bridge subprocess and Socket.IO, bypassing the API Server /v1/responses path. Supports streaming, slash commands (/new, /undo, /retry, /branch, /compress, /save, /title), interrupt, and steer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat: update CLI chat session bridge * fix: extend agent bridge startup timeouts * docs: update bridge chat session design * feat: align bridge compression and provider registry * chore: bump version to 0.5.20 --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -129,14 +129,16 @@ function mapMessageRow(row: Record<string, unknown>): HermesMessageRow {
|
||||
export function createSession(data: {
|
||||
id: string
|
||||
profile?: string
|
||||
source?: string
|
||||
model?: string
|
||||
title?: string
|
||||
workspace?: string
|
||||
}): HermesSessionRow {
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const source = data.source || 'api_server'
|
||||
if (!isSqliteAvailable()) {
|
||||
return {
|
||||
id: data.id, profile: data.profile || 'default', source: 'api_server',
|
||||
id: data.id, profile: data.profile || 'default', source,
|
||||
user_id: null, model: data.model || '', title: data.title || null,
|
||||
started_at: now, ended_at: null, end_reason: null,
|
||||
message_count: 0, tool_call_count: 0,
|
||||
@@ -148,8 +150,8 @@ export function createSession(data: {
|
||||
const db = getDb()!
|
||||
db.prepare(
|
||||
`INSERT INTO ${SESSIONS_TABLE} (id, profile, source, model, title, started_at, last_active, workspace)
|
||||
VALUES (?, ?, 'api_server', ?, ?, ?, ?, ?)`,
|
||||
).run(data.id, data.profile || 'default', data.model || '', data.title || null, now, now, data.workspace || null)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
).run(data.id, data.profile || 'default', source, data.model || '', data.title || null, now, now, data.workspace || null)
|
||||
return getSession(data.id)!
|
||||
}
|
||||
|
||||
|
||||
@@ -565,6 +565,10 @@ function aggregateSessionDetail(
|
||||
}
|
||||
}
|
||||
|
||||
function chainOrderSql(ids: string[]): string {
|
||||
return ids.map((_, index) => `WHEN ? THEN ${index}`).join(' ')
|
||||
}
|
||||
|
||||
async function openSessionDb() {
|
||||
if (!SQLITE_AVAILABLE) {
|
||||
throw new Error(`node:sqlite requires Node >= 22.5, current: ${process.versions.node}`)
|
||||
@@ -598,7 +602,7 @@ export async function getSessionMessagesFromDb(sessionId: string): Promise<{
|
||||
const messageRows = db.prepare(`
|
||||
SELECT * FROM messages
|
||||
WHERE session_id = ?
|
||||
ORDER BY timestamp, id
|
||||
ORDER BY id
|
||||
`).all(sessionId) as Record<string, unknown>[]
|
||||
|
||||
return {
|
||||
@@ -622,11 +626,12 @@ export async function getSessionDetailFromDb(sessionId: string): Promise<HermesS
|
||||
|
||||
const ids = chain.map(session => session.id)
|
||||
const placeholders = ids.map(() => '?').join(', ')
|
||||
const orderSql = chainOrderSql(ids)
|
||||
const messageRows = db.prepare(`
|
||||
SELECT * FROM messages
|
||||
WHERE session_id IN (${placeholders})
|
||||
ORDER BY timestamp, id
|
||||
`).all(...ids) as Record<string, unknown>[]
|
||||
ORDER BY CASE session_id ${orderSql} ELSE ${ids.length} END, id
|
||||
`).all(...ids, ...ids) as Record<string, unknown>[]
|
||||
const messages = messageRows.map(mapMessageRow)
|
||||
return aggregateSessionDetail(chain, messages, sessionId)
|
||||
} finally {
|
||||
@@ -648,11 +653,12 @@ export async function getSessionDetailFromDbWithProfile(sessionId: string, profi
|
||||
|
||||
const ids = chain.map(session => session.id)
|
||||
const placeholders = ids.map(() => '?').join(', ')
|
||||
const orderSql = chainOrderSql(ids)
|
||||
const messageRows = db.prepare(`
|
||||
SELECT * FROM messages
|
||||
WHERE session_id IN (${placeholders})
|
||||
ORDER BY timestamp, id
|
||||
`).all(...ids) as Record<string, unknown>[]
|
||||
ORDER BY CASE session_id ${orderSql} ELSE ${ids.length} END, id
|
||||
`).all(...ids, ...ids) as Record<string, unknown>[]
|
||||
const messages = messageRows.map(mapMessageRow)
|
||||
return aggregateSessionDetail(chain, messages, sessionId)
|
||||
} finally {
|
||||
@@ -672,7 +678,7 @@ export async function getExactSessionDetailFromDbWithProfile(sessionId: string,
|
||||
const messageRows = db.prepare(`
|
||||
SELECT * FROM messages
|
||||
WHERE session_id = ?
|
||||
ORDER BY timestamp, id
|
||||
ORDER BY id
|
||||
`).all(sessionId) as Record<string, unknown>[]
|
||||
const messages = messageRows.map(mapMessageRow)
|
||||
return aggregateSessionDetail([requested], messages, sessionId)
|
||||
@@ -818,10 +824,6 @@ export async function getUsageStatsFromDb(
|
||||
const apiCallsExpr = tableHasColumn(db, 'sessions', 'api_call_count')
|
||||
? 'COALESCE(SUM(api_call_count), 0)'
|
||||
: '0'
|
||||
const sourceFilter = tableHasColumn(db, 'sessions', 'source')
|
||||
? " AND COALESCE(source, '') != 'api_server'"
|
||||
: ''
|
||||
|
||||
const totals = db.prepare(`
|
||||
SELECT
|
||||
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
||||
@@ -833,7 +835,7 @@ export async function getUsageStatsFromDb(
|
||||
COUNT(*) AS sessions,
|
||||
${apiCallsExpr} AS total_api_calls
|
||||
FROM sessions
|
||||
WHERE started_at > ?${sourceFilter}
|
||||
WHERE started_at > ?
|
||||
`).get(since) as Record<string, unknown> | undefined
|
||||
|
||||
if (!totals) return empty
|
||||
@@ -848,7 +850,7 @@ export async function getUsageStatsFromDb(
|
||||
COALESCE(SUM(reasoning_tokens), 0) AS reasoning_tokens,
|
||||
COUNT(*) AS sessions
|
||||
FROM sessions
|
||||
WHERE started_at > ?${sourceFilter} AND model IS NOT NULL
|
||||
WHERE started_at > ? AND model IS NOT NULL
|
||||
GROUP BY model
|
||||
ORDER BY COALESCE(SUM(input_tokens), 0) + COALESCE(SUM(output_tokens), 0) DESC
|
||||
`).all(since).map(row => ({
|
||||
@@ -871,7 +873,7 @@ export async function getUsageStatsFromDb(
|
||||
COUNT(*) AS sessions,
|
||||
COALESCE(SUM(COALESCE(actual_cost_usd, estimated_cost_usd, 0)), 0) AS cost
|
||||
FROM sessions
|
||||
WHERE started_at > ?${sourceFilter}
|
||||
WHERE started_at > ?
|
||||
GROUP BY date
|
||||
ORDER BY date ASC
|
||||
`).all(since).map(row => ({
|
||||
|
||||
Reference in New Issue
Block a user