Slim subagent runtime surface and remove legacy interfaces

This commit is contained in:
LPF
2026-03-17 13:41:12 +08:00
parent 341e578c9f
commit 0674d85ae1
76 changed files with 778 additions and 8782 deletions

View File

@@ -40,7 +40,8 @@ func TestDispatchOutbound_DeduplicatesRepeatedSend(t *testing.T) {
t.Fatalf("new manager: %v", err)
}
rc := &recordingChannel{}
mgr.RegisterChannel("test", rc)
mgr.channels["test"] = rc
mgr.refreshSnapshot()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@@ -85,7 +86,8 @@ func TestDispatchOutbound_DifferentButtonsShouldNotDeduplicate(t *testing.T) {
t.Fatalf("new manager: %v", err)
}
rc := &recordingChannel{}
mgr.RegisterChannel("test", rc)
mgr.channels["test"] = rc
mgr.refreshSnapshot()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@@ -410,25 +410,6 @@ func (c *FeishuChannel) buildFeishuMediaOutbound(ctx context.Context, media stri
return larkim.MsgTypeFile, string(b), nil
}
func (c *FeishuChannel) buildFeishuFileFromBytes(ctx context.Context, name string, data []byte) (string, string, error) {
fileReq := larkim.NewCreateFileReqBuilder().
Body(larkim.NewCreateFileReqBodyBuilder().
FileType("stream").
FileName(name).
Duration(0).
File(bytes.NewReader(data)).
Build()).
Build()
fileResp, err := c.client.Im.File.Create(ctx, fileReq)
if err != nil {
return "", "", fmt.Errorf("failed to upload feishu file: %w", err)
}
if !fileResp.Success() {
return "", "", fmt.Errorf("feishu file upload error: code=%d msg=%s", fileResp.Code, fileResp.Msg)
}
b, _ := json.Marshal(fileResp.Data)
return larkim.MsgTypeFile, string(b), nil
}
func readFeishuMedia(media string) (string, []byte, error) {
if strings.HasPrefix(media, "http://") || strings.HasPrefix(media, "https://") {

View File

@@ -406,21 +406,6 @@ func (m *Manager) dispatchOutbound(ctx context.Context) {
}
}
func (m *Manager) GetChannel(name string) (Channel, bool) {
cur, _ := m.snapshot.Load().(map[string]Channel)
channel, ok := cur[name]
return channel, ok
}
func (m *Manager) GetStatus() map[string]interface{} {
cur, _ := m.snapshot.Load().(map[string]Channel)
status := make(map[string]interface{}, len(cur))
for name := range cur {
status[name] = map[string]interface{}{}
}
return status
}
func (m *Manager) GetEnabledChannels() []string {
cur, _ := m.snapshot.Load().(map[string]Channel)
names := make([]string, 0, len(cur))
@@ -430,20 +415,6 @@ func (m *Manager) GetEnabledChannels() []string {
return names
}
func (m *Manager) RegisterChannel(name string, channel Channel) {
m.mu.Lock()
defer m.mu.Unlock()
m.channels[name] = channel
m.refreshSnapshot()
}
func (m *Manager) UnregisterChannel(name string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.channels, name)
m.refreshSnapshot()
}
func (m *Manager) SendToChannel(ctx context.Context, channelName, chatID, content string) error {
m.mu.RLock()
channel, exists := m.channels[channelName]

View File

@@ -854,10 +854,6 @@ func (c *TelegramChannel) handleStreamAction(ctx context.Context, chatID int64,
return nil
}
func renderTelegramStreamChunks(content string) []telegramRenderedChunk {
return renderTelegramStreamChunksWithFinalize(content, false)
}
func renderTelegramStreamChunksWithFinalize(content string, finalizeRich bool) []telegramRenderedChunk {
raw := strings.TrimSpace(content)
if raw == "" {

View File

@@ -7,71 +7,65 @@ import (
"testing"
)
func TestMarkdownToTelegramHTMLFormatsChineseAndInlineMarkup(t *testing.T) {
got := markdownToTelegramHTML("中文 **加粗** *斜体* `代码`")
if strings.Contains(got, "鈹") || strings.Contains(got, "鈥") {
t.Fatalf("unexpected mojibake in output: %q", got)
}
if !strings.Contains(got, "中文 <b>加粗</b> <i>斜体</i> <code>代码</code>") {
func TestMarkdownToTelegramHTMLFormatsInlineMarkup(t *testing.T) {
got := markdownToTelegramHTML("plain **bold** *italic* `code`")
if !strings.Contains(got, "plain <b>bold</b> <i>italic</i> <code>code</code>") {
t.Fatalf("unexpected formatted output: %q", got)
}
}
func TestMarkdownToTelegramHTMLFormatsQuoteAndListsWithoutMojibake(t *testing.T) {
input := "> 引用\n- 列表\n* 另一项\n1. 有序"
func TestMarkdownToTelegramHTMLFormatsQuoteAndLists(t *testing.T) {
input := "> quote\n- bullet\n* other\n1. ordered"
got := markdownToTelegramHTML(input)
if strings.Contains(got, "鈹") || strings.Contains(got, "鈥") {
t.Fatalf("unexpected mojibake in output: %q", got)
}
if !strings.Contains(got, "&gt; 引用") {
if !strings.Contains(got, "&gt; quote") {
t.Fatalf("expected escaped quote marker, got %q", got)
}
if !strings.Contains(got, "• 列表") || !strings.Contains(got, "• 另一项") {
if !strings.Contains(got, "• bullet") || !strings.Contains(got, "• other") {
t.Fatalf("expected bullet list markers, got %q", got)
}
if !strings.Contains(got, "1. 有序") {
if !strings.Contains(got, "1. ordered") {
t.Fatalf("expected ordered list marker preserved, got %q", got)
}
}
func TestRenderTelegramStreamChunksDoesNotInjectMojibake(t *testing.T) {
chunks := renderTelegramStreamChunks("> 引用\n- 列表\n1. 有序\n中文内容")
func TestRenderTelegramStreamChunksDoesNotInjectBrokenPayload(t *testing.T) {
chunks := renderTelegramStreamChunksWithFinalize("> quote\n- bullet\n1. ordered\nplain text", false)
if len(chunks) == 0 {
t.Fatal("expected stream chunks")
}
for _, chunk := range chunks {
if strings.Contains(chunk.payload, "鈹") || strings.Contains(chunk.payload, "鈥") {
t.Fatalf("unexpected mojibake chunk payload: %q", chunk.payload)
if strings.TrimSpace(chunk.payload) == "" {
t.Fatalf("unexpected empty chunk payload: %+v", chunk)
}
}
}
func TestShouldFlushTelegramStreamSnapshotRejectsUnclosedMarkdown(t *testing.T) {
cases := []string{
"中文 **加粗",
"中文 *斜体",
"中文 `代码",
"text **bold",
"text *italic",
"text `code",
"```go\nfmt.Println(\"hi\")",
"[链接](https://example.com",
"[link](https://example.com",
}
for _, input := range cases {
if shouldFlushTelegramStreamSnapshot(input) {
t.Fatalf("expected unsafe snapshot to be rejected: %q", input)
}
if chunks := renderTelegramStreamChunks(input); len(chunks) != 0 {
if chunks := renderTelegramStreamChunksWithFinalize(input, false); len(chunks) != 0 {
t.Fatalf("expected no chunks for unsafe snapshot %q, got %+v", input, chunks)
}
}
}
func TestShouldFlushTelegramStreamSnapshotAcceptsBalancedMarkdown(t *testing.T) {
input := "> 引用\n- 列表\n1. 有序\n中文 **加粗** *斜体* `代码` [链接](https://example.com)"
input := "> quote\n- bullet\n1. ordered\ntext **bold** *italic* `code` [link](https://example.com)"
if !shouldFlushTelegramStreamSnapshot(input) {
t.Fatalf("expected balanced snapshot to flush: %q", input)
}
chunks := renderTelegramStreamChunks(input)
chunks := renderTelegramStreamChunksWithFinalize(input, false)
if len(chunks) == 0 {
t.Fatalf("expected chunks for balanced snapshot")
}
@@ -81,7 +75,7 @@ func TestShouldFlushTelegramStreamSnapshotAcceptsBalancedMarkdown(t *testing.T)
}
func TestRenderTelegramStreamChunksFinalizeRecoversRichFormatting(t *testing.T) {
input := "> 引用\n- 列表\n中文 **加粗** *斜体* `代码` [链接](https://example.com)"
input := "> quote\n- bullet\ntext **bold** *italic* `code` [link](https://example.com)"
chunks := renderTelegramStreamChunksWithFinalize(input, true)
if len(chunks) == 0 {
t.Fatalf("expected finalize chunks")
@@ -89,25 +83,25 @@ func TestRenderTelegramStreamChunksFinalizeRecoversRichFormatting(t *testing.T)
if chunks[0].parseMode != "HTML" {
t.Fatalf("expected finalize chunk to use HTML, got %q", chunks[0].parseMode)
}
if !strings.Contains(chunks[0].payload, "<b>加粗</b>") {
if !strings.Contains(chunks[0].payload, "<b>bold</b>") {
t.Fatalf("expected rich formatting restored, got %q", chunks[0].payload)
}
}
func TestMarkdownToTelegramHTMLHandlesEdgeFormatting(t *testing.T) {
input := "> 第一段引用\n> 第二段引用\n- 列表一\n - 子项\n1. 有序项\n\n```go\nfmt.Println(\"hi\")\nfmt.Println(\"bye\")\n```\n[链接](https://example.com/path?q=1)"
input := "> first quote\n> second quote\n- item one\n - child item\n1. ordered\n\n```go\nfmt.Println(\"hi\")\nfmt.Println(\"bye\")\n```\n[link](https://example.com/path?q=1)"
got := markdownToTelegramHTML(input)
if !strings.Contains(got, "&gt; 第一段引用\n&gt; 第二段引用") {
if !strings.Contains(got, "&gt; first quote\n&gt; second quote") {
t.Fatalf("expected consecutive quote lines to stay stable, got %q", got)
}
if !strings.Contains(got, "• 列表一") || !strings.Contains(got, "• 子项") {
if !strings.Contains(got, "• item one") || !strings.Contains(got, "• child item") {
t.Fatalf("expected nested list lines to normalize to bullets, got %q", got)
}
if !strings.Contains(got, "<pre><code>fmt.Println(\"hi\")\nfmt.Println(\"bye\")\n</code></pre>") {
t.Fatalf("expected code block newlines preserved, got %q", got)
}
if !strings.Contains(got, `<a href="https://example.com/path?q=1">链接</a>`) {
if !strings.Contains(got, `<a href="https://example.com/path?q=1">link</a>`) {
t.Fatalf("expected link conversion, got %q", got)
}
}

View File

@@ -18,15 +18,6 @@ func truncateString(s string, maxLen int) string {
return s[:maxLen]
}
func safeCloseSignal(v interface{}) {
ch, ok := v.(chan struct{})
if !ok || ch == nil {
return
}
defer func() { _ = recover() }()
close(ch)
}
type cancelGuard struct {
mu sync.Mutex
cancel context.CancelFunc

View File

@@ -769,14 +769,6 @@ func (s *WhatsAppBridgeService) updateStatus(mut func(*WhatsAppBridgeStatus)) {
s.status.UpdatedAt = time.Now().Format(time.RFC3339)
}
func (s *WhatsAppBridgeService) broadcastWS(payload whatsappBridgeWSMessage) {
s.wsClientsMu.Lock()
defer s.wsClientsMu.Unlock()
for conn := range s.wsClients {
_ = conn.WriteJSON(payload)
}
}
func (s *WhatsAppBridgeService) broadcastWSMap(payload map[string]interface{}) {
s.wsClientsMu.Lock()
defer s.wsClientsMu.Unlock()