Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions agent/app/api/v2/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,26 @@ func (b *BaseApi) UpdateAgentOtherConfig(c *gin.Context) {
helper.Success(c)
}

// @Tags AI
// @Summary Login Agent Weixin channel
// @Accept json
// @Param request body dto.AgentWeixinLoginReq true "request"
// @Success 200
// @Security ApiKeyAuth
// @Security Timestamp
// @Router /ai/agents/channel/weixin/login [post]
func (b *BaseApi) LoginAgentWeixinChannel(c *gin.Context) {
var req dto.AgentWeixinLoginReq
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if err := agentService.LoginWeixinChannel(req); err != nil {
helper.BadRequest(c, err)
return
}
helper.Success(c)
}

// @Tags AI
// @Summary Approve Agent channel pairing code
// @Accept json
Expand Down
9 changes: 7 additions & 2 deletions agent/app/dto/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,11 @@ type AgentDingTalkConfig struct {
Installed bool `json:"installed"`
}

type AgentWeixinLoginReq struct {
AgentID uint `json:"agentId" validate:"required"`
TaskID string `json:"taskID" validate:"required"`
}

type AgentQQBotConfigReq struct {
AgentID uint `json:"agentId" validate:"required"`
}
Expand All @@ -285,13 +290,13 @@ type AgentQQBotConfig struct {

type AgentPluginInstallReq struct {
AgentID uint `json:"agentId" validate:"required"`
Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk"`
Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk weixin"`
TaskID string `json:"taskID" validate:"required"`
}

type AgentPluginCheckReq struct {
AgentID uint `json:"agentId" validate:"required"`
Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk"`
Type string `json:"type" validate:"required,oneof=qqbot wecom dingtalk weixin"`
}

type AgentPluginStatus struct {
Expand Down
8 changes: 4 additions & 4 deletions agent/app/provider/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ var catalog = map[string]Meta{
APIType: "openai-completions",
ContextWindow: 256000,
MaxTokens: 8192,
Input: []string{"text"},
Input: []string{"text", "image"},
},
Models: []Model{
{ID: "kimi/kimi-k2.5", Name: "Kimi K2.5", Reasoning: true},
Expand Down Expand Up @@ -233,7 +233,7 @@ var catalog = map[string]Meta{
APIType: "openai-completions",
ContextWindow: 256000,
MaxTokens: 8192,
Input: []string{"text"},
Input: []string{"text", "image"},
},
Models: []Model{
{ID: "openai/codex-mini-latest", Name: "Codex Mini", Reasoning: true},
Expand Down Expand Up @@ -270,7 +270,7 @@ var catalog = map[string]Meta{
APIType: "openai-completions",
ContextWindow: 256000,
MaxTokens: 8192,
Input: []string{"text"},
Input: []string{"text", "image"},
},
Models: []Model{
{ID: "anthropic/claude-3-haiku-20240307", Name: "Claude 3 Haiku"},
Expand All @@ -290,7 +290,7 @@ var catalog = map[string]Meta{
APIType: "openai-completions",
ContextWindow: 256000,
MaxTokens: 8192,
Input: []string{"text"},
Input: []string{"text", "image"},
},
Models: []Model{
{ID: "google/gemini-3-flash-preview", Name: "Gemini 3 Flash Preview", Reasoning: true},
Expand Down
1 change: 1 addition & 0 deletions agent/app/service/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type IAgentService interface {
UpdateWecomConfig(req dto.AgentWecomConfigUpdateReq) error
GetDingTalkConfig(req dto.AgentDingTalkConfigReq) (*dto.AgentDingTalkConfig, error)
UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq) error
LoginWeixinChannel(req dto.AgentWeixinLoginReq) error
GetQQBotConfig(req dto.AgentQQBotConfigReq) (*dto.AgentQQBotConfig, error)
UpdateQQBotConfig(req dto.AgentQQBotConfigUpdateReq) error
InstallPlugin(req dto.AgentPluginInstallReq) error
Expand Down
62 changes: 59 additions & 3 deletions agent/app/service/agents_channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,11 @@ func (a AgentService) UpdateDingTalkConfig(req dto.AgentDingTalkConfigUpdateReq)
}

func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
_, install, err := a.loadAgentAndInstall(req.AgentID)
agent, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return err
}
spec, _, err := resolvePluginMeta(req.Type)
spec, pluginID, err := resolvePluginMeta(req.Type)
if err != nil {
return err
}
Expand All @@ -166,7 +166,15 @@ func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
}
installTask.AddSubTask("Install OpenClaw plugin", func(t *task.Task) error {
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute))
return mgr.RunBashCf("docker exec %s openclaw plugins install %s", install.ContainerName, spec)
if err := mgr.RunBashCf("docker exec %s openclaw plugins install %s", install.ContainerName, spec); err != nil {
return err
}
conf, err := readOpenclawConfig(agent.ConfigPath)
if err != nil {
return err
}
appendPluginAllow(conf, pluginID)
Comment on lines +172 to +176

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enable the Weixin plugin in plugins.entries after install

All of the existing plugin-backed channels in this file (setQQBotConfig, setWecomConfig, setDingTalkConfig, setFeishuPluginEnabled) mark their plugin under plugins.entries before the channel can be used, but the new Weixin flow only appends to plugins.allow. A repo-wide search for openclaw-weixin only finds this install path and the login command, so on OpenClaw configs that require an enabled plugins.entries.<id> for discovery, install+login will still leave Weixin unavailable and there is no later UI/API path to turn it on.

Useful? React with 👍 / 👎.

return writeOpenclawConfigRaw(agent.ConfigPath, conf)
}, nil)
go func() {
if err := installTask.Execute(); err != nil {
Expand All @@ -176,6 +184,27 @@ func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error {
return nil
}

func (a AgentService) LoginWeixinChannel(req dto.AgentWeixinLoginReq) error {
_, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
return err
}
loginTask, err := task.NewTaskWithOps("weixin", task.TaskExec, task.TaskScopeAI, req.TaskID, req.AgentID)
if err != nil {
return err
}
loginTask.AddSubTask("Login OpenClaw Weixin channel", func(t *task.Task) error {
mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(30*time.Minute))
return mgr.RunBashCf("docker exec %s openclaw channels login --channel openclaw-weixin", install.ContainerName)
}, nil)
go func() {
if err := loginTask.Execute(); err != nil {
global.LOG.Errorf("login openclaw weixin channel failed: %v", err)
}
}()
return nil
}

func (a AgentService) CheckPlugin(req dto.AgentPluginCheckReq) (*dto.AgentPluginStatus, error) {
_, install, err := a.loadAgentAndInstall(req.AgentID)
if err != nil {
Expand Down Expand Up @@ -514,6 +543,31 @@ func setQQBotConfig(conf map[string]interface{}, config dto.AgentQQBotConfig) {
qqbotEntry["enabled"] = config.Enabled
}

func appendPluginAllow(conf map[string]interface{}, pluginID string) {
plugins := ensureChildMap(conf, "plugins")
allow := make([]string, 0, 4)
seen := map[string]struct{}{}
switch values := plugins["allow"].(type) {
case []interface{}:
for _, value := range values {
text, ok := value.(string)
if !ok || text == "" {
continue
}
if _, ok := seen[text]; ok {
continue
}
seen[text] = struct{}{}
allow = append(allow, text)
}
}
if _, ok := seen[pluginID]; ok {
plugins["allow"] = allow
return
}
plugins["allow"] = append(allow, pluginID)
}

func resolvePluginMeta(pluginType string) (string, string, error) {
switch pluginType {
case "qqbot":
Expand All @@ -522,6 +576,8 @@ func resolvePluginMeta(pluginType string) (string, string, error) {
return "@wecom/wecom-openclaw-plugin", "wecom-openclaw-plugin", nil
case "dingtalk":
return "@dingtalk-real-ai/dingtalk-connector", "dingtalk-connector", nil
case "weixin":
return "@tencent-weixin/openclaw-weixin", "openclaw-weixin", nil
default:
return "", "", fmt.Errorf("unsupported plugin type")
}
Expand Down
16 changes: 0 additions & 16 deletions agent/app/service/agents_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,6 @@ func extractSecurityConfig(conf map[string]interface{}) dto.AgentSecurityConfig
result.AllowedOrigins = append(result.AllowedOrigins, strings.TrimSpace(text))
}
}
case []string:
for _, value := range values {
if strings.TrimSpace(value) != "" {
result.AllowedOrigins = append(result.AllowedOrigins, strings.TrimSpace(value))
}
}
}
return result
}
Expand Down Expand Up @@ -1359,16 +1353,6 @@ func extractStringList(value interface{}) []string {
result = append(result, text)
}
return result
case []string:
result := make([]string, 0, len(values))
for _, value := range values {
text := strings.TrimSpace(value)
if text == "" {
continue
}
result = append(result, text)
}
return result
default:
return []string{}
}
Expand Down
1 change: 1 addition & 0 deletions agent/router/ro_ai.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
aiToolsRouter.POST("/agents/channel/wecom/update", baseApi.UpdateAgentWecomConfig)
aiToolsRouter.POST("/agents/channel/dingtalk/get", baseApi.GetAgentDingTalkConfig)
aiToolsRouter.POST("/agents/channel/dingtalk/update", baseApi.UpdateAgentDingTalkConfig)
aiToolsRouter.POST("/agents/channel/weixin/login", baseApi.LoginAgentWeixinChannel)
aiToolsRouter.POST("/agents/channel/qqbot/get", baseApi.GetAgentQQBotConfig)
aiToolsRouter.POST("/agents/channel/qqbot/update", baseApi.UpdateAgentQQBotConfig)
aiToolsRouter.POST("/agents/plugin/install", baseApi.InstallAgentPlugin)
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/api/interface/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,11 @@ export namespace AI {
groupAllowFrom: string[];
}

export interface AgentWeixinLoginReq {
agentId: number;
taskID: string;
}

export interface AgentQQBotConfigReq {
agentId: number;
}
Expand All @@ -519,13 +524,13 @@ export namespace AI {

export interface AgentPluginInstallReq {
agentId: number;
type: 'qqbot' | 'wecom' | 'dingtalk';
type: 'qqbot' | 'wecom' | 'dingtalk' | 'weixin';
taskID: string;
}

export interface AgentPluginCheckReq {
agentId: number;
type: 'qqbot' | 'wecom' | 'dingtalk';
type: 'qqbot' | 'wecom' | 'dingtalk' | 'weixin';
}

export interface AgentPluginStatus {
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/api/modules/ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ export const updateAgentDingTalkConfig = (req: AI.AgentDingTalkConfigUpdateReq)
return http.post(`/ai/agents/channel/dingtalk/update`, req);
};

export const loginAgentWeixinChannel = (req: AI.AgentWeixinLoginReq) => {
return http.post(`/ai/agents/channel/weixin/login`, req);
};

export const getAgentQQBotConfig = (req: AI.AgentQQBotConfigReq) => {
return http.post<AI.AgentQQBotConfig>(`/ai/agents/channel/qqbot/get`, req);
};
Expand Down
45 changes: 34 additions & 11 deletions frontend/src/components/log/file/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@
<slot name="button"></slot>
</span>
</div>
<div class="log-container" ref="logContainer" @scroll="onScroll" :style="containerStyle">
<div
:class="['log-container', { 'task-mode': isTaskMode }]"
ref="logContainer"
@scroll="onScroll"
:style="containerStyle"
>
<div class="log-spacer" :style="{ height: `${totalHeight}px` }"></div>
<div class="log-viewport" :style="{ transform: `translateY(${offsetY}px)` }">
<div
v-for="(log, index) in visibleLogs"
:key="`${startIndex + index}-${log}`"
class="log-item"
:class="['log-item', { 'task-mode': isTaskMode }]"
:style="{ height: `${logHeight}px` }"
>
<hightlight :log="log" :type="config.colorMode ?? 'nginx'"></hightlight>
Expand Down Expand Up @@ -146,7 +151,8 @@ const isTailDisabled = ref();
const firstLoading = ref(false);
const logs = ref<string[]>([]);
const logContainer = ref<HTMLElement | null>(null);
const logHeight = 23;
const isTaskMode = computed(() => props.config.colorMode === 'task');
const logHeight = computed(() => (isTaskMode.value ? 16 : 23));
const containerHeight = ref(500);
const scrollTop = ref(0);
const lastScrollTop = ref(0);
Expand All @@ -155,16 +161,16 @@ const totalPages = ref(0);
let resizeObserver: ResizeObserver | null = null;
const isEndOfFile = ref(false);

const totalHeight = computed(() => logs.value.length * logHeight);
const totalHeight = computed(() => logs.value.length * logHeight.value);

const visibleCount = computed(() => {
const buffer = 5;
return Math.ceil(containerHeight.value / logHeight) + buffer * 2;
return Math.ceil(containerHeight.value / logHeight.value) + buffer * 2;
});

const startIndex = computed(() => {
const buffer = 5;
const index = Math.floor(scrollTop.value / logHeight) - buffer;
const index = Math.floor(scrollTop.value / logHeight.value) - buffer;
return Math.max(0, index);
});

Expand All @@ -177,7 +183,7 @@ const visibleLogs = computed(() => {
});

const offsetY = computed(() => {
return startIndex.value * logHeight;
return startIndex.value * logHeight.value;
});

const updateContainerHeight = () => {
Expand Down Expand Up @@ -333,7 +339,7 @@ const getContent = async (pre: boolean) => {
if (pre) {
if (readReq.page > 1) {
const addedLines = newLogs.length;
const newScrollPosition = lastScrollTop.value + addedLines * logHeight;
const newScrollPosition = lastScrollTop.value + addedLines * logHeight.value;
logContainer.value.scrollTop = newScrollPosition;
}
} else {
Expand Down Expand Up @@ -361,7 +367,7 @@ const getContent = async (pre: boolean) => {
}
if (logs.value && logs.value.length > 3000) {
const removedCount = readReq.pageSize;
const currentScrollRatio = scrollTop.value / (logs.value.length * logHeight);
const currentScrollRatio = scrollTop.value / (logs.value.length * logHeight.value);

if (pre) {
logs.value.splice(logs.value.length - removedCount, removedCount);
Expand All @@ -373,8 +379,8 @@ const getContent = async (pre: boolean) => {
logs.value.splice(0, removedCount);
nextTick(() => {
if (logContainer.value) {
const newScrollTop = currentScrollRatio * (logs.value.length * logHeight);
logContainer.value.scrollTop = Math.max(0, newScrollTop - removedCount * logHeight);
const newScrollTop = currentScrollRatio * (logs.value.length * logHeight.value);
logContainer.value.scrollTop = Math.max(0, newScrollTop - removedCount * logHeight.value);
}
});
if (minPage.value > 1) {
Expand Down Expand Up @@ -497,6 +503,23 @@ defineExpose({ changeTail, onDownload, clearLog });
white-space: nowrap;
}

.log-item.task-mode {
padding: 0 5px;
font-family: 'SFMono-Regular', 'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 14px;
line-height: 16px;
letter-spacing: 0;
font-variant-ligatures: none;
}

.log-container.task-mode :deep(.token),
.log-container.task-mode :deep(.whitespace-pre) {
font-family: inherit;
line-height: inherit;
letter-spacing: inherit;
font-variant-ligatures: inherit;
}

.log-content {
font-size: 14px;
line-height: 20px;
Expand Down
Loading
Loading