feat(web-ui): add pinned sessions and live monitor in Chat (#118)

* feat: add single-page live session monitor and chat pinning

* fix: restore full test green after main merge

* fix: use Array.from instead of Set spread for ts-node compatibility

[...new Set()] requires downlevelIteration which isn't enabled in
ts-node dev mode, causing sonic-boom crash on startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: ekko <fqsy1416@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Zhicheng Han
2026-04-22 02:09:58 +02:00
committed by GitHub
parent 83ad9642e2
commit 3f88553765
34 changed files with 2497 additions and 278 deletions
@@ -1,7 +1,39 @@
import * as hermesCli from '../../services/hermes/hermes-cli'
import { getConversationDetail, listConversationSummaries } from '../../services/hermes/conversations'
import { listSessionSummaries } from '../../services/hermes/sessions-db'
import { logger } from '../../services/logger'
function parseHumanOnly(value: unknown): boolean {
if (typeof value !== 'string') return true
return value !== 'false' && value !== '0'
}
function parseLimit(value: unknown): number | undefined {
if (typeof value !== 'string') return undefined
const parsed = parseInt(value, 10)
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined
}
export async function listConversations(ctx: any) {
const source = (ctx.query.source as string) || undefined
const humanOnly = parseHumanOnly(ctx.query.humanOnly)
const limit = parseLimit(ctx.query.limit)
const sessions = await listConversationSummaries({ source, humanOnly, limit })
ctx.body = { sessions }
}
export async function getConversationMessages(ctx: any) {
const source = (ctx.query.source as string) || undefined
const humanOnly = parseHumanOnly(ctx.query.humanOnly)
const detail = await getConversationDetail(ctx.params.id, { source, humanOnly })
if (!detail) {
ctx.status = 404
ctx.body = { error: 'Conversation not found' }
return
}
ctx.body = detail
}
export async function list(ctx: any) {
const source = (ctx.query.source as string) || undefined
const limit = ctx.query.limit ? parseInt(ctx.query.limit as string, 10) : undefined