mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-20 03:37:31 +08:00
Merge branch 'main' into codex/conduct-risk-and-bug-assessment-4fecjc
This commit is contained in:
@@ -115,7 +115,9 @@ func main() {
|
|||||||
case "agent":
|
case "agent":
|
||||||
agentCmd()
|
agentCmd()
|
||||||
case "gateway":
|
case "gateway":
|
||||||
maybePromptAndEscalateRoot("gateway")
|
if shouldPromptGatewayRoot(os.Args) {
|
||||||
|
maybePromptAndEscalateRoot("gateway")
|
||||||
|
}
|
||||||
gatewayCmd()
|
gatewayCmd()
|
||||||
case "status":
|
case "status":
|
||||||
statusCmd()
|
statusCmd()
|
||||||
@@ -426,11 +428,11 @@ Ultra-lightweight personal AI assistant written in Go, inspired by nanobot.
|
|||||||
MIT License - Free and open source
|
MIT License - Free and open source
|
||||||
|
|
||||||
## Repository
|
## Repository
|
||||||
https://github.com/sipeed/clawgo
|
https://github.com/YspCoder/clawgo
|
||||||
|
|
||||||
## Contact
|
## Contact
|
||||||
Issues: https://github.com/sipeed/clawgo/issues
|
Issues: https://github.com/YspCoder/clawgo/issues
|
||||||
Discussions: https://github.com/sipeed/clawgo/discussions
|
Discussions: https://github.com/YspCoder/clawgo/discussions
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -906,6 +908,11 @@ func maybePromptAndEscalateRoot(command string) {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func shouldPromptGatewayRoot(args []string) bool {
|
||||||
|
// Only prompt on plain `clawgo gateway` registration flow.
|
||||||
|
return len(args) == 2 && args[1] == "gateway"
|
||||||
|
}
|
||||||
|
|
||||||
func isInteractiveStdin() bool {
|
func isInteractiveStdin() bool {
|
||||||
info, err := os.Stdin.Stat()
|
info, err := os.Stdin.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1651,7 +1658,7 @@ func skillsHelp() {
|
|||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Examples:")
|
fmt.Println("Examples:")
|
||||||
fmt.Println(" clawgo skills list")
|
fmt.Println(" clawgo skills list")
|
||||||
fmt.Println(" clawgo skills install sipeed/clawgo-skills/weather")
|
fmt.Println(" clawgo skills install YspCoder/clawgo-skills/weather")
|
||||||
fmt.Println(" clawgo skills install-builtin")
|
fmt.Println(" clawgo skills install-builtin")
|
||||||
fmt.Println(" clawgo skills list-builtin")
|
fmt.Println(" clawgo skills list-builtin")
|
||||||
fmt.Println(" clawgo skills remove weather")
|
fmt.Println(" clawgo skills remove weather")
|
||||||
@@ -1678,7 +1685,7 @@ func skillsListCmd(loader *skills.SkillsLoader) {
|
|||||||
func skillsInstallCmd(installer *skills.SkillInstaller) {
|
func skillsInstallCmd(installer *skills.SkillInstaller) {
|
||||||
if len(os.Args) < 4 {
|
if len(os.Args) < 4 {
|
||||||
fmt.Println("Usage: clawgo skills install <github-repo>")
|
fmt.Println("Usage: clawgo skills install <github-repo>")
|
||||||
fmt.Println("Example: clawgo skills install sipeed/clawgo-skills/weather")
|
fmt.Println("Example: clawgo skills install YspCoder/clawgo-skills/weather")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,8 @@
|
|||||||
"proxy": {
|
"proxy": {
|
||||||
"api_key": "YOUR_CLIPROXYAPI_KEY",
|
"api_key": "YOUR_CLIPROXYAPI_KEY",
|
||||||
"api_base": "http://localhost:8080/v1",
|
"api_base": "http://localhost:8080/v1",
|
||||||
"auth": "bearer"
|
"auth": "bearer",
|
||||||
|
"timeout_sec": 90
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tools": {
|
"tools": {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import (
|
|||||||
|
|
||||||
var errGatewayNotRunningSlash = errors.New("gateway not running")
|
var errGatewayNotRunningSlash = errors.New("gateway not running")
|
||||||
|
|
||||||
const llmCallTimeout = 90 * time.Second
|
|
||||||
const perSessionQueueSize = 64
|
const perSessionQueueSize = 64
|
||||||
|
|
||||||
type sessionWorker struct {
|
type sessionWorker struct {
|
||||||
@@ -53,6 +52,7 @@ type AgentLoop struct {
|
|||||||
orchestrator *tools.Orchestrator
|
orchestrator *tools.Orchestrator
|
||||||
running atomic.Bool
|
running atomic.Bool
|
||||||
compactionCfg config.ContextCompactionConfig
|
compactionCfg config.ContextCompactionConfig
|
||||||
|
llmCallTimeout time.Duration
|
||||||
workersMu sync.Mutex
|
workersMu sync.Mutex
|
||||||
workers map[string]*sessionWorker
|
workers map[string]*sessionWorker
|
||||||
}
|
}
|
||||||
@@ -137,6 +137,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
|
|||||||
tools: toolsRegistry,
|
tools: toolsRegistry,
|
||||||
orchestrator: orchestrator,
|
orchestrator: orchestrator,
|
||||||
compactionCfg: cfg.Agents.Defaults.ContextCompaction,
|
compactionCfg: cfg.Agents.Defaults.ContextCompaction,
|
||||||
|
llmCallTimeout: time.Duration(cfg.Providers.Proxy.TimeoutSec) * time.Second,
|
||||||
workers: make(map[string]*sessionWorker),
|
workers: make(map[string]*sessionWorker),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,7 +562,7 @@ func (al *AgentLoop) runLLMToolLoop(
|
|||||||
})
|
})
|
||||||
|
|
||||||
llmStart := time.Now()
|
llmStart := time.Now()
|
||||||
llmCtx, cancelLLM := context.WithTimeout(ctx, llmCallTimeout)
|
llmCtx, cancelLLM := context.WithTimeout(ctx, al.llmCallTimeout)
|
||||||
response, err := al.callLLMWithModelFallback(llmCtx, messages, providerToolDefs, map[string]interface{}{
|
response, err := al.callLLMWithModelFallback(llmCtx, messages, providerToolDefs, map[string]interface{}{
|
||||||
"max_tokens": 8192,
|
"max_tokens": 8192,
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
@@ -680,7 +681,7 @@ func (al *AgentLoop) runLLMToolLoop(
|
|||||||
})
|
})
|
||||||
finalizeMessages = sanitizeMessagesForToolCalling(finalizeMessages)
|
finalizeMessages = sanitizeMessagesForToolCalling(finalizeMessages)
|
||||||
|
|
||||||
llmCtx, cancelLLM := context.WithTimeout(ctx, llmCallTimeout)
|
llmCtx, cancelLLM := context.WithTimeout(ctx, al.llmCallTimeout)
|
||||||
finalResp, err := al.callLLMWithModelFallback(llmCtx, finalizeMessages, nil, map[string]interface{}{
|
finalResp, err := al.callLLMWithModelFallback(llmCtx, finalizeMessages, nil, map[string]interface{}{
|
||||||
"max_tokens": 1024,
|
"max_tokens": 1024,
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
|
|||||||
@@ -63,6 +63,13 @@ func NewTelegramChannel(cfg config.TelegramConfig, bus *bus.MessageBus) (*Telegr
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withTelegramAPITimeout(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
return context.WithTimeout(ctx, telegramAPICallTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *TelegramChannel) SetTranscriber(transcriber *voice.GroqTranscriber) {
|
func (c *TelegramChannel) SetTranscriber(transcriber *voice.GroqTranscriber) {
|
||||||
c.transcriber = transcriber
|
c.transcriber = transcriber
|
||||||
}
|
}
|
||||||
@@ -84,7 +91,9 @@ func (c *TelegramChannel) Start(ctx context.Context) error {
|
|||||||
|
|
||||||
c.setRunning(true)
|
c.setRunning(true)
|
||||||
|
|
||||||
botInfo, err := c.bot.GetMe(context.Background())
|
getMeCtx, cancelGetMe := withTelegramAPITimeout(ctx)
|
||||||
|
botInfo, err := c.bot.GetMe(getMeCtx)
|
||||||
|
cancelGetMe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get bot info: %w", err)
|
return fmt.Errorf("failed to get bot info: %w", err)
|
||||||
}
|
}
|
||||||
@@ -205,13 +214,12 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
|||||||
}
|
}
|
||||||
chatID := telegoutil.ID(chatIDInt)
|
chatID := telegoutil.ID(chatIDInt)
|
||||||
|
|
||||||
// Stop thinking animation
|
// Stop thinking animation first to avoid animation/update races.
|
||||||
if stop, ok := c.stopThinking.Load(msg.ChatID); ok {
|
if stop, ok := c.stopThinking.LoadAndDelete(msg.ChatID); ok {
|
||||||
logger.DebugCF("telegram", "Telegram thinking stop signal", map[string]interface{}{
|
logger.DebugCF("telegram", "Telegram thinking stop signal", map[string]interface{}{
|
||||||
logger.FieldChatID: msg.ChatID,
|
logger.FieldChatID: msg.ChatID,
|
||||||
})
|
})
|
||||||
safeCloseSignal(stop)
|
safeCloseSignal(stop)
|
||||||
c.stopThinking.Delete(msg.ChatID)
|
|
||||||
} else {
|
} else {
|
||||||
logger.DebugCF("telegram", "Telegram thinking stop skipped (not found)", map[string]interface{}{
|
logger.DebugCF("telegram", "Telegram thinking stop skipped (not found)", map[string]interface{}{
|
||||||
logger.FieldChatID: msg.ChatID,
|
logger.FieldChatID: msg.ChatID,
|
||||||
@@ -222,18 +230,21 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
|||||||
|
|
||||||
// Try to edit placeholder
|
// Try to edit placeholder
|
||||||
if pID, ok := c.placeholders.Load(msg.ChatID); ok {
|
if pID, ok := c.placeholders.Load(msg.ChatID); ok {
|
||||||
c.placeholders.Delete(msg.ChatID)
|
// Always reset placeholder state even when edit/send fails.
|
||||||
|
defer c.placeholders.Delete(msg.ChatID)
|
||||||
logger.DebugCF("telegram", "Telegram editing thinking placeholder", map[string]interface{}{
|
logger.DebugCF("telegram", "Telegram editing thinking placeholder", map[string]interface{}{
|
||||||
logger.FieldChatID: msg.ChatID,
|
logger.FieldChatID: msg.ChatID,
|
||||||
"message_id": pID.(int),
|
"message_id": pID.(int),
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err := c.bot.EditMessageText(ctx, &telego.EditMessageTextParams{
|
editCtx, cancelEdit := withTelegramAPITimeout(ctx)
|
||||||
|
_, err := c.bot.EditMessageText(editCtx, &telego.EditMessageTextParams{
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
MessageID: pID.(int),
|
MessageID: pID.(int),
|
||||||
Text: htmlContent,
|
Text: htmlContent,
|
||||||
ParseMode: telego.ModeHTML,
|
ParseMode: telego.ModeHTML,
|
||||||
})
|
})
|
||||||
|
cancelEdit()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.DebugCF("telegram", "Telegram placeholder updated", map[string]interface{}{
|
logger.DebugCF("telegram", "Telegram placeholder updated", map[string]interface{}{
|
||||||
@@ -252,14 +263,18 @@ func (c *TelegramChannel) Send(ctx context.Context, msg bus.OutboundMessage) err
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = c.bot.SendMessage(ctx, telegoutil.Message(chatID, htmlContent).WithParseMode(telego.ModeHTML))
|
sendCtx, cancelSend := withTelegramAPITimeout(ctx)
|
||||||
|
_, err = c.bot.SendMessage(sendCtx, telegoutil.Message(chatID, htmlContent).WithParseMode(telego.ModeHTML))
|
||||||
|
cancelSend()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WarnCF("telegram", "HTML parse failed, fallback to plain text", map[string]interface{}{
|
logger.WarnCF("telegram", "HTML parse failed, fallback to plain text", map[string]interface{}{
|
||||||
logger.FieldError: err.Error(),
|
logger.FieldError: err.Error(),
|
||||||
})
|
})
|
||||||
plain := plainTextFromTelegramHTML(htmlContent)
|
plain := plainTextFromTelegramHTML(htmlContent)
|
||||||
_, err = c.bot.SendMessage(ctx, telegoutil.Message(chatID, plain))
|
sendPlainCtx, cancelSendPlain := withTelegramAPITimeout(ctx)
|
||||||
|
_, err = c.bot.SendMessage(sendPlainCtx, telegoutil.Message(chatID, plain))
|
||||||
|
cancelSendPlain()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorCF("telegram", "Telegram plain-text fallback send failed", map[string]interface{}{
|
logger.ErrorCF("telegram", "Telegram plain-text fallback send failed", map[string]interface{}{
|
||||||
logger.FieldChatID: msg.ChatID,
|
logger.FieldChatID: msg.ChatID,
|
||||||
|
|||||||
@@ -107,9 +107,10 @@ type ProvidersConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProviderConfig struct {
|
type ProviderConfig struct {
|
||||||
APIKey string `json:"api_key" env:"CLAWGO_PROVIDERS_{{.Name}}_API_KEY"`
|
APIKey string `json:"api_key" env:"CLAWGO_PROVIDERS_{{.Name}}_API_KEY"`
|
||||||
APIBase string `json:"api_base" env:"CLAWGO_PROVIDERS_{{.Name}}_API_BASE"`
|
APIBase string `json:"api_base" env:"CLAWGO_PROVIDERS_{{.Name}}_API_BASE"`
|
||||||
Auth string `json:"auth" env:"CLAWGO_PROVIDERS_{{.Name}}_AUTH"`
|
Auth string `json:"auth" env:"CLAWGO_PROVIDERS_{{.Name}}_AUTH"`
|
||||||
|
TimeoutSec int `json:"timeout_sec" env:"CLAWGO_PROVIDERS_PROXY_TIMEOUT_SEC"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GatewayConfig struct {
|
type GatewayConfig struct {
|
||||||
@@ -276,7 +277,8 @@ func DefaultConfig() *Config {
|
|||||||
},
|
},
|
||||||
Providers: ProvidersConfig{
|
Providers: ProvidersConfig{
|
||||||
Proxy: ProviderConfig{
|
Proxy: ProviderConfig{
|
||||||
APIBase: "http://localhost:8080/v1",
|
APIBase: "http://localhost:8080/v1",
|
||||||
|
TimeoutSec: 90,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Gateway: GatewayConfig{
|
Gateway: GatewayConfig{
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ func Validate(cfg *Config) []error {
|
|||||||
if cfg.Providers.Proxy.APIBase == "" {
|
if cfg.Providers.Proxy.APIBase == "" {
|
||||||
errs = append(errs, fmt.Errorf("providers.proxy.api_base is required"))
|
errs = append(errs, fmt.Errorf("providers.proxy.api_base is required"))
|
||||||
}
|
}
|
||||||
|
if cfg.Providers.Proxy.TimeoutSec <= 0 {
|
||||||
|
errs = append(errs, fmt.Errorf("providers.proxy.timeout_sec must be > 0"))
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Gateway.Port <= 0 || cfg.Gateway.Port > 65535 {
|
if cfg.Gateway.Port <= 0 || cfg.Gateway.Port > 65535 {
|
||||||
errs = append(errs, fmt.Errorf("gateway.port must be in 1..65535"))
|
errs = append(errs, fmt.Errorf("gateway.port must be in 1..65535"))
|
||||||
|
|||||||
@@ -24,18 +24,18 @@ type HTTPProvider struct {
|
|||||||
apiKey string
|
apiKey string
|
||||||
apiBase string
|
apiBase string
|
||||||
authMode string
|
authMode string
|
||||||
|
timeout time.Duration
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultChatTimeout = 90 * time.Second
|
func NewHTTPProvider(apiKey, apiBase, authMode string, timeout time.Duration) *HTTPProvider {
|
||||||
|
|
||||||
func NewHTTPProvider(apiKey, apiBase, authMode string) *HTTPProvider {
|
|
||||||
return &HTTPProvider{
|
return &HTTPProvider{
|
||||||
apiKey: apiKey,
|
apiKey: apiKey,
|
||||||
apiBase: apiBase,
|
apiBase: apiBase,
|
||||||
authMode: authMode,
|
authMode: authMode,
|
||||||
|
timeout: timeout,
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Timeout: defaultChatTimeout,
|
Timeout: timeout,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,7 +50,7 @@ func (p *HTTPProvider) Chat(ctx context.Context, messages []Message, tools []Too
|
|||||||
"model": model,
|
"model": model,
|
||||||
"messages_count": len(messages),
|
"messages_count": len(messages),
|
||||||
"tools_count": len(tools),
|
"tools_count": len(tools),
|
||||||
"timeout": defaultChatTimeout.String(),
|
"timeout": p.timeout.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
requestBody := map[string]interface{}{
|
requestBody := map[string]interface{}{
|
||||||
@@ -208,6 +208,9 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) {
|
|||||||
if apiBase == "" {
|
if apiBase == "" {
|
||||||
return nil, fmt.Errorf("no API base (CLIProxyAPI) configured")
|
return nil, fmt.Errorf("no API base (CLIProxyAPI) configured")
|
||||||
}
|
}
|
||||||
|
if cfg.Providers.Proxy.TimeoutSec <= 0 {
|
||||||
|
return nil, fmt.Errorf("invalid providers.proxy.timeout_sec: %d", cfg.Providers.Proxy.TimeoutSec)
|
||||||
|
}
|
||||||
|
|
||||||
return NewHTTPProvider(apiKey, apiBase, authMode), nil
|
return NewHTTPProvider(apiKey, apiBase, authMode, time.Duration(cfg.Providers.Proxy.TimeoutSec)*time.Second), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ func (si *SkillInstaller) Uninstall(skillName string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (si *SkillInstaller) ListAvailableSkills(ctx context.Context) ([]AvailableSkill, error) {
|
func (si *SkillInstaller) ListAvailableSkills(ctx context.Context) ([]AvailableSkill, error) {
|
||||||
url := "https://raw.githubusercontent.com/sipeed/clawgo-skills/main/skills.json"
|
url := "https://raw.githubusercontent.com/YspCoder/clawgo-skills/main/skills.json"
|
||||||
|
|
||||||
client := &http.Client{Timeout: 15 * time.Second}
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ func NewExecTool(cfg config.ShellConfig, workspace string) *ExecTool {
|
|||||||
allowPatterns = append(allowPatterns, regexp.MustCompile(`(?i)\b`+regexp.QuoteMeta(p)+`\b`))
|
allowPatterns = append(allowPatterns, regexp.MustCompile(`(?i)\b`+regexp.QuoteMeta(p)+`\b`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowPatterns := make([]*regexp.Regexp, 0, len(cfg.AllowedCmds))
|
||||||
|
for _, p := range cfg.AllowedCmds {
|
||||||
|
allowPatterns = append(allowPatterns, regexp.MustCompile(`\b`+regexp.QuoteMeta(p)+`\b`))
|
||||||
|
}
|
||||||
|
|
||||||
return &ExecTool{
|
return &ExecTool{
|
||||||
workingDir: workspace,
|
workingDir: workspace,
|
||||||
timeout: cfg.Timeout,
|
timeout: cfg.Timeout,
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ npx tsx /root/.clawgo/skills/context7/query.ts context <owner/repo> <query>
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
```bash
|
```bash
|
||||||
npx tsx /root/.clawgo/skills/context7/query.ts context sipeed/clawgo "How does the skill system work?"
|
npx tsx /root/.clawgo/skills/context7/query.ts context YspCoder/clawgo "How does the skill system work?"
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user