mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-17 09:07:30 +08:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
FROM golang:1.25.5-bookworm AS builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . ./
|
||||
|
||||
RUN rm -rf cmd/workspace \
|
||||
&& mkdir -p cmd/workspace \
|
||||
&& cp -a workspace/. cmd/workspace/
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -buildvcs=false -ldflags="-s -w" -o /out/clawgo ./cmd
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN useradd --create-home --shell /bin/sh clawgo
|
||||
|
||||
USER clawgo
|
||||
WORKDIR /home/clawgo
|
||||
|
||||
COPY --from=builder /out/clawgo /usr/local/bin/clawgo
|
||||
|
||||
ENV CLAWGO_CONFIG=/home/clawgo/.clawgo/config.json
|
||||
|
||||
EXPOSE 18790
|
||||
|
||||
VOLUME ["/home/clawgo/.clawgo"]
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "-c", "if [ ! -f \"$CLAWGO_CONFIG\" ]; then /usr/local/bin/clawgo onboard; fi; exec /usr/local/bin/clawgo gateway run --config \"$CLAWGO_CONFIG\""]
|
||||
8
Makefile
8
Makefile
@@ -3,7 +3,7 @@
|
||||
# Build variables
|
||||
BINARY_NAME=clawgo
|
||||
BUILD_DIR=build
|
||||
CMD_DIR=cmd/$(BINARY_NAME)
|
||||
CMD_DIR=cmd
|
||||
MAIN_GO=$(CMD_DIR)/main.go
|
||||
|
||||
# Version
|
||||
@@ -46,7 +46,7 @@ WORKSPACE_DIR?=$(CLAWGO_HOME)/workspace
|
||||
WORKSPACE_SKILLS_DIR=$(WORKSPACE_DIR)/skills
|
||||
BUILTIN_SKILLS_DIR=$(CURDIR)/skills
|
||||
WORKSPACE_SOURCE_DIR=$(CURDIR)/workspace
|
||||
EMBED_WORKSPACE_DIR=$(CURDIR)/cmd/$(BINARY_NAME)/workspace
|
||||
EMBED_WORKSPACE_DIR=$(CURDIR)/cmd/workspace
|
||||
EMBED_WEBUI_DIR=$(EMBED_WORKSPACE_DIR)/webui
|
||||
DEV_CONFIG?=$(if $(wildcard $(CURDIR)/config.json),$(CURDIR)/config.json,$(CLAWGO_HOME)/config.json)
|
||||
DEV_ARGS?=--debug gateway run
|
||||
@@ -189,11 +189,11 @@ package-all: build-all
|
||||
fi
|
||||
@echo "Package complete: $(BUILD_DIR)"
|
||||
|
||||
## sync-embed-workspace: Sync workspace seed files and built WebUI into cmd/clawgo/workspace for go:embed
|
||||
## sync-embed-workspace: Sync workspace seed files and built WebUI into cmd/workspace for go:embed
|
||||
sync-embed-workspace: sync-embed-workspace-base sync-embed-webui
|
||||
@echo "✓ Embed assets ready in $(EMBED_WORKSPACE_DIR)"
|
||||
|
||||
## sync-embed-workspace-base: Sync root workspace files into cmd/clawgo/workspace for go:embed
|
||||
## sync-embed-workspace-base: Sync root workspace files into cmd/workspace for go:embed
|
||||
sync-embed-workspace-base:
|
||||
@echo "Syncing workspace seed files for embedding..."
|
||||
@if [ ! -d "$(WORKSPACE_SOURCE_DIR)" ]; then \
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
func copyDirectory(src, dst string) error {
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/agent"
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/cron"
|
||||
"clawgo/pkg/logger"
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/agent"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
)
|
||||
@@ -27,7 +27,7 @@ func agentCmd() {
|
||||
switch args[i] {
|
||||
case "--debug", "-d":
|
||||
logger.SetLevel(logger.DEBUG)
|
||||
fmt.Println("🔍 Debug mode enabled")
|
||||
fmt.Println("馃攳 Debug mode enabled")
|
||||
case "-m", "--message":
|
||||
if i+1 < len(args) {
|
||||
message = args[i+1]
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/channels"
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/channels"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
|
||||
qrterminal "github.com/mdp/qrterminal/v3"
|
||||
)
|
||||
@@ -55,7 +55,7 @@ func channelHelp() {
|
||||
func channelTestCmd() {
|
||||
to := ""
|
||||
channelName := ""
|
||||
message := "This is a test message from ClawGo 🦞"
|
||||
message := "This is a test message from ClawGo 馃"
|
||||
|
||||
args := os.Args[3:]
|
||||
for i := 0; i < len(args); i++ {
|
||||
@@ -104,11 +104,11 @@ func channelTestCmd() {
|
||||
|
||||
fmt.Printf("Sending test message to %s (%s)...\n", channelName, to)
|
||||
if err := mgr.SendToChannel(ctx, channelName, to, message); err != nil {
|
||||
fmt.Printf("✗ Failed to send message: %v\n", err)
|
||||
fmt.Printf("鉁?Failed to send message: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("✓ Test message sent successfully!")
|
||||
fmt.Println("鉁?Test message sent successfully!")
|
||||
}
|
||||
|
||||
func whatsAppChannelCmd() {
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/configops"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/configops"
|
||||
)
|
||||
|
||||
func configCmd() {
|
||||
@@ -100,7 +100,7 @@ func configSetCmd() {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Updated %s = %v\n", path, value)
|
||||
fmt.Printf("鉁?Updated %s = %v\n", path, value)
|
||||
running, err := triggerGatewayReload()
|
||||
if err != nil {
|
||||
if running {
|
||||
@@ -113,7 +113,7 @@ func configSetCmd() {
|
||||
}
|
||||
fmt.Printf("Updated config file. Hot reload not applied: %v\n", err)
|
||||
} else {
|
||||
fmt.Println("✓ Gateway hot reload signal sent")
|
||||
fmt.Println("鉁?Gateway hot reload signal sent")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ func configReloadCmd() {
|
||||
fmt.Printf("Hot reload not applied: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("✓ Gateway hot reload signal sent")
|
||||
fmt.Println("鉁?Gateway hot reload signal sent")
|
||||
}
|
||||
|
||||
func configCheckCmd() {
|
||||
@@ -161,11 +161,11 @@ func configCheckCmd() {
|
||||
}
|
||||
validationErrors := config.Validate(cfg)
|
||||
if len(validationErrors) == 0 {
|
||||
fmt.Println("✓ Config validation passed")
|
||||
fmt.Println("鉁?Config validation passed")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("✗ Config validation failed:")
|
||||
fmt.Println("鉁?Config validation failed:")
|
||||
for _, ve := range validationErrors {
|
||||
fmt.Printf(" - %v\n", ve)
|
||||
}
|
||||
@@ -265,7 +265,7 @@ func providerCmd() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("✓ Provider configuration saved.")
|
||||
fmt.Println("鉁?Provider configuration saved.")
|
||||
running, err := triggerGatewayReload()
|
||||
if err != nil {
|
||||
if running {
|
||||
@@ -275,7 +275,7 @@ func providerCmd() {
|
||||
fmt.Printf("Gateway not running, reload skipped: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("✓ Gateway hot reload signal sent")
|
||||
fmt.Println("鉁?Gateway hot reload signal sent")
|
||||
}
|
||||
|
||||
func providerNames(cfg *config.Config) []string {
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
)
|
||||
|
||||
func cronCmd() {
|
||||
@@ -177,15 +177,15 @@ func cronAddCmd(storePath string) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Added job '%s' (%s)\n", job.Name, job.ID)
|
||||
fmt.Printf("鉁?Added job '%s' (%s)\n", job.Name, job.ID)
|
||||
}
|
||||
|
||||
func cronRemoveCmd(storePath, jobID string) {
|
||||
cs := cron.NewCronService(storePath, nil)
|
||||
if cs.RemoveJob(jobID) {
|
||||
fmt.Printf("✓ Removed job %s\n", jobID)
|
||||
fmt.Printf("鉁?Removed job %s\n", jobID)
|
||||
} else {
|
||||
fmt.Printf("✗ Job %s not found\n", jobID)
|
||||
fmt.Printf("鉁?Job %s not found\n", jobID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,8 +205,8 @@ func cronEnableCmd(storePath string, disable bool) {
|
||||
if disable {
|
||||
status = "disabled"
|
||||
}
|
||||
fmt.Printf("✓ Job '%s' %s\n", job.Name, status)
|
||||
fmt.Printf("鉁?Job '%s' %s\n", job.Name, status)
|
||||
} else {
|
||||
fmt.Printf("✗ Job %s not found\n", jobID)
|
||||
fmt.Printf("鉁?Job %s not found\n", jobID)
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
@@ -12,18 +13,18 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/agent"
|
||||
"clawgo/pkg/api"
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/channels"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/cron"
|
||||
"clawgo/pkg/heartbeat"
|
||||
"clawgo/pkg/logger"
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/providers"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"clawgo/pkg/sentinel"
|
||||
"github.com/YspCoder/clawgo/pkg/agent"
|
||||
"github.com/YspCoder/clawgo/pkg/api"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/channels"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/heartbeat"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/sentinel"
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
@@ -86,6 +87,9 @@ func gatewayCmd() {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
if shouldEmbedWhatsAppBridge(cfg) {
|
||||
cfg.Channels.WhatsApp.BridgeURL = embeddedWhatsAppBridgeURL(cfg)
|
||||
}
|
||||
|
||||
agentLoop, channelManager, err := buildGatewayRuntime(ctx, cfg, msgBus, cronService)
|
||||
if err != nil {
|
||||
@@ -103,30 +107,26 @@ func gatewayCmd() {
|
||||
|
||||
enabledChannels := channelManager.GetEnabledChannels()
|
||||
if len(enabledChannels) > 0 {
|
||||
fmt.Printf("✓ Channels enabled: %s\n", enabledChannels)
|
||||
fmt.Printf("鉁?Channels enabled: %s\n", enabledChannels)
|
||||
} else {
|
||||
fmt.Println("⚠ Warning: No channels enabled")
|
||||
fmt.Println("鈿?Warning: No channels enabled")
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Gateway started on %s:%d\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
fmt.Printf("鉁?Gateway started on %s:%d\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
fmt.Println("Press Ctrl+C to stop. Send SIGHUP to hot-reload config.")
|
||||
|
||||
if err := cronService.Start(); err != nil {
|
||||
fmt.Printf("Error starting cron service: %v\n", err)
|
||||
}
|
||||
fmt.Println("✓ Cron service started")
|
||||
fmt.Println("鉁?Cron service started")
|
||||
|
||||
if err := heartbeatService.Start(); err != nil {
|
||||
fmt.Printf("Error starting heartbeat service: %v\n", err)
|
||||
}
|
||||
fmt.Println("✓ Heartbeat service started")
|
||||
fmt.Println("鉁?Heartbeat service started")
|
||||
if cfg.Sentinel.Enabled {
|
||||
sentinelService.Start()
|
||||
fmt.Println("✓ Sentinel service started")
|
||||
}
|
||||
|
||||
if err := channelManager.StartAll(ctx); err != nil {
|
||||
fmt.Printf("Error starting channels: %v\n", err)
|
||||
fmt.Println("鉁?Sentinel service started")
|
||||
}
|
||||
|
||||
registryServer := api.NewServer(cfg.Gateway.Host, cfg.Gateway.Port, cfg.Gateway.Token, nodes.DefaultManager())
|
||||
@@ -223,6 +223,10 @@ func gatewayCmd() {
|
||||
registryServer.SetToolsCatalogHandler(func() interface{} {
|
||||
return agentLoop.GetToolCatalog()
|
||||
})
|
||||
whatsAppBridge, whatsAppEmbedded := setupEmbeddedWhatsAppBridge(ctx, cfg)
|
||||
if whatsAppBridge != nil {
|
||||
registryServer.SetWhatsAppBridge(whatsAppBridge, embeddedWhatsAppBridgeBasePath)
|
||||
}
|
||||
registryServer.SetCronHandler(func(action string, args map[string]interface{}) (interface{}, error) {
|
||||
getStr := func(k string) string {
|
||||
v, _ := args[k].(string)
|
||||
@@ -363,7 +367,11 @@ func gatewayCmd() {
|
||||
if err := registryServer.Start(ctx); err != nil {
|
||||
fmt.Printf("Error starting node registry server: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("✓ Node registry server started on %s:%d\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
fmt.Printf("鉁?Node registry server started on %s:%d\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
}
|
||||
|
||||
if err := channelManager.StartAll(ctx); err != nil {
|
||||
fmt.Printf("Error starting channels: %v\n", err)
|
||||
}
|
||||
|
||||
go agentLoop.Run(ctx)
|
||||
@@ -373,10 +381,10 @@ func gatewayCmd() {
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, gatewayNotifySignals()...)
|
||||
applyReload := func() {
|
||||
fmt.Println("\n↻ Reloading config...")
|
||||
fmt.Println("\n鈫?Reloading config...")
|
||||
newCfg, err := config.LoadConfig(getConfigPath())
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Reload failed (load config): %v\n", err)
|
||||
fmt.Printf("鉁?Reload failed (load config): %v\n", err)
|
||||
return
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(os.Getenv(envRootGranted)), "1") || strings.EqualFold(strings.TrimSpace(os.Getenv(envRootGranted)), "true") {
|
||||
@@ -390,10 +398,14 @@ func gatewayCmd() {
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(cfg, newCfg) {
|
||||
fmt.Println("✓ Config unchanged, skip reload")
|
||||
fmt.Println("鉁?Config unchanged, skip reload")
|
||||
return
|
||||
}
|
||||
|
||||
if shouldEmbedWhatsAppBridge(newCfg) {
|
||||
newCfg.Channels.WhatsApp.BridgeURL = embeddedWhatsAppBridgeURL(newCfg)
|
||||
}
|
||||
|
||||
runtimeSame := reflect.DeepEqual(cfg.Agents, newCfg.Agents) &&
|
||||
reflect.DeepEqual(cfg.Providers, newCfg.Providers) &&
|
||||
reflect.DeepEqual(cfg.Tools, newCfg.Tools) &&
|
||||
@@ -425,24 +437,32 @@ func gatewayCmd() {
|
||||
cfg = newCfg
|
||||
runtimecfg.Set(cfg)
|
||||
configureGatewayNodeP2P(agentLoop, registryServer, cfg)
|
||||
fmt.Println("✓ Config hot-reload applied (logging/metadata only)")
|
||||
fmt.Println("鉁?Config hot-reload applied (logging/metadata only)")
|
||||
return
|
||||
}
|
||||
|
||||
newAgentLoop, newChannelManager, err := buildGatewayRuntime(ctx, newCfg, msgBus, cronService)
|
||||
if err != nil {
|
||||
fmt.Printf("✗ Reload failed (init runtime): %v\n", err)
|
||||
fmt.Printf("鉁?Reload failed (init runtime): %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
newWhatsAppBridge, _ := setupEmbeddedWhatsAppBridge(ctx, newCfg)
|
||||
|
||||
channelManager.StopAll(ctx)
|
||||
agentLoop.Stop()
|
||||
if whatsAppBridge != nil {
|
||||
whatsAppBridge.Stop()
|
||||
}
|
||||
|
||||
channelManager = newChannelManager
|
||||
agentLoop = newAgentLoop
|
||||
cfg = newCfg
|
||||
whatsAppBridge = newWhatsAppBridge
|
||||
whatsAppEmbedded = newWhatsAppBridge != nil
|
||||
runtimecfg.Set(cfg)
|
||||
configureGatewayNodeP2P(agentLoop, registryServer, cfg)
|
||||
registryServer.SetWhatsAppBridge(whatsAppBridge, embeddedWhatsAppBridgeBasePath)
|
||||
sentinelService.Stop()
|
||||
sentinelService = sentinel.NewService(
|
||||
getConfigPath(),
|
||||
@@ -465,11 +485,11 @@ func gatewayCmd() {
|
||||
sentinelService.SetManager(channelManager)
|
||||
|
||||
if err := channelManager.StartAll(ctx); err != nil {
|
||||
fmt.Printf("✗ Reload failed (start channels): %v\n", err)
|
||||
fmt.Printf("鉁?Reload failed (start channels): %v\n", err)
|
||||
return
|
||||
}
|
||||
go agentLoop.Run(ctx)
|
||||
fmt.Println("✓ Config hot-reload applied")
|
||||
fmt.Println("鉁?Config hot-reload applied")
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -483,18 +503,23 @@ func gatewayCmd() {
|
||||
default:
|
||||
fmt.Println("\nShutting down...")
|
||||
cancel()
|
||||
if whatsAppEmbedded && whatsAppBridge != nil {
|
||||
whatsAppBridge.Stop()
|
||||
}
|
||||
heartbeatService.Stop()
|
||||
sentinelService.Stop()
|
||||
cronService.Stop()
|
||||
agentLoop.Stop()
|
||||
channelManager.StopAll(ctx)
|
||||
fmt.Println("✓ Gateway stopped")
|
||||
fmt.Println("鉁?Gateway stopped")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const embeddedWhatsAppBridgeBasePath = "/whatsapp"
|
||||
|
||||
func runGatewayStartupCompactionCheck(parent context.Context, agentLoop *agent.AgentLoop) {
|
||||
if agentLoop == nil {
|
||||
return
|
||||
@@ -581,7 +606,7 @@ func gatewayInstallServiceCmd() error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("✓ Gateway service registered: %s (%s)\n", gatewayServiceName, scope)
|
||||
fmt.Printf("鉁?Gateway service registered: %s (%s)\n", gatewayServiceName, scope)
|
||||
fmt.Printf(" Unit file: %s\n", unitPath)
|
||||
fmt.Println(" Start service: clawgo gateway start")
|
||||
fmt.Println(" Restart service: clawgo gateway restart")
|
||||
@@ -741,9 +766,9 @@ func buildGatewayRuntime(ctx context.Context, cfg *config.Config, msgBus *bus.Me
|
||||
startupInfo := agentLoop.GetStartupInfo()
|
||||
toolsInfo := startupInfo["tools"].(map[string]interface{})
|
||||
skillsInfo := startupInfo["skills"].(map[string]interface{})
|
||||
fmt.Println("\n📦 Agent Status:")
|
||||
fmt.Printf(" • Tools: %d loaded\n", toolsInfo["count"])
|
||||
fmt.Printf(" • Skills: %d/%d available\n", skillsInfo["available"], skillsInfo["total"])
|
||||
fmt.Println("\n馃摝 Agent Status:")
|
||||
fmt.Printf(" 鈥?Tools: %d loaded\n", toolsInfo["count"])
|
||||
fmt.Printf(" 鈥?Skills: %d/%d available\n", skillsInfo["available"], skillsInfo["total"])
|
||||
|
||||
logger.InfoCF("agent", logger.C0098,
|
||||
map[string]interface{}{
|
||||
@@ -843,3 +868,65 @@ func buildHeartbeatService(cfg *config.Config, msgBus *bus.MessageBus) *heartbea
|
||||
return "queued", nil
|
||||
}, hbInterval, cfg.Agents.Defaults.Heartbeat.Enabled, cfg.Agents.Defaults.Heartbeat.PromptTemplate)
|
||||
}
|
||||
|
||||
func setupEmbeddedWhatsAppBridge(ctx context.Context, cfg *config.Config) (*channels.WhatsAppBridgeService, bool) {
|
||||
if cfg == nil || !cfg.Channels.WhatsApp.Enabled || !shouldEmbedWhatsAppBridge(cfg) {
|
||||
return nil, false
|
||||
}
|
||||
cfg.Channels.WhatsApp.BridgeURL = embeddedWhatsAppBridgeURL(cfg)
|
||||
stateDir := filepath.Join(filepath.Dir(getConfigPath()), "channels", "whatsapp")
|
||||
svc := channels.NewWhatsAppBridgeService(fmt.Sprintf("%s:%d", cfg.Gateway.Host, cfg.Gateway.Port), stateDir, false)
|
||||
if err := svc.StartEmbedded(ctx); err != nil {
|
||||
fmt.Printf("Error starting embedded WhatsApp bridge: %v\n", err)
|
||||
return nil, false
|
||||
}
|
||||
return svc, true
|
||||
}
|
||||
|
||||
func shouldEmbedWhatsAppBridge(cfg *config.Config) bool {
|
||||
raw := strings.TrimSpace(cfg.Channels.WhatsApp.BridgeURL)
|
||||
if raw == "" {
|
||||
return true
|
||||
}
|
||||
hostPort := comparableBridgeHostPort(raw)
|
||||
if hostPort == "" {
|
||||
return false
|
||||
}
|
||||
if hostPort == "127.0.0.1:3001" || hostPort == "localhost:3001" {
|
||||
return true
|
||||
}
|
||||
return hostPort == comparableGatewayHostPort(cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
}
|
||||
|
||||
func embeddedWhatsAppBridgeURL(cfg *config.Config) string {
|
||||
host := strings.TrimSpace(cfg.Gateway.Host)
|
||||
switch host {
|
||||
case "", "0.0.0.0", "::", "[::]":
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
return fmt.Sprintf("ws://%s:%d%s/ws", host, cfg.Gateway.Port, embeddedWhatsAppBridgeBasePath)
|
||||
}
|
||||
|
||||
func comparableBridgeHostPort(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
if !strings.Contains(raw, "://") {
|
||||
return strings.ToLower(raw)
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.ToLower(strings.TrimSpace(u.Host))
|
||||
}
|
||||
|
||||
func comparableGatewayHostPort(host string, port int) string {
|
||||
host = strings.TrimSpace(strings.ToLower(host))
|
||||
switch host {
|
||||
case "", "0.0.0.0", "::", "[::]":
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
@@ -19,14 +19,14 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/agent"
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/cron"
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/providers"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/agent"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
@@ -151,11 +151,11 @@ func nodeRegisterCmd(args []string) {
|
||||
fmt.Printf("Error registering node: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✓ Node registered: %s -> %s\n", info.ID, opts.GatewayBase)
|
||||
fmt.Printf("鉁?Node registered: %s -> %s\n", info.ID, opts.GatewayBase)
|
||||
if !opts.Watch {
|
||||
return
|
||||
}
|
||||
fmt.Printf("✓ Heartbeat loop started: every %ds\n", opts.HeartbeatSec)
|
||||
fmt.Printf("鉁?Heartbeat loop started: every %ds\n", opts.HeartbeatSec)
|
||||
if err := runNodeHeartbeatLoop(client, opts, info); err != nil {
|
||||
fmt.Printf("Heartbeat loop stopped: %v\n", err)
|
||||
os.Exit(1)
|
||||
@@ -175,7 +175,7 @@ func nodeHeartbeatCmd(args []string) {
|
||||
fmt.Printf("Error sending heartbeat: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("✓ Heartbeat sent: %s -> %s\n", opts.ID, opts.GatewayBase)
|
||||
fmt.Printf("鉁?Heartbeat sent: %s -> %s\n", opts.ID, opts.GatewayBase)
|
||||
}
|
||||
|
||||
func parseNodeRegisterArgs(args []string, cfg *config.Config) (nodeRegisterOptions, error) {
|
||||
@@ -409,23 +409,23 @@ func runNodeHeartbeatLoop(client *http.Client, opts nodeRegisterOptions, info no
|
||||
for {
|
||||
if err := runNodeHeartbeatSocket(ctx, opts, info); err != nil {
|
||||
if ctx.Err() != nil {
|
||||
fmt.Println("✓ Node heartbeat stopped")
|
||||
fmt.Println("鉁?Node heartbeat stopped")
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("Warning: node socket closed for %s: %v\n", info.ID, err)
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
fmt.Println("✓ Node heartbeat stopped")
|
||||
fmt.Println("鉁?Node heartbeat stopped")
|
||||
return nil
|
||||
}
|
||||
if regErr := postNodeRegister(ctx, client, opts.GatewayBase, opts.Token, info); regErr != nil {
|
||||
fmt.Printf("Warning: re-register failed for %s: %v\n", info.ID, regErr)
|
||||
} else {
|
||||
fmt.Printf("✓ Node re-registered: %s\n", info.ID)
|
||||
fmt.Printf("鉁?Node re-registered: %s\n", info.ID)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Println("✓ Node heartbeat stopped")
|
||||
fmt.Println("鉁?Node heartbeat stopped")
|
||||
return nil
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
@@ -466,7 +466,7 @@ func runNodeHeartbeatSocket(ctx context.Context, opts nodeRegisterOptions, info
|
||||
if err := waitNodeAck(ctx, acks, errs, "registered", info.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("✓ Node socket connected: %s\n", info.ID)
|
||||
fmt.Printf("鉁?Node socket connected: %s\n", info.ID)
|
||||
|
||||
ticker := time.NewTicker(time.Duration(opts.HeartbeatSec) * time.Second)
|
||||
pingTicker := time.NewTicker(nodeSocketPingInterval(opts.HeartbeatSec))
|
||||
@@ -493,7 +493,7 @@ func runNodeHeartbeatSocket(ctx context.Context, opts nodeRegisterOptions, info
|
||||
if err := waitNodeAck(ctx, acks, errs, "heartbeat", info.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("✓ Heartbeat ok: %s\n", info.ID)
|
||||
fmt.Printf("鉁?Heartbeat ok: %s\n", info.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/agent"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/agent"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
type stubNodeProvider struct {
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
type onboardOptions struct {
|
||||
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestParseOnboardOptionsSyncWebUI(t *testing.T) {
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/skills"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/skills"
|
||||
)
|
||||
|
||||
func skillsCmd() {
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
func statusCmd() {
|
||||
@@ -24,16 +24,16 @@ func statusCmd() {
|
||||
fmt.Printf("%s clawgo Status\n\n", logo)
|
||||
|
||||
if _, err := os.Stat(configPath); err == nil {
|
||||
fmt.Println("Config:", configPath, "✓")
|
||||
fmt.Println("Config:", configPath, "[ok]")
|
||||
} else {
|
||||
fmt.Println("Config:", configPath, "✗")
|
||||
fmt.Println("Config:", configPath, "[missing]")
|
||||
}
|
||||
|
||||
workspace := cfg.WorkspacePath()
|
||||
if _, err := os.Stat(workspace); err == nil {
|
||||
fmt.Println("Workspace:", workspace, "✓")
|
||||
fmt.Println("Workspace:", workspace, "[ok]")
|
||||
} else {
|
||||
fmt.Println("Workspace:", workspace, "✗")
|
||||
fmt.Println("Workspace:", workspace, "[missing]")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configPath); err == nil {
|
||||
@@ -59,7 +59,7 @@ func statusCmd() {
|
||||
hasKey := strings.TrimSpace(activeProvider.APIKey) != ""
|
||||
status := "not set"
|
||||
if hasKey {
|
||||
status = "✓"
|
||||
status = "configured"
|
||||
}
|
||||
fmt.Printf("Provider API Key: %s\n", status)
|
||||
fmt.Printf("Logging: %v\n", cfg.Logging.Enabled)
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestStatusCmdUsesActiveProviderDetails(t *testing.T) {
|
||||
@@ -71,7 +71,7 @@ func TestStatusCmdUsesActiveProviderDetails(t *testing.T) {
|
||||
if !strings.Contains(out, "Provider API Base: https://backup.example/v1") {
|
||||
t.Fatalf("expected active provider api base in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "Provider API Key: ✓") {
|
||||
if !strings.Contains(out, "Provider API Key: configured") {
|
||||
t.Fatalf("expected active provider api key status in output, got: %s", out)
|
||||
}
|
||||
if !strings.Contains(out, "Nodes P2P: enabled=true transport=webrtc") {
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
//go:embed workspace
|
||||
@@ -22,7 +22,7 @@ var embeddedFiles embed.FS
|
||||
var version = "dev"
|
||||
var buildTime = "unknown"
|
||||
|
||||
const logo = "🦞"
|
||||
const logo = "馃"
|
||||
const gatewayServiceName = "clawgo-gateway.service"
|
||||
const envRootGranted = "CLAWGO_ROOT_GRANTED"
|
||||
|
||||
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
clawgo:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: clawgo
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "18790:18790"
|
||||
environment:
|
||||
TZ: Asia/Shanghai
|
||||
CLAWGO_CONFIG: /home/clawgo/.clawgo/config.json
|
||||
volumes:
|
||||
- ./.clawgo:/home/clawgo/.clawgo
|
||||
69
go.mod
69
go.mod
@@ -1,6 +1,6 @@
|
||||
module clawgo
|
||||
module github.com/YspCoder/clawgo
|
||||
|
||||
go 1.25.5
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/bwmarrin/discordgo v0.29.0
|
||||
@@ -9,20 +9,22 @@ require (
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/larksuite/oapi-sdk-go/v3 v3.5.3
|
||||
github.com/mdp/qrterminal/v3 v3.2.1
|
||||
github.com/mymmrac/telego v1.6.0
|
||||
github.com/mymmrac/telego v1.7.0
|
||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1
|
||||
github.com/pion/webrtc/v4 v4.1.6
|
||||
github.com/pion/webrtc/v4 v4.2.9
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/tencent-connect/botgo v0.2.1
|
||||
go.mau.fi/whatsmeow v0.0.0-20260305215846-fc65416c22c4
|
||||
golang.org/x/oauth2 v0.35.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/time v0.15.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
modernc.org/sqlite v1.46.1
|
||||
rsc.io/qr v0.2.0
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/beeper/argo-go v1.1.2 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
@@ -32,31 +34,32 @@ require (
|
||||
github.com/coder/websocket v1.8.14 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.1 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grbit/go-json v0.11.0 // indirect
|
||||
github.com/klauspost/compress v1.18.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
|
||||
github.com/pion/datachannel v1.5.10 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.7 // indirect
|
||||
github.com/pion/ice/v4 v4.0.10 // indirect
|
||||
github.com/pion/interceptor v0.1.41 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 // indirect
|
||||
github.com/pion/datachannel v1.6.0 // indirect
|
||||
github.com/pion/dtls/v3 v3.1.2 // indirect
|
||||
github.com/pion/ice/v4 v4.2.1 // indirect
|
||||
github.com/pion/interceptor v0.1.44 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/mdns/v2 v2.0.7 // indirect
|
||||
github.com/pion/mdns/v2 v2.1.0 // indirect
|
||||
github.com/pion/randutil v0.1.0 // indirect
|
||||
github.com/pion/rtcp v1.2.15 // indirect
|
||||
github.com/pion/rtp v1.8.23 // indirect
|
||||
github.com/pion/sctp v1.8.40 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.16 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.8 // indirect
|
||||
github.com/pion/stun/v3 v3.0.0 // indirect
|
||||
github.com/pion/transport/v3 v3.0.8 // indirect
|
||||
github.com/pion/turn/v4 v4.1.1 // indirect
|
||||
github.com/pion/rtcp v1.2.16 // indirect
|
||||
github.com/pion/rtp v1.10.1 // indirect
|
||||
github.com/pion/sctp v1.9.2 // indirect
|
||||
github.com/pion/sdp/v3 v3.0.18 // indirect
|
||||
github.com/pion/srtp/v3 v3.0.10 // indirect
|
||||
github.com/pion/stun/v3 v3.1.1 // indirect
|
||||
github.com/pion/transport/v3 v3.1.1 // indirect
|
||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||
github.com/pion/turn/v4 v4.1.4 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
@@ -65,21 +68,19 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||
github.com/valyala/fastjson v1.6.7 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 // indirect
|
||||
github.com/valyala/fastjson v1.6.10 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.5.32 // indirect
|
||||
github.com/wlynxg/anet v0.0.5 // indirect
|
||||
go.mau.fi/libsignal v0.2.1 // indirect
|
||||
go.mau.fi/util v0.9.6 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/arch v0.25.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
modernc.org/libc v1.67.6 // indirect
|
||||
modernc.org/libc v1.70.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
)
|
||||
|
||||
65
go.sum
65
go.sum
@@ -1,6 +1,8 @@
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=
|
||||
@@ -49,6 +51,8 @@ github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Px
|
||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||
github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4=
|
||||
github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk=
|
||||
github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -88,8 +92,12 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
@@ -111,6 +119,8 @@ github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFe
|
||||
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
|
||||
github.com/mymmrac/telego v1.6.0 h1:Zc8rgyHozvd/7ZgyrigyHdAF9koHYMfilYfyB6wlFC0=
|
||||
github.com/mymmrac/telego v1.6.0/go.mod h1:xt6ZWA8zi8KmuzryE1ImEdl9JSwjHNpM4yhC7D8hU4Y=
|
||||
github.com/mymmrac/telego v1.7.0 h1:yRO/l00tFGG4nY66ufUKb4ARqv7qx9+LsjQv/b0NEyo=
|
||||
github.com/mymmrac/telego v1.7.0/go.mod h1:pdLV346EgVuq7Xrh3kMggeBiazeHhsdEoK0RTEOPXRM=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
@@ -125,38 +135,70 @@ github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 h1:Lb/Uzkiw2Ugt2Xf03J5wmv
|
||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU=
|
||||
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
|
||||
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6 h1:rh2lKw/P/EqHa724vYH2+VVQ1YnW4u6EOXl0PMAovZE=
|
||||
github.com/petermattis/goid v0.0.0-20260226131333-17d1149c6ac6/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
|
||||
github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
|
||||
github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0=
|
||||
github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk=
|
||||
github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
|
||||
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
|
||||
github.com/pion/dtls/v3 v3.1.2 h1:gqEdOUXLtCGW+afsBLO0LtDD8GnuBBjEy6HRtyofZTc=
|
||||
github.com/pion/dtls/v3 v3.1.2/go.mod h1:Hw/igcX4pdY69z1Hgv5x7wJFrUkdgHwAn/Q/uo7YHRo=
|
||||
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
|
||||
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
|
||||
github.com/pion/ice/v4 v4.2.1 h1:XPRYXaLiFq3LFDG7a7bMrmr3mFr27G/gtXN3v/TVfxY=
|
||||
github.com/pion/ice/v4 v4.2.1/go.mod h1:2quLV1S5v1tAx3VvAJaH//KGitRXvo4RKlX6D3tnN+c=
|
||||
github.com/pion/interceptor v0.1.41 h1:NpvX3HgWIukTf2yTBVjVGFXtpSpWgXjqz7IIpu7NsOw=
|
||||
github.com/pion/interceptor v0.1.41/go.mod h1:nEt4187unvRXJFyjiw00GKo+kIuXMWQI9K89fsosDLY=
|
||||
github.com/pion/interceptor v0.1.44 h1:sNlZwM8dWXU9JQAkJh8xrarC0Etn8Oolcniukmuy0/I=
|
||||
github.com/pion/interceptor v0.1.44/go.mod h1:4atVlBkcgXuUP+ykQF0qOCGU2j7pQzX2ofvPRFsY5RY=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
|
||||
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
|
||||
github.com/pion/mdns/v2 v2.1.0 h1:3IJ9+Xio6tWYjhN6WwuY142P/1jA0D5ERaIqawg/fOY=
|
||||
github.com/pion/mdns/v2 v2.1.0/go.mod h1:pcez23GdynwcfRU1977qKU0mDxSeucttSHbCSfFOd9A=
|
||||
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
||||
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
|
||||
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
|
||||
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
||||
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
||||
github.com/pion/rtp v1.8.23 h1:kxX3bN4nM97DPrVBGq5I/Xcl332HnTHeP1Swx3/MCnU=
|
||||
github.com/pion/rtp v1.8.23/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/rtp v1.10.1 h1:xP1prZcCTUuhO2c83XtxyOHJteISg6o8iPsE2acaMtA=
|
||||
github.com/pion/rtp v1.10.1/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
||||
github.com/pion/sctp v1.8.40 h1:bqbgWYOrUhsYItEnRObUYZuzvOMsVplS3oNgzedBlG8=
|
||||
github.com/pion/sctp v1.8.40/go.mod h1:SPBBUENXE6ThkEksN5ZavfAhFYll+h+66ZiG6IZQuzo=
|
||||
github.com/pion/sctp v1.9.2 h1:HxsOzEV9pWoeggv7T5kewVkstFNcGvhMPx0GvUOUQXo=
|
||||
github.com/pion/sctp v1.9.2/go.mod h1:OTOlsQ5EDQ6mQ0z4MUGXt2CgQmKyafBEXhUVqLRB6G8=
|
||||
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
|
||||
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
|
||||
github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI=
|
||||
github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8=
|
||||
github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM=
|
||||
github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg=
|
||||
github.com/pion/srtp/v3 v3.0.10 h1:tFirkpBb3XccP5VEXLi50GqXhv5SKPxqrdlhDCJlZrQ=
|
||||
github.com/pion/srtp/v3 v3.0.10/go.mod h1:3mOTIB0cq9qlbn59V4ozvv9ClW/BSEbRp4cY0VtaR7M=
|
||||
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
|
||||
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
|
||||
github.com/pion/stun/v3 v3.1.1 h1:CkQxveJ4xGQjulGSROXbXq94TAWu8gIX2dT+ePhUkqw=
|
||||
github.com/pion/stun/v3 v3.1.1/go.mod h1:qC1DfmcCTQjl9PBaMa5wSn3x9IPmKxSdcCsxBcDBndM=
|
||||
github.com/pion/transport/v3 v3.0.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc=
|
||||
github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
|
||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
|
||||
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
|
||||
github.com/pion/turn/v4 v4.1.4 h1:EU11yMXKIsK43FhcUnjLlrhE4nboHZq+TXBIi3QpcxQ=
|
||||
github.com/pion/turn/v4 v4.1.4/go.mod h1:ES1DXVFKnOhuDkqn9hn5VJlSWmZPaRJLyBXoOeO/BmQ=
|
||||
github.com/pion/webrtc/v4 v4.1.6 h1:srHH2HwvCGwPba25EYJgUzgLqCQoXl1VCUnrGQMSzUw=
|
||||
github.com/pion/webrtc/v4 v4.1.6/go.mod h1:wKecGRlkl3ox/As/MYghJL+b/cVXMEhoPMJWPuGQFhU=
|
||||
github.com/pion/webrtc/v4 v4.2.9 h1:DZIh1HAhPIL3RvwEDFsmL5hfPSLEpxsQk9/Jir2vkJE=
|
||||
github.com/pion/webrtc/v4 v4.2.9/go.mod h1:9EmLZve0H76eTzf8v2FmchZ6tcBXtDgpfTEu+drW6SY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -203,8 +245,12 @@ github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZy
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/valyala/fastjson v1.6.7 h1:ZE4tRy0CIkh+qDc5McjatheGX2czdn8slQjomexVpBM=
|
||||
github.com/valyala/fastjson v1.6.7/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4=
|
||||
github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/vektah/gqlparser/v2 v2.5.32 h1:k9QPJd4sEDTL+qB4ncPLflqTJ3MmjB9SrVzJrawpFSc=
|
||||
github.com/vektah/gqlparser/v2 v2.5.32/go.mod h1:c1I28gSOVNzlfc4WuDlqU7voQnsqI6OG2amkBAFmgts=
|
||||
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
|
||||
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
@@ -222,6 +268,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.25.0 h1:qnk6Ksugpi5Bz32947rkUgDt9/s5qvqDPl/gBKdMJLE=
|
||||
golang.org/x/arch v0.25.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -232,6 +280,8 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o=
|
||||
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
@@ -253,9 +303,13 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
|
||||
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -264,6 +318,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -288,6 +344,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -306,6 +364,8 @@ golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
@@ -346,16 +406,21 @@ modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/ccgo/v4 v4.32.0 h1:hjG66bI/kqIPX1b2yT6fr/jt+QedtP2fqojG2VrFuVw=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
||||
modernc.org/libc v1.70.0 h1:U58NawXqXbgpZ/dcdS9kMshu08aiA6b7gusEusqzNkw=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/logger"
|
||||
"clawgo/pkg/providers"
|
||||
"clawgo/pkg/skills"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/skills"
|
||||
)
|
||||
|
||||
type ContextBuilder struct {
|
||||
|
||||
@@ -23,17 +23,17 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/cron"
|
||||
"clawgo/pkg/ekg"
|
||||
"clawgo/pkg/logger"
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/providers"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"clawgo/pkg/scheduling"
|
||||
"clawgo/pkg/session"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/ekg"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/scheduling"
|
||||
"github.com/YspCoder/clawgo/pkg/session"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
type AgentLoop struct {
|
||||
|
||||
@@ -3,8 +3,8 @@ package agent
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestNewAgentLoopDisablesNodeP2PByDefault(t *testing.T) {
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
type stubLLMProvider struct{}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
)
|
||||
|
||||
func TestPrepareOutboundSubagentNoReplyFallback(t *testing.T) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
)
|
||||
|
||||
func TestAppendDailySummaryLogUsesSubagentNamespaceAndTitle(t *testing.T) {
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func (al *AgentLoop) maybeAutoRoute(ctx context.Context, msg bus.InboundMessage) (string, bool, error) {
|
||||
|
||||
@@ -4,10 +4,10 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func TestResolveAutoRouteTarget(t *testing.T) {
|
||||
@@ -27,9 +27,9 @@ func TestResolveAutoRouteTargetRulesFirst(t *testing.T) {
|
||||
cfg.Agents.Router.Strategy = "rules_first"
|
||||
cfg.Agents.Subagents["coder"] = config.SubagentConfig{Enabled: true, Role: "coding", SystemPromptFile: "agents/coder/AGENT.md"}
|
||||
cfg.Agents.Subagents["tester"] = config.SubagentConfig{Enabled: true, Role: "testing", SystemPromptFile: "agents/tester/AGENT.md"}
|
||||
cfg.Agents.Router.Rules = []config.AgentRouteRule{{AgentID: "coder", Keywords: []string{"登录", "bug"}}}
|
||||
cfg.Agents.Router.Rules = []config.AgentRouteRule{{AgentID: "coder", Keywords: []string{"鐧诲綍", "bug"}}}
|
||||
|
||||
agentID, task := resolveAutoRouteTarget(cfg, "请帮我修复登录接口的 bug 并改代码")
|
||||
agentID, task := resolveAutoRouteTarget(cfg, "璇峰府鎴戜慨澶嶇櫥褰曟帴鍙g殑 bug 骞舵敼浠g爜")
|
||||
if agentID != "coder" || task == "" {
|
||||
t.Fatalf("expected coder route, got %s / %s", agentID, task)
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func TestMaybeAutoRouteDispatchesRulesFirstMatch(t *testing.T) {
|
||||
Channel: "cli",
|
||||
ChatID: "direct",
|
||||
SessionKey: "main",
|
||||
Content: "请做一次回归测试并验证这个修复",
|
||||
Content: "璇峰仛涓€娆″洖褰掓祴璇曞苟楠岃瘉杩欎釜淇",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("rules-first auto route failed: %v", err)
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func (al *AgentLoop) HandleSubagentRuntime(ctx context.Context, action string, args map[string]interface{}) (interface{}, error) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func TestHandleSubagentRuntimeDispatchAndWait(t *testing.T) {
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/ekg"
|
||||
"clawgo/pkg/providers"
|
||||
"clawgo/pkg/scheduling"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/ekg"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/scheduling"
|
||||
)
|
||||
|
||||
type plannedTask struct {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
func TestSplitPlannedSegmentsDoesNotSplitPlainNewlines(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"clawgo/pkg/scheduling"
|
||||
"github.com/YspCoder/clawgo/pkg/scheduling"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func (al *AgentLoop) maybeHandleSubagentConfigIntent(ctx context.Context, msg bus.InboundMessage) (string, bool, error) {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func TestDispatchNodeSubagentTaskUsesNodeAgentTask(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
)
|
||||
|
||||
func TestBuildSubagentTaskInputPrefersPromptFile(t *testing.T) {
|
||||
|
||||
@@ -19,12 +19,12 @@ type triggerAudit struct {
|
||||
}
|
||||
|
||||
type triggerEvent struct {
|
||||
Time string `json:"time"`
|
||||
Trigger string `json:"trigger"`
|
||||
Channel string `json:"channel"`
|
||||
Session string `json:"session"`
|
||||
Time string `json:"time"`
|
||||
Trigger string `json:"trigger"`
|
||||
Channel string `json:"channel"`
|
||||
Session string `json:"session"`
|
||||
Suppressed bool `json:"suppressed,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func newTriggerAudit(workspace string) *triggerAudit {
|
||||
@@ -37,10 +37,10 @@ func (ta *triggerAudit) Record(trigger, channel, session string, suppressed bool
|
||||
}
|
||||
trigger = normalizeTrigger(trigger)
|
||||
e := triggerEvent{
|
||||
Time: time.Now().UTC().Format(time.RFC3339),
|
||||
Trigger: trigger,
|
||||
Channel: channel,
|
||||
Session: session,
|
||||
Time: time.Now().UTC().Format(time.RFC3339),
|
||||
Trigger: trigger,
|
||||
Channel: channel,
|
||||
Session: session,
|
||||
Suppressed: suppressed,
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -28,10 +28,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/channels"
|
||||
cfgpkg "clawgo/pkg/config"
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/tools"
|
||||
"github.com/YspCoder/clawgo/pkg/channels"
|
||||
cfgpkg "github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/tools"
|
||||
"github.com/gorilla/websocket"
|
||||
"rsc.io/qr"
|
||||
)
|
||||
@@ -71,6 +71,8 @@ type Server struct {
|
||||
liveRuntimeOn bool
|
||||
liveSubagentMu sync.Mutex
|
||||
liveSubagents map[string]*liveSubagentGroup
|
||||
whatsAppBridge *channels.WhatsAppBridgeService
|
||||
whatsAppBase string
|
||||
}
|
||||
|
||||
var nodesWebsocketUpgrader = websocket.Upgrader{
|
||||
@@ -311,6 +313,42 @@ func (s *Server) SetNodeWebRTCTransport(t *nodes.WebRTCTransport) {
|
||||
func (s *Server) SetNodeP2PStatusHandler(fn func() map[string]interface{}) {
|
||||
s.nodeP2PStatus = fn
|
||||
}
|
||||
func (s *Server) SetWhatsAppBridge(service *channels.WhatsAppBridgeService, basePath string) {
|
||||
s.whatsAppBridge = service
|
||||
s.whatsAppBase = strings.TrimSpace(basePath)
|
||||
}
|
||||
|
||||
func (s *Server) handleWhatsAppBridgeWS(w http.ResponseWriter, r *http.Request) {
|
||||
if s.whatsAppBridge == nil {
|
||||
http.Error(w, "whatsapp bridge unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
s.whatsAppBridge.ServeWS(w, r)
|
||||
}
|
||||
|
||||
func (s *Server) handleWhatsAppBridgeStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if s.whatsAppBridge == nil {
|
||||
http.Error(w, "whatsapp bridge unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
s.whatsAppBridge.ServeStatus(w, r)
|
||||
}
|
||||
|
||||
func (s *Server) handleWhatsAppBridgeLogout(w http.ResponseWriter, r *http.Request) {
|
||||
if s.whatsAppBridge == nil {
|
||||
http.Error(w, "whatsapp bridge unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
s.whatsAppBridge.ServeLogout(w, r)
|
||||
}
|
||||
|
||||
func joinServerRoute(base, endpoint string) string {
|
||||
base = strings.TrimRight(strings.TrimSpace(base), "/")
|
||||
if base == "" || base == "/" {
|
||||
return "/" + strings.TrimPrefix(endpoint, "/")
|
||||
}
|
||||
return base + "/" + strings.TrimPrefix(endpoint, "/")
|
||||
}
|
||||
|
||||
func (s *Server) rememberNodeConnection(nodeID, connID string) {
|
||||
nodeID = strings.TrimSpace(nodeID)
|
||||
@@ -440,6 +478,16 @@ func (s *Server) Start(ctx context.Context) error {
|
||||
mux.HandleFunc("/webui/api/logs/stream", s.handleWebUILogsStream)
|
||||
mux.HandleFunc("/webui/api/logs/live", s.handleWebUILogsLive)
|
||||
mux.HandleFunc("/webui/api/logs/recent", s.handleWebUILogsRecent)
|
||||
if strings.TrimSpace(s.whatsAppBase) != "" {
|
||||
base := strings.TrimRight(strings.TrimSpace(s.whatsAppBase), "/")
|
||||
if base == "" {
|
||||
base = "/whatsapp"
|
||||
}
|
||||
mux.HandleFunc(base, s.handleWhatsAppBridgeWS)
|
||||
mux.HandleFunc(joinServerRoute(base, "ws"), s.handleWhatsAppBridgeWS)
|
||||
mux.HandleFunc(joinServerRoute(base, "status"), s.handleWhatsAppBridgeStatus)
|
||||
mux.HandleFunc(joinServerRoute(base, "logout"), s.handleWhatsAppBridgeLogout)
|
||||
}
|
||||
s.server = &http.Server{Addr: s.addr, Handler: mux}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cfgpkg "clawgo/pkg/config"
|
||||
"clawgo/pkg/nodes"
|
||||
cfgpkg "github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
@@ -120,6 +120,53 @@ func TestHandleWebUIWhatsAppQR(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIWhatsAppStatusWithNestedBridgePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bridge := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/whatsapp/status":
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"state": "connected",
|
||||
"connected": true,
|
||||
"logged_in": true,
|
||||
"bridge_addr": "127.0.0.1:7788",
|
||||
"user_jid": "8613012345678@s.whatsapp.net",
|
||||
"qr_available": false,
|
||||
"last_event": "connected",
|
||||
"updated_at": "2026-03-09T12:00:00+08:00",
|
||||
})
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer bridge.Close()
|
||||
|
||||
tmp := t.TempDir()
|
||||
cfgPath := filepath.Join(tmp, "config.json")
|
||||
cfg := cfgpkg.DefaultConfig()
|
||||
cfg.Logging.Enabled = false
|
||||
cfg.Channels.WhatsApp.Enabled = true
|
||||
cfg.Channels.WhatsApp.BridgeURL = "ws" + strings.TrimPrefix(bridge.URL, "http") + "/whatsapp/ws"
|
||||
if err := cfgpkg.SaveConfig(cfgPath, cfg); err != nil {
|
||||
t.Fatalf("save config: %v", err)
|
||||
}
|
||||
|
||||
srv := NewServer("127.0.0.1", 0, "", nil)
|
||||
srv.SetConfigPath(cfgPath)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webui/api/whatsapp/status", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
srv.handleWebUIWhatsAppStatus(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), `"bridge_running":true`) {
|
||||
t.Fatalf("expected bridge_running=true, got: %s", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebUIConfigRequiresConfirmForProviderAPIBaseChange(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package bus
|
||||
|
||||
import (
|
||||
"clawgo/pkg/logger"
|
||||
"context"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type Channel interface {
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
type recordingChannel struct {
|
||||
@@ -15,11 +15,11 @@ type recordingChannel struct {
|
||||
sent []bus.OutboundMessage
|
||||
}
|
||||
|
||||
func (r *recordingChannel) Name() string { return "test" }
|
||||
func (r *recordingChannel) Start(ctx context.Context) error { return nil }
|
||||
func (r *recordingChannel) Stop(ctx context.Context) error { return nil }
|
||||
func (r *recordingChannel) IsRunning() bool { return true }
|
||||
func (r *recordingChannel) IsAllowed(senderID string) bool { return true }
|
||||
func (r *recordingChannel) Name() string { return "test" }
|
||||
func (r *recordingChannel) Start(ctx context.Context) error { return nil }
|
||||
func (r *recordingChannel) Stop(ctx context.Context) error { return nil }
|
||||
func (r *recordingChannel) IsRunning() bool { return true }
|
||||
func (r *recordingChannel) IsAllowed(senderID string) bool { return true }
|
||||
func (r *recordingChannel) HealthCheck(ctx context.Context) error { return nil }
|
||||
func (r *recordingChannel) Send(ctx context.Context, msg bus.OutboundMessage) error {
|
||||
r.mu.Lock()
|
||||
@@ -78,7 +78,6 @@ func TestBaseChannel_HandleMessage_ContentHashFallbackDedupe(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestDispatchOutbound_DifferentButtonsShouldNotDeduplicate(t *testing.T) {
|
||||
mb := bus.NewMessageBus()
|
||||
mgr, err := NewManager(&config.Config{}, mb)
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot"
|
||||
"github.com/open-dingtalk/dingtalk-stream-sdk-go/client"
|
||||
)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package channels
|
||||
|
||||
import (
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
larksheets "github.com/larksuite/oapi-sdk-go/v3/service/sheets/v3"
|
||||
larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type FeishuChannel struct {
|
||||
@@ -135,7 +135,7 @@ func (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) error
|
||||
logger.WarnCF("feishu", logger.C0045, map[string]interface{}{logger.FieldError: lerr.Error(), logger.FieldChatID: msg.ChatID})
|
||||
continue
|
||||
}
|
||||
links = append(links, fmt.Sprintf("表格%d: %s", i+1, link))
|
||||
links = append(links, fmt.Sprintf("琛ㄦ牸%d: %s", i+1, link))
|
||||
}
|
||||
if len(links) > 0 {
|
||||
if strings.TrimSpace(workMsg.Content) != "" {
|
||||
@@ -903,9 +903,9 @@ func normalizeFeishuText(s string) string {
|
||||
// Headers: "## title" -> "title"
|
||||
s = regexp.MustCompile(`(?m)^#{1,6}\s+`).ReplaceAllString(s, "")
|
||||
// Bullet styles
|
||||
s = regexp.MustCompile(`(?m)^[-*]\s+`).ReplaceAllString(s, "• ")
|
||||
s = regexp.MustCompile(`(?m)^[-*]\s+`).ReplaceAllString(s, "鈥?")
|
||||
// Ordered list to bullet for readability
|
||||
s = regexp.MustCompile(`(?m)^\d+\.\s+`).ReplaceAllString(s, "• ")
|
||||
s = regexp.MustCompile(`(?m)^\d+\.\s+`).ReplaceAllString(s, "鈥?")
|
||||
// Bold/italic/strike markers
|
||||
s = regexp.MustCompile(`\*\*(.*?)\*\*`).ReplaceAllString(s, `$1`)
|
||||
s = regexp.MustCompile(`__(.*?)__`).ReplaceAllString(s, `$1`)
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type MaixCamChannel struct {
|
||||
@@ -161,7 +161,7 @@ func (c *MaixCamChannel) handlePersonDetection(msg MaixCamMessage) {
|
||||
w, _ := msg.Data["w"].(float64)
|
||||
h, _ := msg.Data["h"].(float64)
|
||||
|
||||
content := fmt.Sprintf("📷 Person detected!\nClass: %s\nConfidence: %.2f%%\nPosition: (%.0f, %.0f)\nSize: %.0fx%.0f",
|
||||
content := fmt.Sprintf("馃摲 Person detected!\nClass: %s\nConfidence: %.2f%%\nPosition: (%.0f, %.0f)\nSize: %.0fx%.0f",
|
||||
classInfo, score*100, x, y, w, h)
|
||||
|
||||
metadata := map[string]string{
|
||||
|
||||
@@ -16,9 +16,9 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/tencent-connect/botgo/token"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type QQChannel struct {
|
||||
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
"github.com/mymmrac/telego/telegoutil"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -1144,15 +1144,15 @@ func markdownToTelegramHTML(text string) string {
|
||||
text = escapeHTML(text)
|
||||
|
||||
text = regexp.MustCompile("(?m)^#{1,6}\\s+(.+)$").ReplaceAllString(text, "<b>$1</b>")
|
||||
text = regexp.MustCompile("(?m)^>\\s*(.*)$").ReplaceAllString(text, "│ $1")
|
||||
text = regexp.MustCompile("(?m)^>\\s*(.*)$").ReplaceAllString(text, "鈹?$1")
|
||||
text = regexp.MustCompile("\\[([^\\]]+)\\]\\(([^)]+)\\)").ReplaceAllString(text, `<a href="$2">$1</a>`)
|
||||
text = regexp.MustCompile("\\*\\*(.+?)\\*\\*").ReplaceAllString(text, "<b>$1</b>")
|
||||
text = regexp.MustCompile("__(.+?)__").ReplaceAllString(text, "<b>$1</b>")
|
||||
text = regexp.MustCompile("\\*([^*\\n]+)\\*").ReplaceAllString(text, "<i>$1</i>")
|
||||
text = regexp.MustCompile("_([^_\\n]+)_").ReplaceAllString(text, "<i>$1</i>")
|
||||
text = regexp.MustCompile("~~(.+?)~~").ReplaceAllString(text, "<s>$1</s>")
|
||||
text = regexp.MustCompile("(?m)^[-*]\\s+").ReplaceAllString(text, "• ")
|
||||
text = regexp.MustCompile("(?m)^\\d+\\.\\s+").ReplaceAllString(text, "• ")
|
||||
text = regexp.MustCompile("(?m)^[-*]\\s+").ReplaceAllString(text, "鈥?")
|
||||
text = regexp.MustCompile("(?m)^\\d+\\.\\s+").ReplaceAllString(text, "鈥?")
|
||||
|
||||
for i, code := range inlineCodes.codes {
|
||||
escaped := escapeHTML(code)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
func truncateString(s string, maxLen int) string {
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type WhatsAppChannel struct {
|
||||
|
||||
@@ -67,6 +67,7 @@ type WhatsAppBridgeService struct {
|
||||
status WhatsAppBridgeStatus
|
||||
wsClientsMu sync.Mutex
|
||||
markReadFn func(ctx context.Context, ids []types.MessageID, timestamp time.Time, chat, sender types.JID) error
|
||||
localOnly bool
|
||||
}
|
||||
|
||||
type whatsappBridgeWSMessage struct {
|
||||
@@ -100,6 +101,34 @@ func NewWhatsAppBridgeService(addr, stateDir string, printQR bool) *WhatsAppBrid
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) Start(ctx context.Context) error {
|
||||
if err := s.startRuntime(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
s.RegisterRoutes(mux, "")
|
||||
s.httpServer = &http.Server{
|
||||
Addr: s.addr,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen whatsapp bridge: %w", err)
|
||||
}
|
||||
|
||||
if err := s.httpServer.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) StartEmbedded(ctx context.Context) error {
|
||||
s.localOnly = true
|
||||
return s.startRuntime(ctx)
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) startRuntime(ctx context.Context) error {
|
||||
if strings.TrimSpace(s.addr) == "" {
|
||||
return fmt.Errorf("bridge address is required")
|
||||
}
|
||||
@@ -116,26 +145,13 @@ func (s *WhatsAppBridgeService) Start(ctx context.Context) error {
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
s.cancel = cancel
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", s.handleWS)
|
||||
mux.HandleFunc("/ws", s.handleWS)
|
||||
mux.HandleFunc("/status", s.handleStatus)
|
||||
mux.HandleFunc("/logout", s.handleLogout)
|
||||
s.httpServer = &http.Server{
|
||||
Addr: s.addr,
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", s.addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen whatsapp bridge: %w", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-runCtx.Done()
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = s.httpServer.Shutdown(shutdownCtx)
|
||||
if s.httpServer != nil {
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = s.httpServer.Shutdown(shutdownCtx)
|
||||
}
|
||||
s.closeWSClients()
|
||||
if s.client != nil {
|
||||
s.client.Disconnect()
|
||||
@@ -148,10 +164,6 @@ func (s *WhatsAppBridgeService) Start(ctx context.Context) error {
|
||||
go func() {
|
||||
_ = s.connectClient(runCtx)
|
||||
}()
|
||||
|
||||
if err := s.httpServer.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -161,6 +173,17 @@ func (s *WhatsAppBridgeService) Stop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) RegisterRoutes(mux *http.ServeMux, basePath string) {
|
||||
if mux == nil {
|
||||
return
|
||||
}
|
||||
basePath = normalizeBridgeBasePath(basePath)
|
||||
mux.HandleFunc(basePath, s.ServeWS)
|
||||
mux.HandleFunc(joinBridgeRoute(basePath, "ws"), s.ServeWS)
|
||||
mux.HandleFunc(joinBridgeRoute(basePath, "status"), s.ServeStatus)
|
||||
mux.HandleFunc(joinBridgeRoute(basePath, "logout"), s.ServeLogout)
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) StatusSnapshot() WhatsAppBridgeStatus {
|
||||
s.statusMu.RLock()
|
||||
defer s.statusMu.RUnlock()
|
||||
@@ -421,11 +444,29 @@ func (s *WhatsAppBridgeService) handleWS(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) ServeWS(w http.ResponseWriter, r *http.Request) {
|
||||
s.wrapHandler(s.handleWS)(w, r)
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) wrapHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.localOnly && !isLoopbackRequest(r) {
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(s.StatusSnapshot())
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) ServeStatus(w http.ResponseWriter, r *http.Request) {
|
||||
s.wrapHandler(s.handleStatus)(w, r)
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
@@ -795,36 +836,16 @@ func ParseWhatsAppBridgeListenAddr(raw string) (string, error) {
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
func (s *WhatsAppBridgeService) ServeLogout(w http.ResponseWriter, r *http.Request) {
|
||||
s.wrapHandler(s.handleLogout)(w, r)
|
||||
}
|
||||
|
||||
func BridgeStatusURL(raw string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", fmt.Errorf("bridge url is required")
|
||||
}
|
||||
if !strings.Contains(raw, "://") {
|
||||
raw = "ws://" + raw
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse bridge url: %w", err)
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "wss":
|
||||
u.Scheme = "https"
|
||||
default:
|
||||
u.Scheme = "http"
|
||||
}
|
||||
u.Path = "/status"
|
||||
u.RawQuery = ""
|
||||
u.Fragment = ""
|
||||
return u.String(), nil
|
||||
return bridgeEndpointURL(raw, "status")
|
||||
}
|
||||
|
||||
func BridgeLogoutURL(raw string) (string, error) {
|
||||
statusURL, err := BridgeStatusURL(raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSuffix(statusURL, "/status") + "/logout", nil
|
||||
return bridgeEndpointURL(raw, "logout")
|
||||
}
|
||||
|
||||
func normalizeWhatsAppRecipientJID(raw string) (types.JID, error) {
|
||||
@@ -874,3 +895,73 @@ func extractWhatsAppMessageText(msg *waProto.Message) string {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func bridgeEndpointURL(raw, endpoint string) (string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", fmt.Errorf("bridge url is required")
|
||||
}
|
||||
if !strings.Contains(raw, "://") {
|
||||
raw = "ws://" + raw
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parse bridge url: %w", err)
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "wss":
|
||||
u.Scheme = "https"
|
||||
default:
|
||||
u.Scheme = "http"
|
||||
}
|
||||
u.Path = bridgeSiblingPath(u.Path, endpoint)
|
||||
u.RawQuery = ""
|
||||
u.Fragment = ""
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func bridgeSiblingPath(pathValue, endpoint string) string {
|
||||
pathValue = strings.TrimSpace(pathValue)
|
||||
if endpoint == "" {
|
||||
endpoint = "status"
|
||||
}
|
||||
if pathValue == "" || pathValue == "/" {
|
||||
return "/" + endpoint
|
||||
}
|
||||
trimmed := strings.TrimSuffix(pathValue, "/")
|
||||
if strings.HasSuffix(trimmed, "/ws") {
|
||||
return strings.TrimSuffix(trimmed, "/ws") + "/" + endpoint
|
||||
}
|
||||
return trimmed + "/" + endpoint
|
||||
}
|
||||
|
||||
func normalizeBridgeBasePath(basePath string) string {
|
||||
basePath = strings.TrimSpace(basePath)
|
||||
if basePath == "" || basePath == "/" {
|
||||
return "/"
|
||||
}
|
||||
if !strings.HasPrefix(basePath, "/") {
|
||||
basePath = "/" + basePath
|
||||
}
|
||||
return strings.TrimSuffix(basePath, "/")
|
||||
}
|
||||
|
||||
func joinBridgeRoute(basePath, endpoint string) string {
|
||||
basePath = normalizeBridgeBasePath(basePath)
|
||||
if basePath == "/" {
|
||||
return "/" + strings.TrimPrefix(endpoint, "/")
|
||||
}
|
||||
return basePath + "/" + strings.TrimPrefix(endpoint, "/")
|
||||
}
|
||||
|
||||
func isLoopbackRequest(r *http.Request) bool {
|
||||
if r == nil {
|
||||
return false
|
||||
}
|
||||
host, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr))
|
||||
if err != nil {
|
||||
host = strings.TrimSpace(r.RemoteAddr)
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
return ip != nil && ip.IsLoopback()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
waProto "go.mau.fi/whatsmeow/proto/waE2E"
|
||||
"go.mau.fi/whatsmeow/types"
|
||||
"google.golang.org/protobuf/proto"
|
||||
@@ -53,6 +53,16 @@ func TestBridgeStatusURL(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBridgeStatusURLWithNestedPath(t *testing.T) {
|
||||
got, err := BridgeStatusURL("ws://localhost:7788/whatsapp/ws")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != "http://localhost:7788/whatsapp/status" {
|
||||
t.Fatalf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeWhatsAppRecipientJID(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
|
||||
@@ -3,7 +3,7 @@ package channels
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestWhatsAppShouldHandleIncomingMessage(t *testing.T) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func LoadConfigAsMap(path string) (map[string]interface{}, error) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/lifecycle"
|
||||
"github.com/YspCoder/clawgo/pkg/lifecycle"
|
||||
robcron "github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/lifecycle"
|
||||
"github.com/YspCoder/clawgo/pkg/lifecycle"
|
||||
)
|
||||
|
||||
type HeartbeatService struct {
|
||||
|
||||
@@ -3,11 +3,11 @@ package providers
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
@@ -3,7 +3,7 @@ package runtimecfg
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
var current atomic.Value // *config.Config
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/channels"
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/lifecycle"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/channels"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/lifecycle"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type AlertFunc func(msg string)
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/browser"
|
||||
"github.com/YspCoder/clawgo/pkg/browser"
|
||||
)
|
||||
|
||||
type BrowserTool struct {
|
||||
|
||||
@@ -46,13 +46,13 @@ func (t *CameraTool) Execute(ctx context.Context, args map[string]interface{}) (
|
||||
} else {
|
||||
filename = fmt.Sprintf("snap_%d.jpg", time.Now().Unix())
|
||||
}
|
||||
|
||||
|
||||
// Ensure filename is safe and within workspace
|
||||
filename = filepath.Clean(filename)
|
||||
if filepath.IsAbs(filename) {
|
||||
return "", fmt.Errorf("filename must be relative to workspace")
|
||||
}
|
||||
|
||||
|
||||
outputPath := filepath.Join(t.workspace, filename)
|
||||
|
||||
// Check if video device exists
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
)
|
||||
|
||||
type CronTool struct {
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
const mcpProtocolVersion = "2025-06-18"
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
func TestMCPToolListServers(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
)
|
||||
|
||||
type SendCallback func(channel, chatID, action, content, media, messageID, emoji string, buttons [][]bus.Button) error
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
)
|
||||
|
||||
// NodesTool provides an OpenClaw-style control surface for paired nodes.
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/events"
|
||||
"github.com/YspCoder/clawgo/pkg/events"
|
||||
)
|
||||
|
||||
type processSession struct {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
type ToolRegistry struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
)
|
||||
|
||||
type RemindTool struct {
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/cron"
|
||||
"github.com/YspCoder/clawgo/pkg/cron"
|
||||
)
|
||||
|
||||
func TestRemindTool_UsesToolContextForDeliveryTarget(t *testing.T) {
|
||||
@@ -15,7 +15,7 @@ func TestRemindTool_UsesToolContextForDeliveryTarget(t *testing.T) {
|
||||
tool.SetContext("telegram", "chat-123")
|
||||
|
||||
_, err := tool.Execute(context.Background(), map[string]interface{}{
|
||||
"message": "喝水",
|
||||
"message": "鍠濇按",
|
||||
"time_expr": "10m",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
type SessionInfo struct {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
type ExecTool struct {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
)
|
||||
|
||||
type SkillExecTool struct {
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/ekg"
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/ekg"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
type SubagentTask struct {
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/configops"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/configops"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
)
|
||||
|
||||
func DraftConfigSubagent(description, agentIDHint string) map[string]interface{} {
|
||||
@@ -287,11 +287,11 @@ func normalizeKeywords(items []string) []string {
|
||||
|
||||
func inferDraftRole(lower string) string {
|
||||
switch {
|
||||
case containsDraftKeyword(lower, "test", "regression", "qa", "回归", "测试", "验证"):
|
||||
case containsDraftKeyword(lower, "test", "regression", "qa", "鍥炲綊", "娴嬭瘯", "楠岃瘉"):
|
||||
return "testing"
|
||||
case containsDraftKeyword(lower, "doc", "docs", "readme", "文档", "说明"):
|
||||
case containsDraftKeyword(lower, "doc", "docs", "readme", "鏂囨。", "璇存槑"):
|
||||
return "docs"
|
||||
case containsDraftKeyword(lower, "research", "investigate", "analyze", "调研", "分析", "研究"):
|
||||
case containsDraftKeyword(lower, "research", "investigate", "analyze", "璋冪爺", "鍒嗘瀽", "鐮旂┒"):
|
||||
return "research"
|
||||
default:
|
||||
return "coding"
|
||||
@@ -301,7 +301,7 @@ func inferDraftRole(lower string) string {
|
||||
func inferDraftAgentID(role, lower string) string {
|
||||
switch role {
|
||||
case "testing":
|
||||
if containsDraftKeyword(lower, "review", "审查", "reviewer") {
|
||||
if containsDraftKeyword(lower, "review", "瀹℃煡", "reviewer") {
|
||||
return "reviewer"
|
||||
}
|
||||
return "tester"
|
||||
@@ -310,10 +310,10 @@ func inferDraftAgentID(role, lower string) string {
|
||||
case "research":
|
||||
return "researcher"
|
||||
default:
|
||||
if containsDraftKeyword(lower, "frontend", "ui", "前端") {
|
||||
if containsDraftKeyword(lower, "frontend", "ui", "鍓嶇") {
|
||||
return "frontend-coder"
|
||||
}
|
||||
if containsDraftKeyword(lower, "backend", "api", "后端") {
|
||||
if containsDraftKeyword(lower, "backend", "api", "鍚庣") {
|
||||
return "backend-coder"
|
||||
}
|
||||
return "coder"
|
||||
@@ -356,19 +356,19 @@ func inferDraftKeywords(role, lower string) []string {
|
||||
seed := []string{}
|
||||
switch role {
|
||||
case "testing":
|
||||
seed = []string{"test", "regression", "verify", "回归", "测试", "验证"}
|
||||
seed = []string{"test", "regression", "verify", "鍥炲綊", "娴嬭瘯", "楠岃瘉"}
|
||||
case "docs":
|
||||
seed = []string{"docs", "readme", "document", "文档", "说明"}
|
||||
seed = []string{"docs", "readme", "document", "鏂囨。", "璇存槑"}
|
||||
case "research":
|
||||
seed = []string{"research", "analyze", "investigate", "调研", "分析", "研究"}
|
||||
seed = []string{"research", "analyze", "investigate", "璋冪爺", "鍒嗘瀽", "鐮旂┒"}
|
||||
default:
|
||||
seed = []string{"code", "implement", "fix", "refactor", "代码", "实现", "修复", "重构"}
|
||||
seed = []string{"code", "implement", "fix", "refactor", "浠g爜", "瀹炵幇", "淇", "閲嶆瀯"}
|
||||
}
|
||||
if containsDraftKeyword(lower, "frontend", "前端", "ui") {
|
||||
seed = append(seed, "frontend", "ui", "前端")
|
||||
if containsDraftKeyword(lower, "frontend", "鍓嶇", "ui") {
|
||||
seed = append(seed, "frontend", "ui", "鍓嶇")
|
||||
}
|
||||
if containsDraftKeyword(lower, "backend", "后端", "api") {
|
||||
seed = append(seed, "backend", "api", "后端")
|
||||
if containsDraftKeyword(lower, "backend", "鍚庣", "api") {
|
||||
seed = append(seed, "backend", "api", "鍚庣")
|
||||
}
|
||||
return normalizeKeywords(seed)
|
||||
}
|
||||
@@ -376,13 +376,13 @@ func inferDraftKeywords(role, lower string) []string {
|
||||
func inferDraftSystemPrompt(role, description string) string {
|
||||
switch role {
|
||||
case "testing":
|
||||
return "你负责测试、验证、回归检查与风险反馈。任务描述:" + description
|
||||
return "浣犺礋璐f祴璇曘€侀獙璇併€佸洖褰掓鏌ヤ笌椋庨櫓鍙嶉銆備换鍔℃弿杩帮細" + description
|
||||
case "docs":
|
||||
return "你负责文档编写、结构整理和说明补全。任务描述:" + description
|
||||
return "浣犺礋璐f枃妗g紪鍐欍€佺粨鏋勬暣鐞嗗拰璇存槑琛ュ叏銆備换鍔℃弿杩帮細" + description
|
||||
case "research":
|
||||
return "你负责调研、分析、比较方案,并输出结论与依据。任务描述:" + description
|
||||
return "浣犺礋璐h皟鐮斻€佸垎鏋愩€佹瘮杈冩柟妗堬紝骞惰緭鍑虹粨璁轰笌渚濇嵁銆備换鍔℃弿杩帮細" + description
|
||||
default:
|
||||
return "你负责代码实现与重构,输出具体修改建议和变更结果。任务描述:" + description
|
||||
return "浣犺礋璐d唬鐮佸疄鐜颁笌閲嶆瀯锛岃緭鍑哄叿浣撲慨鏀瑰缓璁拰鍙樻洿缁撴灉銆備换鍔℃弿杩帮細" + description
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
)
|
||||
|
||||
func TestSubagentConfigToolUpsert(t *testing.T) {
|
||||
@@ -34,7 +34,7 @@ func TestSubagentConfigToolUpsert(t *testing.T) {
|
||||
"role": "testing",
|
||||
"notify_main_policy": "internal_only",
|
||||
"display_name": "Review Agent",
|
||||
"description": "负责回归与评审",
|
||||
"description": "handles review and regression checks",
|
||||
"system_prompt_file": "agents/reviewer/AGENT.md",
|
||||
"routing_keywords": []interface{}{"review", "regression"},
|
||||
"tool_allowlist": []interface{}{"shell", "sessions"},
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
)
|
||||
|
||||
type SubagentProfile struct {
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
"clawgo/pkg/nodes"
|
||||
"clawgo/pkg/runtimecfg"
|
||||
"github.com/YspCoder/clawgo/pkg/config"
|
||||
"github.com/YspCoder/clawgo/pkg/nodes"
|
||||
"github.com/YspCoder/clawgo/pkg/runtimecfg"
|
||||
)
|
||||
|
||||
func TestSubagentProfileStoreNormalization(t *testing.T) {
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/bus"
|
||||
"clawgo/pkg/providers"
|
||||
"github.com/YspCoder/clawgo/pkg/bus"
|
||||
"github.com/YspCoder/clawgo/pkg/providers"
|
||||
)
|
||||
|
||||
func TestSubagentSpawnEnforcesTaskQuota(t *testing.T) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/logger"
|
||||
"github.com/YspCoder/clawgo/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
Reference in New Issue
Block a user