Merge branch 'main_bak'

This commit is contained in:
chenhg5 committed 2026-03-06 21:32:27 +08:00
commit ddff00577b
8 files changed
+117 -41

No files matched your search

+9
View File
@@ -10,6 +10,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
@@ -326,12 +327,20 @@ func scanSessionMeta(path string) (string, int) {
}
}
}
summary = stripXMLTags(summary)
summary = strings.TrimSpace(summary)
if utf8.RuneCountInString(summary) > 40 {
summary = string([]rune(summary)[:40]) + "..."
}
return summary, count
}
var xmlTagRe = regexp.MustCompile(`<[^>]+>`)
func stripXMLTags(s string) string {
return xmlTagRe.ReplaceAllString(s, "")
}
// GetSessionHistory reads the Claude Code JSONL transcript and returns user/assistant messages.
func (a *Agent) GetSessionHistory(_ context.Context, sessionID string, limit int) ([]core.HistoryEntry, error) {
homeDir, err := os.UserHomeDir()
+8 -5
View File
@@ -247,12 +247,15 @@ func main() {
spcfg.MinDeltaChars = *cfg.StreamPreview.MinDeltaChars
}
if cfg.StreamPreview.MaxChars != nil {
spcfg.MaxChars = *cfg.StreamPreview.MaxChars
}
engine.SetStreamPreviewCfg(spcfg)
}
spcfg.MaxChars = *cfg.StreamPreview.MaxChars
}
if cfg.StreamPreview.DisabledPlatforms != nil {
spcfg.DisabledPlatforms = cfg.StreamPreview.DisabledPlatforms
}
engine.SetStreamPreviewCfg(spcfg)
}
// Wire rate limiting
// Wire rate limiting
{
maxMsg := 20
windowSecs := 60
+5 -4
View File
@@ -44,10 +44,11 @@ type DisplayConfig struct {
// StreamPreviewConfig controls real-time streaming preview in IM.
type StreamPreviewConfig struct {
Enabled *bool `toml:"enabled"` // default true
IntervalMs *int `toml:"interval_ms"` // min ms between updates; default 1500
MinDeltaChars *int `toml:"min_delta_chars"` // min new chars before update; default 30
MaxChars *int `toml:"max_chars"` // max preview length; default 2000
Enabled *bool `toml:"enabled"` // default true
DisabledPlatforms []string `toml:"disabled_platforms,omitempty"` // platforms where preview is disabled (e.g. ["feishu"])
IntervalMs *int `toml:"interval_ms"` // min ms between updates; default 1500
MinDeltaChars *int `toml:"min_delta_chars"` // min new chars before update; default 30
MaxChars *int `toml:"max_chars"` // max preview length; default 2000
}
// RateLimitConfig controls per-session message rate limiting.
+17 -2
View File
@@ -972,7 +972,15 @@ func (e *Engine) processInteractiveEvents(state *interactiveState, session *Sess
if !quiet {
sp.freeze()
inputPreview := truncateIf(event.ToolInput, e.display.ToolMaxLen)
e.send(p, replyCtx, fmt.Sprintf(e.i18n.T(MsgTool), toolCount, event.ToolName, inputPreview))
// Use code block if content is long (>5 lines or >200 chars), otherwise inline code
lineCount := strings.Count(inputPreview, "\n") + 1
var formattedInput string
if lineCount > 5 || utf8.RuneCountInString(inputPreview) > 200 {
formattedInput = fmt.Sprintf("```\n%s\n```", inputPreview)
} else {
formattedInput = fmt.Sprintf("`%s`", inputPreview)
}
e.send(p, replyCtx, fmt.Sprintf(e.i18n.T(MsgTool), toolCount, event.ToolName, formattedInput))
}
case EventText:
@@ -1428,7 +1436,7 @@ func (e *Engine) cmdSwitch(p Platform, msg *Message, args []string) {
displayName = matched.Summary
}
e.reply(p, msg.ReplyCtx,
fmt.Sprintf("✅ Switched to: %s (%s, %d msgs)", displayName, shortID, matched.MessageCount))
e.i18n.Tf(MsgSwitchSuccess, displayName, shortID, matched.MessageCount))
}
// matchSession resolves a user query to an agent session. Priority:
@@ -1655,6 +1663,13 @@ func (e *Engine) cmdStatus(p Platform, msg *Message) {
}
}
// Quiet mode
quietStr := e.i18n.T(MsgQuietOffShort)
if state, ok := e.interactiveStates[msg.SessionKey]; ok && state.quiet {
quietStr = e.i18n.T(MsgQuietOnShort)
}
modeStr += e.i18n.Tf(MsgStatusQuiet, quietStr)
// Session info
s := e.sessions.GetOrCreateActive(msg.SessionKey)
sessionDisplayName := e.sessions.GetSessionName(s.AgentSessionID)
+50 -17
View File
@@ -215,6 +215,9 @@ const (
MsgStatusMode MsgKey = "status_mode"
MsgStatusSession MsgKey = "status_session"
MsgStatusCron MsgKey = "status_cron"
MsgStatusQuiet MsgKey = "status_quiet"
MsgQuietOnShort MsgKey = "quiet_on_short"
MsgQuietOffShort MsgKey = "quiet_off_short"
MsgModelDefault MsgKey = "model_default"
MsgModelListTitle MsgKey = "model_list_title"
@@ -257,8 +260,8 @@ const (
MsgDoctorTitle MsgKey = "doctor_title"
MsgDoctorSummary MsgKey = "doctor_summary"
MsgRestarting MsgKey = "restarting"
MsgRestartSuccess MsgKey = "restart_success"
MsgRestarting MsgKey = "restarting"
MsgRestartSuccess MsgKey = "restart_success"
MsgUpgradeChecking MsgKey = "upgrade_checking"
MsgUpgradeUpToDate MsgKey = "upgrade_up_to_date"
@@ -267,12 +270,12 @@ const (
MsgUpgradeSuccess MsgKey = "upgrade_success"
MsgUpgradeDevBuild MsgKey = "upgrade_dev_build"
MsgAliasEmpty MsgKey = "alias_empty"
MsgAliasEmpty MsgKey = "alias_empty"
MsgAliasListHeader MsgKey = "alias_list_header"
MsgAliasAdded MsgKey = "alias_added"
MsgAliasDeleted MsgKey = "alias_deleted"
MsgAliasNotFound MsgKey = "alias_not_found"
MsgAliasUsage MsgKey = "alias_usage"
MsgAliasAdded MsgKey = "alias_added"
MsgAliasDeleted MsgKey = "alias_deleted"
MsgAliasNotFound MsgKey = "alias_not_found"
MsgAliasUsage MsgKey = "alias_usage"
MsgNewSessionCreated MsgKey = "new_session_created"
MsgNewSessionCreatedName MsgKey = "new_session_created_name"
@@ -282,6 +285,8 @@ const (
MsgDeleteActiveDenied MsgKey = "delete_active_denied"
MsgDeleteNotSupported MsgKey = "delete_not_supported"
MsgSwitchSuccess MsgKey = "switch_success"
MsgBannedWordBlocked MsgKey = "banned_word_blocked"
MsgCommandDisabled MsgKey = "command_disabled"
MsgRateLimited MsgKey = "rate_limited"
@@ -296,11 +301,11 @@ const (
MsgRelayNotFound MsgKey = "relay_not_found"
MsgRelayNoTarget MsgKey = "relay_no_target"
MsgSearchUsage MsgKey = "search_usage"
MsgSearchError MsgKey = "search_error"
MsgSearchNoResult MsgKey = "search_no_result"
MsgSearchResult MsgKey = "search_result"
MsgSearchHint MsgKey = "search_hint"
MsgSearchUsage MsgKey = "search_usage"
MsgSearchError MsgKey = "search_error"
MsgSearchNoResult MsgKey = "search_no_result"
MsgSearchResult MsgKey = "search_result"
MsgSearchHint MsgKey = "search_hint"
)
var messages = map[MsgKey]map[Language]string{
@@ -316,11 +321,11 @@ var messages = map[MsgKey]map[Language]string{
LangChinese: "💭 %s",
},
MsgTool: {
LangEnglish: "🔧 Tool #%d: **%s**\n`%s`",
LangChinese: "🔧 工具 #%d: **%s**\n`%s`",
LangTraditionalChinese: "🔧 工具 #%d: **%s**\n`%s`",
LangJapanese: "🔧 ツール #%d: **%s**\n`%s`",
LangSpanish: "🔧 Herramienta #%d: **%s**\n`%s`",
LangEnglish: "🔧 **Tool #%d: %s**\n---\n%s",
LangChinese: "🔧 **工具 #%d: %s**\n---\n%s",
LangTraditionalChinese: "🔧 **工具 #%d: %s**\n---\n%s",
LangJapanese: "🔧 **ツール #%d: %s**\n---\n%s",
LangSpanish: "🔧 **Herramienta #%d: %s**\n---\n%s",
},
MsgExecutionStopped: {
LangEnglish: "⏹ Execution stopped.",
@@ -1147,6 +1152,27 @@ var messages = map[MsgKey]map[Language]string{
LangJapanese: "スケジュールタスク: %d (有効: %d)\n",
LangSpanish: "Tareas programadas: %d (habilitadas: %d)\n",
},
MsgStatusQuiet: {
LangEnglish: "Quiet mode: %s\n",
LangChinese: "安静模式: %s\n",
LangTraditionalChinese: "安靜模式: %s\n",
LangJapanese: "出力抑制モード: %s\n",
LangSpanish: "Modo silencioso: %s\n",
},
MsgQuietOnShort: {
LangEnglish: "ON",
LangChinese: "开启",
LangTraditionalChinese: "開啟",
LangJapanese: "ON",
LangSpanish: "Activado",
},
MsgQuietOffShort: {
LangEnglish: "OFF",
LangChinese: "关闭",
LangTraditionalChinese: "關閉",
LangJapanese: "OFF",
LangSpanish: "Desactivado",
},
MsgModelDefault: {
LangEnglish: "Current model: (not set, using agent default)\n",
LangChinese: "当前模型: (未设置,使用 Agent 默认值)\n",
@@ -1545,6 +1571,13 @@ var messages = map[MsgKey]map[Language]string{
LangJapanese: "🗑️ セッション削除:%s",
LangSpanish: "🗑️ Sesión eliminada: %s",
},
MsgSwitchSuccess: {
LangEnglish: "✅ Switched to: %s (%s, %d msgs)",
LangChinese: "✅ 已切换到:%s(%s,%d 条消息)",
LangTraditionalChinese: "✅ 已切換到:%s(%s,%d 則訊息)",
LangJapanese: "✅ 切り替え:%s(%s、%d件)",
LangSpanish: "✅ Cambiado a: %s (%s, %d mensajes)",
},
MsgDeleteActiveDenied: {
LangEnglish: "❌ Cannot delete the currently active session. Switch to another session first.",
LangChinese: "❌ 不能删除当前活跃会话,请先切换到其他会话。",
+19 -9
View File
@@ -3,25 +3,28 @@ package core
import (
"context"
"log/slog"
"strings"
"sync"
"time"
)
// StreamPreviewCfg controls the streaming preview behavior.
type StreamPreviewCfg struct {
Enabled bool // global toggle
IntervalMs int // minimum ms between updates (default 1500)
MinDeltaChars int // minimum new chars before sending an update (default 30)
MaxChars int // max preview length (default 2000)
Enabled bool // global toggle
DisabledPlatforms []string // platforms where streaming preview is disabled (e.g. "feishu")
IntervalMs int // minimum ms between updates (default 1500)
MinDeltaChars int // minimum new chars before sending an update (default 30)
MaxChars int // max preview length (default 2000)
}
// DefaultStreamPreviewCfg returns sensible defaults.
func DefaultStreamPreviewCfg() StreamPreviewCfg {
return StreamPreviewCfg{
Enabled: true,
IntervalMs: 1500,
MinDeltaChars: 30,
MaxChars: 2000,
Enabled: true,
DisabledPlatforms: nil,
IntervalMs: 1500,
MinDeltaChars: 30,
MaxChars: 2000,
}
}
@@ -73,11 +76,18 @@ func newStreamPreview(cfg StreamPreviewCfg, p Platform, replyCtx any, ctx contex
}
}
// canPreview returns true if the platform supports message updating.
// canPreview returns true if the platform supports message updating and is not disabled.
func (sp *streamPreview) canPreview() bool {
if sp.degraded || !sp.cfg.Enabled {
return false
}
// Check if platform is in disabled list
platformName := sp.platform.Name()
for _, disabled := range sp.cfg.DisabledPlatforms {
if strings.EqualFold(disabled, platformName) {
return false
}
}
_, ok := sp.platform.(MessageUpdater)
return ok
}
+1 -1
View File
@@ -10,13 +10,13 @@ require (
github.com/larksuite/oapi-sdk-go/v3 v3.5.3
github.com/line/line-bot-sdk-go/v8 v8.19.0
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/slack-go/slack v0.15.0
)
require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
)
+8 -3
View File
@@ -449,12 +449,16 @@ func buildReplyContent(content string) (msgType string, body string) {
b, _ := json.Marshal(map[string]string{"text": content})
return larkim.MsgTypeText, string(b)
}
// Dual rendering strategy (aligned with Claude-to-IM / Openclaw):
// - Code blocks / tables → interactive card (schema 2.0 markdown), smaller font but renders code properly
// - Other markdown → post with md tag, normal chat font size
// Three-tier rendering strategy:
// 1. Code blocks / tables → card (schema 2.0 markdown)
// 2. Many \n\n paragraphs (help, status, etc.) → post rich-text (preserves blank lines)
// 3. Other markdown → post md tag (best native rendering)
if hasComplexMarkdown(content) {
return larkim.MsgTypeInteractive, buildCardJSON(preprocessFeishuMarkdown(content))
}
if strings.Count(content, "\n\n") >= 4 {
return larkim.MsgTypePost, buildPostJSON(content)
}
return larkim.MsgTypePost, buildPostMdJSON(content)
}
@@ -937,3 +941,4 @@ func (p *Platform) extractPostParts(messageID string, post *postLang) ([]string,
}
return textParts, images
}