Commit Graph

222 Commits

Author SHA1 Message Date
ekko 74a3bd4acf feat(chat-run-socket): record usage from Hermes session in syncFromHermes
Call updateUsage() in syncFromHermes to record token usage data from Hermes
ephemeral session to local DB. This ensures accurate usage tracking including:
- input_tokens
- output_tokens
- cache_read_tokens
- cache_write_tokens
- reasoning_tokens
- model

The usage data comes from the Hermes session detail which contains
accurate token counts from the upstream LLM provider.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:46:53 +08:00
ekko dbe6c30d8e fix(chat-run-socket): exclude last user message instead of comparing timestamps
Replace timestamp-based filtering (m.timestamp !== now) with position-based filtering.
This is more reliable because:
1. No precision issues with second-level timestamps
2. Handles edge cases where multiple messages have the same timestamp
3. Works correctly even if there's a small time difference between now and DB record

New logic:
1. Filter valid messages first
2. Find the last user message from the end
3. Exclude it from history (it's the one we just added in handleRun)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:40:27 +08:00
ekko 541d296d58 fix(chat-run-socket): exclude current user message from conversation_history
When loading conversation_history from DB, exclude the message that was just
added (with timestamp === now) to avoid duplication in the upstream request.

Since user messages are now written immediately to DB on run start,
we need to filter them out when building history for the upstream call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:39:18 +08:00
ekko 9ddf595111 fix(chat-run-socket): write user message to DB immediately on run start
Changes:
- Move addMessage() call to handleRun start, before conversation_history loading
- Remove delayed addMessage() after history loading (no longer needed)
- Remove useLocalSessionStore() check - always write user message immediately
- Simplify syncFromHermes to always skip user messages

This ensures user messages are persisted immediately when a run starts,
improving reliability and user experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:37:20 +08:00
ekko 7540150bf8 fix(chat-run-socket): sync user messages from Hermes when not using local store
When using Hermes state.db (not local store), user messages were never written
to local DB because:
1. handleRun only calls addMessage() when useLocalSessionStore() is true
2. syncFromHermes was filtering out all user messages

Fix: Conditionally sync user messages based on store mode:
- Local store mode: skip user messages (already written in handleRun)
- Hermes state.db mode: sync all messages including user messages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:35:23 +08:00
ekko 485a028743 fix(chat-run-socket): use profile-specific upstream from GatewayManager
Replace hardcoded UPSTREAM env var with dynamic lookup via gatewayManager.getUpstream(profile).
This ensures each profile connects to its own gateway instance with correct port and host.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:32:27 +08:00
ekko edfbfff6e8 feat(usage): enhance usage tracking with cache tokens and model info
Backend changes:
- Add cache_read_tokens, cache_write_tokens, reasoning_tokens, model fields
- Migrate from session_id PRIMARY KEY to separate id column with session_id index
- Update updateUsage() to accept data object instead of separate params
- Add migration logic to preserve existing data during schema upgrade
- Add UsageRecord interface for type safety

Frontend changes:
- Update UsageView to display new token types (cache, reasoning)
- Update usage store to handle new usage structure
- Update sessions API to fetch enhanced usage data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:29:36 +08:00
ekko 3cb215a92b fix(chat-run-socket): defer addMessage call to avoid duplicate in conversation_history
- Move `const now` outside session_id block for broader scope
- Defer addMessage() call until after conversation_history is loaded
- This prevents the user message from appearing twice in history
- Remove updateUsage call from calcAndUpdateUsage to avoid double counting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-29 10:28:01 +08:00
ekko 09441f0401 feat(chat): add streaming spinner to session list and hide mode toggle
- Show rotating loading icon before session title when actively streaming
- Hide chat/live mode toggle buttons
- Fix isSessionLive to only return true during actual streaming
- Remove unused LIVE_BADGE_WINDOW_MS constant
- Fix resumeSession callback type to include inputTokens/outputTokens
- Remove unused fetchSessionUsageSingle import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-29 07:46:49 +08:00
ekko 5fa1fc60b9 feat(sessions): add local session store support to conversation endpoints
Live mode (ConversationMonitorPane) now reads from local session-store
when useLocalSessionStore() is enabled, instead of always hitting
Hermes state.db.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-29 00:38:18 +08:00
ekko 95fc6692ec fix(chat): use totalTokens for compression.started token_count
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-29 00:34:37 +08:00
ekko e3c5352d69 fix(chat): unify token calculation via calcAndUpdateUsage and fix session search
- Make calcAndUpdateUsage the single entry point for all inputTokens/outputTokens
  calculation, always loading from DB with snapshot awareness
- Remove overrideInputTokens parameter; compression path calls calcAndUpdateUsage
  before and after compress, letting DB state be the source of truth
- Add inputTokens + outputTokens as totalTokens for compression threshold comparison
- Fix session search to match message content (not just title), return snippets
  and matched_message_id via two-step query
- Fall back to preview for session title display when title is null
- Remove isStreaming guard from newChat() to allow creating sessions anytime

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-29 00:20:13 +08:00
ekko 4ddd2c12a5 feat(chat): use ephemeral Hermes session per run and sync tool results from state.db
- Generate ephemeral session_id for each Hermes run, sync complete data
  (including tool results) from Hermes state.db after run completion
- Resolve tool_name from assistant message's tool_calls JSON (Hermes
  stores tool_name as NULL in its messages table)
- Fall back to preview as title in mapSessionRow when title is empty
- Set preview from first user message when creating local sessions
- Enqueue ephemeral sessions for deferred deletion via gc_pending_session_deletes
- Fix enqueueEphemeralDelete: use top-level import instead of require,
  set next_attempt_at to now (was 0, preventing drain)
- Remove isStreaming guard from newChat() to allow creating sessions anytime

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 22:54:45 +08:00
ekko 94d581a112 feat(sessions): add local session store with SessionDeleter and config toggle
- Add session-store.ts: self-built SQLite CRUD for sessions/messages
- Add session-deleter.ts: timer-based singleton for deferred session deletion
- Add SESSION_STORE env var (local|remote) to toggle between local SQLite and Hermes CLI
- Update sessions controller to branch on useLocalSessionStore()
- Update chat-run-socket to persist messages to local DB on run completion
- Improve SSE event handling: tool_call_id capture, finish_reason tracking
- Update group-chat to use SessionDeleter instead of direct CLI delete
- Update context-compressor to enqueue compression sessions for deferred deletion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 22:09:47 +08:00
ekko d4c0596204 fix(chat): remove upstream usage values and pre-send inputTokens overwrite
- Remove all evt.usage/parsed.usage references, only use local countTokens
- Remove pre-send inputTokens calculation that was overwriting resume value
  with compressed context, causing incorrect context drop (70k → 40k)
- run.completed now recalculates inputTokens with current snapshot + full
  messages including new ones from this run

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 20:32:40 +08:00
ekko acf1da16f6 feat(chat): add server-side sessionMap with message tracking and resume-based loading
- Add sessionMap to ChatRunSocket consolidating activeRuns + sessionStates,
  tracking messages, isWorking status, events, and token usage per session
- Load messages from DB on resume when not in memory, return via resumed event
- Track streaming messages (user/assistant/tool/reasoning) into sessionMap
  so reconnecting clients get full message history without HTTP fetch
- Calculate token usage locally with countTokens, snapshot-aware for compressed sessions
- Add usage.updated event broadcast on run.completed with recalculated tokens
- Replace HTTP fetchSession with Socket.IO resume for message loading
- Add serverWorking state to drive streaming indicator from server isWorking status
- Clear events immediately on run completion instead of delayed cleanup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 20:12:39 +08:00
ekko cacf8a2bac feat(chat): replace HTTP+SSE with Socket.IO for chat runs and add context compression
- Replace HTTP POST + SSE streaming with Socket.IO /chat-run namespace
  for decoupled message handling that survives client disconnect/refresh
- Add SQLite-backed context compression with snapshot-based incremental updates
- Unify server-side session state tracking (completedSessions + compressingSessions
  → sessionStates) for reliable state replay on reconnect
- Filter compress_ sessions from session list queries
- Add compression snapshot store with proper snake_case→camelCase column aliases
- Delete temporary compress_ sessions after compression completes
- Change compressed summary role from 'system' to 'user'
- Add compression.started/completed events to frontend chat store

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-28 16:44:34 +08:00
ekko 88ca3c865d fix(chat): prevent duplicate user messages in conversation history (#258)
Build conversation_history before adding the new user message to the
session, so the message is sent only via `input` and not duplicated
in `conversation_history` as well.

Closes #257

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-27 17:18:12 +08:00
356252190-star 5193dbc49e feat: add copy bubble button to copy entire message content (#245)
- Hover over any message to reveal a copy icon button
- Click to copy the full message text to clipboard
- Shows success/error toast notification
- Skips tool messages (no copy button shown)
- i18n support for all 8 languages (EN/ZH/DE/ES/FR/JA/KO/PT)
- Dark mode compatible styling

Co-authored-by: 356252190-star <356252190-star@users.noreply.github.com>
2026-04-26 22:59:43 +08:00
ww 610f3eb9d0 feat(copilot): integrate GitHub Copilot provider with dynamic model list / 集成 GitHub Copilot provider 与动态模型列表 (#239)
* feat(copilot): integrate GitHub Copilot provider with dynamic model list

集成 GitHub Copilot provider 与动态模型列表

EN:
- New copilot-models service: fetch live model list from GitHub /models API
  - Filter noise IDs (accounts/, text-embedding, rerank prefixes)
  - Pass through preview/disabled metadata to frontend
  - Cache isolated per OAuth token (FNV-1a hash key) to prevent cross-account leak
  - Multi-source token resolution: env > apps.json > gh CLI
- ModelSelector renders PREVIEW (orange) and UNAVAILABLE (gray, non-selectable)
  badges with tooltips
- ProviderFormModal exposes Copilot OAuth login entry
- New CopilotLoginModal component: guides gh auth login device flow
- ProviderCard hides delete button for OAuth-only builtin providers
  (copilot/codex/nous) since their credentials live outside auth.json

ZH:
- 新增 copilot-models 服务:从 GitHub /models live API 拉取模型列表
  - 噪音 ID 过滤(accounts/、text-embedding、rerank 前缀)
  - preview/disabled 元数据透传至前端
  - 缓存按 OAuth token 隔离(FNV-1a hash key),避免切换 profile 串账号
  - 多源 token 解析优先级:env > apps.json > gh CLI
- ModelSelector 渲染 PREVIEW(橙色)/ UNAVAILABLE(灰色、不可选)badge,附 tooltip
- ProviderFormModal 提供 Copilot OAuth 登录入口
- 新增 CopilotLoginModal 组件:引导 gh auth login 设备流程
- ProviderCard 对 OAuth-only builtin(copilot/codex/nous)隐藏删除按钮
  其凭证不在 auth.json,删除按钮原本无效

Tests / 测试: new copilot-models suite (cache isolation, noise filter,
preview/disabled passthrough) + copilot-login-modal — 24/24 passed.
Pre-existing sessions-db-lineage failure on upstream/main is unrelated.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* refactor(copilot): switch to explicit opt-in per maintainer feedback

回应 PR #239 review:上一版会自动把系统级 GitHub OAuth 凭证(VS Code Copilot
插件、gh CLI 登录态)当作 hermes provider 拉到列表里,对未在 hermes 中注册过
Copilot 的用户造成困扰。本次改为显式 opt-in:用户必须通过 Add Provider 主动添加,
删除时按 token 来源决定是否清 ~/.hermes/.env,并避免误清理 VS Code / gh CLI 用户的
全局凭证。

Address PR #239 review feedback. Previously Copilot would silently appear in the
provider list whenever the host had any GitHub OAuth token (VS Code plugin, gh CLI
login). This caused confusion for users who never explicitly registered Copilot
in hermes. Now Copilot requires explicit opt-in via Add Provider; on delete we only
clear ~/.hermes/.env when the token actually originated there, leaving VS Code /
gh CLI credentials untouched.

What changed
- 新增 ~/.hermes-web-ui/config.json 的 copilotEnabled flag 控制可见性
- 即便能解析到 token,未启用时也不在列表中显示
- resolveCopilotOAuthTokenWithSource 区分 token 来源(env / gh-cli / apps-json)
- ProviderFormModal 增加 GitHub Copilot 入口;无 token 时进 device flow modal
- CopilotLoginModal 重写为 in-app device flow 状态机(不再要求用户在终端跑 gh)
- 删除 Copilot 时仅 source='env' 才清 ~/.hermes/.env,并自动 fallback 默认模型
- 老用户升级兼容:若 default 仍指向已禁用的 copilot,后端清空 default 让前端兜底

API
- POST /api/hermes/copilot-auth/check-token
- POST /api/hermes/copilot-auth/enable
- POST /api/hermes/copilot-auth/disable
- POST /api/hermes/copilot-auth/start  (device flow)
- POST /api/hermes/copilot-auth/poll   (device flow)

Tests
- tests/server/copilot-auth-controller.test.ts (11 cases)
- tests/server/copilot-device-flow.test.ts (12 cases)
- tests/client/copilot-login-modal.test.ts 重写覆盖状态机

Follow-ups (留作后续 PR)
- device flow session 未绑定 profile,登录中切 profile 会写到错的 .env
- copilot device-code 接口的 expires_in 字段未使用,硬编码 15 分钟超时

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 22:51:35 +08:00
356252190-star b07a8fc76f fix: add X-Hermes-Session-Id header to preserve tool context across turns (#240)
Fixes #237

When web-ui sends a chat message, it includes conversation_history with only
user and assistant messages — tool calls and tool results are filtered out.
Meanwhile, it sends session_id in the request body but does NOT pass the
X-Hermes-Session-Id header.

Without the header, the gateway falls back to using the incomplete
conversation_history from the body, so the AI agent loses context about
previous tool calls, especially after idle periods.

Fix: Pass X-Hermes-Session-Id as a request header so the gateway loads
complete session history from the session DB (including tool messages).

Co-authored-by: 356252190-star <356252190-star@users.noreply.github.com>
2026-04-26 17:47:39 +08:00
ekko c936585174 chore: bump version to 0.4.9 and remove session switch transition overlay (#235)
- Remove isTransitioning overlay that caused white screen on session switch
- Simplify scroll logic: just scrollToBottom() on session change
- Remove changelog entry for removed transition feature

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 13:59:01 +08:00
ekko 0446385a37 chore: add v0.4.8 changelog and improve scroll behavior (#234)
* chore: add v0.4.8 changelog and improve scroll behavior

- Add v0.4.8 changelog entries for recent fixes
- Fix forced scroll to bottom when returning from other tabs
- Smooth session switch with loading transition overlay
- Auto-scroll to bottom after mermaid diagram rendering
- Bump version to 0.4.8

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

* fix: replace blob URLs with persistent download URLs and add image preview

- Replace blob URLs with /api/hermes/download URLs after upload so
  attachments survive page refresh
- Add click-to-preview overlay for image attachments
- Move upload directory from /tmp to ~/.hermes-web-ui/upload

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

* fix: replace findLast with reverse+find for ES2022 compat

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

* chore: bump TypeScript lib target from ES2022 to ES2023

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

* docs: add changelog entries for blob URL fix, image preview and upload dir

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 13:28:08 +08:00
ekko bc66998650 chore: remove old changelog entries (v0.4.1, v0.4.2, v0.4.3) (#233)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 12:06:36 +08:00
Zhicheng Han ed12e958d0 fix: report web ui version in dev health checks (#231) 2026-04-26 10:55:08 +08:00
ekko 8db644496e fix(sessions): optimize N+1 queries and fix search 500 on non-CJK input (#230)
Replace per-session SQL queries in listSessionSummaries/searchSessionSummaries
with a single bulk load via loadAllSessions() + in-memory map traversal,
eliminating N+1 round-trips. Fix search 500 error for pure numbers,
English letters, and other FTS5-incompatible input by extending the
catch fallback beyond CJK-only to all FTS query failures.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-26 10:44:51 +08:00
Zhicheng Han 2053da1c10 fix: use Hermes session endpoint for login token validation (#227)
Co-authored-by: Han <hanzckernel@users.noreply.github.com>
2026-04-26 10:41:43 +08:00
Zhicheng Han 1e0dc69840 fix(markdown): 安全渲染 Mermaid code fence (#229)
* fix(markdown): render mermaid fences safely

* chore: drop local smoke screenshot asset
2026-04-26 10:38:05 +08:00
Zhicheng Han b68ba8bcb9 fix chat session lineage visibility (#228) 2026-04-26 10:29:17 +08:00
Zhicheng Han f1a6d97c8b fix(sessions): harden compressed session lineage projection (#226)
- Project compressed roots to their continuation tip in session lists.
- Search title/content candidates through logical compression lineage.
- Hydrate detail views along the requested continuation branch while preserving requested ids.
- Scope model-context cache lookup by provider to avoid same-name cross-provider matches.
- Add regression coverage for lineage and provider lookup behavior.
2026-04-26 10:10:01 +08:00
Zhicheng Han d0bd09ae57 fix: 修复嵌套 markdown fence 导致的渲染截断 (#222)
* fix: keep nested markdown fences rendered

* fix: prevent thinking placeholder leaks

* fix: normalize nested markdown example fences
2026-04-26 09:39:49 +08:00
Zhicheng Han d2ab2bca08 fix(sessions): 修复压缩续接会话详情为空 (#218)
Session detail now prefers DB-backed reconstruction for compressed continuation chains, with CLI fallback preserved and pending-deletion guard covered by tests.
2026-04-25 22:23:33 +08:00
ekko 3993dda013 docs: add v0.4.7 changelog and remove unused assets (#216)
* fix: align group chat room sidebar background with session list

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

* docs: add v0.4.7 changelog and remove v0.4.3/v0.4.1 entries

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

* chore: remove unused video.mp4 asset

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 20:39:53 +08:00
ekko 94e6d7bbb9 fix: align group chat room sidebar background with session list (#214)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 20:26:43 +08:00
ekko bc3e1b2971 docs: update README screenshots and bump version to 0.4.7 (#213)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 20:19:48 +08:00
ekko bc9b43f06a fix: model switch reset, custom provider resolution and base_url_env cleanup (#212)
* fix: reset entire config.model on model switch

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

* fix: resolve custom provider from CLI config and clean base_url_env on delete

- When config.model.provider is "custom" (set by hermes CLI), match
  base_url + model against custom_providers to resolve custom:name
- Clear base_url_env from .env when deleting a builtin provider

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 19:38:46 +08:00
Zhicheng Han 00c6b9532c fix: make context length lookup provider-aware (#207) 2026-04-25 18:57:22 +08:00
ww 97fbfac2d6 fix(chat): 检测并提示被静默吞掉的 run 错误 (#206)
当上游 hermes-agent 的 agent 层捕获了 LLM 错误(如无效 API key、
模型不被 provider 支持)后,gateway 仍会发送 run.completed 事件,
但 output 为空、usage 为零。前端因此误认为成功,UI 完全无任何
错误提示。

本次修复在 chat store 的 SSE 处理中加入两个 per-run 标志:
- runProducedAssistantText:仅 reasoning/thinking/message.delta 置位
- runHadToolActivity:仅 tool.started/tool.completed 置位

run.completed 时:
1. 兜底渲染:若本次 run 没有 assistant 文本但 evt.output 非空,
   将 output 作为 assistant 消息落盘(兼容某些只在 completed
   事件给出最终文本的 provider)。
2. 静默错误检测:若三者均不成立(无 assistant 文本 + 无 tool
   活动 + output 空),追加一条 system 消息提示用户检查
   hermes-agent 日志。usage=0 不再作为强条件,避免某些 provider
   或本地模型不返回 usage 时的误报。

api/hermes/chat.ts 给 RunEvent 增加 output 字段类型。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 16:21:07 +08:00
ekko 65e712edfc fix: group chat UX polish and model switch cleanup (#205)
* fix(chat): clamp context remaining tokens to 0 instead of showing negative

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

* fix: group chat mention popup position, timestamp style, and model switch cleanup

- Move @ mention popup above input to avoid blocking the textarea
- Fix .msg-time scoping (was nested inside .msg-header, now top-level)
- Reduce timestamp opacity and set to 12px for subtler display
- Clean up stale base_url/api_key from config.yaml on model switch

Closes #204

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

* fix: remove unused variables in GroupChatInput

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 16:16:11 +08:00
ekko a2c57ccaf0 fix(chat): clamp context remaining tokens to 0 instead of showing negative (#202)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-25 14:23:37 +08:00
ww 4bdcaa6258 feat: add Alibaba Coding Plan provider with .env base_url support (#200)
* feat(providers): 新增 Alibaba Cloud (Coding Plan) 内置 provider

对齐 hermes-agent 上游 PR #15045(commit 727d1088),新增
alibaba-coding-plan provider,鉴权使用 ALIBABA_CODING_PLAN_API_KEY
环境变量,base_url 可通过 ALIBABA_CODING_PLAN_BASE_URL 覆盖。

默认 base_url 使用国际版端点 coding-intl.dashscope.aliyuncs.com/v1,
与上游 auth.py:255 保持一致。中国大陆 DashScope 账号
(dashscope.aliyun.com 颁发的 sk-sp-* 密钥)需要通过
ALIBABA_CODING_PLAN_BASE_URL=https://coding.dashscope.aliyuncs.com/v1
(不带 -intl)覆盖,因为 -intl 端点对该类密钥返回 HTTP 401。
该差异在源码注释中已说明。

模型列表覆盖 8 个 Coding Plan 支持的模型:qwen3.5-plus、
qwen3-max-2026-01-23、qwen3-coder-next/plus、glm-5、glm-4.7、
kimi-k2.5、MiniMax-M2.5(基于实测可用列表)。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(providers): Alibaba Coding Plan 添加国内/国际区域切换

在 ProviderFormModal 中针对 alibaba-coding-plan preset 增加一个
"区域"字段,可在国际版(coding-intl)与中国大陆(coding,无 -intl)
两个端点之间切换,切换时自动更新 base_url。

默认选中国际版以对齐上游 hermes-agent 默认值。中国大陆 DashScope
账号(dashscope.aliyun.com 颁发的 sk-sp-* 密钥)只需在表单里点一下
"中国大陆"即可,无需手动改 base_url 或设环境变量。

8 个 locale(zh/en/de/es/fr/ja/ko/pt)都补全了 region/regionIntl/
regionCn 三个 i18n key。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(providers): builtin provider 列表优先读取 base_url env override

之前服务端 getAvailable 在渲染 builtin provider 列表时直接
用 PROVIDER_PRESETS 里的默认 base_url,忽略了用户保存到 .env
的 base_url override。这导致用户在 Alibaba Coding Plan 选了"中国
大陆"保存后,列表里仍然显示国际版 URL。

修复:envMapping.base_url_env 如果存在且 .env 中有值,优先
使用该值;否则 fallback 到 preset 默认。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 14:00:07 +08:00
356252190-star 12ae840234 fix: skip remote profiles in startAll() to prevent startup hang (#197) 2026-04-25 09:22:01 +08:00
ww 369001824e feat(chat): 支持思考块实时流式与历史展示 (#191)
* feat: 添加文件下载功能,支持多 Terminal Backend

实现基于 FileProvider 抽象的文件下载能力,支持 local、Docker、SSH、
Singularity 四种 backend。

主要变更:
- 新增 FileProvider 接口及四种后端实现(含 SSH 命令注入防护)
- 新增 GET /api/hermes/download 下载路由(含 MIME 类型检测)
- 前端 Markdown 文件链接拦截下载 + 附件下载按钮
- 中英文 i18n 翻译
- 更新 README、CLAUDE.md 和设计文档

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: 添加文件浏览器与下载功能,支持目录浏览、文件编辑、预览和上传

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* build: add prepare script so 'npm install git+url' auto-builds dist/

Allows installing this package directly from git without a pre-built dist/.
When cloned via npm, prepare runs 'npm run build' if dist/ is missing,
producing the artifacts declared in the files[] field before packing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: use clipboard fallback for non-secure HTTP contexts

navigator.clipboard is undefined on HTTP intranet deployments (only
available in secure contexts). The previous synchronous calls threw
silently and the success toast still fired, making 'copy' actions
appear broken.

- Add packages/client/src/utils/clipboard.ts with execCommand fallback
  via a hidden textarea
- Use the helper in FileContextMenu (copy file path), CodexLoginModal
  (copy user code), NousLoginModal (copy user code), ChatPanel (copy
  session id)
- Each call now awaits the result and shows success/failure based on
  the actual outcome

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* i18n: backfill files/download translations for de, es, fr, ja, ko, pt

Add nav.files, files.* (39 keys), and download.* (9 keys) so the file
browser UI is fully localized in these six locales instead of falling
back to English.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(files): close preview when navigating or affected file changes

Opening a preview and then navigating directories, deleting the
previewed file, or renaming it left the preview pane stuck on stale
content because previewFile was never cleared.

- stores/hermes/files.ts:
  - fetchEntries clears previewFile on path change (in-place refresh
    keeps the preview).
  - deleteEntry / renameEntry clear preview/editor state when the
    affected entry matches the previewed/edited file or its parent.
  - Add isAffected(target, changed, isDir) helper.
- components/hermes/files/FilePreview.vue: replace the misleading
  common.cancel close button with a dedicated files.closePreview key
  plus an X icon and quaternary style.
- i18n: add files.closePreview to all 8 locales.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: 清理已完成功能的计划与设计文档

文件浏览器与文件下载功能均已被上游合并,对应的开发计划
与设计稿不再需要在 fork 中保留:
- plans/2025-07-20-file-browser.md
- plans/2026-04-20-file-download.md
- specs/2025-07-20-file-browser-design.md
- specs/2026-04-20-file-download-design.md

清理后本 fork 与 upstream/main 代码层面完全对齐。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: 添加 thinking 块分离与折叠展示设计稿(#164)

针对上游 issue #164,设计 assistant 消息中 <think>/<thinking>/<reasoning>
标签的识别、分离与可折叠展示方案。

关键决策(经 rubber-duck 审查修订):
- 不修改 Message.content 与持久化字段,确保 localStorage 向前兼容
- 耗时摘要改为纯运行时派生(store 内 Map),避免刷新/重连丢失
- 首版即实现代码块保护,避免误识别
- 流结束时未闭合标签降级为正文,防止吞答案
- 解析 computed 与 duration interval 分离,规避性能风险
- 解析器放置 packages/client/src/utils/ 避免反向依赖
- 显式不支持同名嵌套(罕见场景文档化)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: 添加 thinking 块分离与折叠实施计划(#164)

12 Task TDD 计划:
- Task 1-7:utils/thinking-parser.ts 纯函数模块 + 单元测试
- Task 8-9:chat store thinkingObservation Map 接入 SSE
- Task 10:8 语言 i18n 新增 6 条 key
- Task 11:MessageItem.vue 渲染折叠 UI + SCSS
- Task 12:构建/测试/手动验证/推送

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(thinking-parser): 首个闭合 <think> 标签拆分

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(thinking-parser): 覆盖多段/变体标签/大小写/空输入

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(thinking-parser): 流式 pending 与终止态降级

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(thinking-parser): 代码块保护避免误识别伪标签

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* test(thinking-parser): 同名嵌套与 chunk 边界行为

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(thinking-parser): countThinkingChars 辅助函数

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(thinking-parser): detectThinkingBoundary 边界检测

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(chat-store): 新增 thinkingObservation 运行时 Map

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(chat-store): message.delta 写入 thinking 边界 + switchSession 清理

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* i18n: 新增 thinking 块 6 条 key(8 语言)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(chat): MessageItem 渲染 thinking 折叠区

- 复用 tool-line 风格 chevron
- 两条响应链:parse computed + duration interval
- 流式+pending 强制展开
- show_reasoning 控制默认态

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(chat): 支持思考块实时流式与历史展示

- 扩展 Message 接口增加 reasoning 字段,mapHermesMessages 从
  HermesMessage.reasoning 透传历史会话的思考内容。
- RunEvent 类型新增 text 字段,chat store 处理三个新 SSE 事件:
  reasoning.delta / thinking.delta / reasoning.available。
- 思考时长观察:仅在 reasoning.delta 累积时记录起始时间戳,
  reasoning.available 时记录结束时间戳;无实时 delta 时不显示时长。
- MessageItem 采用双源渲染(reasoning 字段优先,<think> 标签作
  fallback),duration > 0 才展示耗时。
- 新增 3 条单测覆盖三个 SSE 事件;测试 32/32 通过。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(chat): reasoning 块不再短暂展示正文

根因:上游 hermes-agent run_agent.py:11275 在每次模型响应结束时用
assistant content[:500] 作为 reasoning.available 的 preview 负载,
致使 Web UI 把正文写入 last.reasoning,思考块短暂显示正文直到会话
轮询/刷新从 session DB 读回正确的 reasoning 字段。

修复:
- reasoning.available 事件不再写入 last.reasoning,仅用于标记计时
  结束(noteReasoningEnd);真实推理由 reasoning.delta 或会话 DB
  提供
- 新增 scrubBuggyReasoningInCache:hydration 时治愈 localStorage 里
  已被污染的 assistant 消息(reasoning == content 或前缀时丢弃)
- 两个 cache 加载入口(loadSessions / switchSession)均接入 scrubber

测试:新增 4 条单测,全套 280/280 通过。

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 08:46:50 +08:00
ekko c1e72942ad fix(docker): skip prepare script during npm install (#196)
#142 added a `prepare` script that triggers `npm run build` when
dist/ is missing. During Docker build, `npm install` runs before
source files and tsconfig.json are copied, causing build failure.
Use --ignore-scripts to defer build to the explicit step.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 23:52:54 +08:00
ekko 833fe06f92 fix: restore fetchAvailableModels to fix provider lost as custom (#194)
* fix: restore fetchAvailableModels to fix provider lost as custom (#174)

#174 replaced fetchAvailableModels with fetchConfigModels, but
config/models response lacks default_provider, label, base_url and
api_key fields. This caused selectedProvider to always be empty,
making all models appear as "custom" in the model selector.

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

* chore: bump version to 0.4.6

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
v0.4.6
2026-04-24 23:30:34 +08:00
ekko 70ed0e0dc2 revert: harden Hermes stream recovery around tool-call boundaries (#189) (#192)
Reverts #189 due to reported bugs.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 22:18:32 +08:00
ekko bff6f844e6 docs: add group chat feature to README (#190)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 21:47:10 +08:00
Zhicheng Han 009acc1c28 fix: harden Hermes stream recovery around tool boundaries (#189) 2026-04-24 21:42:42 +08:00
ekko edd41e6eb7 fix: group chat mobile UX and UI polish (#188)
* fix: group chat UI background colors and replace console.log in context-engine

- Set message list background to $bg-card to match single chat
- Set status-bar background to transparent
- Replace all console.log/warn with logger in context-engine compressor

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

* fix: group chat mobile UX improvements

- Add backdrop overlay for mobile sidebar with tap-to-close
- Auto-collapse sidebar on room select in mobile
- Move timestamp below message bubble
- Widen msg-body max-width to 85% to match single chat
- Add left padding to chat-header to avoid hamburger overlap

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
v0.4.5
2026-04-24 21:28:06 +08:00
ekko 3df369afc0 chore: add changelog for v0.4.5 (#187)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 20:54:06 +08:00