1ce3a917a5
feat(status+webui): 运行态图形化 —— 排队/四级中断队列/中断栈/驻留子/通道拓扑
...
需求:首页不该只有文字,要能一眼看出内核在忙什么——排队消息数、各级中断
排队与中断栈、驻留子 agent 数量;这些要向**内部 SDK 暴露接口**,供 WebUI 等应用
展示;通道划分也要能画出来。
## 一、内核状态面(internal/sdk,内部 SDK,不受公开 SDK 冻结约束)
* SchedulerStatus 补:
- interrupt_queues[5]:**四级中断队列各自的深度**(下标即级别 1..4,下标 0 恒 0,
这样 level 能直接当数组下标用)。此前只有 pending_interrupts 总数,
看不出"堵在 L1 还是 L4"——四级是抢占优先级,堵在哪级是完全不同的运行状态。
- immediate:刚抢占成功、下一个安全点立即运行的那个中断(此前完全不可见)。
- suspend_frames:中断栈的帧(栈底→栈顶,只给任务标识),depth 之外还能看出
"谁被谁打断"。
- interrupts_by_level / preempts_by_level:各级累计登记数与抢占成功数。
* KernelStatus 补 residents(驻留子运行时视图:状态/轮次/上下文已满/输入通道/允许输出)。
刻意**不带**每个驻留子的 inputch 登记明细——状态面会被反复轮询,明细会让
每次 /status 背上几十 KB;只给表大小,要明细走专门接口。
* ChannelInfo 补 direction(in/out/io)、description、tools、output_caps、caps_text。
此前 collectKernelStatus 只透传 Name/Type,把描述/工具/能力**全丢了**,
前端只能画出一排光秃秃的名字。
## 二、WebUI
* 新增只读 `/api/v1/runtime`:只回运行态三件事(scheduler/residents/channels),
实测 **1.0KB**(/kernel 是 30KB 级)——所以能 3 秒轮询做"实时"感,
而不必反复拉全量状态。
* 首页新增「运行态」面板(纯 CSS + 内联 SVG,前端仍无构建链):
- 四个数字块:排队任务 / 待处理中断 / 中断栈(深度/上限) / 驻留子 Agent,带占比条;
- 四级中断队列条形图:每级"深度 · 登记/抢占",四级语义**照抄内核**
(L4 内核独占 / L3 交互 / L2 消息 / L1 后台),不自己起名字;
- 中断栈层叠图(栈顶在上)+ ⚡ 立即运行项;
- 通道拓扑:输入通道 → 内核 → 输出通道,双向通道两侧都出现,能力以胶囊标签显示。
* 3 秒轮询只在总览页可见时才发请求;切回总览时 renderAll 会立刻补一次。
## 验证
* 新增 TestSchedulerStatusExposesLevelsAndStack(四级队列/立即项/栈帧/各级计数映射,
并断言"未使用的级别必须为 0"与"下标 0 恒 0")、TestChannelInfoCarriesTopology、
TestRuntimeEndpoint(形状 + 不携带 tools/plugins + 无状态源时 503)。
* go build / vet / agent+core+sdk+plugin+webui 全量测试绿。
* 真实浏览器实测(CDP 驱动,注入运行态样本走真实渲染路径):
数字块 [3, 5, 2/4, 2];四级条 L4=1/L3=2/L2=1/L1=1 与数据一致;
栈帧按"栈底→栈顶"渲染且标出栈顶;通道左右分列、io 通道两侧都出现。
2026-09-14 09:05:14 +08:00
c0e9dc1818
fix(webui): 聊天记录不再"每次都发完整记录",并修掉视口跳顶
...
两个都是你指出的症状,都定位到了具体代码路径。
① 「每次都发完整聊天记录」
a) API 缺省值错了:/api/v1/chat/history 的 limit 缺省是 0 = **不限制**,
于是任何不带 limit 的调用每次都拿到整段记录。实测(126 条):
不带 limit 635,297 字节;现在默认只回一页 180,668 字节。
显式 limit=0 仍可整取(逃生口)。WebUI/GUI 本来都带 limit,不受影响。
b) 前端 30s 轮询(以及每次 SSE 报错)都直接拉一页 40 条:
浏览器实测单次 180,813 字节。现在先做"尾巴探测"(limit=1,362 字节),
尾巴一致就直接跳过;不一致才拉整页。
② 「聊天记录会跳到顶部」——两条会导致视口丢失的路径都堵上
a) syncChatFromHistory 在"找不到重合点"时直接 `state.messages = serverMsgs`:
服务端只回一页,而本地可能已经向上翻了好几页;一覆盖,容器立刻变矮,
视口被夹回顶部,用户翻过的旧消息也凭空消失。现在只在服务端页**不短于**本地时
才整体替换。
b) renderChat 全量重建 innerHTML 后,仅在粘底时滚到底;非粘底(用户正在向上读)
时位置没人管。改为重建前记住 scrollTop、非粘底时原样还回去。
浏览器实测(CDP 驱动真实页面,126 条历史):
* 15s/30s 定时器跑满 40s:聊天区滚动位置 **0 px 变化**,未跳顶;
* 期间 chat/history 请求:limit=1 × 2(各 362 字节)+ 首屏 limit=40 一次;
* 控制台无报错;新增消息后轮询仍能正确并进来(尾巴探测→拉整页→合并)。
测试:新增 TestChatHistoryDefaultIsPaged(默认一页 / has_more / limit=0 整取 / 显式分页)。
顺带修测试串味:迁移用例往 os.TempDir() 写共享历史文件,会让其它用例的
NewHandler 加载到脏历史(表现为条数多 1);现在各用例用自己的临时文件。
2026-09-14 08:06:16 +08:00
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