Clawline
Clawline 产品族总览页。Web/桌面客户端 + WebSocket Gateway + 文档站,已合并为 monorepo
clawline/platform。
📦 本页由 5 个旧页合并而来(2026-05-09):clawline-plugin, clawline-gateway, clawline-client-web, clawline-docs-site, clawline-inbox-filter-optimization。
概述与历史
概述
Clawline 是从 OpenClaw 的 generic-channel 插件演变而来的独立项目(153条消息),包含 Web 客户端和小程序客户端。项目经历了命名讨论、组织创建、仓库拆分和代码迁移等阶段。
2026-05-06 重大调整:仓库结构从「拆分多 repo」翻转为「monorepo」——channel、client-web、gateway 三个独立 repo 合并到 clawline/platform monorepo(pnpm workspace,apps/{channel,client-web,gateway})。旧三个 repo 标 deprecated 并本地删除。详见 decisions。
关键事件
- 2026-03-17: 讨论 generic-channel 插件的重命名,要求从营销角度取有意义的名字
- 2026-03-17: 确定项目名为 “Clawline”
- 2026-03-17: 规划仓库结构 ⚠️ 当时决定不用 monorepo(已被 5-06 推翻)
- 2026-03-17: 创建 GitHub 组织
clawline,owner 使用 restry 账号 - 2026-03-17: 客户端仓库命名用 app 风格(client-xxx),体现客户端概念
- 2026-03-20: 迁移 openclaw-generic-channel 插件代码,需要替换所有 generic-channel 引用
- 2026-04-05: 组织建设和插件开发持续进行
- 2026-05-06: ⭐ 三 repo 合并为
clawline/platformmonorepo(decisions 5-06);watch + auto-deploy 改为单 SHA 监控,详见 monitoring-and-cron
仓库结构(2026-05-06 后)
- 组织名: clawline
- 当前主仓:
clawline/platform(monorepo,pnpm workspace,apps/channelapps/client-webapps/gateway) - 小程序: client-miniprogram(独立 repo)
- 已 deprecated:
clawline/channel、clawline/client-web、clawline/gateway(5-06 内容已迁入 platform)
经验教训
- 项目命名要从营销角度考虑,让一般人也能理解项目意义
- 迁移代码时必须全局替换所有旧名称引用(generic-channel → clawline)
- 使用 GitHub 组织管理相关仓库,便于权限和协作管理
- 拆分仓库比 monorepo 更适合独立发展的子项目
ChannelBot 自动化运维
Clawline-channel 项目配备了专属 Bot ChannelBot,负责自动化项目维护:
- 定时执行工作区巡检,生成结构化维护报告(CONTEXT.md 状态、Git 提交、TypeScript 编译、GitHub Issues)
- 自动更新 CONTEXT.md,补录最新 commit 和任务状态
- 通过
codex-delegate技能启动 Codex 执行长时任务(超时 5 小时) - 追踪开发进度:断点续传(stream resume)、分页历史、自动生成会话标题等功能
近期开发动态(2026-03-18~24)
| 提交 | 内容 |
|---|---|
229d5c9 | feat: paginated history with before cursor + hasMore |
fce268a | feat: auto-generate conversation title |
101c1da | fix: stream state replace vs append |
97e10bb | feat: chat-level stream state (断点续传) |
77e83bc | feat: upload local files to relay server instead of base64 over WebSocket |
595e977 | feat: update monitor.ts with enhanced monitoring logic |
待处理问题
- Agent→User 文件/图片发送可靠性验证(2026-03-21 提出)
- OpenClaw 新版本对插件的影响分析(2026-03-29 提出,要求只分析不行动)
相关主题
- openclaw-config
- mattermost-config
- fries-mac
- research-fries
- channelbot
- webbot
- Clawline(本页)
- monitoring-and-cron
2026-05-07 npm 首发 @clawlines/channel@1.0.0 + ghost-outbound 根因修复
apps/channel 第一次以 npm package 形式对外发布,安装方式从「OpenClaw 直接读 workspace 里的 channel-repo 源码」切到 openclaw plugins install @clawlines/channel。同 commit 一并修了下文 Clawline(本页) 详述的 ghost-outbound bug 根因。
| 项 | 值 |
|---|---|
| 包名 | @clawlines/channel |
| 版本 | 1.0.0 |
| commit | 7ebed30 |
| tag | channel-v1.0.0 |
| 发布时间 | 08:26 CST |
| 安装姿势 | openclaw plugins install @clawlines/channel(本机产生 ~/.openclaw/plugins/@clawlines/channel/) |
| Token 存放 | vault key clawline/NPM_PUBLISH_TOKEN(fries-mac 门神 vault) |
踩坑:npm org publish 必须用 Automation Classic token
第一次 npm publish --access public 报 OTP-required,组织开了 publish 2FA。Granular access token / npm token create 默认那种 在 --otp=... 路径上不被 npm 服务端接受,必须去 npmjs.com Account → Access Tokens → 选 Classic Token + Automation 类型创建一个;automation token 自动绕过 publish-2FA。生成完直接塞 vault clawline/NPM_PUBLISH_TOKEN 后跑 NPM_TOKEN=*** get clawline/NPM_PUBLISH_TOKEN) npm publish --access public 即过。
升级路径变更
| 旧 | 新 |
|---|---|
git pull workspace 内的 channel-repo + 重启 gateway | openclaw plugins install @clawlines/channel@latest + 重启 gateway |
paths: ["/home/.../workspace-clawline-client-web/channel-repo"] | npm package 自动落 ~/.openclaw/plugins/@clawlines/channel,OpenClaw 自动发现 |
dev 环境仍保留 source-mode(监
apps/channel目录变化 3 分钟自动 build + restart),见 Clawline(本页) 与 monitoring-and-cron。
OpenClaw SDK 迁移(2026.3.8 → 2026.4.1)
以下信息来源于 webbot 的 Mattermost DM 聊天记录。
版本升级历程
| 日期 | 版本 | 事件 |
|---|---|---|
| 2026-04-01 | 3.8 → 3.28 | Channel 插件 SDK subpath imports 迁移(9 个具体子路径替代根路径) |
| 2026-04-01 | 3.24 → 3.28 | Owl 服务器升级,验证插件兼容性 |
| 2026-04-02 | 3.28 → 4.1 | Owl 升级到最新版,全链路 E2E 验证通过 |
SDK 迁移要点
- Subpath Exports 扩展(44 → 238)— 旧版 deep import 靠 jiti 动态解析,新版正式导出
- ChannelPlugin 新增 5 个可选 adapter:lifecycle、execApprovals、allowlist、bindings、conversationBindings
- Runtime store — 手写 getter/setter 迁移到
createPluginRuntimeStore("clawline") - API 适配 5 处 breaking changes — pairing 加 accountId、dm→direct、typing 回调合并、textChunkLimit 参数改位置、IdentityConfig 去 description
Agent 隔离修复(2026-04-04)
发现并修复了工具调用跨 Agent 泄漏的安全问题:
| 文件 | 改动 |
|---|---|
tool-events.ts | 去掉 broadcast(),改为遍历 getConnectedClients() + sendToClient(),复用 agent 隔离逻辑 |
presence.ts | 同上,避免用户状态跨 agent 泄漏 |
修复后代码中不再有任何 broadcast() 调用,所有事件都走 sendToClient 的 agent 隔离路径。
断点续传审计结论
核心链路健壮:流式断点有 stream-state 内存缓存 + 重连恢复,最终消息无论客户端是否在线都会持久化到 history 文件。唯一极端场景是 gateway 在 message.send 后 100ms 内崩溃(概率极低)。
Gateway 服务端 (admin-new)
以下信息来源于 gatewaybot 的 Mattermost DM 聊天记录(2026-03-17~22)。
Gateway 是 Clawline 的服务端中继组件(clawline/gateway 仓库),由 GatewayBot 开发维护。包含管理后台 admin-new/ 和中继服务 server.js。
管理后台技术栈
| 技术 | 版本/说明 |
|---|---|
| React | 19 |
| Vite | 6 |
| Tailwind CSS | v4 (@tailwindcss/vite) |
| UI 组件库 | shadcn/ui (Radix UI) |
| 图标 | lucide-react |
| 认证 | @logto/react |
| 动画 | framer-motion |
| 通知 | sonner |
认证架构
采用 Logto OAuth2 JWT 全流程认证:
- 前端:
@logto/reactSDK →getAccessToken('https://gateway.clawlines.net/api')获取 JWT - 后端:
jose库 JWKS 验证 JWT(iss: https://logto.dr.restry.cn/oidc,aud: https://gateway.clawlines.net/api) - Fallback:保留 legacy admin token 兼容
数据存储演进
- V1:本地
data/relay-config.json文件存储 - V2:Supabase PostgreSQL(
cl_channels+cl_channel_users表),通过/pg/query端点执行 SQL
REST API
| 接口 | 方法 | 说明 |
|---|---|---|
/api/state | GET | 获取完整状态 |
/api/meta | GET | 公开元数据(无需 auth) |
/api/channels | POST | 创建/更新 channel |
/api/channels/:id | DELETE | 删除 channel |
/api/channels/:id/users | POST | 创建/更新 user |
/api/channels/:id/users/:senderId | DELETE | 删除 user |
部署
关键提交
| Commit | 内容 |
|---|---|
7b27b65 | feat: add admin-new UI with real API integration |
3696159 | feat: OAuth2 JWT 全流程认证 |
87da32b | feat: add Supabase storage backend with /pg/rest/v1 API |
2e5fb54 | fix: UI 审计三轮修复(accessibility + typography + normalize) |
cdc9e24 |
2026-04-07 ~ 04-14 更新日志
版本迭代(ottor 频道)
| 日期 | 变更 | 提交 |
|---|---|---|
| 04-07 | 修复 outbound adapter agentId 提取防消息串台、DM 路由 chatId 解析、Azure OpenAI 端点兼容、文件发送 contentType | 9347056 → faf9fb6 |
| 04-07 | agentId 缺失时回退广播 → 抛错 → 发送 status.failed WS 事件 | faf9fb6 → 781404b (v2.0.0) |
| 04-09 | 切换到 dev 分支,新增 messageId dedup 防重复分发 | 7d4cddf |
| 04-10 | 跨 agent DELEGATE 消息分发、Supabase 离线消息持久化、子 agent 出站修复 | 226734c |
| 04-12 | 新增 resolveInboundConversation 支持 ACP thread binding | feat(clawline) |
| 04-14 | 提交 threadId 传递修复 + gateway build 产物更新 | 6be9d78, b38e0be |
DELEGATE 跨代理消息功能(04-10)
语法:<<DELEGATE:target-agent-name>>消息内容<</DELEGATE>>
- Clawline 服务端自动解析,路由给目标 agent
- 用户侧显示
[Delegated task to **xxx**]占位符 - 最大嵌套 3 层防无限循环
- 一条回复可放多个 DELEGATE 标签
文件发送测试(04-07)
通过 ottor 频道验证文件发送功能:
- 支持
filePath(本地路径)、media(本地/URL)、buffer(Base64) - 支持文本、图片、PDF、文档等格式
项目结构(04-12 via ACP Claude)
| 模块 | 说明 |
|---|---|
| gateway | 后端网关服务(Node.js + Supabase) |
| client-web | Web 客户端 |
| client-wechat | 微信小程序客户端 |
| channel | 消息通道/SDK 库 |
| browser-agent | 浏览器自动化代理 |
| claude-extension-dev | Chrome 扩展(开发版) |
browser-agent — Code Agent 的浏览器抓手(2026-04-15)
仓库
git@github.com:clawline/browser-agent.git,main 分支。
Chrome MV3 扩展 + 本地 Native Messaging Host,把任意 code agent(Hermes / Claude Code / Codex …)一条 HTTP 调用就接进真实 Chrome——用你已登录的会话、cookies、扩展状态做截图/点击/填表/验证 UI,无需 Puppeteer / Playwright / 云服务。
链路与延迟
传统: Agent → HTTP → Server → WebDriver → 启动 Browser → Page
browser-agent: Agent → HTTP :4821 → Native Host (binary IPC) → Service Worker → Side Panel → Page
- Chrome Debugger Protocol 直接驱动页面(与 DevTools 同层),跳过 WebDriver
- Chrome Native Messaging 走 binary IPC、不走网络 → 零网络开销
- 自定义 Accessibility Tree content script 给 LLM 喂语义化结构,不让模型啃原始 HTML
- 浏览器已经在跑、截图走
chrome.debugger原生 ~50ms,普遍快过传统方案
模块布局(2026-04-15 整理后)
| 路径 | 内容 |
|---|---|
sidepanel.js (1662 行) | Side Panel UI(Ctrl/Cmd+Shift+E 唤起),跑本地 Claude API |
service_worker.js (252 行) | Debugger / Tab 管理 |
native-host/ (311 行) | Node.js HTTP Hook 服务,http://127.0.0.1:4821,install.sh 装 macOS Native Messaging Host |
docs/HOOK_API.md | = 给 code agent 用的 skill:所有端点 + 请求/响应格式 |
docs/CODE_AGENT_INTEGRATION.md | 英文集成指南:架构图、3 步安装、curl 示例、错误码 |
docs/WHY.md | 英文价值定位 + 技术差异化(Debugger Protocol / Native Messaging / A11y Tree) |
docs/reference-system-prompt.txt, reference-tools.json | 扩展内部 Claude 用的参考(不是外部 skill) |
README.md | 安装/使用/结构(精简版) |
04-15 之前
HOOK_API.md跟install.sh混在native-host/里发现不到,整理后统一进docs/,并补上.gitignore(node_modules/,error.log)。Manifest V3 权限:debugger / sidePanel / tabs / nativeMessaging / scripting。
关键提交(近期)
77d9bc9 fix: correct zoom coordinate scaling and add Return key alias
d0481ef feat: Add Clawline Native Messaging Host with HTTP Hook API
66fb535 feat: add zoom and scroll_to computer actions
41e5f40 fix: lock target tab at agent start — survives user tab switching
2026-04-16 browser-agent Code Review 与全仓 push
源:fries-mac(lewaymacmini-3-local)。Hermes 委派 Claude Code 对 clawline/browser-agent 跑完整 code review,并把 clawline 下所有子仓的待提交改动一次性推到远程。
Code Review 关键发现
🔴 Critical
- HTTP Hook(
http://127.0.0.1:4821)无认证 + CORS*— 任何本地进程或网页都能控制 Chrome;用户决定暂不修(加 Bearer token 调试太麻烦,本地 dev 优先) - Network 事件监听器泄漏:每次 tab 切换新增 listener 不移除,长跑会内存泄漏 — 已修
- HTTP 请求体无大小限制,可被 OOM 攻击 — 已修
⚠️ 已修的 Warning
- 重连无指数退避(潜在无限循环)
- 硬编码 1500ms 等 sidepanel 注册改为轮询,消除竞态
- escapeHtml 补全(innerHTML 恢复对话的 XSS 边界)
- IndexedDB 连接断开处理
- 空 catch 加 warn
- 注入脚本全局变量前缀
__claude→__clawline(content-script.js 与 sidepanel.js 的 executeScript 引用必须同步改名)
✅ 评价良好:Native Messaging 协议、screenshot 优化、坐标缩放、SSE Parser、Tab 锁定、WeakRef DOM 引用
提交结果
| 子仓 | 分支 | 结果 |
|---|---|---|
| browser-agent | main | pushed(review 修复) |
| channel | dev | pushed(docs) |
| client-wechat | main | pushed,pre-push hook 强制 0.1.0 → 0.1.1 版本 bump |
| docs | main | pushed(.gitignore) |
| client-web / gateway | dev | 无变更 |
同日把 clawline/channel 的 ralph/thread-discord-style 分支 fast-forward 合并到 dev 并 push(+494 行:thread 类型定义、session bindings、WebSocket 客户端扩展,共 8 个文件)——thread 支持完成主干合并。
经验
- Claude Code 跑 40 轮上限会停在中途,需要人工补完同步改动(如 content-script ↔ sidepanel 的注入变量重命名)
- Hermes 启动 Claude Code 必须先
source ~/.zshrc,否则拿不到自定义的 Copilot→Anthropic 代理 API key - Dev 阶段安全修复要权衡调试成本,不是所有 Critical 都立刻修
2026-04-19 Reliability v2 重构(first-principles)
ottor-laptop 全天主线:把 channel + gateway + client-web 三仓的可靠性问题用第一性原理推倒重来。
PRD v1.5 — 6 大产品支柱
整理出可执行的产品 PRD(docs/prd/PRD.md v1.5),围绕 6 个支柱:① 实时连通性 ② 会话/线程一致性 ③ 消息送达零丢失 ④ 跨端体验对齐 ⑤ 失败自愈 ⑥ 可观测。配套 7 项用户决策表(thread 编号策略、断网重发口径、消息序号机制等)。
90 case e2e 测试框架
tests/e2e/ 三类共 90+ 用例:
- REL- 可靠性*(断连/重连/丢包/重复/乱序/慢消费)
- TH- 线程*(thread 创建/切换/嵌套/历史拉取)
- INBOX- 收件箱*(21 case,截至 04-19 18 PASS / 3 FAIL,剩余在 D-12 修)
测试 baseline 从 v1 跑到 v8,每轮锁定一类 root cause。
12 D-items + 7 ADD-BACK
按第一性原理拆问题 → 12 个删除项(D-1..D-12,移除「猜测/绕路/补丁」代码)+ 7 个加回项(ADD-BACK-1..7,补「正确的最小机制」):
| 类 | 代表项 |
|---|---|
| D-Delete | F1b FIFO fallback 路由(猜目标 connection)、broadcast() 兜底、双层 reconnect timer、stream resume 内存表外的磁盘脏副本 |
| ADD-BACK | apiSessions Map(virtualConn 与 realClient 物理隔离)、requestId-based callback key、单一 fanOut 出口(4 个 broadcast loop 合 1)、可配置超时 600s |
配对 URL 修复
pairing 流程 client-web 接出 URL 长期错写 ws://localhost:19180/?...,正确为 ws://localhost:19180/client?channelId=fires&...,修后桌面 mock-backend 可一次拉起完整 e2e。
相关 Hermes skill
落地 software-development/first-principles-thinking/ skill,并写入 user memory:Daddy 默认思维方式 = 第一性原理。
详见 Clawline(本页) reliability v2 server.js 改动、clawline-thread-support TH-1..TH-8 修复。
2026-04-21 Gateway SSE + browser-agent 加固 WIP
源:fries-mac(lewaymacmini-3-local)。Reliability v2 收尾后主线推进 SSE streaming,browser-agent 攒了一笔硬化 WIP。
Gateway / docs / client-web / channel 进展
- gateway(22h 前):
/api/chat改 SSE 流式 + 取消 600s 硬上限;D6/REL-06 inbound 持久化无条件化。 - docs:同步 SSE timeout 语义 + onboarding flag 清理 + 测试 baseline v10。
- client-web:D12 keyspace 统一为
clawline.*(带迁移)、TH-7 thread 时序、Bug A bounce、Get Started flag 收尾。 - channel:D4 reply-dispatcher 由 3 层 fallback 缩为 1 层;threadId 从协议直接取。
browser-agent WIP 86b99f8(2026-04-19,+765/-226,4 文件,未合并)
单次提交捆 6 方向,整体「安全 + 体验 + 稳定」:
- XSS / 密钥防泄露
sidepanel.js加sanitizeHtml()— 基于 DOM 的 allowlist 清洗(~67 行),强制<a target=_blank rel=noopener>,href/src仅放行 http(s),<img>额外允许data:image/。覆盖saveCurrentState/switchConversation/messagesEl.innerHTML三个 AI 输出 / 工具结果 / 历史恢复路径。native-host/index.js加redactSecrets()— 写error.log前过滤sk-ant-*/sk-*/Bearer xxx/x-api-key|api_key|authorization:四类。
- Hook API 稳定性(native-host)
- 动态端口:
EADDRINUSE不再原地重试,4821 → 4822 → … +10 递增;监听成功后通过{type:'hook_port'}推给扩展,GET /状态 JSON 也带port。 - HTTP 客户端断开清理:
res.on('close')→onClientGone(),清 timer + 删pendingRequests+ 给 Chrome 发hook_stop。修了断开后 timer 累积到 REQUEST_TIMEOUT 的旧坑。 hook_response幂等:多次started不再覆盖 resolver,防 rogue started 吞掉最终响应让 HTTP 挂死。
- 动态端口:
- Sidepanel 体验
- Sidebar 改浮层(
position: absolute+transform: translateX+ backdrop,240→260px)、5px 细 webkit scrollbar、去掉头部 “Clawline” 标题、设置面板加 History Export/Import。
- Sidebar 改浮层(
- Agent 能力:新
SKILL_MODES常量、_ensureDebuggerInner / _waitForLoadComplete / _waitForSelector / waitForSettle / postActionWait等待原语。
Daddy 2026-04-21 决定:browser-agent WIP 先挂着不动。
多实例 busy 路由(2026-04-21 复盘)
每个扩展实例 = 1 个 native-host + 1 个 sidepanel,进程独立,互相不发现、不协调、不 load-balance。
sidepanel.js:2331if (isRunning)→ 立即error: "Agent is busy with another task",无队列。- 两个实例分别占 4821 / 4822,第三个请求无论打哪边都即刻拒绝;
body.tabId也救不了 — service-worker 路由按windowId查 sidepanel port,到不了别的扩展实例。 - 客户端唯一稳妥姿势:先
GET /看chromeConnected+port+pendingTasks===0再选端口发。 - 想要排队 → native-host
/hook自己加队列 + 返回202 Accepted+Location: /status/:taskId,但单实例仍是一次一任务。
2026-05-08 sync /api/messages/sync 缺省过滤导致跨 chat fanout
来源:fries-mac 排查 nexora main agent 把一份「Nexora 平台全面代码分析报告」的 5 条 outbound 错落到 chat=nexora-ops 而非 chat=nexora。
根因:sync 接口三个过滤参数中 channelId 必传,chatId 与 agentId 都是可选。前端在 cobra 里拉 main agent 历史时只传 agentId=main,未带 chatId,server 端遂把所有 chat 上 main agent 的最近条目都拉回来;恰好 ops 在同一 channel 也用 agent_id=main 写入了 5 条毫秒级 push 的子任务消息(22:55:50.799–.831),就跨 chat 串到了 cobra 端展示。是预先存在的 bug,不是 thread 移除引入的,但 thread 移除把这条 fanout 路径放大可见。
修法方向:sync filter 把 chatId 改为强制必传(或在 chatId 缺失时按 channel+agent 限定到该会话最新一条而不是全量历史)。Skill / 历史调用方需同步跟进传参。
教训:远端 ops agent 的 outbound 与本端 main agent 同 agent_id=main 共用全局命名空间,任何 ops/main 共名 + chatId 缺失 的组合都会触发同款泄漏。
2026-04-27 channel feat(stream): tag delta with phase=thinking|answer(commit 1518eb4)
reply-dispatcher.ts +35/-5 与 send.ts 小改:流式 text.delta 帧带 phase=thinking|answer 标签,让 client-web 能区分 AI 思考阶段与最终回答阶段(之前两段混在同一气泡里)。
OpenClaw 的 clawline 插件实际加载路径不走 plugins/ 也不走 extensions/,而是直接从 workspace channel-repo 读:
"paths": ["/home/resley/.openclaw/workspace-clawline-client-web/channel-repo"]升级流程 = git pull channel-repo + 重启 gateway。
相关页面
Gateway
Clawline 的 WebSocket 消息中继网关 + 管理端,负责 Agent ↔ Client 通信、Channel/User 管理、认证和消息持久化。
概述
Clawline Gateway 是 Clawline(本页) 产品族的后端中枢,提供 WebSocket 消息中继、管理端 UI、认证集成和数据持久化。最初由 gatewaybot(已淡出)开发,现由 Clawline(本页) Bot 统一维护。
项目信息
| 属性 | 值 |
|---|---|
| 仓库 | clawline/platform (monorepo apps/gateway),旧 clawline/gateway 已 deprecated(2026-05-06 合并) |
| 技术栈 | Node.js + React 19 + Vite 6 + Tailwind v4 + shadcn/ui |
| 生产环境 | https://gateway.clawlines.net |
| 开发环境 | https://gw.dev.dora.restry.cn |
| 开发端口 | 前端 3020 + 后端 3021 |
| 现维护 Bot | Clawline(本页) Bot(原 gatewaybot 已淡出) |
架构
消息中继
Gateway 作为 WebSocket 中继层,连接 OpenClaw Agent 和各客户端(Web、微信小程序等):
Client (Web/WeChat) ←→ Gateway (WS Relay) ←→ OpenClaw Agent
- 心跳机制:客户端 30s 间隔 ping,Gateway 回复 pong(commit
fc02056修复) - 容错:允许 2 次未回复再判定超时
- 断点续传:客户端断连后通过
stream.resume恢复流式内容
管理端 UI (admin-new)
科幻风格三层架构:
| 层级 | 内容 | 说明 |
|---|---|---|
| TIER_1 | Gateway 状态概览 | channels、backend links、live clients |
| TIER_2 | Channel 管理 | 水果命名 + emoji 图标(🥭 mango、🍒 cherry) |
| TIER_3 | User 配置 | 动物命名(falcon、octopus) |
功能:Channel/User CRUD、自动刷新(5s)、连接 URL 生成(openclaw://connect?serverUrl=...)
shadcn/ui 重构
引入 Button/Dialog/Input/Table 组件,App.tsx 从 2000+ 行拆分到 <200 行。
认证系统
完成了三代认证方案演进:
| 版本 | 方案 | 说明 |
|---|---|---|
| V1 | Admin Token 手动输入 | X-Relay-Admin-Token header |
| V2 | Logto SSO + Admin Token | 二次认证 |
| V3 (最终) | Logto SSO → JWT | Authorization: Bearer + 后端 JWKS 验证 |
Logto 配置:
| 属性 | 值 |
|---|---|
| SPA App ID | anbr9zjc6bgd8099ecnx3 |
| API Resource | https://gateway.clawlines.net/api |
| JWKS Endpoint | https://logto.dr.restry.cn/oidc/jwks |
详见 logto-sso。
数据持久化 (Supabase)
从本地 data/relay-config.json 迁移到 Supabase PostgreSQL:
| 表 | 用途 |
|---|---|
cl_channels | Channel 配置 |
cl_channel_users | Channel 用户 |
API 演进:/pg/query → /pg/rest/v1(因 PostgREST schema cache 未刷新,REST API 曾返回 404)。
消息持久化已验证(Supabase 有真实消息记录)。
CI/CD
- GitHub Actions:移除 tarball 打包(tar “file changed” 竞态问题),只保留 Docker 镜像构建
- 关键提交:
7b27b65(UI 集成)、3696159(JWT 认证)、87da32b(Supabase 存储)、2e5fb54(UI 审计修复)
UI 审计历史
经历完整三轮 UI 审计修复:
| 轮次 | 技能 | 修复内容 |
|---|---|---|
| Round 1 | /harden | Focus indicators、WCAG AA 对比度、Modal Escape、触摸目标 44px+ |
| Round 2 | /typeset + /distill | 字体大小 10px→12px、Header 简化、移除装饰性动画 |
| Round 3 | /normalize | CSS 变量设计令牌、sans/mono 字体统一(47→13 处) |
全流程测试记录
researcher agent 通过 Claude Code CLI 进行本地测试:
| 功能 | 结果 |
|---|---|
| Admin UI | ✅ |
| AI Settings GET/PUT | ✅ |
| Suggestions API | ✅ 返回 7 条建议 |
| Voice Refine API | ✅ 语音转写清理 |
| 消息持久化 | ✅ Supabase 有真实消息 |
Bug:max_tokens → max_completion_tokens 兼容性问题(GPT-5.4-mini),commit 12bb93d 修复。
部署迁移(2026-04-06)
gw.dev.dora.restry.cn 管理端经历了一次重大迁移:
- 原状:pm2
dev-gw从旧的workspace-clawline-gateway/repo/admin/distserve 静态文件 - 根因:research agent build 到了错误目录
- 迁移:切换到
workspace-clawline-client-web/gateway-repo/public,从独立 gateway 仓库转移到 Clawline(本页) Bot 管辖 - 遗留:旧版 Logto token 残留导致白屏,需手动清除浏览器 logto 缓存
2026-05-08 — sync 接口 scope 防呆 + 前端预热分桶(commit 4754d6b)
排查到一个隐蔽 bug:cobra 端 main agent chat 视图里出现来自 nexora-ops chat 的 5 条 outbound 消息(22:55:50 同毫秒密集 push)。链路追踪后不是 thread 移除回归,是 /api/messages/sync 过滤规则一开始就允许「传 agentId 不传 chatId」→ 跨 chat 拉到全频道历史;agentInbox 的 500 条预热(fetchOlderMessages(channelId, ..., undefined, 500))也同样不带 chatId,把跨 chat 消息塞进了 cache,进入 cobra/main 视图就被读出来。
修法(单 commit):
- A1 Gateway:
/api/messages/sync加scope防呆 —— 默认 chat 模式必传chatId;要跨 chat 拉必须显式scope=channel。漏配返 400。 - A2 前端:
useChatMessages/fetchOlderMessages没chatId时不发请求;agentInbox 预热改按(agentId, chatId)二维分桶 seed cache,避免单桶被跨 chat 历史污染。 - C bindings:
bindings.ts重建(去 thread 化),channel.ts重新挂bindings: clawlineBindingsProvider。
CC 自纠 2 个隐蔽问题:① supabase env 短路检查放校验前面,避免 401 被误报;② 修了 useChatMessages 加 enabled = !!chatId 后引入的 first-entry 历史空显示回归 —— ChatRoom 首次进入 chatId 经常 undefined(要等 WS connection.open 回包),hotfix 在 ChatRoom 调用处用 chatId || activeConn?.chatId 兜底再 refetch。
环境分清:
web.dev.dora.restry.cn是 build 部署版(dev 分支构建产物),跟本地 Vite HMR:4026不是同一个东西 —— hotfix push 到 dev 分支不自动触发前者的部署,要看线上必须等 GitHub Actions / 手动 build。CLAUDE.md 已记。
2026-05-09 Gateway admin favicon + 部署域名澄清
apps/gateway/admin/ 加站标 SVG(品牌橙 EF5A23)替换 Vite 默认图标,覆盖 light + dark theme。commit 96e6015 推到 platform monorepo dev,3 分钟自动部署链路同步刷到 gw.dev.dora.restry.cn。
部署域名澄清(5-08 web.dev/gw.dev 混淆延伸):gateway admin 的 dev 部署落点是 gw.dev.dora.restry.cn,不是 web.dev.dora.restry.cn(后者是 client-web)。两者由独立 pm2 进程承载,build 后 favicon 各自部署,不要在 client-web 仓库改 admin 图标。
时间线
| 日期 | 事件 |
|---|---|
| 2026-03-17 | GatewayBot 上线,完成 admin-new UI 集成 |
| 2026-03-18 | Logto SSO + JWT 全流程、Supabase 迁移 |
| 2026-03-20 | 三轮 UI 审计修复、响应式布局 |
| 2026-03-21 | shadcn/ui 重构,App.tsx 拆分 |
| 2026-04-06 | 管理端部署迁移到 clawline-client-web Bot 管辖 |
| 2026-04-08 | Gateway 代码整理提交(DESIGN.md、server.js、schema.sql) |
相关主题
- Clawline(本页) — 现维护 Bot + Web 客户端
- Clawline(本页) — Channel 插件
- gatewaybot — 原开发 Bot(已淡出)
- logto-sso — 认证方案详情
- supabase-platform — 数据库
- caddy-reverse-proxy — 反向代理
- dev-port-management — 开发端口分配
- chat 端点设计
详见 chat 端点设计:虚拟连接机制、请求参数、meta.source 标识透传和全链路改动汇总。
2026-04-20 更新:D6 inbound 持久化 + REL-06 护栏 + /api/chat SSE
整天三件事一气呵成(fries-mac,Claude Code 派工模式):
测试覆盖盘点(已知缺口)
docs/testing/e2e-test-cases.md 14 模块 90+ 用例,但自动化里仍有空档:REL-04 断线重连补帧(lastSeenMessageId 机制有代码、自动化 SKIP)和 REL-05 重复消息幂等(ADD-BACK #7 有代码、自动化 SKIP)只手测过;Channel 直连模式 C-01~C-06 整组 SKIP(直连保留,需 fork OpenClaw 实例跑);Onboarding W-01/03/04/05/06 卡在 Chrome profile 隔离,已用 clawline.onboarding.done localStorage flag 简化(commits e5b2c03d + f8e2be3d,docs commit 58879c7);分屏 W-22/23/24 受 sidepanel 宽度限制。Daddy 04-20 拍板修复顺序:REL-04/05 → 接 CI → onboarding → 直连 → 分屏。仓库一并清理 media/(e2e 上传产物 7 文件)追加 .gitignore(commit ae28e97)。
REL-06 护栏先行(commit eba48d5)
扩 mock-backend 支持 timeout/reject/drop 三种行为(按 evt.data.chatId 子串识别,无需重启),新增 REL-06a/b/c 断言”inbound 入库与 outbound 是否成功无关”。预期全 FAIL — 跑出来真的全 FAIL,证据精确指向 server.js:3283 D6 注释,确认 D6 设计本身就是 bug。
D6 修复(commit cebb5c4)
旧设计「只有 ack 成功才入库」的代价是丢失用户原问 — 本末倒置(ghost row 可查询过滤,丢问题不可还原)。改为状态机式:inbound 进来就入库(不论 outbound 是否成功),删 pendingInbound Map / D6 stash GC / ack-then-persist 分支 / 所有 D6 注释。净 -20 行,0 新增 null-check / try-catch。DB 写失败:HTTP 500 / WS error/PERSIST_FAILED。REL-06a/b/c 翻绿;REL-02 ghost 断言收紧为 total − (ack+err) === 0。
/api/chat SSE 流式(commit 5bda82d + docs 513f902)
旧实现 /api/chat 同步 HTTP,600s 硬上限(长 agent 任务被掐),且 text.delta 帧只走 WS 不达 HTTP caller。
改造:Accept 头协商同 endpoint — Accept: text/event-stream 走 SSE 分支(事件序列 accepted → delta* → tool_call/result* → done | error,15s 心跳防代理断连,去掉 600s 上限靠心跳+事件驱动),其他 caller 同步 JSON 路径完全不动(路由表/鉴权/idempotency/REL-06 inbound 持久化全复用)。新增 REL-07a/b/c/d(正常 / 长任务 / 超时 / caller 主动断开)全 PASS。
经验教训(Daddy 借此事质问”为什么会出现这种低级问题”):D6 当初设计违反第一性原理 ① — 没问”用户消息是不是物理发生过的事实?“。/api/chat 设计时没问”agent 实际执行时间分布”。结构错了,补丁永远打不完;护栏(先写测试断言”应该的样子”再改代码)是阻断同类反复的唯一办法。
2026-04-12 更新
- Gateway 新增 POST /api/chat 端点(169行),创建虚拟连接注入 channel 插件
- 支持
meta.source标识透传 - 修复了
closeSocket null guard保护虚拟连接 - 部署方式确认:本地打包 → scp 上传 → rsync → restart(不在远程 git pull)
- 详见 relay-gateway-api
2026-04-18 ~ 04-19 更新:API 回复丢失修复(server.js)
Levis 反馈通过 POST /api/chat 发消息后,agent 回复在前端聊天历史里看不到。定位为 gateway-repo/server.js 中两层叠加的 Bug:
| Bug | commit | 修法 |
|---|---|---|
| A. agent isolation 把并发 agent 回复全部 buffer 不 deliver | 98c375a(04-18) | virtualConnId 加入 agentId:api-{channel}-{chatId}-{agentId} |
B. 未传 chatId 时借用其他在线 WS 的 chatId,导致 user:senderId 路由丢失 | 00dec2f(04-19) | `chatId = body.chatId |
两个 commit 已部署到 relay(163.228.142.105)dev 分支。验证链路见 Clawline(本页)。
Dev Gateway WebSocket 配对(04-19 verified)
本地 dev gateway 监听 19180,两个独立 endpoint:
/backend— agent 后端连入(OpenClaw / Hermes)/client— 浏览器/客户端连入(不是/ws)
Web client 必须连 /client 且 channelId 要匹配实际有 backend 的频道,否则收到 1013 backend not connected 立即断开。lewaymacmini 实测:openclaw-gateway 注册的是 fires 频道,配对 URL:
ws://localhost:19180/client?channelId=fires&senderId=Levis&token=***
dora / nexora / ottor 这些 token 虽存在但本地无 backend 连入 → 用了立刻断。
Reliability v2 server.js 重构(04-19)
承接 04-18 ABug 修复,按 first-principles 把 server.js 路由层推倒重写:
- apiSessions Map 拆出:
POST /api/chat创建的 virtualConn 不再挤进realClients,独立到apiSessions: Map<virtualConnId, {res, agentId, chatId, createdAt, timeout}>,物理隔离避免 agent 回复路由错乱 - callback key 由 requestId 决定:旧实现用
chatId+agentId拼 key,并发同一 chat 多 agent 时会覆盖,改成requestId(client 端 uuid)一对一 - fanOut 单出口:原本 4 处
for (client of realClients) client.send(...)各自判断 dedup/agent 隔离,全部合并到fanOut(payload, filter)一个函数,删除 ~80 行重复代码 - F1b FIFO fallback 移除:「找不到精确路由就发给最早连接的 client」是 ABug 的根因之一,删除该兜底,路由失败直接
status.failed+ 日志告警 - 超时可配置:
/api/chatvirtualConn timeout 旧硬编码 60s,长 agent 任务(Claude Code、剧情生成)会被掐,改为body.timeout || env.GATEWAY_API_TIMEOUT_MS || 600000
Step 5–10 重构序列(lewaymacmini,fries 派 Claude Code 自跑)
派工模式:Hermes 写好 /tmp/cc-fp-step*.md 计划文档 → zsh -ic 'cd ~/Projects/clawline && claude -c --dangerously-skip-permissions --print < /tmp/cc-fp-stepN.md' 后台跑 → SYSTEM 退出码 0 后回报。每 step 一个文档、一次执行、一次审计。
| Step | commit | 关键改动 |
|---|---|---|
| 5 / 5.5 | (合入 server.js 路由层) | apiSessions 拆分 + requestId callback key + fanOut 单出口 + F1b FIFO fallback 删除 + timeout 可配 |
| 7 (D9) | 5036c27 | 删 _threadUpdateChain Map + _doUpdateThreadOnNewReply 包装;updateThreadOnNewReply 不再写 reply_count 列,只更 last_reply_at + participant_ids + updated_at;新增 countThreadReplies(threadId) 按需聚合(ADD-BACK #1) |
| 8–10 | 跨 3 仓库 + OpenClaw 重启 | client-web localStorage 迁移:旧双 key(clawline.lastRead.X + openclaw.inbox.lastRead.X)取 max 写回 clawline.lastRead.X,旧 key 清除;Get Started 隐藏闭环(清 flag→Onboarding;点完→flag=1;reload /→直进 chats;显式 /onboarding 仍可访问);UI E2E 23/23 PASS、Console 0 error |
PRD: clawline/docs/prd/message-reliability-first-principles.md。物理真相 1 句:「发出的每条消息要么最终在接收端出现,要么明确返回错误。不存在静默失败。」结构错了,补丁永远打不完 — D1-D12 清单中除 D5(保留但简化为仅 @mention 触发)外全删。
2026-05-07 ghost-outbound bug RCA:WS fanOut 把 inbound 当 outbound 重广播
症状(dora 频道):消息历史里出现「同一条用户消息显示两次 / 一次 inbound + 一次 outbound」幽灵记录,dora 渠道 ghost 率约 34%(41 条 ghost),ottor 2 条 / fires 0 / nexora 0。
初判错向 — gateway server.js:2707-2711:WS 路径 fanOut 在向其他 client 转发 inbound 消息时,没标 direction: 'inbound' 字段就送给后端持久化路径 → 后端按 default 落成 outbound。这一处也确实有缺陷:fanOut 出口应该明确 message direction,不能依赖默认值。但这不是真正的 ghost 来源。
真正根因 — apps/channel/src/generic/client.ts:585-592:channel 客户端在收到 inbound message 后,会把它原样作为 message.send 事件再广播一次给所有已连接 backend,gateway 端 message.send 默认走 outbound 持久化分支 → schema 上 (channel_id, message_id, direction) 是 unique key,允许同一 msgId 同时存在 inbound + outbound 两行,所以「两条都成功落库」不会被去重拦下。
为什么只有 dora 渠道猛:跨渠道差异是因为 dora 频道挂的 daemon 5/7 那天还在跑发版前的旧 channel 代码(workspace-clawline-client-web/channel-repo 没拉新 commit),其他渠道当天已经升上含 fix 的 build。
修法:
apps/channel/src/generic/client.ts:585-592—— 收到 inbound 后不要再以message.send重广播;如果 backend 真需要被通知有新 inbound,使用message.received事件且不进入 outbound 持久化路径apps/gatewayserver.js:2707-2711 —— fanOut 显式带direction: 'inbound',防御默认值漂移- 修复随
@clawlines/channel@1.0.0(commit7ebed30) 一并发布,08:26 上线 - 验证:08:30 dora 频道最后一条 ghost 出现(pre-fix daemon 还在跑),之后 0 新增;fries / ottor / nexora 当日 ghost 数无变化
Schema 启示:(channel_id, message_id, direction) unique key 设计本意是允许 echo(同一 id 既可以是用户发的、也可以是 bot 回的同 id refer),但实践上从未用到这种语义,反而让本 bug 不被去重发现。后续考虑 evolve 成 (channel_id, message_id) 全局唯一 + 显式 direction 字段做语义标注(不参与去重),让”同一 msg id 落两次”在 DB 层就报错。
验证渠道:dev 三分钟自动部署链路(monitoring-and-cron)让 web.dev.dora.restry.cn / gw.dev.dora.restry.cn 在 fix push 后 ~3 min 即承载新 build,无需手动操作。
2026-05-06: 启动时 loadRelaySettings fire-and-forget bug → 重启后必须手动「保存」CORS 才生效
症状(Levis dora 频道):每次 relay gateway 部署后,要去 admin 端随便保存一下东西才能用,否则 CORS 白名单空、前端跨域全挂。
根因:server.js 行 500 启动顺序 void loadRelaySettings().then(...) 是 fire-and-forget 没 await;行 4073 才 await loadRelayConfig() 然后 listen。如果 Supabase 响应慢或首请求在 loadRelaySettings 完成前到达,relaySettingsCache 被设为 {}(空)且永不刷新——admin 端任意保存触发 saveRelaySettings() 强写内存缓存才恢复。
修法:① 启动时 await loadRelaySettings() 在 listen 之前;② loadRelaySettings 加 TTL 刷新(expiresAt),DB 改 CORS 不需 restart 也能生效;③ saveRelaySettings 同步更新 expiresAt;④ 删掉旧的 fire-and-forget 调用。push 到 platform monorepo apps/gateway,cron 3 分钟内自动部署生效。
2026-04-14 更新:微信图片发送修复
Resley 反馈微信收不到图片。定位到 gateway/platforms/weixin.py 的 send_image_file 方法签名与基类 BasePlatformAdapter.send_image_file() 不一致——基类和所有调用方(含 _deliver_media_from_response)传 image_path=...,但微信适配器写的是 path。kwarg 不匹配 → 文件路径丢失 → 图片发不出。
修法:把微信适配器参数名 path 改为 image_path。34 个 weixin 单测全部通过,gateway 重启后微信图片投递恢复。
Client Web
Clawline 的 React Web 聊天客户端,提供多 Agent 对话、技能管理、PWA 离线支持等功能。
项目信息
| 属性 | 值 |
|---|---|
| 仓库 | clawline/platform (monorepo apps/client-web),旧 clawline/client-web 已 deprecated(2026-05-06 合并) |
| 技术栈 | React 19 + TypeScript + Vite + Tailwind CSS |
| 版本 | v0.1.0 → desktop v0.5.2(2026-04-27),版本号自动递增 = git commit 总数 |
| 开发端口 | 3026 |
| 开发环境 | https://web.dev.dora.restry.cn |
| 生产环境 | https://chat.clawlines.net |
| 开发分支 | dev |
核心功能
多 Agent 聊天
- 支持连接多个 OpenClaw 服务器
- Agent 列表网格/列表视图切换,拖拽排序(Reorder 组件 + localStorage 持久化)
- 自定义 Agent 头像(右键/长按设置)
- 消息历史同步、新建/切换会话
消息系统
- 流式消息渲染 — 实时显示 AI 思考过程和 delta 输出
- Markdown 渲染 — 代码高亮、inline code 暖色样式
- 工具调用展示 — JSON 自动解析为可读文本
- 消息操作 — Reply(引用回复)、Copy(剪贴板 + toast 反馈)
- 断点续传 — 客户端断连后通过
stream.resume恢复流式内容 - API 直连标识 —
meta.source = "api"的消息显示 ⚡ API badge(04-12 新增)
技能系统
- 斜杠命令菜单(
/触发自动补全) - 技能三分类展示:工作区技能 + 全局技能(高亮)vs 内置技能(灰色折叠)
- 药丸按钮交互:点击填入输入框,长按 800ms 直接发送
输入交互
- WhatsApp 风格底部输入栏:
+按钮菜单 + 输入框 + Mic/Send - 语音录制(带计时器显示)
- 动态建议条(空输入显示命令 chips,bot 回复后显示上下文建议)
- 移动端文字选择保护(ActionSheet 不劫持长按选中)
- 发送后自动收起键盘(04-14 新增,
document.activeElement.blur())
通知系统(04-14 修复)
- PC 端焦点感知:从只判断
document.hidden改为document.hidden || !document.hasFocus(),窗口失焦但 tab 可见时也触发通知 - 声音去重:agentInbox 判断当前是否在看目标 agent 的聊天,在看时跳过声音
- 通知逻辑统一:删除 ChatRoom 里重复的 Notification 调用,统一到 agentInbox
PWA & 离线
- Service Worker 缓存策略(详见 PWA 部署页面)
- 自动版本更新检测 + UpdateBanner
- BUILD_HASH 从随机 MD5 改为 git commit hash
架构演进
ChatRoom 大重构(2026-03-25)
通过 adversarial-qa(左右互搏 QA)自动化审核,经过 5 轮迭代完成:
| 轮次 | ChatRoom 行数 | 子组件数 | 评分 |
|---|---|---|---|
| 初始 | 2619 | 0 | — |
| R5 | 1879 | 9 | 96 ✅ |
提取的 9 个组件:types.ts、utils.ts、DeliveryTicks、MessageItem、ActionSheet、SuggestionBar、HistoryDrawer、HeaderMenu、ConnectionBanner
UI 风格确立
- 不要聊天气泡 — 坚持 Discord/Slack 平铺风格
- 品牌色 — 从绿色
#67B88B统一为橙色#EF5A23 - 文字对比度 — 主文字
#1F2328,次要#4D5561
扫码即用配对(2026-04-21)
之前 Gateway 管理端生成的用户二维码/链接是 openclaw://connect?serverUrl=...&token=... 自定义协议,浏览器打不开 → 用户必须先打开 web 前端再手动粘贴到 Pairing 页。04-21 改造为扫码直接跳 web 前端自动完成连接:
- Gateway 管理端新增「Web 前端地址」多值配置(如
https://web.dev.dora.restry.cn),写入/api/settings - 分享二维码生成的链接变成 `https://
/pair?serverUrl=…&token=*** pairing - web 端的手动识别兼容同一种 URL 格式
- Gateway
86dff4a、client-weba153820同日 push + pm2 restart 到 dev(web.dev.dora.restry.cn/gw.dev.dora.restry.cn);relay 不涉及部署 - 默认模型同日改为
github-copilot/claude-opus-4.6
单窗口锁定模式 / 扫码直跳聊天(2026-04-23)
在 04-21「扫码即用配对」基础上再前进一步——很多分享场景下,客户不需要看 agent 列表,只想被直接丢进指定聊天窗口(例如把 mazu-cs 客服链接塞进小程序内嵌 webview)。
**Admin 端(gateway-repo UserConnectModal)**新增「Link Behaviour」区域,两个 toggle:
- Direct jump to chat — 开启后生成的链接/二维码带
directChat=1,打开即直跳聊天,跳过列表 - Allow back to list —
directChat=1时出现,对应allowBack=1,关掉就锁定单窗口不能返回列表
Client-web 端(client-web) 配套三处改动:
App.tsx在/connect读取directChat=1,写入localStorage.clawline.singleWindow(getSingleWindowConfig());AppShell初始化 screen 时 direct jump 模式直接进chat_room跳过chatsAppShell.navigate('chats')与handleSwipeBack在singleWindow && !allowBack时被拦截ChatRoom.tsx输入栏单窗口精简版:isSingleWindow为 true 时隐藏ModelPicker、快捷命令 chips、SuggestionBar、voice 按钮和 Row 1 的+菜单;Row 1 只保留 textarea + Image + File 直出,Row 2 只保留 SendChatHeader.tsx新增hideBackprop,单窗口模式隐藏返回按钮
部署踩坑:首轮只 git commit 了 Admin 源码没 build,用户刷新看不到开关。需要跑 pnpm build + pm2 restart 到 gw.dev.dora.restry.cn 才生效——Gateway 前端改动必须 build + restart,不能只 push。Client-web 一路 build + type-check 通过(TS2339 AppErrorBoundary.props 是预存 lint,无关此次改动)。
Web 前端 avatar 限制(2026-04-23)
Dora 尝试把客服代理头像统一成莆阳 Logo:在每个客服代理 openclaw.json 写了 identity.avatar = avatars/puyang-logo.jpg 并通过 openclaw 配置下发。但 web.dev.dora.restry.cn 前端只读 emoji 字段渲染头像,不读 identity.avatar,新用户进来看到的还是 emoji。
- 根因:Clawline Web 前端当前渲染逻辑只有 emoji path,没消费 avatar 字段
- 结论:这是前端渲染限制而非配置问题;若要走图片头像需要前端改造(优先读 avatar,fallback 到 emoji)
- 暂列 TODO,非阻塞(emoji 够用)
2026-04-27 桌面端 v0.2.0 → v0.4.1 连发 + SuggestionBar 恢复 + senderId 展示 + 代码块复制
一连串面向桌面端用户的修复+发布,dev → tag → CI → publish 全流程跑了三次。
SuggestionBar 误删恢复
2f9e315a(Tauri 桌面版 commit)误删了输入框上方的 AI 推荐快捷栏:SuggestionBar.tsx + suggestions.ts 整套 + Voice 润色 (refineVoiceText) + Dashboard 屏幕。Daddy 要求恢复 SuggestionBar:找回组件 + service,ChatRoom.tsx 重新引入并渲染(位置:slash menu </AnimatePresence> 之后、Reply bar 之前)。
senderId 在 API badge 旁展示
Daddy 反馈:通过 /api/chat 调用 nexora-cs 等客服代理时,多个微信用户的消息看起来全部混在 web 端同一对话里。排查链路:
- Gateway 代码
sessionId = "api-${channelId}-${chatId}-${agentId}"已正确隔离,chatId 透传无问题 - 调用方传
chatId = senderId = "wechat-<openid>"也都对 - 实际消息确实路由到不同 OpenClaw session,但 client-web 是按 agent 视角渲染历史,所以多个用户与同一 agent 对话会聚集在该 agent 主对话视图里
短期方案(不改后端):在 API badge 右边追加 senderId,效果 ⚡ API wechat-oXXXX:
ChatRoom.tsx— 实时 / 历史 / user-echo 三处消息构建时都从packet.data.senderId提取放入meta.senderIdMessageItem.tsx— API badge 旁渲染meta.senderId
长期方案(未做):让 /api/chat 强制 senderId 必填或加独立 sessionId 字段,前端按 session 分割视图。
Markdown 代码块 Copy 按钮
代码块头部之前只有语言标签,新增 Copy 按钮(点击后变 ✓ Copied);没有语言标签的代码块也展示。
桌面端发布流程(一日内三个 tag)
| Tag | 内容 | 结果 |
|---|---|---|
desktop-0.1.1 | 手动建的空 tag(漏 v 前缀),污染 GitHub releases/latest → Tauri updater latest.json 404,桌面端”检查更新”全挂。删 | — |
desktop-v0.2.0 | 已有 draft,CI 凌晨已跑完(Mac aarch64/x64 dmg + Win exe + Linux deb/AppImage 全平台齐) | publish |
desktop-v0.3.0 | bump + push + 打 tag → 触发 CI → publish | ✅ |
desktop-v0.4.0 | tag 打在了未含 senderId 改动的旧 commit,CI 产物不含改动 | 作废,删 draft |
desktop-v0.4.1 | 重新 bump 到含 senderId + Copy 按钮的最新 commit → push + tag → CI → publish | ✅ updater manifest 确认 |
踩坑 / 教训:
- pull 前应该先
commit再git pull,不必先 stash(Daddy 当场纠正:“为什么 stash?”) - 打 tag 前必须确认 HEAD 已含目标改动,否则 CI 产物缺改动 → 需要重 bump 重发
- Tauri 桌面端:Mac aarch64 / Mac x64 / Windows x64 / Linux deb + AppImage 五份产物,CI ~8-10 分钟
本地 skill clawline-deploy 同步更新加入桌面端发布完整流程。
2026-04-27 续:v0.5.0 thinking/answer 分相 + v0.5.1 sender 角色 + v0.5.2 UpdateModal
同日下午 / 晚间在 v0.4.1 基础上又连发三版,重点是协议侧把”思考”和”答案”显式分相,以及 React 化 native dialog。
v0.5.0 thinking/answer 分相
之前 channel 把 thinking delta 和 answer delta 混在同一条流,前端要靠 heuristic 拆——会出现”思考内容追加在答案后面”的错位。
协议改动:channel 新增 phase 字段(thinking / answer),每相内 delta 累积(不再增量拼),相切换时前端开新气泡。客户端按 phase 路由到不同 UI 区域,thinking 折叠默认收起。
v0.5.1 determineSenderRole + 滚动加载历史
Bug 1 — 跨客户端 sender 误判:A 客户端发的消息在 B 客户端展示成”自己发的”。根因:旧代码只判断 senderId === localSenderId,但同一用户多设备 senderId 可能不同。抽 determineSenderRole(msg, ctx) helper:综合 senderId / chatId / agentId / 消息源(user vs assistant)综合判定。
Bug 2 — 滚动到顶不加载历史:useEffect 监听 scroll 但 deps 写成 [],handler 闭包永远拿初始 state,hasMore 永远为 true 时也加载不到下一页。修正 deps + 用 ref 存最新分页 cursor。
v0.5.2 UpdateModal React 化
之前 window.confirm() 弹更新提示,桌面端 Tauri 下样式丑 + 无法自定义。改为 UpdateModal React 组件:shadcn 风格,“立即更新 / 稍后” 双按钮,“稍后” 24h 内不再弹同版本。
v0.5.3 滚动加载历史的真根因(commit 80ffbd5e)
v0.5.1 修过一次”滚动到顶不加载历史”,CC 当时把 useEffect deps 改成 [] 想”只挂一次”,没修透。Daddy 自查代码定位两个真 bug:
- scroll listener 永远不挂载:
scrollContainerRef.current在 ChatRoom 首次渲染(lazy load + React StrictMode 双调)时是null,effect 早退后再也不会重试。修法:deps 从[]改回[scrollContainerRef.current, agentId, chatId],ref 挂上后 effect 重跑。 - 缓存短时 hasMore 锁死:
setHasMoreHistory(cached.length > INITIAL_PAGE)—— 缓存少于 30 条直接 false。但warmCache只拉有界窗口,缓存少不代表 server 没数据。修法:默认setHasMoreHistory(true),让 HTTP 路径在真正没东西时才置 false。
教训沉淀:CC 修两次都没修对,小逻辑改动以后 Daddy 自己 review。bump 0.5.3 + tag desktop-v0.5.3 push 触发 CI 自动 publish。
MsgId namespace collision RCA
排查”同一消息在两个客户端 id 冲突”时定位:
- Channel 端 msgId =
substring(7)取 5 位 base36(约 60M 空间) - Client 端 msgId = 6 位 base36(约 2.1B 空间)
两边 namespace 不一致,channel 短 id 偶发撞上 client 长 id 的前 5 位 → dedup 集合误命中、消息被吞。修法:统一到 6 位 base36,channel 端补一位。沉淀防再撞 note 在 channel-repo lib/msgid.ts 注释。
2026-04-30 desktop-v0.5.4 — 滚动 anchor 保留 + CC 测试卡死治理
接 v0.5.3「滚到顶能加载历史」之后,视觉跳动还在:滚到顶 prepend 30 条后,原本视野里那条消息 boundingClientRect.top 整体被推走,体验上就是”页面跳了一下”。
修法:scroll anchor preservation
ChatRoom.tsx 加 anchor 逻辑——prepend 前记录可视区一条 anchor message + 它的 boundingClientRect.top,prepend 后用 scrollTop 补差,使 |ΔT| < 5px。commit 1f8d0670(CC 提)→ 330848df 推到 dev。
发版
| Tag | 内容 | 结果 |
|---|---|---|
desktop-v0.5.4 | bump 0.5.3 → 0.5.4(tauri.conf.json / Cargo.toml / Cargo.lock 三处版本号),tag push 触发 CI(run id 25169843617,~8-10 min × 4 matrix) | ✅ publish 为 Latest,latest.json 返回 0.5.4,4 平台 8 个产物齐全 |
老用户 macOS app 「检查更新」如果第一时间还显示 5.3,是 GitHub Releases CDN 对 latest.json 的短期缓存,再点一次基本就刷新;Preferences 的「当前版本=0.5.3」是已装版本号,装完 0.5.4 才会变(不是 bug)。
CC 跑 E2E 卡死治理(重要操作教训)
本次发版前两轮派 CC 重测 scroll-anchor 都卡死:
- 9:06PM 派出去测 → 20 分钟无产出,卡在 puppeteer-mcp(明明要求用 browser-agent)
- 9:29PM 二次派 → 卡在 browser-agent hook 的
curl 120s等待 - Daddy 让停,全部
kill干净;连同 Wed 21:00 残留的playwright test-server旧进程一并清理
教训:
- 派 CC 跑 E2E 测试必须给硬超时 + 出问题立刻 BLOCK 报告,不然容易在浏览器自动化层(puppeteer / browser-agent)静默转圈,外面看不到任何输出
- CC “上一轮验证翻车”复盘:上次自报 PASS 的是「8px spinner 微量 prepend」,根本没复现 60+ 条消息真实滚顶场景——测试要求必须明确指定数据规模、操作路径、断言指标(如 |ΔT| < 5px + 截屏),不然 CC 会偷懒挑最容易过的子集
最终 v0.5.4 修没修透没有跑通自动化验证,是 Daddy 自查代码 + 看用户反馈点头放行的——open loop:scroll-anchor 真值验证欠一份。
2026-04-28 dev 分支同步收尾 + auto-deploy 是死的
接 04-27 桌面端连发,client-web dev 分支还有 1 条本地领先 origin 的 commit 3ed92def feat: add copy button to code blocks in markdown renderer,Levis 让 push → 推上去 ✅。其他 5 个 repo(channel / gateway / docs / sdk / wechat)dev 分支均与 origin 同步,无落后。
dist/ 跟踪噪声仍未根治:client-web 工作树仍有 M dist/index.html M dist/sw.js 的未提交噪声,结构上 04-20 应已 git rm --cached,下次工作区干净时确认是否回潮。
channel-repo 拉到 1518eb4(变化集中在 reply-dispatcher.ts +35/-5 与 send.ts 小改),同时澄清 OpenClaw clawline 插件的实际加载路径不走 plugins/ 也不走 extensions/,而是直接从 workspace channel-repo 读:
"paths": ["/home/resley/.openclaw/workspace-clawline-client-web/channel-repo"]→ 升级流程 = git pull channel-repo + 重启 gateway 即可。
auto-deploy 现状(重要):AGENTS.md 写的 skills/auto-deploy/watch-repos.sh “每次心跳运行”,但 agent 没有心跳 cron,唯一相关的 web-project-sync cron 还是 disabled 状态 → watch-repos.sh 实际从不运行,自动部署链路是死的,所有部署目前只能手动 git pull + build + restart。Daddy 暂不要求加心跳 cron,留 open loop。
POST /api/chat HTTP 直连接口(04-12)
新增约 100 行改动跨 5 个文件,实现 HTTP 直接调用 Agent(无需 WS 连接):
- Gateway 创建虚拟连接注入 channel 插件
meta.source = "api"标识贯穿全链路- 前端 MessageItem 渲染 ⚡ API badge
- 支持 Admin Token / Bearer JWT / Channel Token 三种认证
- 详见 relay-gateway-api
GET /api/agents 接口(04-14)
新增返回所有机器人信息的接口,包含所属服务器、在线状态和元数据信息,通过 Admin Token 调用。
API 代理回复列表不可见(2026-04-18 ~ 04-19 已修复)
Levis 报告:通过 POST /api/chat 发消息到代理后,代理回复不出现在聊天历史列表里(nexora-xipu 最后 8 条验证)。Supabase cl_messages 表 inbound(api- 前缀)与 outbound(msg- 前缀)都有入库,gateway 无 persist 失败日志,问题出在 channel 侧。
最终定位两层叠加的 Bug,均在 gateway-repo/server.js 修复并部署到 relay(163.228.142.105):
Bug A — agent isolation 导致回复不 deliver(commit 98c375a)
多个 agent 并发调 /api/chat 时,共用同一个 virtualConnId = api-{channel}-{chatId}。channel 端 agent isolation 把其他 agent 的回复全部 buffer,永远不 deliver 也不 persist → Supabase 看不到 outbound。
修法:virtualConnId 加入 agentId → api-{channel}-{chatId}-{agentId},每个 agent 独立虚拟连接。
Bug B — chatId 借用导致路由断裂(commit 00dec2f,04-19)
原逻辑在 body.chatId 未传时,gateway 去”借”一个在线真实 WS 连接的 chatId(例如 cobra)。导致:
- 虚拟连接注册到
chatId = 'cobra' bot.tsDM 回复用user:${senderId}(例如user:eagle)sendToClient('eagle')找不到注册在'cobra'下的虚拟连接 → fallback 到relay.server.persist,写入错乱- history 中 inbound 在
'cobra'、outbound 在'eagle'→history.sync两边都不完整 → 前端列表空白
修法:chatId = body.chatId || senderId,整条链路统一 chatId,user:senderId 路由命中。
验证步骤:POST /api/chat 不传 chatId → Supabase inbound + outbound 的 sender_id 一致 → 前端刷新聊天列表正常出现 conversation → 并发发给不同 agent 验证 isolation。04-20 在 Nexora 9 个 agent 全员验证通过(HTTP 200,25–89s)。
Gateway reliability-v2 大重构(2026-04-19)
04-19 下午 17:21 三组件联合部署:gateway 约 25 个 commit(reliability-v2)、client-web 1 个 commit(TH-7 thread 消息时间顺序修复)、channel 一轮 code review。重点改动:
| 系列 | 主题 |
|---|---|
| API chat | 消息路由、多设备同步、超时配置、502 错误上报 |
| Thread (TH-2~TH-7) | threadId 路由、reply_count 序列化、mark_read 广播、search 防护、消息时间顺序 |
| 可靠性 (D1~D11) | 精简状态机;D9 删除 _threadUpdateChain mutex;D11 把 outbox/history/persist/online 四路广播合并为 fanOut();D6 ack-then-persist |
| 测试基础 | mock-backend + Makefile |
04-20 23:28 在 relay (relay.restry.cn / 163.228.142.105) 比对确认最新 commit 5036c27 (04-19 21:30) 已上线:fanOut() 在、_threadUpdateChain 仅剩注释、ack-then-persist 生效,5 个后端连接,11 客户端在线,/healthz ok。
/api/chat 502/504 根因与超时策略(2026-04-20 诊断)
Levis 汇报 relay.restry.cn 有大量 504/“消息没进队列”错误。gateway 侧日志比对后定位:
历史 502(已在 reliability-v2 修复)
TypeError: Cannot read properties of null (reading 'readyState') — sibling 广播缺 null-guard,某连接已断 ws = null 触发整进程 crash,systemd 拉起,Caddy 把在途 /api/chat 响应为 502(不是 504)。04-14、04-16、04-18 期间共 18 次 502,gateway 自动重启约 48 次。04-18 05:03 crash 与 Caddy 502 精确同秒。reliability-v2 加 null-guard + fanOut() 后根治。
当前 504 触发场景
- Agent 真超时:默认 300s,可通过
body.timeout或RELAY_API_CHAT_TIMEOUT_MS调整,硬上限 600s。LLM 慢、卡死都走这条 replyTo不匹配:D3 之后用messageId严格匹配回调,若 channel 插件端没把replyTo原样带回,或走旧 FIFO,sess.requests.get(messageId)拿不到,timer 到期 504relay.client.open发出但 50ms settle 没等够(硬编码),极端并发下偶发
流式输出仍未实现
/api/chat 是同步 HTTP,等到 message.send 才回包:
- 硬超时 600s,没有逃生路径
text.delta中间帧只 persist + fanOut 给 WS 客户端,HTTP caller 看不到- 临时方案:caller 传
body.timeout(≤600s)或改走 WS;根本解是把/api/chat改成 SSE / chunked 流式响应(TODO)
用户 inbound 不落库(D6 设计 bug,2026-04-20 已修复)
历史症状:/api/chat 下用户消息不 persist,前端历史只看到 bot 回复。根因是 D6 注释 // D6: do NOT persist inbound here. Persistence is deferred to the ack path —— 等 channel ack 再写,但 api 流程没对应 ack 触发 → inbound 丢库。
2026-04-20 早间三连提交(gateway):
| 时间 | Commit | 说明 |
|---|---|---|
| 10:02 | REL-06 测试先行 | 先写失败的 guardrail 测试,精确暴露 timeout/reject/drop 三种场景下 inbound 行缺失 FAIL |
| 10:15 | D6-fix | inbound 持久化无条件执行 — 消息进 gateway 立即写库,彻底推翻”等 ack 才写” |
| ~10:30 | 自动部署 | 全部通过 auto-deploy 上 relay,healthz 每次 OK |
三个 commit 随自动发布流水线上线,现 /api/chat 的 inbound/outbound 在 Supabase cl_messages 同步落库。
部署流水线:client-web dist/sw.js 冲突卡住(2026-04-20 修复)
auto-deploy 报 dist/sw.js: needs merge / fatal: Exiting because of an unmerged files,导致 client-web 若干 commit 没部署成功。根因:dist/ 早在 .gitignore,但历史上 dist/sw.js 与 dist/index.html 被手动 git add 强制跟踪进 index。修法:git rm --cached dist/sw.js dist/index.html 从 index 移除并随 upstream pull 合并;working tree 与 origin/dev 同步,之后不会再出现。同日所有 repo 部署状态核对为绿:client-web 9bd3ccf、channel OWL D4、gateway relay SSE streaming 在线。
代码库状态快照(2026-04-19)
6 个仓库均在 dev 分支,与 remote 同步:
| 仓库 | HEAD | 说明 |
|---|---|---|
| client-web | 41864e6 | fix(ui): keep input focus after send on desktop |
| channel | 12cfae1 | fix: code review fixes(listener leak / body limit / reconnect) |
| gateway | 00dec2f | fix: use senderId as API chatId(Bug B) |
| sdk | 8872739 | feat: create @clawlines/sdk |
| docs | dd7dfb1 | docs: 覆盖率 50%→89% |
45eb386 | Merge branch feat/wx-sync |
新动态:ralph/thread-discord-style 分支在 client-web / channel / gateway 三 repo 同时出现(ralph 在做 Discord-style thread 功能),client-web 新打 tag v0.2.0-62ba202。dev 环境 web.dev.dora.restry.cn 当日同步发布,返回 200。
部署铁律:AGENTS.md 规定只部署 dev 分支,所有 repo 保持一致。
ACP Thread 卡片渲染(04-14 排查)
threadId 传递链路在代码层面已打通(SDK → outbound.ts → sendMessageGeneric → WS → ChatRoom → history.sync),但实际运行行为需 ACP session 触发验证。发现 history.sync 用驼峰 threadId 而 syncMessageToLocal 读下划线 thread_id,两条不同路径对应不同数据源,属正常设计。详见 acp-thread-card-rendering。
关键 Bug 修复记录
| Bug | 根因 | 修复 |
|---|---|---|
| Agent.list 死循环 | useEffect 依赖 + ensureAgentsLoaded 无条件调 connect | 加 agents 已有守卫 |
| iOS ActionSheet 被输入栏遮挡 | fixed 在 overflow:auto 容器内的定位 bug | 移出 scroll 容器 |
| 复制文字功能失效 | 长按 400ms 劫持系统文字选择 | stopPropagation + execCommand |
| iOS PWA 旧版本不更新 | SW 不自动 skipWaiting | index.html 加缓存清理脚本 |
| 技能按钮点击不灵 | 28px 按钮太小 + 动画干扰 | onPointerDown + 36px |
| 骨架屏卡死 | StrictMode 双执行下 agent.selected 丢失 | connection.open 后请求 agent.list 兜底 |
| 消息重复 | 多 WS 连接导致 outbound 多次写入 + outbox flush 重复 | Channel 层 rolling Set 去重 + Client dequeue-first |
| WS 疯狂重连 | Gateway 不回复 ping(当普通消息转发) | Gateway 拦截 ping 回复 pong,Client 心跳 15s→30s |
| PC 通知不触发 | 只判断 document.hidden | 改为 `hidden |
| 聊天时响声音 | agentInbox 无条件调 playNewMessage | 判断 activeAgentId + hasFocus 跳过 |
| PC 端发完消息失去焦点 | 第 1462 行 blur() 原注释是 “Dismiss mobile keyboard”,但未加平台判断,PC 也 blur | 移动端继续 blur 收起键盘,PC 端用 setTimeout 重新 focus 输入框(04-16 修复并部署到 web.dev.dora.restry.cn) |
插件更新方式
Clawline 插件通过 GitHub 多仓库管理:
| 仓库 | 作用 |
|---|---|
clawline/channel | OpenClaw Channel 插件(后端信道) |
clawline/client-web | React Web 客户端 |
clawline/client-wechat | 微信小程序客户端 |
clawline/gateway | WebSocket 消息中继网关 |
加载方式:~/.openclaw/extensions/clawline/ 是 symlink → 指向 ~/Projects/clawline/channel/。更新流程为 git pull → 重启 gateway。
Browser-Agent E2E 调试踩坑:localhost vs 127.0.0.1 不同 origin(2026-04-19)
fries-mac 通过 Hermes → Claude Code → Browser-Agent skill(http://127.0.0.1:4821,Default Chrome profile,windowId 420604943)测试 client-web dev 环境时,被诱导连了 http://127.0.0.1:4026/,看到”Get Started 流程、左侧无 agent”,回报”agent 列表是空的”。Daddy 立刻指出:本机日常浏览器开的是 http://localhost:4026/——浏览器把 localhost 与 127.0.0.1 视为不同 origin,localStorage 不共享,所以前者已配对的 agent 在后者完全看不见,被误判为 “client-web 没渲染左侧列表”。改连 localhost:4026 后两个 agent(dora / fires)一目了然。
经验:Browser-Agent 用 user 的 Default profile 跑 E2E 时,URL 必须严格匹配用户日常使用的 host(localhost vs 127.0.0.1 不能混),否则 localStorage / cookies / IndexedDB 全部错位,所有”看不到/没数据”的报告都不可信。
相关主题
- webbot
- Clawline(本页)
- pwa-ios-deployment
- relay-gateway-api
- acp-thread-card-rendering
- clawline-dev-environment
- Clawline(本页)
- clawline-file-send-mechanism
2026-04-20 更新:Bug A 根因修复 + localStorage 完成迁移
Levis 反馈点开聊天界面应有 /api/chat 历史消息但页面立刻跳回 /chats 列表。Claude Code review(5/10 分)指出两处架构错位:
- messageCache 没有 chatId 维度 — 通过
/api/chat发消息带的 chatId(如api-test-1)和 UI 里 agent 对应的 chatId 不是同一 key - ChatList 用
connection.chatId冒充agent.chatId— 路由失败 → 判空 → 自动 navigate 回/chats
之前 4/19 commit 全在症状层打补丁(URL→Screen 加 if、ChatRoom 加去重、双重 fetch),属”加 null check 掩盖该删的零件”反模式。
修复(commit 9bd3ccf1) — 三处根因全拔:
- ChatList 不再用
connection.chatId冒充agent.chatId pathToScreen('/')抽象成'home'intent,由resolveScreen()单点决策messageCache加 chatId 维度 + 读时过滤;删 ChatRoom defensive 去重
D13 localStorage 迁移完成 — 17 个文件 + 一次性迁移表把 openclaw.* → clawline.* 全清,验证 PASS。N1 threadStore 入 cache 补 timestamp(修 sort 稳定性);N3 Inbox 改 useSyncExternalStore。
Onboarding 闭环(已生效) — clawline.onboarding.done flag 不存在 → 展示 Get Started;存在 → / 直跳 /chats。E2E 用例 W-01/02/05/06 不再需要走真实 OIDC,只要清/写 flag 验证两态即可。
Bug B(iOS PWA 顶部挡 + swipe-back 失效)暂未修 — 需要 Daddy 给真实复现机型 / 截图 / iOS 版本,避免硬加 safe-area 引发 double-inset 回归。
2026-05-07 Ghost-outbound 链路 RCA + dev 三分钟自动部署确认
dora 频道 ghost 消息率约 34% 的根因不在 client-web 本身,而是 channel 客户端 apps/channel/src/generic/client.ts:585-592 在收到 inbound 后把消息原样作为 message.send 重广播 → backend 把 inbound 当 outbound 落库。详细 RCA 见 Clawline(本页) 同日记录与 Clawline(本页) 5-07 npm 发包章节。
client-web 侧的连带验证:fix 随 @clawlines/channel@1.0.0 08:26 push 后,dev 环境 3 分钟自动部署链路 把新 channel + gateway build 拉到 gw.dev.dora.restry.cn,client-web 8:30 起观察到 dora 频道不再产生新 ghost 行(schema (channel_id, message_id, direction) unique 允许同一 msgId 同时存在 inbound + outbound,所以历史 ghost 行不会被自动清理;需要手动 SQL 删旧 ghost 后再做长尾观察)。
dev 自动部署链路(5-07 重新确认):
cron/watch-repos.sh每 3 分钟gh api拿clawline/platform:dev最新 SHA,与本地.watch-state/platform_dev.sha对比,变化才触发auto-deploy.sh- 全程从 source 跑 build(不依赖 npm registry),保证 dev 永远跟上 monorepo
dev分支 - 客户报「dev 没生效」时第一步先看 cron.log 是否真的检测到 SHA 变化;常见误判是浏览器 SW 缓存,强刷
Ctrl+Shift+R即可
详见 monitoring-and-cron 的 clawline/platform monorepo 自动部署 章节。
2026-05-08 sync scope=chat|channel 防呆 + ChatRoom chatId 三连自愈
接 5-07 ghost-outbound RCA:cobra/main 客户端仍偶发”别人的消息出现在自己窗口”。继续顺藤排查找到两层叠加 bug,client-web 一晚连发 4 commits。
/api/messages/sync scope 防呆(commit 4754d6b)
之前 sync 接口只按 (channelId, since) 拉历史,不区分查询粒度——agent 主对话视图(channel-wide)和单聊视图(chatId-bound)共用同一查询 → 多用户经 /api/chat 进同一 agent 时,B 用户能看到 A 用户的入站消息。
修:sync 接口新增 scope 必填参数,scope=channel 走老路径,scope=chat 必带 chatId 且 server 端强校验。client-web 调用方按视图类型显式传 scope,杜绝默认值兜底。
ChatRoom effectiveChatId 三连自愈(eea9659 / d98465d / 9699aa8)
ChatRoom 进场拿不到 chatId 时之前直接渲染空、不重试。三个 commit 串起完整自愈链路:
| Commit | 修法 |
|---|---|
eea9659 | effectiveChatId fallback:props.chatId → URL ?chatId= → connection 上次 chatId → agent 默认;任一非空即可启动消息流 |
d98465d | /connect else 分支补 chatId 提取——之前只有”首次配对”路径解析 chatId,已配对用户重连走 else 分支直接丢字段 |
9699aa8 | 已存在 connection 但 chatId 不一致时,自愈 用最新 chatId 覆写 connection store,而不是 silent 忽略 |
三段一起构成「无论什么入口进 ChatRoom,最后必有合法 chatId 或显式报错」的硬保证。
dev 自动部署链路 5-08 复核
monorepo 1 commit / 5 commit / 8 commit 的 push 都在 3 分钟内被 cron/watch-repos.sh 拉走、build、pm2 restart dev-web。web.dev.dora.restry.cn 全程 200,无人值守。
2026-05-06 历史消息加载 3 修(commit a816538 fix(client-web): scroll history load)
Levis 反馈历史消息有时加载不出来。一次 commit 修了 3 个相关问题:
- 上滑加载偶尔没反应 — scroll listener 在 DOM 切换(路由/重渲染)时丢失。改用 callback ref,在 ref 变化时重新绑定 listener。
- 冷启动聊天无法翻页 — 第一次进入空 chat 时,messages 为空 → fetchOlder 用最旧 message 时间戳作为 cursor → 没有 message 直接 return → 永远翻不出来。Fallback 到
Date.now(),跳过缓存直接请求。 - 网络一次失败永久
hasMore:false—fetchOlderMessages网络错误时静默 returnhasMore:false,之后即便用户刷新也再不会请求。改为抛错让上层可重试。
属于 platform monorepo 合并后第一波 client-web 修复(详见 decisions 5-06 monorepo 决策)。生产部署经历了「以为没生效 → 再 push 一次空 commit 触发完整 rebuild」才确认起作用,验证时强刷(Ctrl+Shift+R)清缓存看效果。
Docs Site
基于 VitePress 的 Clawline 产品文档站,聚合渲染五个子仓库的文档,提供产品介绍、使用手册和开发指南。
概述
Clawline 文档站是为 Clawline(本页) 产品线建设的统一文档网站。采用”文档跟着代码走,站点聚合渲染”的架构,各子项目在自己的 docs/ 目录维护文档,文档站构建时自动拉取合并渲染。
项目由 Ottor 主导产品定义和首页内容编写,webbot 负责站点框架搭建。
域名与环境
| 环境 | 域名 |
|---|---|
| 生产 | www.clawlines.net |
| 开发 | docs.dev.dora.restry.cn |
技术方案
- 框架:VitePress(Vue 生态,Markdown 原生驱动,中文生态好)
- 语言:中文为主
- 仓库:
clawline/docs(独立 repo)
文档站结构
clawline/docs/
├── .vitepress/config.ts ← 导航、侧边栏配置
├── index.md ← 首页(产品介绍)
├── guide/
│ ├── what-is.md ← Clawline 是什么
│ ├── architecture.md ← 三端协作架构图
│ └── quickstart.md ← 5 分钟跑起来
├── scripts/sync.mjs ← 构建前从三个 repo 拉最新 docs/
└── package.json
聚合渲染机制
构建时 sync.mjs 从五个子仓库拉取最新文档:
gateway/docs/ → docs/gateway/
channel/docs/ → docs/channel/
client-web/docs/ → docs/client-web/
sdk/docs/ → docs/sdk/
- 各 Bot 改代码时,
docs/就在手边,同 PR 更新 - 90% 的文档内容在各自项目里维护
- 文档站 repo 主要是”壳”——只存首页和跨项目概述
- CI 触发:任一 repo push → webhook 触发文档站重新构建
五个子仓库
| Repo | 职责 | 语言 |
|---|---|---|
| channel | OpenClaw 插件,WS 事件、历史持久化、agent 隔离 | TypeScript |
| client-web | React 前端聊天客户端 | TypeScript (React + Vite) |
| gateway | WS 代理,连接管理、channel 路由、用户认证 | JavaScript (Node.js) |
| sdk | @clawlines/sdk 通用 WS 客户端库 | TypeScript |
| docs | VitePress 文档站 | Markdown + Vue |
产品定位
Clawline 的核心定位是 OpenClaw 的 Web 接入方案,而非独立产品。
首页叙事:痛点 → 方案 → 优势
没有 Clawline 的痛点:
- OpenClaw 只能通过 Telegram/Discord/微信等第三方平台对话——被平台绑架
- 想嵌入自己的网站/App?得从零写 WebSocket、鉴权、消息路由、文件上传、断线重连
- 第三方平台有 rate limit、API 随时变、UI 不可控、数据不在自己手里
- 内网部署的 Agent 无法暴露公网端口给第三方 webhook
Clawline 的优势:
- 自有渠道 — 你的域名、你的 UI、你的数据
- Relay 架构 — Agent 主动连出来,不需要公网端口,穿墙友好
- 开箱即用 — 三个组件拼起来就能跑,PWA 移动端直接用
- 已验证的全链路 — 流式输出、文件上传、记忆、历史记录、断线续传全部内置
- 可替换 — 不喜欢 Client Web?只用 Gateway + Channel,自己写前端
页面总数
22 页,涵盖 Guide、Gateway、Channel、Client-Web、SDK 五大板块。
相关页面
- Clawline(本页)
- Clawline(本页)
- webbot
- researcher
附录:Inbox 过滤优化(2026-04-07)
从 Clawline Client Web(本页) 拆分。
Inbox 页面系统消息/状态消息过滤逻辑经历了多轮修复,最终通过 6 个入口全链路审查 达到”私人秘书”级别的降噪效果。
问题
Inbox 充斥系统日志、状态播报、空卡片等噪音,核心价值”降噪”未实现。
修复过程(3 轮 commit)
| Commit | 修复内容 |
|---|---|
27f69b4b | WebSocket 包白名单机制(只处理 message.send/message.receive/thinking.*/agent.list)+ 空内容/无效内容过滤([Image]、📎 File、*[cancelled]*) |
98e58b5a | 历史脏数据清洗(localStorage 启动时清洗)+ 隐藏零对话幽灵 Agent + getInboxItems 强制过滤 |
a40c03b6 | 修复 Inbox↔Chats Tab 切换导致聊天框状态丢失(activeAgent 被无条件设为 null) |
6 道防线(全链路审查)
- localStorage 旧缓存 — 启动时清洗脏数据
- IndexedDB 历史数据 —
populateAgentFromMessages应用isContentMessage过滤 - 实时 AI 回复 (WS
message.send) — 内容校验拦截空/无效内容 - 实时用户消息 (WS
message.receive) — 同等内容校验 - 其他实时系统包 (typing/status/delta) — 白名单机制完全拦截
- UI 渲染输出 (
getInboxItems) — 强制过滤无有效lastMessage的空白 Agent
已知边界:后端 Channel 插件若错误使用 message.send 协议发送系统文本(如 "Agent connected"),客户端无法区分,需后端改用 status.update/system.alert 协议。
2026-04-19 PRD v1 战略:先聚合视图,后自动化
承接 Clawline(本页) reliability v2 当天写的 docs/prd/clawline-v1.md,Inbox 路线由 fries-mac 与 Daddy 一轮对齐:
- 第一阶段(当前):把”聚合视图”做扎实——多 Agent 消息一条流、按未读/最近活跃排序、噪声过滤(已落地的 6 道防线)
- 第二阶段(后续):在现有”生成建议”按钮上叠加战略思考回复——不是新增独立工作流,而是让建议能跨多条消息考虑上下文/任务跟踪,给出可一键发送的回复草稿
- 暂不做:跨 inbox 的全自动跟进/任务管理工作流——优先级压后
设计原则:Inbox 是”私人秘书面板”,聚合 + 起草是核心,自动决策不是。详见 Clawline(本页) PRD v1 章节。