### Install Frontend Dependencies Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Install dependencies required for the frontend. ```bash cd frontend pnpm install ``` -------------------------------- ### Platform Configuration Examples Source: https://github.com/zhimaai/chatclaw/blob/main/internal/services/channels/channels-create.md JSON configuration structures for Feishu and DingTalk integration using app_id and app_secret. ```json { "app_id": "cli_xxxxxxxx", "app_secret": "xxxxxxxxxxxxxxxx" } ``` ```json { "app_id": "xxxxxxxx", "app_secret": "xxxxxxxxxxxxxxxx" } ``` -------------------------------- ### Install Windows CGO Dependencies Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Install GCC and SQLite3 development libraries via MSYS2 UCRT64. ```bash # 安装 GCC 编译器 pacman -S mingw-w64-ucrt-x86_64-gcc # 安装 SQLite3 开发库(包含 sqlite3.h 头文件) pacman -S mingw-w64-ucrt-x86_64-sqlite3 ``` -------------------------------- ### Install Node.js via nvm Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Use nvm to install and switch to Node.js version 22.22. ```bash # 使用 nvm 安装 Node.js 22.22(与 Cursor AI 保持一致) nvm install 22.22 nvm use 22.22 ``` -------------------------------- ### Start Docker Compose Service Source: https://github.com/zhimaai/chatclaw/blob/main/README.md Command to initialize the services defined in the docker-compose.yml file. ```bash docker compose up -d ``` -------------------------------- ### Verify GCC Installation Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Check the installed GCC version. ```bash gcc --version # 应输出类似: gcc.exe (Rev8, Built by MSYS2 project) 15.x.x ``` -------------------------------- ### Install pnpm Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Install pnpm globally as the project's package manager. ```bash # 安装 pnpm(项目统一使用 pnpm 作为包管理器) npm install -g pnpm ``` -------------------------------- ### Install Wails3 CLI Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Install the latest version of the Wails3 CLI. ```shell go install github.com/wailsapp/wails/v3/cmd/wails3@latest ``` -------------------------------- ### Diagnostic Log Examples Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-multi-monitor-popup-positioning.md Sample output from the monitor and service diagnostic logs used for troubleshooting positioning issues. ```text // screen_windows.go [MONITOR-DEBUG] getDPIScaleForPoint(3741,320) → dpi=96 scale=1.00 [MONITOR-DEBUG] getWorkAreaAtPoint(3741,320) → workArea=(2560,-80,1920,1080) // service.go [POPUP-DEBUG] showPopupOnlyAtScreenPos physMouseX=3741 physMouseY=320 dpiScale=1.00 [POPUP-DEBUG] afterClamp finalX=3636 finalY=230 [POPUP-DEBUG] showPopupPhysical FINAL position actualLeft=3636 actualTop=230 ``` -------------------------------- ### Build Backend for Testing Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Build the Go backend binary for WebView2 testing. ```bash # 在项目根目录执行 go build -o bin/ChatClaw.exe . ``` -------------------------------- ### Implement New Tool Source: https://github.com/zhimaai/chatclaw/blob/main/internal/eino/tools/im/im-tools-create.md Create a new tool implementation by satisfying the tool.BaseTool interface, including metadata definition and execution logic. ```go package newtool import ( "context" "encoding/json" "fmt" "chatclaw/internal/eino/tools" "chatclaw/internal/services/i18n" "github.com/cloudwego/eino/components/tool" "github.com/cloudwego/eino/schema" ) // selectDesc returns i18n description based on locale func selectDesc(eng, zh string) string { if i18n.GetLocale() == i18n.LocaleZhCN { return zh } return eng } // NewToolConfig configures the new tool type NewToolConfig struct { // Required dependencies Gateway *channels.Gateway // Optional defaults (auto-filled from context) DefaultChannelID int64 DefaultTargetID string } // NewNewTool creates the tool instance func NewNewTool(config *NewToolConfig) (tool.BaseTool, error) { if config == nil || config.Gateway == nil { return nil, fmt.Errorf("Gateway is required for new_tool") } return &newTool{ gateway: config.Gateway, defaultChannelID: config.DefaultChannelID, defaultTargetID: config.DefaultTargetID, }, nil } type newTool struct { gateway *channels.Gateway defaultChannelID int64 defaultTargetID string } type newToolInput struct { ChannelID int64 `json:"channel_id"` TargetID string `json:"target_id"` Content string `json:"content"` } // Info returns tool metadata (name, description, parameters) func (t *newTool) Info(_ context.Context) (*schema.ToolInfo, error) { return &schema.ToolInfo{ Name: tools.ToolIDNewTool, Desc: selectDesc( "English description of the tool", "工具的中文描述", ), ParamsOneOf: schema.NewParamsOneOfByParams(map[string]*schema.ParameterInfo{ "channel_id": { Type: schema.Integer, Desc: selectDesc("Channel ID", "渠道 ID"), Required: true, }, "target_id": { Type: schema.String, Desc: selectDesc("Target ID", "目标 ID"), Required: true, }, "content": { Type: schema.String, Desc: selectDesc("Message content", "消息内容"), Required: true, }, }), }, nil } // InvokableRun executes the tool logic func (t *newTool) InvokableRun(ctx context.Context, argsJSON string, _ ...tool.Option) (string, error) { var in newToolInput if err := json.Unmarshal([]byte(argsJSON), &in); err != nil { return "", fmt.Errorf("parse arguments: %w", err) } // Apply defaults if not provided if in.ChannelID <= 0 && t.defaultChannelID > 0 { in.ChannelID = t.defaultChannelID } if in.TargetID == "" && t.defaultTargetID != "" { in.TargetID = t.defaultTargetID } // Validate required fields if in.ChannelID <= 0 { return "Error: channel_id is required", nil } // Execute tool logic... // Return success message or error string (not error type) return "Success message", nil } ``` -------------------------------- ### Create a Webview Panel Source: https://github.com/zhimaai/chatclaw/blob/main/pkg/webviewpanel/README.md Define and instantiate a new webview panel with specific dimensions, URL, and visibility settings. ```go visible := true panel := manager.NewPanel(webviewpanel.WebviewPanelOptions{ Name: "my-panel", X: 0, Y: 60, // 例如:在标签栏下方 Width: 800, Height: 500, URL: "https://www.example.com", Visible: &visible, ZIndex: 1, }) ``` -------------------------------- ### Project File Structure for Window Management Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-focus-handling.md Overview of the directory structure for window activation, z-order control, and snapping services across different platforms. ```text pkg/winutil/ # 通用窗口激活工具包(公共包,可被任意包导入) ├── activate_windows.go # Windows: SetForegroundWindow + BringWindowToTop ├── activate_darwin.go # macOS: NSRunningApplication + Focus ├── activate_darwin_nocgo.go # macOS 无 CGO 回退 └── activate_other.go # 其他平台: 校验有效性后 Focus internal/services/textselection/ ├── activate_windows.go # Windows API proc 变量(供 mouse_hook 等使用) ├── popup_noactivate_windows.go # Windows WndProc Hook 阻止激活 ├── popup_noactivate_darwin.go # macOS 弹窗不激活配置 (NSFloatingWindowLevel) ├── popup_noactivate_darwin_nocgo.go # macOS 无 CGO 空实现 ├── popup_noactivate_other.go # 其他平台空实现 ├── popup_zorder_windows.go # Windows 窗口层级控制 ├── popup_zorder_darwin.go # macOS 窗口层级控制 (orderFrontRegardless) ├── popup_zorder_darwin_nocgo.go # macOS 无 CGO 空实现 └── popup_zorder_other.go # 其他平台空实现 internal/bootstrap/app.go # 主窗口管理(safeShow/safeWake 调用 winutil.ForceActivateWindow) pkg/winsnap/ ├── winsnap.go # 吸附窗口接口定义 ├── winsnap_windows.go # Windows 吸附窗口实现 ├── winsnap_darwin.go # macOS 吸附窗口实现 (AX Observer) ├── winsnap_darwin_nocgo.go # macOS 无 CGO 实现 ├── wake_windows.go # Windows 唤醒窗口实现 ├── wake_darwin.go # macOS 唤醒窗口实现 ├── wake_darwin_nocgo.go # macOS 无 CGO 唤醒实现 ├── zorder_windows.go # Windows Z-Order 控制 ├── zorder_darwin.go # macOS 可见性检测与隐藏 └── zorder_darwin_nocgo.go # macOS 无 CGO 实现 ``` -------------------------------- ### WebviewPanelOptions Source: https://github.com/zhimaai/chatclaw/blob/main/pkg/webviewpanel/README.md Configuration options for initializing a new webview panel. ```APIDOC ## WebviewPanelOptions ### Description Defines the configuration parameters for creating a webview panel. ### Fields - **Name** (string) - Unique identifier for the panel. - **X, Y** (int) - Position relative to the parent window in CSS pixels. - **Width, Height** (int) - Dimensions of the panel in CSS pixels. - **URL** (string) - Initial URL to load. - **HTML** (string) - Initial HTML content. - **JS** (string) - JavaScript to execute after page load. - **CSS** (string) - CSS styles to inject. - **Visible** (*bool) - Initial visibility (default true). - **DevToolsEnabled** (*bool) - Whether to enable developer tools. - **Zoom** (float64) - Zoom level (default 1.0). - **BackgroundColour** (RGBA) - Background color. - **Transparent** (bool) - Whether the background is transparent. - **Anchor** (AnchorType) - Anchoring/docking configuration. ``` -------------------------------- ### Build Windows Package Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Commands to build and package the application for Windows. ```bash # amd64 wails3 task windows:build ARCH=amd64 DEV=false cd bin && 7z a ChatClaw_windows_amd64.zip ChatClaw.exe && cd .. wails3 task windows:package ARCH=amd64 DEV=false # 包含 openclaw的包 最好自己弄zip压缩包 build\openclaw-runtime\windows-amd64内的文件 压缩到build\openclaw-runtime\windows-amd64.zip中,方便直接导出 wails3 task windows:package ARCH=amd64 DEV=false BUNDLE_OPENCLAW=true ``` -------------------------------- ### macOS Popup Debug Log Output Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-multi-monitor-popup-positioning.md Example of the diagnostic log format used to verify screen geometry and calculated popup coordinates. ```text [POPUP-DEBUG-MAC] screen=LG UltraFine visibleFrame=(2560,0,1920,1055) mouse=(3200,500) final=(3130,510) size=(140,50) ``` -------------------------------- ### Get selected text using UI Automation API in C++ Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-text-selection-detection-research.md Uses the UI Automation framework to retrieve text from a foreground window's TextPattern. ```cpp // 伪代码 IUIAutomation* automation; CoCreateInstance(CLSID_CUIAutomation, NULL, CLSCTX_INPROC_SERVER, IID_IUIAutomation, (void**)&automation); HWND hwnd = GetForegroundWindow(); IUIAutomationElement* element; automation->ElementFromHandle(hwnd, &element); IUIAutomationTextPattern* textPattern; element->GetCurrentPatternAs(UIA_TextPatternId, IID_IUIAutomationTextPattern, (void**)&textPattern); IUIAutomationTextRangeArray* ranges; textPattern->GetSelection(&ranges); // 提取文本 BSTR text; ranges->GetElement(0)->GetText(-1, &text); ``` -------------------------------- ### Creating tools with gateway dependency Source: https://github.com/zhimaai/chatclaw/blob/main/internal/eino/tools/im/im-tools-create.md Instantiate and register a tool that requires a gateway connection within buildExtraToolsAndHandlers. ```go func (s *ChatService) buildExtraToolsAndHandlers(ctx context.Context, gc *generationContext, agentConfig einoagent.Config) ([]tool.BaseTool, []adk.ChatModelAgentMiddleware) { var extraTools []tool.BaseTool var extraHandlers []adk.ChatModelAgentMiddleware // ... 其他工具 ... // 添加新工具(需要 gateway 的情况) if s.gateway != nil { chID, tgtID, hasChannelSource := s.resolveChannelSource(ctx, gc.db, gc.conversationID) // 创建新工具 newToolCfg := &newtool.NewToolConfig{Gateway: s.gateway} if hasChannelSource { newToolCfg.DefaultChannelID = chID newToolCfg.DefaultTargetID = tgtID } newToolInstance, toolErr := newtool.NewNewTool(newToolCfg) if toolErr != nil { s.app.Logger.Warn("[chat] failed to create new_tool", "error", toolErr) } else { extraTools = append(extraTools, newToolInstance) s.app.Logger.Info("[chat] new_tool added", "default_channel", newToolCfg.DefaultChannelID) } } return extraTools, extraHandlers } ``` -------------------------------- ### Platform Adapter Implementation Template Source: https://github.com/zhimaai/chatclaw/blob/main/internal/services/channels/channels-create.md Template for creating a new platform adapter, requiring registration in the init function. ```go package channels import ( "context" "encoding/json" "sync/atomic" ) func init() { // 关键:在 init 中注册适配器 RegisterAdapter(Platform, func() PlatformAdapter { return &Adapter{} }) } type Config struct { // 平台特定的配置字段 AppKey string `json:"app_key"` AppSecret string `json:"app_secret"` } type Adapter struct { connected atomic.Bool channelID int64 handler MessageHandler config Config } func (a *Adapter) Platform() string { return Platform } func (a *Adapter) Connect(ctx context.Context, channelID int64, configJSON string, handler MessageHandler) error { // 1. 解析配置 JSON var cfg Config if err := json.Unmarshal([]byte(configJSON), &cfg); err != nil { return err } // 2. 初始化平台 SDK // 3. 建立连接 // 4. 设置消息回调 a.connected.Store(true) return nil } func (a *Adapter) Disconnect(ctx context.Context) error { // 关闭连接 a.connected.Store(false) return nil } func (a *Adapter) IsConnected() bool { return a.connected.Load() } func (a *Adapter) SendMessage(ctx context.Context, targetID string, content string) error { // 调用平台 API 发送消息 return nil } ``` -------------------------------- ### Deploy with Docker Source: https://github.com/zhimaai/chatclaw/blob/main/README.md Run the ChatClaw container with volume mapping for data persistence. ```bash docker run -d \ --name chatclaw-server \ -p 8080:8080 \ -v chatclaw-data:/root/.config/chatclaw \ registry.cn-hangzhou.aliyuncs.com/chatwiki/chatclaw:latest ``` -------------------------------- ### Initialize Panel Manager Source: https://github.com/zhimaai/chatclaw/blob/main/pkg/webviewpanel/README.md Create a manager instance associated with a window handle, with an option to enable developer tools. ```go // 创建管理器,debugMode 启用开发者工具 manager := webviewpanel.NewPanelManager(hwnd, true) ``` -------------------------------- ### 模式二:回调已是包级函数 Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-syscall-callback-limit.md 当回调函数本身就是顶级函数时,直接使用 sync.Once 包装 NewCallback 调用。 ```go var ( enumChildCBOnce sync.Once enumChildCB uintptr ) // enumChildCallback is already a top-level function func enumChildCallback(hwnd uintptr, lParam uintptr) uintptr { // ... uses package-level variables ... return 1 } func enumerateChildWindows(parentHwnd uintptr) []childWindowInfo { enumChildCBOnce.Do(func() { enumChildCB = syscall.NewCallback(enumChildCallback) }) enumChildResults = nil procEnumChildWindows.Call(parentHwnd, enumChildCB, 0) return enumChildResults } ``` -------------------------------- ### Correct MonitorFromPoint API Call Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-multi-monitor-popup-positioning.md Demonstrates the correct way to pack a POINT structure into a 64-bit register for x64 Windows API calls. ```go // ❌ WRONG: 传递 3 个独立参数 procMonitorFromPoint.Call( uintptr(pt.X), // → RCX = x (高 32 位为 0,POINT.y 永远是 0) uintptr(pt.Y), // → RDX = y(被 API 当作 dwFlags!) uintptr(flags), // → R8 = flags(API 不读取此寄存器) ) ``` ```go // ✅ CORRECT: 将 POINT 打包为 64 位值 func monitorFromPointPacked(x, y int32, flags uintptr) uintptr { // Pack POINT: low 32 bits = x, high 32 bits = y packed := uintptr(uint32(x)) | (uintptr(uint32(y)) << 32) hMonitor, _, _ := procMonitorFromPoint.Call(packed, flags) return hMonitor } ``` -------------------------------- ### TabBrowser Initialization and Lifecycle Source: https://github.com/zhimaai/chatclaw/blob/main/pkg/webviewpanel/README.md How to instantiate and activate the TabBrowser component. ```APIDOC ## TabBrowser Initialization ### Description Creates a new TabBrowser instance and activates it within a specified layout area. ### Method Go Constructor / Method ### Signature webviewpanel.NewTabBrowser(hwnd uintptr, config TabBrowserConfig) ### Parameters - **hwnd** (uintptr) - Required - The window handle for the browser. - **config** (TabBrowserConfig) - Required - Configuration object including DebugMode, DefaultURL, InitialTabs, and OnTabsChanged callback. ### Activation - **browser.SetDispatchSync(fn func(func()))** - Required - Sets the main thread dispatcher for Windows WebView2/COM thread affinity. - **browser.Activate(rect Rect)** - Required - Displays the browser within the specified Rect boundaries. ``` -------------------------------- ### Implement Windows Callback with sync.Once Source: https://github.com/zhimaai/chatclaw/blob/main/AGENTS.md Ensure Windows callbacks are created only once using sync.Once and avoid closures by using package-level variables. ```go var ( myCBOnce sync.Once myCB uintptr myMu sync.Mutex myParam string ) func myEnumProc(hwnd uintptr, _ uintptr) uintptr { return 1 } func myFunc(param string) uintptr { myMu.Lock() defer myMu.Unlock() myParam = param myCBOnce.Do(func() { myCB = syscall.NewCallback(myEnumProc) }) procEnumWindows.Call(myCB, 0) return 0 } ``` -------------------------------- ### 模式一:EnumWindows 类同步回调 Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-syscall-callback-limit.md 使用 sync.Once 确保回调只创建一次,并利用包级变量和互斥锁在同步 API 调用中传递数据。 ```go var ( fwpCBOnce sync.Once fwpCB uintptr fwpMu sync.Mutex // serialise concurrent callers fwpTarget string // per-call input fwpResult windows.HWND // per-call output ) // Package-level callback function (never a closure) func fwpEnumProc(hwnd uintptr, _ uintptr) uintptr { h := windows.HWND(hwnd) pid, _ := getWindowProcessID(h) exe, _ := getProcessImageBaseName(pid) if strings.ToLower(exe) == fwpTarget { fwpResult = h return 0 // stop enumeration } return 1 } func findWindowByProcess(processName string) windows.HWND { fwpMu.Lock() defer fwpMu.Unlock() fwpTarget = strings.ToLower(processName) fwpResult = 0 fwpCBOnce.Do(func() { fwpCB = syscall.NewCallback(fwpEnumProc) }) procEnumWindows.Call(fwpCB, 0) return fwpResult } ``` -------------------------------- ### Obtain Window Handle Source: https://github.com/zhimaai/chatclaw/blob/main/pkg/webviewpanel/README.md Retrieve a window handle by its exact title or by a partial title match to initialize the panel manager. ```go import "chatclaw/pkg/webviewpanel" // 通过窗口标题查找 hwnd := webviewpanel.FindWindowByTitle("My Window Title") // 或通过部分标题匹配 hwnd := webviewpanel.FindWindowByTitleContains("My Window") ``` -------------------------------- ### Configure PATH Environment Variable Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Add the MSYS2 UCRT64 bin directory to the system PATH. ```text C:\msys64\ucrt64\bin ``` -------------------------------- ### ForceActivateWindow 函数定义 Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-focus-handling.md 封装的原生窗口激活函数,用于替代 Wails 的 Focus 方法以规避 WebView2 错误。 ```go // ForceActivateWindow uses Windows API to activate window // (doesn't call Wails Focus to avoid WebView2 error). func ForceActivateWindow(w *application.WebviewWindow) { // Windows: 使用 SetForegroundWindow + BringWindowToTop + AttachThreadInput 等原生 API // macOS: 使用 NSRunningApplication.activateWithOptions + w.Focus() // 其他平台: 校验窗口有效性后调用 w.Focus() } ``` -------------------------------- ### Build Linux Server Mode Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Commands to build and package the application for Linux server environments. ```bash docker login registry.cn-hangzhou.aliyuncs.com wails3 generate bindings -clean -ts && cd frontend && pnpm install && pnpm run build && cd .. wails3 task build:docker PLATFORM=multi (wails3 task build:docker PLATFORM=amd64) mv ./bin/linux_amd64/server ./bin/ChatClaw_server_linux_amd64 mv ./bin/linux_arm64/server ./bin/ChatClaw_server_linux_arm64 # 单独导出 OpenClaw runtime 压缩包 wails3 task bundle:openclaw:runtime PLATFORM=multi (wails3 task bundle:openclaw:runtime PLATFORM=amd64) # 输出: # build/openclaw-runtime/linux-amd64.tar.gz # build/openclaw-runtime/linux-arm64.tar.gz ``` -------------------------------- ### Development Commands Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Commands for running the application in various modes. ```bash # 安装 openclaw go run ./internal/tools/openclawbundle -config build/runtime.yml # gui模式 wails3 dev # gui模式 + CDP调试(用于Playwright E2E测试) wails3 dev -config build/config.dev.cdp.yml # server模式 (only linux) wails3 task build:server wails3 task run:server ``` -------------------------------- ### Native Window Visibility Management Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-focus-handling.md Platform-specific implementations for hiding and showing windows to avoid focus-related issues. ```go // macOS: 使用 Hide/Show func MoveOffscreen(window *application.WebviewWindow) error { window.Hide() // 可靠隐藏 return nil } func EnsureWindowVisible(window *application.WebviewWindow) error { window.Show() // 恢复显示 return nil } // Windows: 使用原生 ShowWindow API(避免 Wails Focus 崩溃和多屏问题) func MoveOffscreen(window *application.WebviewWindow) error { ShowWindow(hwnd, SW_HIDE) // 原生隐藏 return nil } func EnsureWindowVisible(window *application.WebviewWindow) error { if isMinimized { ShowWindow(hwnd, SW_RESTORE) } else { ShowWindow(hwnd, SW_SHOWNOACTIVATE) } return nil } ``` -------------------------------- ### WebviewPanel Implementation Comparison Source: https://github.com/zhimaai/chatclaw/blob/main/pkg/webviewpanel/README.md Comparison between the current custom implementation and the future official Wails v3 API usage. ```go import "chatclaw/pkg/webviewpanel" hwnd := webviewpanel.FindWindowByTitle("Main") manager := webviewpanel.NewPanelManager(hwnd, true) panel := manager.NewPanel(webviewpanel.WebviewPanelOptions{ Name: "my-panel", X: 0, Y: 60, Width: 800, Height: 500, URL: "https://example.com", }) ``` ```go import "github.com/wailsapp/wails/v3/pkg/application" // 假设你已经拿到了 mainWindow(*application.WebviewWindow) panel := mainWindow.NewPanel(application.WebviewPanelOptions{ Name: "my-panel", X: 0, Y: 60, Width: 800, Height: 500, URL: "https://example.com", }) ``` -------------------------------- ### Importing the new tool package Source: https://github.com/zhimaai/chatclaw/blob/main/internal/eino/tools/im/im-tools-create.md Add the new tool package to the imports section of internal/services/chat/generation.go. ```go import ( // ... 其他导入 newtool "chatclaw/internal/eino/tools/path/to/newtool" ) ``` -------------------------------- ### 验证工具加载 Source: https://github.com/zhimaai/chatclaw/blob/main/internal/eino/tools/im/im-tools-create.md 执行编译检查并查看日志以确认工具是否成功加载。 ```bash # 编译检查 go build ./internal/services/chat/... # 运行应用后,在日志中确认工具已加载 # [chat] new_tool added default_channel=xxx ``` -------------------------------- ### Build macOS Package Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Commands to build and package the application for macOS across different architectures. ```bash # arm64 wails3 task darwin:sign:notarize ARCH=arm64 DEV=false cd bin && tar -czf ChatClaw_darwin_arm64.tar.gz -C ChatClaw.app/Contents/MacOS ChatClaw && mv ChatClaw-arm64.dmg ./ChatClaw_AppleCPU_arm64.dmg && cd .. # amd64 wails3 task darwin:sign:notarize ARCH=amd64 DEV=false cd bin && tar -czf ChatClaw_darwin_amd64.tar.gz -C ChatClaw.app/Contents/MacOS ChatClaw && mv ChatClaw-amd64.dmg ./ChatClaw_IntelCPU_amd64.dmg && cd .. # arm64+amd64 wails3 task darwin:sign:notarize UNIVERSAL=true DEV=false cd bin && mv ChatClaw-universal.dmg ./ChatClaw_MacOS_universal.dmg && cd .. ``` -------------------------------- ### 使用 SWP_NOACTIVATE 移动窗口 Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-focus-handling.md 在调用 SetWindowPos 时添加 SWP_NOACTIVATE 标志,确保窗口在移动时不会被激活。 ```go const swpNoActivate = 0x0010 // 移动窗口但不激活 func setWindowPosNoActivate(hwnd windows.HWND, x, y int32) error { flags := uintptr(swpNoSize | swpNoZOrder | swpNoActivate) _, _, _ = procSetWindowPos.Call(uintptr(hwnd), 0, uintptr(x), uintptr(y), 0, 0, flags) return nil } ``` -------------------------------- ### 错误示例:循环中重复分配回调 Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-syscall-callback-limit.md 在循环或频繁调用的函数中调用 syscall.NewCallback 会导致回调槽位快速耗尽。 ```go // ❌ 错误:每次调用都分配新的回调槽位 func findWindowByProcess(processName string) windows.HWND { var result windows.HWND cb := syscall.NewCallback(func(hwnd uintptr, _ uintptr) uintptr { // ... 枚举逻辑 ... result = windows.HWND(hwnd) return 0 }) procEnumWindows.Call(cb, 0) return result } ``` -------------------------------- ### Configure Server Host and Port Source: https://github.com/zhimaai/chatclaw/blob/main/README.md Override the default listening address and port using environment variables. ```bash WAILS_SERVER_HOST=127.0.0.1 WAILS_SERVER_PORT=3000 ./ChatClaw-server-linux-amd64 ``` -------------------------------- ### Cross-Platform Window Activation Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-focus-handling.md Conditional compilation for window activation logic, avoiding w.Focus() on Windows to prevent crashes. ```go // activate_windows.go //go:build windows package winutil func ForceActivateWindow(w *application.WebviewWindow) { // Windows: 使用 SetForegroundWindow + BringWindowToTop + AttachThreadInput // 不调用 w.Focus() } // activate_darwin.go //go:build darwin && cgo package winutil func ForceActivateWindow(w *application.WebviewWindow) { // macOS: 使用 NSRunningApplication + w.Focus() C.winutil_activate_current_app() w.Focus() } // activate_other.go //go:build !windows && !darwin package winutil func ForceActivateWindow(w *application.WebviewWindow) { // 其他平台: 校验窗口有效性后调用 w.Focus() w.Focus() } ``` -------------------------------- ### ForceActivateWindow 使用示例 Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-focus-handling.md 展示了正确调用封装后的原生 API 与错误调用 Wails Focus 方法的对比。 ```go import "chatclaw/pkg/winutil" // ✅ 正确:使用封装的原生 API winutil.ForceActivateWindow(mainWindow) // ❌ 错误:直接调用 Wails Focus mainWindow.Focus() ``` -------------------------------- ### Creating standalone tools without gateway Source: https://github.com/zhimaai/chatclaw/blob/main/internal/eino/tools/im/im-tools-create.md Instantiate tools that do not require a gateway dependency directly within the buildExtraToolsAndHandlers function. ```go func (s *ChatService) buildExtraToolsAndHandlers(...) (...) { var extraTools []tool.BaseTool // 无依赖的工具直接创建 myTool, err := newtool.NewMyTool(&newtool.Config{}) if err == nil { extraTools = append(extraTools, myTool) } // ... } ``` -------------------------------- ### Run ChatClaw Binary Source: https://github.com/zhimaai/chatclaw/blob/main/README.md Execute the binary directly on Linux systems after granting execution permissions. ```bash chmod +x ChatClaw-server-linux-amd64 ./ChatClaw-server-linux-amd64 ``` -------------------------------- ### 拦截激活消息的 WndProc 实现 Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-focus-handling.md 通过 Hook WndProc 拦截特定的 Windows 消息,防止窗口在不需要时获取焦点。 ```go func popupWndProc(hwnd, msg, wParam, lParam uintptr) uintptr { switch msg { case wmMouseActivate: return maNoActivate // MA_NOACTIVATE = 3 case wmActivate: if wParam&0xFFFF != waInactive { return 0 // Block activation } case wmNCActivate: if wParam != 0 { return 0 // Block activation } case wmSetFocus: return 0 // Block focus } // Pass other messages to original WndProc return callWindowProc(originalWndProc, hwnd, msg, wParam, lParam) } ``` -------------------------------- ### 定义 PlatformAdapter 接口 Source: https://github.com/zhimaai/chatclaw/blob/main/internal/services/channels/channels-create.md 定义了平台适配器必须实现的核心方法,包括连接管理和消息发送。 ```go type PlatformAdapter interface { // 返回平台标识 (e.g. "feishu") Platform() string // 建立长连接,configJSON 包含平台特定凭证 Connect(ctx context.Context, channelID int64, configJSON string, handler MessageHandler) error // 断开连接 Disconnect(ctx context.Context) error // 检查连接状态 IsConnected() bool // 发送消息到平台 SendMessage(ctx context.Context, targetID string, content string) error } ``` -------------------------------- ### Coordinate Pipeline Workflow Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-multi-monitor-popup-positioning.md Visual representation of the popup positioning flow from mouse hook to final window placement. ```text 鼠标 Hook 回调 ↓ GetPhysicalCursorPos() // 始终返回物理像素坐标 ↓ showPopupOnlyAtScreenPos(physX, physY) ↓ getDPIScaleForPoint(physX, physY) // monitorFromPointPacked → GetDpiForMonitor → physW = popWidth * scale // 将 DIP 弹窗尺寸转为物理像素 → physH = popHeight * scale ↓ clampToWorkArea(...) // monitorFromPointPacked → GetMonitorInfoW → 确保弹窗不超出目标屏幕边界 ↓ showPopupPhysical(physX, physY, physW, physH) → ensurePopWindowCreate() // 创建窗口(首次)/ 复用 → setPopupPositionPhysical() // 原生 SetWindowPos(绕过 Wails DIP) → w.Show() → forcePopupTopMostNoActivate() ``` -------------------------------- ### Deploy with Docker Compose Source: https://github.com/zhimaai/chatclaw/blob/main/README.md Use a YAML configuration file to manage the ChatClaw service and its volumes. ```yaml services: chatclaw: image: registry.cn-hangzhou.aliyuncs.com/chatwiki/chatclaw:latest container_name: chatclaw-server volumes: - chatclaw-data:/root/.config/chatclaw ports: - "8080:8080" restart: unless-stopped volumes: chatclaw-data: ``` -------------------------------- ### Project Test Structure Source: https://github.com/zhimaai/chatclaw/blob/main/development.md Overview of the test file directory structure. ```text chatclaw/ ├── internal/**/*_test.go # 后端单元测试 ├── frontend/ │ ├── tests/ │ │ ├── e2e/ # E2E 测试 │ │ │ └── *.spec.ts │ │ └── setup/ # 测试启动器 │ │ ├── chatclaw-launcher.ts │ │ └── chatclaw-teardown.ts │ └── playwright.webview2.config.ts # Playwright WebView2 配置 ``` -------------------------------- ### Manage Multiple Panels Source: https://github.com/zhimaai/chatclaw/blob/main/pkg/webviewpanel/README.md Retrieve or remove panels from the manager by name or ID, and perform bulk cleanup. ```go // 获取面板 panel := manager.GetPanel("my-panel") panel := manager.GetPanelByID(1) panels := manager.GetPanels() // 移除面板 manager.RemovePanel("my-panel") manager.RemovePanelByID(1) // 销毁所有面板 manager.DestroyAll() ``` -------------------------------- ### Register Platform Metadata Source: https://github.com/zhimaai/chatclaw/blob/main/internal/services/channels/channels-create.md Register the platform metadata in the service layer. ```go {ID: Platform, Name: "<显示名称>", AuthType: "token"}, ``` -------------------------------- ### Set Window Position Using Physical Pixels Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-multi-monitor-popup-positioning.md Bypasses Wails' DIP-based positioning by using the Win32 SetWindowPos API directly. ```go // popup_zorder_windows.go func setPopupPositionPhysical(w *application.WebviewWindow, x, y, width, height int) { h := uintptr(w.NativeWindow()) if h == 0 { return } procSetWindowPos.Call( h, hwndTopMost, uintptr(x), uintptr(y), uintptr(width), uintptr(height), uintptr(swpNoActivate), ) } ``` -------------------------------- ### Implement hybrid text selection detection in Go Source: https://github.com/zhimaai/chatclaw/blob/main/docs/windows-text-selection-detection-research.md A fallback strategy that attempts to use UI Automation first and reverts to clipboard access if unsuccessful. ```go func getSelectedText() string { // 尝试 UI Automation if text := getSelectedTextViaUIA(); text != "" { return text } // 回退到剪贴板 return getSelectedTextViaClipboard() } ```