Commit Graph

16 Commits

Author SHA1 Message Date
764939ed90 refactor(webui): dashboard.html 6637 行拆成「外壳 + 样式 + 脚本」
前端刻意没有构建链(纯 CSS + Vanilla JS,go:embed 进二进制),所以拆法是:
外壳 dashboard.html 留 {{DASHBOARD_CSS}} / {{DASHBOARD_JS}} 两个占位符,
init() 启动时把两份资产原样填回去 —— **发出的 HTML 与拆分前逐字节一致**,
但 6637 行的单文件变成三份,便于编辑与评审。

  dashboard.html   168 行   外壳(head/body 结构 + 两个占位符)
  dashboard.css   2099 行   样式
  dashboard.js    4371 行   脚本

逐字节校验(三重):
  * 组装结果 sha256 == git HEAD 里拆分前的 dashboard.html;
  * 真实实例 GET /(带 API key)返回体 sha256 同上:0ef14b49…c053;
  * 新增 TestDashboardAssetsSplit:占位符必须存在、样式/脚本不得再内联回外壳、
    组装结果不得残留占位符且必须含样式与脚本特征串。

按行号切片时踩过一次坑并已修正:`</style>`/`</script>` 两个闭合标签被切掉
(正好少 27 字节)——正是因为当时少了逐字节校验,现在把它固化成断言。
2026-09-14 07:17:23 +08:00
4cbfdc970c perf(webui)+feat(config): 聊天记录写盘节流 + 配置库空闲页回收
两条都是我上一封里点出、你说继续的问题。

① 聊天记录:每条消息都整段重写 → 节流合并写
   原来 persistChatLocked 每次变更就整段重写记录文件,而一轮对话会触发多次
   (用户消息、每个工具事件、收尾消息)。200 条上限下文件可达数 MB,单轮就能
   放大出几十 MB 写。文件里还留着一个 chatSaveThrottle=3s 常量——声明了但从未
   被使用(疑似上次 revert 的遗留),等于节流从来没生效。
   现在:persistChatLocked 只置脏 + 唤醒写盘协程;chatPersistLoop 去抖
   chatSaveThrottle(3s)、并以 chatSaveMaxDelay(10s) 兜底(持续输出也不会无限拖延);
   写盘前把快照拷出来,**不持 chatMu 做文件 IO**;写失败重新标脏下轮重试。
   插件 Stop 里调 Handler.Close():停协程 + 强制落最后一次(幂等),否则丢最后一轮。

   实测(临时实例,连发 3 条消息):3s 窗口内记录文件**尚未创建**(节流生效);
   SIGTERM 后文件出现且 6 条(3 用户 + 3 助手,无 LLM key 故为错误回复)全在
   ——关停落盘没丢。

② config.db:SQLite 的 DELETE 不缩文件 → 空闲页够多时 VACUUM
   新增 ConfigRegistry.MaybeCompact(minFreeBytes, minRatio):空闲页 >= 1MB 且
   占页数 >= 25% 才做一次 VACUUM,避免每次启动都重写整库。库里是 WAL 模式,
   VACUUM 之后必须再 wal_checkpoint(TRUNCATE),否则主库文件看着没变小。
   调用点放在插件加载**之后**(大值的搬走/删除发生在插件 Start 里,之前调没意义)。

   实测(一个刚被搬走 5MB 聊天记录的实例):
     freelist 1288 页 × 4096B;启动日志「配置库已压缩: 5394432 -> 118784 字节」
     config.db 5,394,432 → 118,784 字节;记录文件 5,279,491 字节完好未动。

测试:TestChatPersistenceIsThrottled(节流窗口内不写盘 + Close 必落盘 + Close 幂等)、
TestMaybeCompactReclaimsFreePages(删大值后文件确实变小 + 数据完好 + 阈值不达标时不白做功)。
2026-09-14 07:14:55 +08:00
c4998bb102 feat(webui): 聊天记录改为独立文件存储(位置可配)+ 存量自动迁移
起因:聊天记录原先作为插件配置项 plugin.webui.chathistory 存在 config.db 里,
带来三个后果(都在生产实例上实测过):
  1. 整段记录 5,176,016 字节会被 GET /api/v1/settings 当普通配置项整块返回;
  2. 每来一条消息就把整段记录重新 marshal 后写回 config 表,而那次写要拿
     config registry 的全局写锁 —— 消息频繁时所有配置读写都被拖着排队;
  3. 位置不可配(想放独立挂载盘只能改整个 data_dir)。

改动:
* 新增 internal/plugins/webui/history.go:
  - historyStore:默认 <data>/webui_chat_history.json,写盘用同目录 tmp+rename
    原子替换,崩溃不会留半截 JSON;读失败/JSON 损坏按空历史处理并告警
    (聊天记录不是关键数据,不该让它拖垮 WebUI)。
  - resolveHistoryFile:插件设置 history_file > 默认路径;相对路径按 data 目录
    解析(可指向独立挂载盘),data 目录未知时落到系统临时目录而不是进程 CWD。
  - LoadWithMigration:文件为准;文件为空而老配置项有内容时,把记录搬到文件、
    搬成功才删配置项(删不掉就保留并告警,不丢数据);文件已有数据时顺手清掉
    上次没删干净的遗留键。
* 新增插件设置项 history_file(设置页可见可改):留空 = 默认路径。
* handler.go:chatHistory 的读/写改走 historyStore,不再碰 settings;
  顺带把「写失败静默忽略」改成告警。

存量迁移实测(拿仍持有 5,271,690 字节老记录的实例跑新二进制):
  日志:聊天记录已迁移到独立文件 .../webui_chat_history.json(1300 条),并从插件配置表移除
  迁移后:config_webui 里 chathistory 行数 = 0;记录文件 5,279,491 字节
          GET /api/v1/settings = 33,921 字节(迁移前 8,244,108)
          GET /api/v1/chat/history 正常(从文件读回 1300 条里最新的 3 条)

新增测试:TestResolveHistoryFile、TestHistoryStoreMigratesFromConfig(含二次加载
不重复迁移 + 损坏文件不 panic)、TestHistoryStoreSaveIsAtomicAndRoundTrips。
2026-09-14 07:00:07 +08:00
0beb389223 fix(webui): 设置接口不再吐内部数据;--webui 覆盖生效;端口占用不再静默成功
三处实测确认的缺陷:

① 设置接口整块吐出聊天记录
   plugin.webui.chathistory 是 webui 自己持久化的整段聊天记录(生产实例
   实测 5,176,016 字节),躺在插件配置表里被设置接口当普通配置项整块返回,
   前端还会把它渲染成一个巨大的文本框。
   修复:GET 跳过该键(按插件+键精确判定),PUT 直接 400,避免误改。

② CLI --webui 与 webui.listen_addr 一直是死配置
   内核原本在插件加载前写 settings["addr"],但那时 config_<name> 表还没建
   (表只在插件注册 def 时创建),PluginSettings.Set 的 INSERT 失败,而错误被
   "_ =" 忽略了;随后插件 Start 里 RegisterDef 才建表并写入默认 :8080。
   实测:传 "-webui 127.0.0.1:18099" 仍然监听 :8080。
   修复:覆盖值改由插件自己接收(webui.SetListenOverride,loadPlugins 前调用),
   优先级 CLI > webui.listen_addr(非默认值才算显式配置)> settings["addr"]。
   实测修复后:"-webui 127.0.0.1:18099" 正确监听 18099,与生产的 :8080 并存。

③ 端口被占时 webui 静默死亡
   Start 在后台 goroutine 里 ListenAndServe,先打印 "listening on" 再尝试绑定,
   失败只留一行日志,Start 永远返回 nil → 插件仍被当成加载成功。
   修复:net.Listen 同步做,失败即返回 error(交给加载器/守护),
   成功后才起 Serve,并打印真实绑定地址。
   A/B 实测(两个实例都撞生产的 :8080):
     修复前:"listening on :8080" + "server error: address already in use" + LOADED: webui
     修复后:"[plugin] start webui: webui: 监听 :8080 失败: ...",不再有 LOADED: webui

效果实测(同一实例,先注入 5,271,690 字节 chathistory):
  GET /api/v1/settings   8,244,108 → 28,652 字节(约 1/288)
  meta 条数              5,208 → 105,幻影键 0 条
  设置页仍正常:?prefix=plugin.webui 返回 8 条 def;普通键 PUT 落库;
  校验:GET/PUT 内部键被拒;-webui 覆盖真实生效。

新增测试:TestSettingsNoCrossPluginLeak(跨插件泄漏/幻影键/chathistory 读写)、
TestListenOverrideAndBindFailure(覆盖生效 + 端口占用必须报错)、
TestResolveListenAddrPrecedence(优先级)。
2026-09-14 06:54:16 +08:00
ae42e486de feat(pluginmgr): 插件更新接口(upgrade/downgrade 保留配置)+ skill_install overwrite
内核 Registry 拆出 StopAndUnload:
- 停止并从注册表移除插件但保留 config_<name> 表
- 不触发 onRemove 回调(那是删除专用语义)
- RemovePlugin 改为追加清理配置表示清除,更新场景调 StopAndUnload

pluginmgr:
- installFromData/installFromURL/installFromPath 加 overwrite 参数
- 已存在+overwrite=true:StopAndUnload→备份旧目录→解压新包→失败回滚→
  返回 action=upgraded/downgraded/reinstalled+previous_version+config_kept
- 已存在+overwrite=false:返回 error+hint(指向 overwrite 用法)
- cmpVersion 点分版本号数字比较(非字典序)
- 测试覆盖:首次安装→重装拒绝→升级保留配置→降级→失败回滚

skill_install 加 overwrite 参数:
- 同名技能存在时先卸载旧实例+删除目录再安装新包

SDK PluginMgr 接口同步加 StopAndUnload(name string) error

工具链 plugindev 已重建到 /usr/local/bin(7/29→8/25 版本)
QQ 插件诊断日志版(webhook recv 到达+isAtBot 失败日志)已打包并
通过 upgrade 接口热更新部署,配置保留验证通过。
2026-08-25 22:02:17 +08:00
8d368913a9 feat: webui 消息重放双重防护(client_msg_id 去重 + agent 内容级去重)
回应群聊 08-19 消息轰炸诊断(GUI SSE 重连导致消息重放):

1. webui 层 client_msg_id 单飞去重(与 GUI c29abe9 配套):
   - /api/v1/chat 解析 client_msg_id, 同 ID 重放等待首次结果直接复用
   - 响应带 deduplicated=true 标记; 无 ID 旧客户端完全兼容
   - FIFO 缓存上限 256 条防泄漏

2. agent 核心层内容级短窗口去重(兜底无 ID 客户端):
   - isDuplicateInput: source+content 为 key, 10s 窗口内重复丢弃
   - 持续轰炸时刷新时间戳保持拦截; 过期项自动清理

测试: webui 去重三场景 + agent 核心去重行为验证, 全项目 go test 通过
2026-08-21 10:23:38 +08:00
ef5f010e87 device gateway: remotedevice 插件(设备接入网关) + GUI/waiter 受控设备桥 + 设备页/授权开关/托盘/退出进托盘 + 白屏修复(惰性Tray) + deviceinfo 工具 2026-08-17 09:34:25 +08:00
147d0baaf9 fix: LLM 工具循环 400、中断消息注入、ConPTY 终端支持
- agent: 工具轮请求尾部补 user 占位(zen 网关强制),tool 消息正确配对
- agent: 工具提醒/中断以 system 角色注入并带 [中断消息] 前缀,不进用户履历;系统提示词说明中断消息格式
- agentcli: 基于 ConPTY 的交互式终端(ptywin fork),terminal_create/read/write/resize/close/watch
- webui: server 输出通道适配器(保留 reasoning_content/disable_thinking)
- GUI: 沉浸式标题栏、icon 圆角重制、mascot 等打磨
2026-08-14 00:48:40 +08:00
da1a3cc1fa webui 测试修复:先 RegisterDef 建插件配置表再 Set,修复 AuthMiddleware 全 503 2026-08-05 10:00:05 +08:00
dbbd73b930 refactor: migrate built-in plugins to SDK-only interface
- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter
  now interact with the kernel exclusively via internal/sdk interfaces;
  all Configure() calls and package-level global injection removed
- buildSDK in internal/plugin/registry.go is the single assembly point
- Add internal/sdk/events.go exporting event types/constants
- Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now
  returns the source name instead of lua_<adapter>, so multiple sources
  sharing an adapter (single script load via shared VM AdapterCache) no
  longer share failure-cooldown state
- Verified: build/vet/tests green, deployed to homeagent.service with
  full plugin capability testing via local OpenAI-compatible mock
2026-08-01 12:17:17 +08:00
1cb3e87dde feat: 完整实现 NLP 三元组提取系统 + token budget 上下文分配
- 重写 extractor.go: 分句、17条 POS 模板、依存模板 + COO 链、ATT合并
- parser.go: 分句循环 + TransE 向量验证(h+r≈t)
- fallback.go: jieba POS 降级解析器
- bridge.go: nlp.Triple ↔ memory.Triple 转换
- pipeline.go: extractKeyTriples 改用 NLP 提取器, 删除5条旧前缀规则
- distill.go: docToTriples 改用 NLP 提取器
- reorgGraph: 语义相似度增强检测, 保持纯 LLM 决断
- Provider 接口加 MaxContextTokens() + 模型窗口映射表
- tokenbudget.go: 中文 token 估算器 + budget 分配(80%利用率)
- process.go/buildSystemPrompt: 按 token 预算截断 memory+timeline
2026-07-27 15:26:23 +08:00
c7e22fbe30 refactor: move webui.listen_addr from core.daemon to webui category 2026-07-14 11:22:46 +08:00
b664360f61 test: cover WebUI auth flows and bootstrap credentials
- add auth middleware tests for API key, login, and root redirect
- auto-bootstrap admin username/password and API key when unset
- add logout control to dashboard and cookie-aware client behavior
2026-07-06 21:43:49 +08:00
eb55a8fb98 feat: expand local operator controls across CLI and WebUI
- Add structured CLI commands for status/kernel/settings/plugins/memory/knowledge/agents
- Inject core dependencies directly into CLI plugin for non-HTTP operator workflows
- Add plugin management panel and API proxy endpoints to WebUI
- Let cmd_run inherit default workdir from core.agent.workdir
- Use fixed loopback address for pluginmgr API
- Remove stale MaxToolTurns config usage from homed wiring
2026-07-06 20:32:04 +08:00
fab58e709a feat: complete P0/P1/P2 — WebUI SPA, OpenClaw sidecar+simulator, healthcheck auto-sched+perf
P0: WebUI重构
- 完整 SPA 仪表盘 (7标签页), //go:embed dashboard.html
P1: OpenClaw兼容 (三通道: SKILL.md / sidecar / simulator)
- Node.js 模拟进程统一加载任意 OpenClaw 插件
- JSON-RPC 2.0 over stdio 协议, go:embed 内嵌
P2: Healthcheck 优化
- 定时自动执行 (startAutoCheck, 30min)
- healthcheck_perf 性能监控工具

其他: agentcli/cmd 插件, integration_test, status.go,
      test_deepseek 清理, 多项 bug 修复
2026-07-04 12:51:32 +08:00
2d314b3e9c 重构: 插件自注册 + .so 动态加载 + 中断打断机制
- 所有内置插件 init() 自注册 (plugin.RegisterFactory), 移除 main.go 硬编码
- 新增 .so 动态加载器 (internal/plugin/dynamic.go), 插件可编译为 plugin.so
- 新增 plugin.json 元数据 (internal/plugin/manifest.go)
- 新增 interceptLoop 独立 goroutine:
  (a) cancelLLM() 取消进行中的 HTTP 请求
  (b) interceptCh → drainInterrupt() 注入 [打断消息] 到 LLM 上下文
  (c) InjectInput 空闲时触发新处理循环
- 新增 internal/plugins/all.go 空白导入触发所有内置插件 init()
- internal/sdk/ 作为 PluginSDK 正式 Go API
- internal/api/ → internal/plugins/webui/ 迁移
- 删除旧 cmd/cli/, 使用 cmd/waiter/ 替代
- 更新 PLAN.md / ARCHITECTURE.md / README.md 文档
2026-07-03 16:53:34 +08:00