Unified Agent + reusable Go agent core.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: remove legacy telegram slash commands

Lyric 69611a07 00843d7b

+12 -178
+1 -1
docs/arch.md
··· 98 98 - Group-addressing decision request (this path injects local `message_react` for the addressing loop when context allows, and does not expose runtime registry tools). 99 99 tools: `message_react` only when context allows; otherwise `none` 100 100 files: `telegram/runtime.go`, `telegram/trigger.go`, `telegram/trigger_addressing.go` 101 - - Init flow requests (question generation, profile fill, post-init greeting, SOUL polish, `/humanize`). 101 + - Init flow requests (question generation, profile fill, post-init greeting, SOUL polish). 102 102 tools: `none` 103 103 files: `telegram/init_flow.go`, `telegram/runtime.go` 104 104 - Memory flow requests (session draft and semantic dedupe/merge support).
+1 -1
docs/feat/feat_20260403_session_llm_profile_switch.md
··· 521 521 522 522 做法: 523 523 524 - - 复用当前 `/start /help /reset /echo` 那一层命令分支 524 + - 复用当前 Telegram runtime 的命令分支 525 525 - 新增 `/model` 分支 526 526 - `/model` 不进入 agent run 527 527 - `/model` 不写入聊天 history
-21
internal/channelruntime/telegram/init_flow.go
··· 201 201 }, nil 202 202 } 203 203 204 - func humanizeSoulProfile(ctx context.Context, client llm.Client, model string) (bool, error) { 205 - stateDir := statepaths.FileStateDir() 206 - if err := ensureInitProfileFiles(stateDir); err != nil { 207 - return false, err 208 - } 209 - soulPath := filepath.Join(stateDir, "SOUL.md") 210 - soulRawBytes, err := os.ReadFile(soulPath) 211 - if err != nil { 212 - return false, fmt.Errorf("read SOUL.md: %w", err) 213 - } 214 - original := strings.ReplaceAll(string(soulRawBytes), "\r\n", "\n") 215 - polished := polishInitSoulMarkdown(ctx, client, model, original) 216 - if polished == original { 217 - return false, nil 218 - } 219 - if err := writeFilePreservePerm(soulPath, []byte(polished)); err != nil { 220 - return false, fmt.Errorf("write SOUL.md: %w", err) 221 - } 222 - return true, nil 223 - } 224 - 225 204 func generatePostInitGreeting(ctx context.Context, client llm.Client, model string, draft initProfileDraft, session telegramInitSession, userAnswer string, fallback initApplyResult) (string, error) { 226 205 if client == nil || strings.TrimSpace(model) == "" { 227 206 return fallbackPostInitGreeting(userAnswer, fallback), nil
-70
internal/channelruntime/telegram/init_flow_test.go
··· 185 185 } 186 186 } 187 187 188 - func TestHumanizeSoulProfileUpdatesFileOnValidRewrite(t *testing.T) { 189 - workspaceDir := t.TempDir() 190 - prevStateDir := viper.GetString("file_state_dir") 191 - viper.Set("file_state_dir", workspaceDir) 192 - t.Cleanup(func() { 193 - viper.Set("file_state_dir", prevStateDir) 194 - }) 195 - 196 - input := "---\nstatus: done\n---\n\n# SOUL.md\n\n## Core Truths\n- A\n\n## Boundaries\n- B\n\n## Vibe\n\nC\n" 197 - soulPath := filepath.Join(workspaceDir, "SOUL.md") 198 - if err := os.WriteFile(soulPath, []byte(input), 0o644); err != nil { 199 - t.Fatalf("write SOUL.md: %v", err) 200 - } 201 - 202 - client := &stubInitLLMClient{ 203 - Text: "# SOUL.md\n\n## Core Truths\n- Have a take.\n\n## Boundaries\n- Keep secrets secret.\n\n## Vibe\n\nFast and sharp.\n\nBe the assistant you'd actually want to talk to at 2am. Not a corporate drone. Not a sycophant. Just... good.", 204 - } 205 - updated, err := humanizeSoulProfile(context.Background(), client, "model") 206 - if err != nil { 207 - t.Fatalf("humanizeSoulProfile() error = %v", err) 208 - } 209 - if !updated { 210 - t.Fatalf("expected SOUL.md to be updated") 211 - } 212 - 213 - outBytes, err := os.ReadFile(soulPath) 214 - if err != nil { 215 - t.Fatalf("read SOUL.md: %v", err) 216 - } 217 - out := string(outBytes) 218 - if !strings.Contains(out, "status: done") { 219 - t.Fatalf("expected done status after humanize: %s", out) 220 - } 221 - if !strings.Contains(out, "Have a take.") { 222 - t.Fatalf("expected rewritten content: %s", out) 223 - } 224 - } 225 - 226 - func TestHumanizeSoulProfileKeepsOriginalWhenRewriteInvalid(t *testing.T) { 227 - workspaceDir := t.TempDir() 228 - prevStateDir := viper.GetString("file_state_dir") 229 - viper.Set("file_state_dir", workspaceDir) 230 - t.Cleanup(func() { 231 - viper.Set("file_state_dir", prevStateDir) 232 - }) 233 - 234 - input := "---\nstatus: done\n---\n\n# SOUL.md\n\n## Core Truths\n- A\n\n## Boundaries\n- B\n\n## Vibe\n\nC\n" 235 - soulPath := filepath.Join(workspaceDir, "SOUL.md") 236 - if err := os.WriteFile(soulPath, []byte(input), 0o644); err != nil { 237 - t.Fatalf("write SOUL.md: %v", err) 238 - } 239 - 240 - client := &stubInitLLMClient{Text: "hello"} 241 - updated, err := humanizeSoulProfile(context.Background(), client, "model") 242 - if err != nil { 243 - t.Fatalf("humanizeSoulProfile() error = %v", err) 244 - } 245 - if updated { 246 - t.Fatalf("expected SOUL.md to stay unchanged") 247 - } 248 - 249 - outBytes, err := os.ReadFile(soulPath) 250 - if err != nil { 251 - t.Fatalf("read SOUL.md: %v", err) 252 - } 253 - if string(outBytes) != input { 254 - t.Fatalf("expected original SOUL.md preserved, got: %s", string(outBytes)) 255 - } 256 - } 257 - 258 188 type stubInitLLMClient struct { 259 189 Text string 260 190 Err error
+2 -48
internal/channelruntime/telegram/runtime.go
··· 1035 1035 } 1036 1036 replyToMessageID := int64(0) 1037 1037 switch normalizedCmd { 1038 - case "/start", "/help": 1038 + case "/help": 1039 1039 help := "Send a message and I will run it as an agent task.\n" + 1040 - "Commands: /echo <msg>, /humanize, /model, /workspace, /reset, /id\n\n" + 1040 + "Commands: /model, /workspace, /reset, /id\n\n" + 1041 1041 "Group chats: reply to me, or mention @" + botUser + ".\n" + 1042 1042 "You can also send a file (document/photo). It will be downloaded under file_cache_dir/telegram/ and the agent can process it.\n" + 1043 1043 "Note: if Bot Privacy Mode is enabled, I may not receive normal group messages." ··· 1075 1075 } 1076 1076 _ = api.sendMessageHTML(context.Background(), chatID, htmlstd.EscapeString(reply), true) 1077 1077 continue 1078 - case "/humanize": 1079 - if len(allowed) > 0 && !allowed[chatID] { 1080 - logger.Warn("telegram_unauthorized_chat", "chat_id", chatID) 1081 - sendTelegramUnauthorizedMessage(api, chatID, chatType) 1082 - continue 1083 - } 1084 - if strings.ToLower(strings.TrimSpace(chatType)) != "private" { 1085 - _ = api.sendMessageHTML(context.Background(), chatID, "please use /humanize in the private chat", true) 1086 - continue 1087 - } 1088 - typingStop := startTypingTicker(context.Background(), api, chatID, "typing", 4*time.Second) 1089 - humanizeCtx, cancel := newMessageTimeoutCtx() 1090 - mainClient, mainModel, cleanupMain, mainErr := resolveTelegramMainForUse(execRuntime) 1091 - if mainErr != nil { 1092 - cancel() 1093 - typingStop() 1094 - _ = api.sendMessageHTML(context.Background(), chatID, "humanize failed: "+mainErr.Error(), true) 1095 - continue 1096 - } 1097 - updated, err := humanizeSoulProfile(humanizeCtx, mainClient, mainModel) 1098 - cleanupMain() 1099 - cancel() 1100 - typingStop() 1101 - if err != nil { 1102 - _ = api.sendMessageHTML(context.Background(), chatID, "humanize failed: "+err.Error(), true) 1103 - continue 1104 - } 1105 - if updated { 1106 - _ = api.sendMessageHTML(context.Background(), chatID, "ok (SOUL.md humanized)", true) 1107 - } else { 1108 - _ = api.sendMessageHTML(context.Background(), chatID, "ok (SOUL.md unchanged)", true) 1109 - } 1110 - continue 1111 1078 case "/reset": 1112 1079 if len(allowed) > 0 && !allowed[chatID] { 1113 1080 logger.Warn("telegram_unauthorized_chat", "chat_id", chatID) ··· 1125 1092 delete(planProgressStateByID, chatID) 1126 1093 planProgressEditMu.Unlock() 1127 1094 _ = api.sendMessageHTML(context.Background(), chatID, "ok (reset)", true) 1128 - continue 1129 - case "/echo": 1130 - if len(allowed) > 0 && !allowed[chatID] { 1131 - logger.Warn("telegram_unauthorized_chat", "chat_id", chatID) 1132 - sendTelegramUnauthorizedMessage(api, chatID, chatType) 1133 - continue 1134 - } 1135 - msg := strings.TrimSpace(cmdArgs) 1136 - if msg == "" { 1137 - _ = api.sendMessageHTML(context.Background(), chatID, "usage: /echo <msg>", true) 1138 - continue 1139 - } 1140 - _ = api.sendMessageHTML(context.Background(), chatID, msg, true) 1141 1095 continue 1142 1096 default: 1143 1097 if len(allowed) > 0 && !allowed[chatID] {
+8 -27
internal/chatcommands/dispatcher_test.go
··· 14 14 wantArg string 15 15 }{ 16 16 {"/help", "/help", ""}, 17 - {"/echo hello world", "/echo", "hello world"}, 17 + {"/say hello world", "/say", "hello world"}, 18 18 {" /model set foo ", "/model", "set foo"}, 19 19 {"plain text", "plain", "text"}, 20 20 {"", "", ""}, ··· 41 41 {"/model@bot123", "/model"}, 42 42 {"plain", ""}, 43 43 {"", ""}, 44 - {" /start ", "/start"}, 44 + {" /help ", "/help"}, 45 45 } 46 46 47 47 for _, c := range cases { ··· 83 83 84 84 func TestRegistryDispatchWithBotSuffix(t *testing.T) { 85 85 r := NewRegistry() 86 - r.Register("/start", func(ctx context.Context, args string) (*Result, error) { 87 - return &Result{Reply: "started"}, nil 86 + r.Register("/help", func(ctx context.Context, args string) (*Result, error) { 87 + return &Result{Reply: "help"}, nil 88 88 }) 89 89 90 - res, handled, err := r.Dispatch(context.Background(), "/start@MyBot") 91 - if !handled || err != nil || res == nil || res.Reply != "started" { 90 + res, handled, err := r.Dispatch(context.Background(), "/help@MyBot") 91 + if !handled || err != nil || res == nil || res.Reply != "help" { 92 92 t.Fatalf("unexpected result: %v, %v, %v", res, handled, err) 93 93 } 94 94 } ··· 125 125 func TestHelpHandler(t *testing.T) { 126 126 r := NewRegistry() 127 127 r.Register("/help", nil) 128 - r.Register("/echo", nil) 128 + r.Register("/model", nil) 129 129 130 130 h := HelpHandler(r, "Commands:") 131 131 res, err := h(context.Background(), "") ··· 139 139 if !strings.Contains(reply, "Commands:") { 140 140 t.Fatalf("expected header in reply: %q", reply) 141 141 } 142 - if !strings.Contains(reply, "/echo") || !strings.Contains(reply, "/help") { 142 + if !strings.Contains(reply, "/model") || !strings.Contains(reply, "/help") { 143 143 t.Fatalf("expected command list in reply: %q", reply) 144 144 } 145 145 } 146 - 147 - func TestEchoHandler(t *testing.T) { 148 - h := EchoHandler() 149 - res, err := h(context.Background(), "hello world") 150 - if err != nil { 151 - t.Fatalf("unexpected error: %v", err) 152 - } 153 - if res == nil || res.Reply != "hello world" { 154 - t.Fatalf("unexpected reply: %v", res) 155 - } 156 - 157 - res, err = h(context.Background(), "") 158 - if err != nil { 159 - t.Fatalf("unexpected error: %v", err) 160 - } 161 - if res == nil || !strings.Contains(res.Reply, "usage") { 162 - t.Fatalf("expected usage hint for empty args, got: %v", res) 163 - } 164 - }
-10
internal/chatcommands/handlers.go
··· 33 33 } 34 34 } 35 35 36 - // EchoHandler returns a Handler that echoes back its arguments. 37 - func EchoHandler() Handler { 38 - return func(ctx context.Context, args string) (*Result, error) { 39 - if args == "" { 40 - return &Result{Reply: "usage: /echo <msg>"}, nil 41 - } 42 - return &Result{Reply: args}, nil 43 - } 44 - } 45 - 46 36 // ModelHandler wraps the llmselect package so that /model commands can be 47 37 // handled uniformly across chat front-ends. 48 38 //