Skip to content
Open
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
22 changes: 19 additions & 3 deletions cmd/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/nextlevelbuilder/goclaw/internal/cron"
"github.com/nextlevelbuilder/goclaw/internal/gateway"
"github.com/nextlevelbuilder/goclaw/internal/gateway/methods"
mcpbridge "github.com/nextlevelbuilder/goclaw/internal/mcp"
"github.com/nextlevelbuilder/goclaw/internal/pairing"
"github.com/nextlevelbuilder/goclaw/internal/permissions"
"github.com/nextlevelbuilder/goclaw/internal/providers"
Expand All @@ -32,7 +33,6 @@ import (
"github.com/nextlevelbuilder/goclaw/internal/store"
"github.com/nextlevelbuilder/goclaw/internal/store/file"
"github.com/nextlevelbuilder/goclaw/internal/store/pg"
mcpbridge "github.com/nextlevelbuilder/goclaw/internal/mcp"
"github.com/nextlevelbuilder/goclaw/internal/tools"
"github.com/nextlevelbuilder/goclaw/internal/tracing"
"github.com/nextlevelbuilder/goclaw/pkg/browser"
Expand Down Expand Up @@ -147,6 +147,15 @@ func runGateway() {
toolsReg.Register(tools.NewEditTool(workspace, agentCfg.RestrictToWorkspace))
toolsReg.Register(tools.NewExecTool(workspace, agentCfg.RestrictToWorkspace))
}
toolsReg.Register(tools.NewSearchTool(workspace, agentCfg.RestrictToWorkspace))
toolsReg.Register(tools.NewGlobTool(workspace, agentCfg.RestrictToWorkspace))
toolsReg.Register(tools.NewProcessTool())
toolsReg.Register(tools.NewGatewayTool())
toolsReg.Register(tools.NewCanvasTool(workspace, agentCfg.RestrictToWorkspace))

if editTool, ok := toolsReg.Get("edit"); ok {
toolsReg.Register(tools.NewAliasTool("edit_file", "Compatibility alias for edit", editTool))
}

// Memory system
memMgr := setupMemory(workspace, cfg)
Expand Down Expand Up @@ -185,6 +194,9 @@ func runGateway() {
// Vision fallback tool (for non-vision providers like MiniMax)
toolsReg.Register(tools.NewReadImageTool(providerRegistry))
toolsReg.Register(tools.NewCreateImageTool(providerRegistry))
if createImageTool, ok := toolsReg.Get("create_image"); ok {
toolsReg.Register(tools.NewAliasTool("image", "Compatibility alias for create_image", createImageTool))
}

// TTS (text-to-speech) system
ttsMgr := setupTTS(cfg)
Expand Down Expand Up @@ -253,7 +265,10 @@ func runGateway() {

toolsReg.Register(tools.NewSpawnTool(subagentMgr, "default", 0))
toolsReg.Register(tools.NewSubagentTool(subagentMgr, "default", 0))
slog.Info("subagent system enabled", "tools", []string{"spawn", "subagent"})
if subagentTool, ok := toolsReg.Get("subagent"); ok {
toolsReg.Register(tools.NewAliasTool("subagents", "Compatibility alias for subagent", subagentTool))
}
slog.Info("subagent system enabled", "tools", []string{"spawn", "subagent", "subagents"})
}

// Exec approval system — always active (deny patterns + safe bins + configurable ask mode)
Expand Down Expand Up @@ -486,6 +501,7 @@ func runGateway() {
toolsReg.Register(tools.NewSessionStatusTool())
toolsReg.Register(tools.NewSessionsHistoryTool())
toolsReg.Register(tools.NewSessionsSendTool())
toolsReg.Register(tools.NewSessionsSpawnTool())

// Message tool (send to channels)
toolsReg.Register(tools.NewMessageTool())
Expand Down Expand Up @@ -517,7 +533,7 @@ func runGateway() {
}

// Wire SessionStoreAware + BusAware on tools that need them
for _, name := range []string{"sessions_list", "session_status", "sessions_history", "sessions_send"} {
for _, name := range []string{"sessions_list", "session_status", "sessions_history", "sessions_send", "sessions_spawn"} {
if t, ok := toolsReg.Get(name); ok {
if sa, ok := t.(tools.SessionStoreAware); ok {
sa.SetSessionStore(sessStore)
Expand Down
9 changes: 9 additions & 0 deletions cmd/gateway_builtin_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ func builtinToolSeedData() []store.BuiltinToolDef {
{Name: "write_file", DisplayName: "Write File", Description: "Write or create files in the workspace", Category: "filesystem", Enabled: true},
{Name: "list_files", DisplayName: "List Files", Description: "List files and directories in the workspace", Category: "filesystem", Enabled: true},
{Name: "edit", DisplayName: "Edit File", Description: "Apply targeted edits to files (search and replace)", Category: "filesystem", Enabled: true},
{Name: "edit_file", DisplayName: "Edit File (Compat)", Description: "Compatibility alias for edit", Category: "filesystem", Enabled: true},
{Name: "search", DisplayName: "Search", Description: "Search file contents by text pattern", Category: "filesystem", Enabled: true},
{Name: "glob", DisplayName: "Glob", Description: "Find files by glob pattern", Category: "filesystem", Enabled: true},

// runtime
{Name: "exec", DisplayName: "Execute Command", Description: "Execute shell commands in the workspace", Category: "runtime", Enabled: true,
Metadata: json.RawMessage(`{"config_hint":"Config → Tools → Exec Approval"}`),
},
{Name: "process", DisplayName: "Process", Description: "List, inspect, and signal running processes", Category: "runtime", Enabled: true},

// web
{Name: "web_search", DisplayName: "Web Search", Description: "Search the web using Brave or DuckDuckGo", Category: "web", Enabled: true,
Expand All @@ -47,6 +51,7 @@ func builtinToolSeedData() []store.BuiltinToolDef {
Settings: json.RawMessage(`{"provider":"openrouter","model":"google/gemini-2.5-flash-image"}`),
Requires: []string{"image_gen_provider"},
},
{Name: "image", DisplayName: "Image (Compat)", Description: "Compatibility alias for create_image", Category: "media", Enabled: true},
{Name: "tts", DisplayName: "Text to Speech", Description: "Convert text to speech audio", Category: "media", Enabled: true,
Requires: []string{"tts_provider"},
Metadata: json.RawMessage(`{"config_hint":"Config → TTS"}`),
Expand All @@ -57,12 +62,14 @@ func builtinToolSeedData() []store.BuiltinToolDef {
Requires: []string{"browser"},
Metadata: json.RawMessage(`{"config_hint":"Config → Tools → Browser"}`),
},
{Name: "canvas", DisplayName: "Canvas", Description: "Create Mermaid diagram canvas markdown files", Category: "browser", Enabled: true},

// sessions
{Name: "sessions_list", DisplayName: "List Sessions", Description: "List active chat sessions", Category: "sessions", Enabled: true},
{Name: "session_status", DisplayName: "Session Status", Description: "Get status of a chat session", Category: "sessions", Enabled: true},
{Name: "sessions_history", DisplayName: "Session History", Description: "Get message history of a chat session", Category: "sessions", Enabled: true},
{Name: "sessions_send", DisplayName: "Send to Session", Description: "Send a message to a chat session", Category: "sessions", Enabled: true},
{Name: "sessions_spawn", DisplayName: "Spawn Session", Description: "Create a new session and optionally send an initial message", Category: "sessions", Enabled: true},

// messaging
{Name: "message", DisplayName: "Message", Description: "Send messages to connected channels (Telegram, Discord, etc.)", Category: "messaging", Enabled: true},
Expand All @@ -71,6 +78,7 @@ func builtinToolSeedData() []store.BuiltinToolDef {
{Name: "cron", DisplayName: "Cron Scheduler", Description: "Schedule recurring tasks with cron expressions", Category: "scheduling", Enabled: true,
Metadata: json.RawMessage(`{"config_hint":"Config → Cron"}`),
},
{Name: "gateway", DisplayName: "Gateway", Description: "Gateway status and health helpers", Category: "scheduling", Enabled: true},

// subagents
{Name: "spawn", DisplayName: "Spawn Subagent", Description: "Spawn an asynchronous background subagent", Category: "subagents", Enabled: true,
Expand All @@ -79,6 +87,7 @@ func builtinToolSeedData() []store.BuiltinToolDef {
{Name: "subagent", DisplayName: "Subagent", Description: "Run a synchronous subagent and wait for result", Category: "subagents", Enabled: true,
Metadata: json.RawMessage(`{"config_hint":"Config → Agents Defaults"}`),
},
{Name: "subagents", DisplayName: "Subagents (Compat)", Description: "Compatibility alias for subagent", Category: "subagents", Enabled: true},

// skills
{Name: "skill_search", DisplayName: "Skill Search", Description: "Search available skills by keyword or description", Category: "skills", Enabled: true},
Expand Down
2 changes: 1 addition & 1 deletion cmd/gateway_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func registerProviders(registry *providers.Registry, cfg *config.Config) {
}

if cfg.Providers.MiniMax.APIKey != "" {
registry.Register(providers.NewOpenAIProvider("minimax", cfg.Providers.MiniMax.APIKey, "https://api.minimax.io/v1", "MiniMax-M2.5").
registry.Register(providers.NewOpenAIProvider("minimax", cfg.Providers.MiniMax.APIKey, "https://api.minimaxi.com/v1", "MiniMax-M2.5").
WithChatPath("/text/chatcompletion_v2"))
slog.Info("registered provider", "name", "minimax")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/onboard_managed.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ func resolveProviderAPIBase(providerName string) string {
case "xai":
return "https://api.x.ai/v1"
case "minimax":
return "https://api.minimax.io/v1"
return "https://api.minimaxi.com/v1"
case "cohere":
return "https://api.cohere.com/v2"
case "perplexity":
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.otel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

services:
jaeger:
image: jaegertracing/all-in-one:1.68
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
Expand Down
158 changes: 158 additions & 0 deletions docs/changelog/2026-02-27-i18n-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# 国际化支持(中英文切换)

**日期:** 2026-02-27
**类型:** 新功能
**范围:** `ui/web`

---

## 概述

为 Web 控制面板新增国际化(i18n)支持,用户可在中文和英文之间自由切换。语言偏好持久化保存到 `localStorage`,刷新页面或重新打开后自动恢复。初始语言根据浏览器语言自动检测,中文环境默认中文,其他环境默认英文。

---

## 新增依赖

| 包名 | 版本 | 用途 |
|------|------|------|
| `i18next` | latest | i18n 核心库 |
| `react-i18next` | latest | React 绑定 |

---

## 新增文件

### i18n 基础设施

| 文件 | 说明 |
|------|------|
| `ui/web/src/i18n/index.ts` | i18n 初始化配置、语言持久化逻辑 |
| `ui/web/src/i18n/locales/en.json` | 英文翻译资源文件 |
| `ui/web/src/i18n/locales/zh.json` | 中文翻译资源文件 |

**翻译资源覆盖的命名空间:**

```
common, sidebar, topbar, login, overview, chat, agents, teams,
sessions, skills, cron, config, traces, delegations, usage,
channels, providers, approvals, nodes, logs, customTools,
builtinTools, mcp, tts
```

---

## 修改文件

### 入口文件

| 文件 | 变更说明 |
|------|----------|
| `ui/web/src/main.tsx` | 新增 `import "./i18n"` 以在应用启动前完成 i18n 初始化 |

### 布局组件

| 文件 | 变更说明 |
|------|----------|
| `ui/web/src/components/layout/topbar.tsx` | 新增语言切换按钮(Languages 图标),集成 `useTranslation`,所有文本使用 `t()` |
| `ui/web/src/components/layout/sidebar.tsx` | 所有导航分组标签和菜单项标签使用 `t()` |
| `ui/web/src/components/layout/connection-status.tsx` | 连接/断开文本使用 `t()` |

### 共享组件

| 文件 | 变更说明 |
|------|----------|
| `ui/web/src/components/shared/confirm-dialog.tsx` | "取消"/"确认" 按钮文本使用 `t()` |
| `ui/web/src/components/shared/pagination.tsx` | "条/条"、"行数"、"第 X 页/共 Y 页" 使用 `t()` |

### 聊天组件

| 文件 | 变更说明 |
|------|----------|
| `ui/web/src/components/chat/chat-input.tsx` | 输入框占位符、发送/停止按钮 title 使用 `t()` |
| `ui/web/src/components/chat/agent-selector.tsx` | "选择 Agent"、"暂无 Agent" 文本使用 `t()` |
| `ui/web/src/components/chat/session-switcher.tsx` | "暂无会话"、消息数单位使用 `t()` |

### 页面组件

| 文件 | 变更说明 |
|------|----------|
| `ui/web/src/pages/login/login-page.tsx` | 副标题使用 `t()` |
| `ui/web/src/pages/login/login-tabs.tsx` | 标签页标题使用 `t()` |
| `ui/web/src/pages/login/token-form.tsx` | 表单标签、占位符、按钮文本使用 `t()` |
| `ui/web/src/pages/login/pairing-form.tsx` | 配对流程全部文本使用 `t()`;`PairingCodeDisplay` 子组件同步更新 |
| `ui/web/src/pages/overview/overview-page.tsx` | 页面标题、统计卡片标签使用 `t()` |
| `ui/web/src/pages/chat/chat-page.tsx` | 只读提示使用 `t()` |
| `ui/web/src/pages/chat/chat-sidebar.tsx` | "新建对话" 按钮使用 `t()` |
| `ui/web/src/pages/chat/chat-thread.tsx` | 空状态提示使用 `t()` |
| `ui/web/src/pages/agents/agents-page.tsx` | 页面标题、按钮、空状态、确认对话框使用 `t()` |
| `ui/web/src/pages/teams/teams-page.tsx` | 页面标题、按钮、空状态、确认对话框使用 `t()` |
| `ui/web/src/pages/sessions/sessions-page.tsx` | 页面标题、表头、空状态使用 `t()` |
| `ui/web/src/pages/skills/skills-page.tsx` | 页面标题、表头、操作按钮、空状态使用 `t()` |
| `ui/web/src/pages/cron/cron-page.tsx` | 页面标题、表头、启用/禁用确认对话框使用 `t()` |
| `ui/web/src/pages/channels/channels-page.tsx` | 页面标题、表头、状态标签、操作按钮使用 `t()` |
| `ui/web/src/pages/providers/providers-page.tsx` | 页面标题、表头、状态标签、操作按钮使用 `t()` |
| `ui/web/src/pages/approvals/approvals-page.tsx` | 页面标题、允许/拒绝按钮、确认对话框使用 `t()` |
| `ui/web/src/pages/nodes/nodes-page.tsx` | 页面标题、表头、审批/撤销按钮使用 `t()` |
| `ui/web/src/pages/logs/logs-page.tsx` | 页面标题、按钮、空状态文本使用 `t()` |
| `ui/web/src/pages/traces/traces-page.tsx` | 页面标题、表头、过滤器、空状态使用 `t()` |
| `ui/web/src/pages/usage/usage-page.tsx` | 页面标题、表头、摘要卡片标签使用 `t()` |
| `ui/web/src/pages/delegations/delegations-page.tsx` | 页面标题、表头、过滤器、状态选项使用 `t()` |
| `ui/web/src/pages/custom-tools/custom-tools-page.tsx` | 页面标题、表头、范围标签使用 `t()` |
| `ui/web/src/pages/mcp/mcp-page.tsx` | 页面标题、表头、授权管理按钮使用 `t()` |
| `ui/web/src/pages/builtin-tools/builtin-tools-page.tsx` | 页面标题、工具计数、分类标签、设置按钮使用 `t()` |
| `ui/web/src/pages/config/config-page.tsx` | 页面标题、选项卡、保存按钮、托管模式重定向卡片使用 `t()` |
| `ui/web/src/pages/tts/tts-page.tsx` | 页面标题、提供商选择、模式选项、表单标签全部使用 `t()` |

---

## 实现细节

### i18n 初始化(`ui/web/src/i18n/index.ts`)

```typescript
// 语言初始化优先级:localStorage > 浏览器语言 > 英文
function getInitialLanguage(): string {
const stored = localStorage.getItem("goclaw:language");
if (stored === "en" || stored === "zh") return stored;
const browserLang = navigator.language.toLowerCase();
return browserLang.startsWith("zh") ? "zh" : "en";
}

// 语言切换时自动保存 & 更新 HTML lang 属性
i18n.on("languageChanged", (lng) => {
localStorage.setItem("goclaw:language", lng);
document.documentElement.lang = lng;
});
```

### 语言切换按钮(`topbar.tsx`)

```tsx
const toggleLanguage = () => {
i18n.changeLanguage(i18n.language === "zh" ? "en" : "zh");
};

<button onClick={toggleLanguage} title={t("topbar.language")}>
<Languages className="h-4 w-4" />
</button>
```

---

## 构建验证

| 检查项 | 结果 |
|--------|------|
| TypeScript 编译(`tsc -b`) | ✅ 零错误 |
| Vite 生产构建 | ✅ 成功(2782 模块) |
| Lint 检查(ReadLints) | ✅ 无问题 |
| 产物体积(gzip)主 JS | ~118 KB |

---

## 使用方式

1. 点击顶部栏右侧的 **语言图标**(地球仪/Languages)进行切换
2. 切换后立即生效,无需刷新页面
3. 语言偏好自动保存,下次访问时自动恢复
48 changes: 48 additions & 0 deletions docs/changelog/development-update-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Development Update Log

## 2026-02-28

### 目标
- 对照 `README.md` 功能声明做实现核对、缺口补齐、运行验证与缺陷修复。

### 本次新增与改动
- 新增工具实现:`search`、`glob`、`process`、`gateway`、`canvas`、`sessions_spawn`。
- 新增兼容别名:`edit_file` -> `edit`,`subagents` -> `subagent`,`image` -> `create_image`。
- 修复工具策略与分组不一致问题,确保策略过滤与真实工具注册一致。
- 修复 `tools/invoke` 在 managed 模式下的上下文注入(agent/workspace)问题。
- 修复路径解析在新建多级目录场景下的误拒绝问题。
- 修复 `sessions_spawn` 未注入 session store 导致不可用问题。
- 修复 `process` 在容器内 `ps` 参数兼容问题(增加回退逻辑)。
- 增加兼容工具测试文件,覆盖新增工具核心行为。

### 关键改动文件
- `internal/tools/compat_tools.go`
- `internal/tools/compat_tools_test.go`
- `cmd/gateway.go`
- `cmd/gateway_builtin_tools.go`
- `internal/tools/policy.go`
- `internal/http/tools_invoke.go`
- `internal/tools/filesystem.go`
- `ui/web/eslint.config.mjs`
- `ui/web/package.json`
- `ui/web/pnpm-lock.yaml`

### 已完成验证
- Go 容器内全量测试:`go test ./...` 通过。
- 修改相关包测试:`go test ./internal/tools ./internal/http ./cmd` 通过。
- 后端容器构建与启动:`docker compose ... up -d --build goclaw` 通过。
- 前端构建:`pnpm build` 通过。
- 前端 lint:`pnpm lint` 可执行(当前为 warnings,无 errors)。
- 运行态 API 烟测:`/v1/providers`、`/v1/tools/builtin`、`/v1/tools/custom`、`/v1/channels/instances`、`/v1/delegations`、`/v1/traces`、`/v1/mcp/servers`、`/v1/agents`(带 `X-GoClaw-User-Id`)通过。
- 新增工具实调(`POST /v1/tools/invoke`)通过:`search`、`glob`、`process`、`gateway`、`canvas`、`sessions_spawn`。
- 内置工具清单核验通过:`search/glob/process/gateway/canvas/sessions_spawn/edit_file/subagents/image` 均已出现在 `/v1/tools/builtin`。

### 本次发现并修复的问题
- README 工具声明与实现/命名不一致导致的可用性缺口。
- `sessions_spawn` 运行时报错 `session store not available`。
- `canvas` 写入新路径时报错 `access denied: cannot resolve path`。
- `process` 在容器环境中的 `ps` 调用失败。
- `tools/invoke` 在 managed 模式下缺少 agent workspace 上下文,导致部分工具无法正确解析路径。

### 备注
- 仓库中存在用户先前未完成变更(与本次无关的文件修改),本日志仅记录本次自主开发与验证内容。
Loading