This commit is contained in:
OpenClaw-User 2026-03-14 17:07:39 +08:00
commit 8268840d51
5 changed files with 73 additions and 8 deletions

View file

@ -458,7 +458,7 @@ func (cb *ContextBuilder) LoadBootstrapFiles() string {
//
// See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
// See: https://platform.openai.com/docs/guides/prompt-caching
func (cb *ContextBuilder) buildDynamicContext(channel, chatID string) string {
func (cb *ContextBuilder) buildDynamicContext(channel, chatID, chatName string) string {
now := time.Now().Format("2006-01-02 15:04 (Monday)")
rt := fmt.Sprintf("%s %s, Go %s", runtime.GOOS, runtime.GOARCH, runtime.Version())
@ -466,8 +466,12 @@ func (cb *ContextBuilder) buildDynamicContext(channel, chatID string) string {
fmt.Fprintf(&sb, "## Current Time\n%s\n\n## Runtime\n%s", now, rt)
if channel != "" && chatID != "" {
if chatName != "" {
fmt.Fprintf(&sb, "\n\n## Current Session\nChannel: %s\nChat ID: %s (%s)", channel, chatID, chatName)
} else {
fmt.Fprintf(&sb, "\n\n## Current Session\nChannel: %s\nChat ID: %s", channel, chatID)
}
}
return sb.String()
}
@ -477,7 +481,7 @@ func (cb *ContextBuilder) BuildMessages(
summary string,
currentMessage string,
media []string,
channel, chatID string,
channel, chatID, chatName string,
) []providers.Message {
messages := []providers.Message{}
@ -493,7 +497,7 @@ func (cb *ContextBuilder) BuildMessages(
staticPrompt := cb.BuildSystemPromptWithCache()
// Build short dynamic context (time, runtime, session) — changes per request
dynamicCtx := cb.buildDynamicContext(channel, chatID)
dynamicCtx := cb.buildDynamicContext(channel, chatID, chatName)
// Compose a single system message: static (cached) + dynamic + optional summary.
// Keeping all system content in one message ensures every provider adapter can

View file

@ -82,7 +82,7 @@ func TestSingleSystemMessage(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
msgs := cb.BuildMessages(tt.history, tt.summary, tt.message, nil, "test", "chat1")
msgs := cb.BuildMessages(tt.history, tt.summary, tt.message, nil, "test", "chat1", "")
systemCount := 0
for _, m := range msgs {
@ -576,7 +576,7 @@ func TestConcurrentBuildSystemPromptWithCache(t *testing.T) {
}
// Also exercise BuildMessages concurrently
msgs := cb.BuildMessages(nil, "", "hello", nil, "test", "chat")
msgs := cb.BuildMessages(nil, "", "hello", nil, "test", "chat", "")
if len(msgs) < 2 {
errs <- "BuildMessages returned fewer than 2 messages"
return
@ -664,6 +664,22 @@ func BenchmarkBuildMessagesWithCache(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = cb.BuildMessages(history, "summary", "new message", nil, "cli", "test")
_ = cb.BuildMessages(history, "summary", "new message", nil, "cli", "test", "")
}
}
func TestBuildMessages_IncludesChatNameInDynamicContext(t *testing.T) {
tmpDir := setupWorkspace(t, map[string]string{
"IDENTITY.md": "# Identity\nTest agent.",
})
defer os.RemoveAll(tmpDir)
cb := NewContextBuilder(tmpDir)
msgs := cb.BuildMessages(nil, "", "hello", nil, "discord", "1234567890", "#general")
if len(msgs) == 0 {
t.Fatal("expected messages")
}
if !strings.Contains(msgs[0].Content, "Chat ID: 1234567890 (#general)") {
t.Fatalf("dynamic context missing chat name: %q", msgs[0].Content)
}
}

View file

@ -58,6 +58,7 @@ type processOptions struct {
SessionKey string // Session identifier for history/context
Channel string // Target channel for tool execution
ChatID string // Target chat ID for tool execution
ChatName string // Optional human-readable chat/channel name
UserMessage string // User message content (may include prefix)
Media []string // media:// refs from inbound message
DefaultResponse string // Response when LLM returns empty
@ -70,6 +71,7 @@ const (
defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json."
sessionKeyAgentPrefix = "agent:"
metadataKeyAccountID = "account_id"
metadataKeyChatName = "chat_name"
metadataKeyGuildID = "guild_id"
metadataKeyTeamID = "team_id"
metadataKeyParentPeerKind = "parent_peer_kind"
@ -735,6 +737,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
SessionKey: sessionKey,
Channel: msg.Channel,
ChatID: msg.ChatID,
ChatName: inboundMetadata(msg, metadataKeyChatName),
UserMessage: msg.Content,
Media: msg.Media,
DefaultResponse: defaultResponse,
@ -879,6 +882,7 @@ func (al *AgentLoop) runAgentLoop(
opts.Media,
opts.Channel,
opts.ChatID,
opts.ChatName,
)
// Resolve media:// refs: images→base64 data URLs, non-images→local paths in content
@ -1150,7 +1154,7 @@ func (al *AgentLoop) runLLMIteration(
newSummary := agent.Sessions.GetSummary(opts.SessionKey)
messages = agent.ContextBuilder.BuildMessages(
newHistory, newSummary, "",
nil, opts.Channel, opts.ChatID,
nil, opts.Channel, opts.ChatID, opts.ChatName,
)
continue
}

View file

@ -459,10 +459,28 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
"channel_id": m.ChannelID,
"is_dm": fmt.Sprintf("%t", m.GuildID == ""),
}
if channelName := c.resolveChannelName(m.ChannelID); channelName != "" {
metadata["chat_name"] = channelName
}
c.HandleMessage(c.ctx, peer, m.ID, senderID, m.ChannelID, content, mediaPaths, metadata, sender)
}
func (c *DiscordChannel) resolveChannelName(channelID string) string {
if c == nil || c.session == nil || strings.TrimSpace(channelID) == "" {
return ""
}
if c.session.State != nil {
if ch, err := c.session.State.Channel(channelID); err == nil && ch != nil && strings.TrimSpace(ch.Name) != "" {
return strings.TrimSpace(ch.Name)
}
}
if ch, err := c.session.Channel(channelID); err == nil && ch != nil && strings.TrimSpace(ch.Name) != "" {
return strings.TrimSpace(ch.Name)
}
return ""
}
// startTyping starts a continuous typing indicator loop for the given chatID.
// It stops any existing typing loop for that chatID before starting a new one.
func (c *DiscordChannel) startTyping(chatID string) {

View file

@ -89,3 +89,26 @@ func TestApplyDiscordProxy_InvalidProxyURL(t *testing.T) {
t.Fatal("applyDiscordProxy() expected error for invalid proxy URL, got nil")
}
}
func TestResolveChannelName_FromState(t *testing.T) {
session, err := discordgo.New("Bot test-token")
if err != nil {
t.Fatalf("discordgo.New() error: %v", err)
}
session.State = discordgo.NewState()
if err := session.State.GuildAdd(&discordgo.Guild{ID: "guild-1"}); err != nil {
t.Fatalf("GuildAdd() error: %v", err)
}
if err := session.State.ChannelAdd(&discordgo.Channel{
ID: "123",
GuildID: "guild-1",
Name: "general",
}); err != nil {
t.Fatalf("ChannelAdd() error: %v", err)
}
ch := &DiscordChannel{session: session}
if got := ch.resolveChannelName("123"); got != "general" {
t.Fatalf("resolveChannelName() = %q, want %q", got, "general")
}
}