Merge branch 'main_bak'
This commit is contained in:
commit
ddff00577b
8 files changed
+117
-41
No files matched your search
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user