Blizzard
7c67256f87
feat(web): 薄 Web 面 sundynix-web —— 注册/组织/团队/账单自助入口
...
CI / Go · build + vet + test (pull_request) Failing after 30m3s
CI / Frontend · tsc (sundynix-admin) (pull_request) Failing after 9m42s
CI / Frontend · tsc (sundynix-desktop/frontend) (pull_request) Failing after 16m53s
CI / Frontend · tsc (sundynix-web) (pull_request) Has been cancelled
CI / mcp-py · sandbox guard (pull_request) Has been cancelled
SaaS P3 收口第二刀(设计见 SAAS_DESIGN.md §8)。第三个产品面:desktop=用户
工作产品、admin=平台超管控制塔、web=租户客户的自助柜台——做「装桌面端之前
就要能用」的那些事,三者不合并。
- 骨架抄 admin(HashRouter+路由注册表+me()/sdx:logout 鉴权门+vite/vitest
单文件配置);UI 整目录搬 desktop 的 ui/ 组件+ink/brand 主题 token(亮暗
双主题),品牌观感与桌面端一致;api.ts 抄 desktop 的 auth/tenant/usage 段
(纯 fetch 零 Wails 依赖),成员管理/建组织打新的租户自助接口。
- 页面:登录注册(一页两态)/概览(组织+角色+余额+桌面端下载指引)/团队(名册+
邀请+改角色+移除,写控件按角色显隐、真闸在后端)/组织(列表+自助新建+切换)/
用量与账单(余额 hero+趋势+合计+最近消耗,数据全来自现成 /me/usage;充值
只放说明占位,支付 P5 再接,不做假入口)。
- 工程:vite :5175、launch.json 配置、make webface、ci.yml web 矩阵纳入。
- 测试:tsc 干净;vitest 3 例(fmtCredits 去尾零——测试先行抓出 1.50 毛刺;
ASSIGNABLE_ROLES 不含 owner)。浏览器 live 全流程:甲(owner)登录→概览→
团队邀请乙→乙(member)登录写控件全隐藏、名册只读,组织/用量页真数据渲染。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 17:47:23 +08:00
Blizzard
65340616df
feat(gateway): 租户自助管理接口 —— 薄 Web 面增量1
...
SaaS P3 收口第一刀。成员管理此前只有平台超管口径(/admin/tenants/:id/*),
租户 owner 无法自助管自己的组织;建租户同样只有 admin 能做。
- 成员自助四路由(作用于活跃租户,id 取 ctx 不接受路径任意租户):
GET /tenants/current/members(≥viewer) + POST/PUT/DELETE(≥admin+审计)。
RequireTenantRole 是 RBAC 极简版预留的架子,这次直接挂上,零改动生效。
- handler 挡两个平台超管可以、租户 admin 不该有的动作:邀请为 owner;
借 AddMemberByEmail 的 upsert 语义「重新邀请」owner 实现降权——后者
是真实的越权路径,admin 拿 owner 邮箱重邀成 viewer 就把 owner 拉下马。
- POST /me/tenants 自助建组织:创建者即 owner、建完切入;slug 用雪花 id
生成不让用户填(全局唯一,自助场景撞名只添乱);owner 没挂上按错误返回
而不是 warn+200(孤儿租户没人能管)。
- CORS 支持逗号分隔多 origin(desktop 预览/薄 Web 面/admin 不同源):命中
白名单回显请求 Origin+Vary;单值/通配/生产未配置全拒的旧语义不变。
- 测试:RequireTenantRole 补 ≥admin 门槛用例;cors 四场景单测。
live 验证 13 项全过(门槛/owner 保护/降权攻击挡下/CORS 回显/审计落痕)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 17:33:04 +08:00
Blizzard
d14a10479e
fix(desktop): 补回报告页剥离时漏搬的 PDF 导出与完成系统通知
...
759483b 把报告页剥成纯启动器时,Word/Markdown 导出搬去了运行页,但漏了两个:
printReportHtml(前端打印出 PDF,CJK 零字体依赖)和 notify(完成弹系统通知)
外部调用点归零,成了被孤立的活功能。
- 运行页报告正文面板补 PDF 按钮:previewRef 抓已渲染 DOM 送打印视图,
与剥离前同一条路径。
- 完成通知挪进 attachRun 的 token 流 done 回调并加 label 参数:原先只有
报告会通知,但「跑几分钟、人早切走了」对编排任务一样成立,且 attachRun
是所有运行的唯一汇合点,放这儿不会再漂移。恢复的待审任务也带主题。
验证:tsc 干净、68 个 vitest 全过、两函数调用点恢复非零。
⚠️ 未实机点验(需登录态):PDF 按钮实际点击出打印视图、通知实际弹出,
下次起服务后补验。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 16:28:28 +08:00
Blizzard
027ccc0ccc
chore(desktop): 版本号三处对齐 0.1.1,追平已存在的 v0.1.1 tag
...
git tag 已打到 v0.1.1,但 APP_VERSION/package.json/build/config.yml 全停在
0.1.0 —— 按更新提示的比对逻辑这会错乱。三处统一 0.1.1,并跑
wails3 task common:update:build-assets 重新生成原生元数据(Info.plist 等,
CFBundleShortVersionString 已核实落到 0.1.1,包名/标识符未被冲回模板值)。
生成器顺手拉的 build/ios/ 脚手架已删,不做 iOS。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 16:28:03 +08:00
Blizzard
a779953ed9
docs(go.work): 注释里的 wails build 是 v2 说法,改 wails3 package
...
v3 的 build 只出裸二进制,出 .app 得 package;顺带点明任务链自带前端构建,
不用手动 npm run build。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 15:16:54 +08:00
Blizzard
a5cf2c5f36
ci: 构建链补完 wails v2→v3 迁移,CI 触发收敛到 main
...
代码早就全量迁到 v3(go.mod 只要 wails/v3、main.go import v3/pkg/application),
但"怎么构建"这一层从没跟上,全还是 v2:
- release.yml 装 v2 CLI(wails@v2.12 .0)去编 v3 代码,编不过;产物路径也还
指向 v2 的 build/bin/,而 v3 落在 bin/。这条只在打 v* tag 时触发,迁移后
一次都没跑过,所以坏了半个月没人知道 —— 下次发版必炸。
- Makefile 的 wails dev/build 直接 command not found。
- README 叫人装 v2 CLI;RELEASE.md 还在提早已删除的 wails.json。
改动:
- ci.yml 触发从 [main, dev] 收到 [main]。dev 分支已删,开发在 feat/wails3,
留着 dev 是死条件;PR 以 main 为目标仍会跑,合入前有关卡。
- release.yml 全量转 v3:
* CLI 钉死 v3.0.0-alpha2.117,与 go.mod 的 wails/v3 版本一致 —— alpha 阶段
CLI 生成的 bindings 和运行时配套,版本错开会出难查的怪问题。
* mac 走 wails3 task darwin:package:universal。v3 的 build 只出裸二进制
(macOS 上双击不起窗),必须 package 才有 .app;本地实跑验证过,
lipo -archs 确认是真 universal(x86_64 + arm64)。
* Windows 保持 v2 时代行为发裸 .exe。v3 的 package 默认走 NSIS 出 installer,
依赖 runner 上有 makensis,属额外未知数,要升级安装包再单独议。
* 产物路径 build/bin/ → bin/。
* fail_on_unmatched_files 改 true,并按 matrix 只匹配自己那个产物 ——
原来是 false 且两个平台都列全量文件,产物路径写错会安静发一个没附件的
Release。这次的 bug 正是路径漂移,得让它当场炸。
- RELEASE.md 版本号对齐表补上第三处 build/config.yml 的 info.version(此前写着
"必须三处对齐"却只列了两处,第三处是已删的 wails.json),并记下改完必须跑
wails3 task common:update:build-assets 重新生成 Info.plist —— 这坑踩过。
- 清掉 v2 残留:frontend/wailsjs/(v2 绑定,v3 用已跟踪的 bindings/)及其
gitignore 规则、build/bin/ 里 v2 时代的 .app。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 15:03:32 +08:00
Blizzard
759483bf92
refactor(desktop,gateway): 用户端去重 —— 一个功能一个入口,一份数据一个查法
...
用户反馈「UI 太乱」。数出来的:report/kb/memory 各 4 个入口、studio 5 个;
运行历史两套渲染。乱的机制是同一件事写了两遍,改一处另一处必然掉队。
**一份数据一个查法**
- 工作台「最近任务」和运行页「运行历史」是同一批数据,却各查各的:
前者走 store.RecentTasks(),后者走 RecentRuns()。于是我给运行历史加的
topic 字段工作台完全没跟上,还在 mono 显示 report_<hex>;而且 RecentTasks
没有租户过滤,口径也不一致。现在统一走 RecentRuns,RecentTasks 删掉
(它只有那一个调用点)。前端 recent_runs 也改用 RunSummary 同一个类型。
**一个功能一个入口**
- 工作台删掉「入库知识/生成报告/管理记忆」按钮排 —— 跳的目标和上面四张
能力卡片完全重合,同一页给两个入口。
- 「最近任务」现在点击直达运行页对应那条(App 加 goto(view, taskId) →
RunsView focusTaskId),而不是只把页面切过去让用户自己再找一遍。
**报告归 RUN,只做启动器**
- 报告是「执行」不是「构建」,从 BUILD 组移到 RUN。
- 报告页砍掉「执行轨迹」「报告正文」两个面板 —— 和运行页完全重复,而且
那套是组件本地 state,切页面就没。现在它只有主题输入框。
- 报告改走 App 的全局运行态(新增 onRunReport,与 onRun 同构、复用 attachRun):
提交 → 跳「运行 · 观测」→ 实时看轨迹/正文/导出。App 里 onRun 早就写着
「发起即跳运行页,观测统一收敛在此页」——这条方针一直在,只有报告没遵守。
**顺带清死代码**
- 市场(规划中)从导航下架,ViewKey 里的 "market" 和 Boxes 图标一并删。
- PLACEHOLDERS 整块死代码:它列的 home/kb/report/runs 全都早已真实实现,
market 是唯一还可能用到的、下架后那个 Placeholder 分支永远走不到
(tsc 报 "Spread types" 就是因为 PLACEHOLDERS[view] 已成 never)。
连同 views/Placeholder.tsx 一起删。
live 验证(真账号,桌面端实机):报告页输入主题 → 提交 → 自动跳运行页 →
顶部「当前运行 · live」→ 轨迹实时跑 → 报告正文流式出 + 导出按钮在位 →
report_7a0110a10b534f90 落库带 topic「归口测试」,POST /api/v1/reports → 202。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 14:08:16 +08:00
Blizzard
fd99ba6226
feat(desktop,gateway): 报告归口运行页 —— 能找回、能复盘、能导出
...
承接上一个提交(报告终于落库进运行历史),把「所有执行归口运行页」做完整:
只能看不能存,不算归口。
- 运行页的输出面板:报告类运行显示「报告正文」并在右上角出 Word/Markdown
导出。报告页那两个导出按钮依赖它自己一切页面就没的本地状态,所以从历史
里找回来的报告,只能在运行页导。
- 运行历史列表显示报告主题。此前只有 report_<hex>,谁也认不出是哪份报告。
主题从 graph->>'topic' 取(报告的 graph 就是占位 DSL {"topic":"…"},普通
任务 DSL 没有顶层 topic → 空串,不会误伤)。
- isReportRun() 按 task_id 的 report_ 前缀判定 —— 这是既有契约,导出接口
/reports/:id/export 本来就按同一个 id 寻址。+4 单测(含"不能只看是否包含
report"的误判防线)。
- reportFilename 从 ReportView 提到 lib:运行页也要用,不能私藏在一个 view 里。
live 验证(真账号,桌面端实机):运行历史首条显示「Redis 缓存穿透的三种解法」
→ 点开复盘 8 节点 + 全文 → 点 Word → **原生另存为对话框弹出**(文件名预填
主题)→ 保存 → 落盘 5KB,file 认 Microsoft Word 2007+,解 zip 得
word/document.xml,正文 2446 字。
**顺带把 wails3 迁移最后一个盲区验掉了**:v3 把 runtime.SaveFileDialog 重写成
application.Get().Dialog.SaveFile().SetFilename().AddFilter()
.PromptForSingleSelection(),此前从没被点过一次,记忆里只敢写"理应工作"。
现在实测通了。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 13:20:57 +08:00
Blizzard
fd268664c8
fix(gateway): 报告生成绕过了落库/计费/RBAC —— 生成完就永远找不回
...
用户实测:报告页输入主题、点生成、看着没反应,切到别的页面再回来,什么都没了。
真相是报告在后端好好地生成完了(26s),是 UI 把它弄丢了、而且永远找不回。
根因:两条提交路径漂移。SubmitTask(POST /tasks) 这一年陆续长出了预算门控/
计费租户/积分硬拦截/落库/录像五道,而 GenerateReport(POST /reports) 还停在
最初的「发个 NATS」,一道都没有。后果远不止看不到历史:
- 不落库 → 运行历史(读 sundynix_task)永远看不到报告。实测修复前该表
report_% 前缀 0 行。
- 无 token/轨迹录像 → SSE 没有回放能力,切走即永久丢失。
- 无 MetaTenantID → 报告用量记不到租户头上 = 漏账。
- 无预算门控/积分硬拦截 → 余额为 0 也能生成,绕过全部成本护栏。
- 路由漏了 RequireTenantRole → **viewer 只读角色能生成报告烧积分**,
而隔壁 /tasks 的注释白纸黑字写着「viewer 只读拦下」。报告一样烧钱。
修法不是把代码抄一份(那只会再漂一次),而是抽两个共用函数:
preflight() — 预算 → 计费租户 → 积分硬拦截,返回 billingTenant
launch() — 落库 + PublishTask + token/轨迹录像
两条路径都走它们,不可能再各长各的。SubmitTask 行为逐行不变。
live 验证(真账号 blizzardzhang,桌面端实机):生成 report_5dae9155af5cb500
→ sundynix_task 建行且带 owner+tenant_id → 出现在运行历史首条 → 点开
完整复盘 8 节点轨迹 + 报告全文。gin 路由表也确认 /reports 中间件数
12 → 13(与 /tasks 齐平)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 13:13:21 +08:00
Blizzard
bf3b0048fd
fix(desktop): 原生下载失败会在用户选定路径上留半截文件
...
桌面端 Go 侧此前零测试。补 5 个(download/ReadLocalFile/Ping),
逮到一个真 bug:io.Copy 中途断开时,用户选定的路径上会留下一个半截的
.docx —— 带着用户自己起的名字躺在那儿,虽然前端会弹错误,但以后双击打不开,
而用户会以为是导出功能坏了。现在失败一律 os.Remove 不留残file。
同批修的两处(同一段代码,都没测试盖到):
- Close 的错误被 defer 吞掉。写文件时 io.Copy 成功不代表数据落盘,
flush 失败只在 Close 上报——吞掉就是静默截断,且 download 返回 nil(成功)。
- http.Get 用默认 client,没有超时。上游卡住的话「另存为」会永远转,
用户只能强杀 app。改用带 3 分钟超时的 client。
顺带核实过一个可疑点、结论是不用改:download 走裸 http.Get 不带鉴权头,
但报告导出路由 `/reports/:id/export` 是故意公开的(router.go 注释:
"EventSource/下载无法带 Bearer"),所以能通。
对话框本身(application.Get().Dialog)要真窗口,自动化盖不到,仍需手点。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 12:06:21 +08:00
Blizzard
d52af12f3d
fix(desktop): 打包元数据还是 wails3 脚手架模板值,app 根本起不来
...
现象:wails3 package 出来的 .app 双击报「可执行文件不存在」,macOS 直接拒绝启动。
根因是我迁移 v3 时埋的:build/darwin/Info.plist 里 CFBundleExecutable 写着
"wails3ref"、bundle ID 是 "com.example.wails3ref",全是脚手架模板默认值,
而实际二进制叫 sundynix_desktop —— 对不上,macOS 认为 bundle 坏了。
我当时在 build/config.yml 里填了正确的产品信息(com.sundynix.agentix /
sundynix-agentix),但漏了那个文件顶部写明的一步:改了 info 要跑
`wails3 task common:update:build-assets` 重新生成资产。补跑之后不只 macOS,
Linux 的 desktop/nfpm、Windows 的 info.json/nsis/manifest 里的
"My Product"/"My Company"/"wails3ref" 也一并换成了真实产品信息 ——
这些留到打包分发才发现就麻烦了。
Assets.car/icons.icns 一并提交:体积变化大(1.63→2.03MB / 52→45KB)不是噪声,
是真被重新生成了;但逐像素比对确认图案没变(平均差 0.6/255),无回归。
顺带删掉重生成又造出来的 build/ios —— 项目是 desktop-only。这次它只有
plist/storyboard/pbxproj、没有 .go 文件,不会像当初那样让 go build ./... 挂掉,
但也没必要留。已验 go build ./... 通过。
已知待办(非本次引入):app 图标至今是 Wails 默认的 "W" logo,从没做过自定义。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 11:42:21 +08:00
Blizzard
bfbca08d72
feat(desktop): 办公室换 AI 房间背景,手绘 SVG 场景全部退役
...
第三版房间才可用。前两版地板都只占画面 13%,而场景需要 ~60% 的地面
才摆得下工位——但那不怪模型,是我提示词自相矛盾:同时要求"正面平视"和
"下方 60% 是地板",几何上不可能同时成立(平视时地板向地平线收缩)。
改成"俯视 30 度、娃娃屋视角"后一次就对(实测地板 53% / 墙 47%)。
参考项目的 office.png 本来就是俯视娃娃屋 + 正面角色,它早验证过这个组合。
不拉伸图去迁就场景,而是改场景去迁就图:VB 720×510 → 940×512,正好是
房间图的比例(1.835)。拉伸 30% 的话墙上挂钟会变成椭圆,一眼假。
所有坐标按图里量出的百分比换算:FLOOR_Y=242(地平线 47.3%)、
DOOR_X=279(门中心 29.7%,角色从画上那扇真门里走出来)。
量着修的两个碰撞:
- 白板压住门和窗。实测墙上被门 214~343、钟 443~515、窗 612~791、
绿植 863~932 占着,最大空白段仅 214 宽,放不下 340 的白板。选择盖掉
挂钟(静态装饰、零信息量),白板落在门与窗之间的 347~613。
- 协调者的桌子撞上正下方工位的气泡。可行区间只有 (242, 276):
低于 242 会飘到墙上,高于 276 撞气泡。取 268。
字号 ×1.3 补偿:viewBox 从 720 加宽到 940 后,同样单位在真实面板
(~520px)里会缩小 28%,不补的话白板正文只有 7px。验收也是在 520px
下做的,不是在预览页那个宽容的尺寸下。
顺带清掉 4 个死常量(WALL/FLOOR/DESK_TOP/DESK_FRONT)——房间换贴图后没人用了。
资源合计 148 KB(7 立绘 + 1 房间,全 WebP)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 11:17:41 +08:00
Blizzard
8b19c572f4
feat(desktop): 办公室换 AI 立绘素材(告别手绘 SVG 小人)
...
用户三次反馈手绘 SVG 不好看 —— 是对的,我不是插画师,程序化矢量画不出
商用品质。技术栈换来换去(3D/Lottie/Spine)都绕不开这点:卡的是美术。
素材:用户用 Nano Banana 生成,链式参考锁风格(先出主管定基准,其余 5 个
+ 桌子 + 房间都拿它当参考图)。版权归用户账号,干净。
为什么最终没上 Spine(用户原本要求照搬 ai-office-react 的实现):
- Spine 运行时不是 MIT。原文要求"每个用户须自购 Spine Editor 授权",
对要卖的产品等于不可行;走另一条要我们买 $69/$330 按座授权。
- 那套 chibi-stickers 素材是 Spine 官方示例(皮肤名里有 spineboy 和
Spine 团队成员名),只授权用于评估学习。参考项目 README 自己写着
"注意素材版权问题!"。
有了立绘就不需要骨骼动画:走位/时序/状态全是现成的 CSS 关键帧,
Chibi 那个口直接换贴图,零 Pixi 零 Spine 零授权。
抠像(scratchpad/art/key.py):只抠「与画面边缘连通」的绿。
绿衬衫角色实测 (41,184,60) vs 绿幕 (9,240,2) —— 两者都"绿压过红蓝",
按颜色阈值必然把衣服抠出洞(实测躯干 81.3% 被误杀);连通域一刀切干净,
对绿植/绿屏同样安全。含软边 + 去绿边,7 张残留绿毛均为 0 像素。
体积:PNG 540KB → WebP 117KB(-78%,平涂+透明是 WebP 强项)。
桌子按「桌面」锚点而非底边:底边贴地时桌宽 200 会让显示器顶到 y=-136
糊住人脸。按桌面对齐后桌子下半截自然超出脚线 —— 这是对的,桌子比人更
靠近镜头本就该更低。铭牌/副标题都压在挡板内(实测 local -27~+23),
掉出去会骑在下边线上被切半截。状态辉光改画在贴图之下、用径向渐变,
硬边色斑糊在显示器背壳上物理讲不通也像贴纸。
手绘版保留为 sprite 缺省时的回落,未删。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-16 10:34:05 +08:00
Blizzard
c2811e79a3
fix(desktop,dispatcher): 从编排执行的多 agent 图看不到「团队」tab
...
现象:编排里并排三个 agent(研究/撰写/审查)跑完,运行页没有团队 tab。
两处卡住,不是一处:
1) isMultiAgent 只认 coordinator: 节点。用户自己在图里并排多个 agent 也是
团队,却被整个漏掉。改成:有协调者,或 ≥2 个 agent: 节点。
2) 就算放宽 1,deriveTeam 仍按 kind==="agent" 挑工位——而两条产生 agent 的
路径 kind 并不一致:协调者派发的专家是 kind=agent,图里的 agent 节点是
kind=model。改成按节点名前缀(agent:/tool:)判,这本来就是后端一直遵守的
约定;顺带天然把 retriever:/map:/render: 这些同为 kind=tool 的节点挡在
工位之外(之前它们会混进来当工位)。
连带修一个更要命的:runAgent 把轨迹标签写死成"模型流式推理"、runReactAgent
写死成"ReAct 智能体(自主调工具)",用户在编排里给节点起的名字(研究 Agent /
撰写 Agent / 审查 Agent)整个丢了。后果不止办公室:执行轨迹里三行同名,根本
分不出谁是谁;团队视图只能退回节点 ID,工位显示成 r/w/rev。
改成一律 labelOf(n, 兜底) 由调用方传入,+2 单测钉住。
另:没有协调者时不再凭空画一个"协调者"小人(白板改挂「任务产出」),也不演
递简报那一程(没人可递),✓ 气泡改为收工即冒。
注意:已存的历史轨迹是落库的,仍是旧标签;只有新跑的任务才有节点名。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-15 17:41:21 +08:00
Blizzard
02608bb6a4
feat(desktop): 办公室加走位编排(进场 / 敲键盘 / 递简报)
...
专家被派发 → 从门口走进来 → 在工位敲键盘 → 干完起身走到协调者桌前
递简报(手里真拿一页) → 回工位。协调者的白板仍是思考/综合流。
为什么做这个:重看参考项目(ai-office-react),它的 Spine 动画只有 idle
和 walking 两个——"好玩"来自角色在房间里走动,不是画质。上一版把人钉死
在工位上只让手动,这才是不好玩的根因。
实现:全 CSS 关键帧,无 rAF / 无定时器 / 无状态机。
- 直播(streaming):工位随事件到达而挂载,延迟一律 0,动画即真实进度。
- 复盘:按事件时间戳算出各自的出场/交付时刻,整段压进 PLAY_MS 当一出戏放;
敲击次数由"工作时长 ÷ 敲击周期"换算,所以快的人敲几下、慢的人敲一路。
- 收工时刻强制不早于"进门 + 敲一下",否则压缩比大时人还没进门就来递简报。
- 角色层与桌子层分开画,人才能走到别人桌前而不被自己那张桌子焊死。
- 补了腿:原来是"永远被桌子挡住的半身像",一走出工位就成了悬空上半身。
- reduced-motion 直接就位不演。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-15 17:41:21 +08:00
Blizzard
225fee5519
feat(desktop): 团队视图加「卡通办公室」皮肤(手绘 SVG 贴纸风)
...
多智能体协调演成办公室:协调者在白板前统筹(白板即思考/综合流),
专家在工位敲键盘,完成打✓、失败挂!、派发走虚线。
风格对齐 ai-office-react(粗描边+平涂+大头身),但用手写 SVG 而非
PixiJS+Spine:零外部素材、无 Spine 运行时授权问题、几 KB 进包。
先试过 three.js 真 3D,低模程序化角色出来是玩具味,已弃并卸干净依赖。
数据与「卡片看板」同源(deriveTeam),换皮不换数据,后端零改动。
动效=状态编码(敲键盘/思考流/辉光),守 prefers-reduced-motion。
顺带修一个真 bug:并发时间轴复盘卡在 running 的旧任务时,未收口工位
会一路量到 Date.now(),跨度爆表(实测 32103562.4s)。抽 teamNow():
直播用此刻、复盘用最后一个事件时间戳;两个皮肤共用,+3 单测。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-15 17:41:21 +08:00
Blizzard
0152e7837a
feat(desktop): 多智能体「团队」视图(运行·观测新增,轨迹仍默认)
...
把多智能体协调画成「协调者居中 + 专家工位环绕」,让最难读的一段——并行派发与综合
——变得一眼可读。执行轨迹保持默认视图;「团队」tab 只在多智能体运行时出现,
普通任务不塞多余 tab。
- lib/run.ts 加 isMultiAgent()(据 coordinator: 节点判定) + deriveTeam():
从现有执行事件流派生协调者/工位模型。与 deriveNodes 的区别是保留 start/end 时间戳,
才能画并发时间轴;并解析专家收尾 detail("简报 X → Y")还原简报与产出。
- components/TeamView.tsx:径向布局(工位按角度均分环绕,>6 个降级为网格)、
协调者实时思考流(接 token 流 + 闪烁光标)、并发时间轴(重叠一眼可见)。
动效只标活跃态(派发链路流动/工位脉冲)——是状态编码不是装饰,带 prefers-reduced-motion 守卫。
专家(kind=agent)与工具(kind=tool)图标区分。
- 零后端改动:数据全部来自现有 exec 事件(coordinator:/agent:/tool:)。
补 9 个单测覆盖派生逻辑(简报解析/失败态/时间窗/lead 不混入工位)。
实机经回放路径验证渲染(径向布局/简报/并发条/tab 条件出现)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-15 17:41:21 +08:00
Blizzard
2d5b72930a
fix(dispatcher): 中文专家名导致多智能体协调 400 挂死
...
buildSpecialists 把专家名原样当 OpenAI function-calling 的 tools[].function.name,
而该字段受约束 ^[a-zA-Z0-9_-]+$。中文命名专家(中文产品里最自然的用法,如"条款专家")
会让模型直接 400:
Invalid 'tools[0].function.name': string does not match pattern
整个协调节点挂掉,再沿 failover 链把备用模型也拖垮。
修:toolFuncName() 规范化给模型看的函数名——非法字符→下划线,清空→expert_N,
同批内撞名加序号保证唯一;展示名与执行轨迹(agent:<原名>)仍用原名不变。
模型靠 Desc(spec.Use) 判断何时调用,函数名不承载语义,故退化命名不影响派发质量。
实测(真 deepseek):修前中文名必现 tools[0].function.name 400;修后该 400 归零。
补 TestToolFuncName 覆盖:纯中文/合法ASCII/混合名/撞名唯一性。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-15 17:41:21 +08:00
Blizzard
11bf7d2756
fix(desktop): wails3 绑定 .js→.ts + 实机验证通过
...
wails3 build 重新生成绑定为 TypeScript(比 standalone generate 的 .js 更规范,
tsconfig allowJs 保留以兼容两种)。整体迁移实机验证:
- wails3 build 全过(前端 build → 绑定生成 → go build -tags production 原生编译 → 打包)
- 原生二进制 bin/sundynix_desktop 启动,窗口开、进程稳、日志无 error/panic
- app 前端加载后自动登录(持久态)并打通后端:gateway 收到 /spaces/current、
/me/spaces、/me/tenants、/stats/overview、/runs、/health 一整串鉴权请求全 200
- runtime 版本号不一致(npm alpha.97 vs CLI alpha2.117)未导致问题
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-14 10:55:21 +08:00
Blizzard
1f487c3163
feat(desktop): 迁移 Wails v2 → v3(架构/目录/绑定重写)[WIP]
...
从 wails/v2 v2.12.0 迁到 v3 v3.0.0-alpha2.117,采用 v3 工程规范:
Go 侧:
- main.go:wails.Run(options.App) → application.New(Options{Services,Assets,Mac})
+ app.Window.NewWithOptions(WebviewWindowOptions) + app.Run();窗口选项照搬
(1440×900/min 1100×700/#0b0d12/Mac TitleBarHiddenInset)
- app.go:App 结构体绑定 → v3 Service(application.NewService);去 startup/ctx 注入;
SaveFileDialog 改 v3 链式 API(application.Get().Dialog.SaveFile().SetFilename().
AddFilter().PromptForSingleSelection());5 个原生方法保留
- go.mod wails/v2→v3、go 1.25
构建:
- wails.json → Taskfile.yml + build/(config.yml 填产品信息 + 各平台配置)
- 删移动端脚手架(ios/android),桌面端只留 darwin/windows/linux
- .gitignore 改为只忽略 build/bin(build/ 构建系统需提交)
前端:
- vite.config 接 @wailsio/runtime/plugins/vite 插件 + WAILS_VITE_PORT
- package.json 加 @wailsio/runtime;wails3 generate bindings → frontend/bindings/
- desktop.ts:window.go.main.App → import { App } from bindings;桌面态检测改
window._wails.environment(原生注入);isMacDesktop 用 System.IsMac()
- version.ts:runtime.BrowserOpenURL → @wailsio/runtime Browser.OpenURL;测试同步更新
- tsconfig 加 allowJs(消费 JSDoc 绑定)
验证进度:Go 编译过 + tsc 干净 + 48 前端测试绿 + 前端 vite build 通过。
**未完成:整体 wails3 build/dev 原生窗口实跑 + 原生能力(另存为/打开/外链)联调**——下次续。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 18:00:45 +08:00
Blizzard
8f1aefe13e
refactor(desktop): 去黑话 + 收基建灯(商业化语言铺开)
...
把首屏定的"去黑话+精致克制"语言铺到其余页面:
- 顶栏 5 盏基建灯(Gateway/DB/NATS/Milvus/Neo4j)→ 一枚「系统正常」状态点
(客户不看数据库名;异常时 tooltip 提示项数,顺带缓解窄屏拥挤)
- 知识库:入库阶段标签去实现名(写入向量库/Milvus/Bleve/Neo4j → 建立语义索引/
全文索引/构建知识关系);检索台「混合召回+rerank」→「在库中搜索」,命中分数
0.83→83%,"RAG 未配置"→"该库可能为空";图谱去「Neo4j·力导向」
- 运行:流式中→生成中;"token 在此流式呈现"→"回复逐字呈现";忠实→可信度/引用
- 编排节点「检索(RAG)」→「知识检索」,描述去"混合检索"
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 16:57:24 +08:00
Blizzard
2eb38d3eaf
feat(desktop): 首屏工作台商业化重做(定调)
...
把"工程师内部控制台"改成"给客户用的产品门面"(精致深色专业版方向):
- 问候式头部(下午好,X · 工作区上下文)+ 主 CTA「新建编排」抬到显眼位
- 指标去黑话:评测均分/忠实 → 「回答质量 89%」;Token → 「今日用量」
- 能力卡改结果语言(不再暴露 Eino/向量·全文·图谱/Word 等架构黑话)
- 删「服务·网关/总线/PG/Redis/Milvus/Neo4j」健康灯带(客户不看数据库名)
- 空态改引导(还没运行记录 → 从新建编排开始 CTA)
- 图标去彩虹化,单色 + hover 才亮 brand,右上箭头暗示可点
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 16:51:41 +08:00
Blizzard
1efe29aed3
refactor(desktop): 清理死 UI / 假功能(P0 完善)
...
让桌面端只暴露"真的能用"的东西,提升完成度观感、避免误导:
- 删顶栏假"版本选择器"(通用版/法律版/医疗版)——死 defaultValue、无 onChange
- 删「管理」导航项 + 其占位页/命令——内容是 admin 控制台的事(租户/计费/护栏/模型),
违背 desktop/admin 产品边界,留着只是让用户点了扑空
- 「市场·Packs」标记 ready:false(规划中徽标 + 标题标注),不再伪装成已就绪功能;
移除其命令面板项(命令面板只列真实可达目的地)
- Agent 节点模型选项去掉假的"占位 Pool/ollama:qwen/vllm:custom",改由已登记模型
运行时填充(无模型时显示"(无,先创建)"而非可误选的假项);默认 model 置空
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 16:24:56 +08:00
Blizzard
8f766a75a7
feat(space): 桌面端空间成员管理界面 —— 补齐"临时组队协作"前端闭环
...
此前能建项目空间/切换/启用全员空间,但邀请队友进空间只有后端 API、无界面,
协作故事前端断了一半。本次补上成员管理 UI,让 Space 协作端到端可用。
- api.ts: spaceMembers/addSpaceMember/setSpaceMemberRole/removeSpaceMember + SpaceMemberInfo
- SpaceMembers 弹窗: 列成员(名/邮箱/角色 badge); 空间 admin/owner 可邀请(邮箱+角色)/
下拉改角色/移除, owner 受保护(无下拉/移除); 非管理员只读提示。复用 Dialog/Button/Select
- 顶栏加"管理成员"入口(UserCog): 活跃空间为 project/tenant 时显示; App 管弹窗开关,
传 spaceId/spaceName/canManage(空间角色 admin/owner)/selfUserId
实机验证(Wails 桌面 + 全栈后端): demospace(owner)在项目空间邀请 teammate → 成员列表
即时出现(切换器 1人→2人) → 下拉改 teammate 成员→管理员(服务端确认 owner/admin) →
owner 自身受保护无改删入口。tsc + 48 前端测试通过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 14:29:28 +08:00
Blizzard
9dab25a5f0
feat(space): 全员空间(tenant-kind) —— 整租户共享的"开关",自动纳入全部成员
...
补齐 Space 三类型的最后一种:tenant-kind「全员空间」= 用户最初想要的"共享工作区开关"。
与 project 空间的本质区别:自动纳入全租户成员(含之后新加入的),而非手动逐个邀请。
后端:
- EnableTenantSpace(幂等):建 kind=tenant 空间 + 把全部 active 租户成员按租户角色纳入
(owner→owner…billing_admin→viewer);tenantSpaceID 查询;spaceRoleFromTenant 角色映射
- AddMemberByEmail 加租户成员时 autoJoinTenantSpace:若已启用全员空间则自动补纳新成员
- SpaceCreate handler:kind=tenant 路由到 EnableTenantSpace,且须租户 admin/owner(inline 403)
桌面端:
- 空间切换器加"启用全员空间"入口(Globe 图标):仅租户 admin/owner 且尚未启用时显示,
带二次确认;启用后该按钮自动隐藏(hasTenantSpace),全员空间进切换器
验证(gateway+docker):member 启用 403 / owner 启用纳入全部现有成员(owner·member·viewer
按租户角色映射) / 新加租户成员自动纳入 / 幂等(再启用返回同一个不重复建) / viewer 在全员空间
入库 403(只读) / owner 建库 200;浏览器实机:启用后全员空间进切换器 + 启用按钮消失
至此 Space 三类型(个人/项目/全员)全部落地。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 13:55:29 +08:00
Blizzard
a8c0bb42a7
feat(space): 共享工作区增量3b —— KB 知识库按 Space 共享(存储 re-key)
...
把 3a 的 Space 作用域推到 KB 层:KB/Doc/DocLink owner→space_id,作用域键
owner/name→space_id/name,同空间成员共享知识库(检索/入库/文库/双链/图谱)。
后端:
- store: KB/Doc/DocLink 加 space_id,唯一索引 (owner,*)→(space_id,*),owner 降级创建人;
查询全改 space 作用域;SaveDoc/ListVault/GetDocByID/DeleteDocByID/ReplaceDocLinks/
ResolveInboundLinks/ListLinks 改 space;tenantIDForSpace 补异步入库租户
- MigrateKBSpaces 启动迁移(space_id 回填 + 唯一索引换新,同 Agent 顺序坑规避)
- scopedKB owner/name→space_id/name;IngestJob 契约加 SpaceID;enqueueIngest/runIngest
穿 space;MinIO 对象键改 space/kb/doc(避免跨空间同名撞键,老键不透明不迁)
- KB 写路由(create/ingest/ingest_file/note/delete)挂 RequireSpaceRole(member):viewer 只读
- 存储层重灌迁移端点 POST /admin/migrate-kb-storage(异步):为存量文档入队新 space
作用域的重灌作业(复用 JetStream 入库 worker 池),先删旧键;避免同步重嵌撑爆 HTTP 超时
桌面端:
- KbView 收 spaceId(变则重拉库)+spaceReadOnly(viewer 禁建库/入库/文件/笔记);VaultPanel 同
验证(gateway+mcp-go+Milvus/Neo4j/embedding 全栈):
- PG 迁移: 20/21 KB + 50/54 doc 回填 space_id(4 未迁=pre-多租户 owner='wt' 空租户遗留,
正确跳过),唯一索引 idx_kb_sn/idx_doc_skn 换新、旧索引删除
- KB 共享: member 见共享库 / viewer 建库·入库 403 / 切回个人空间隔离(看不到)
- 全向量链路: RagA 入库(真 dashscope embedding)→ RagB(空间member)检索命中 RagA 内容
- 存储重灌: 端点异步入队 49 作业(worker 池背压处理),重灌后老文档在新 space 键可检索
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 13:00:34 +08:00
Blizzard
addaa1b34f
feat(space): 共享工作区增量3a —— Agent 编排按 Space 共享(纯 PG 打样)
...
引入 Space 中间容器(租户>Space>成员),资源作用域从 owner 改为 space_id,
让"个人私有/项目临时组队/整租户共享"出自同一模型(设计见 SPACE_DESIGN.md)。
先在纯 PG 的 Agent 上打样,零存储风险,验证协作+RBAC+切换 UX。
后端:
- 新表 Space{tenant_id,name,kind,creator,archived} + SpaceMember{space_id,user_id,role}
(如 Tenant 般不 isTenantScoped);User.ActiveSpaceID;Agent 作用域 owner→space_id,
owner 降级为创建人(供 UI 显示 / 删他人鉴权)
- store/space.go:个人空间幂等/活跃空间解析/切换/列表/建/成员CRUD/归档
- 迁移顺序坑:结构体只放非唯一 index,MigrateAgentSpaces 回填 space_id 后再建唯一
索引 idx_agent_sn + DROP 旧 idx_agent_on(否则存量空 space_id 撞车);启动序4步幂等
- 中间件 SpaceContext(注入 space_id) + RequireSpaceRole(照 RequireTenantRole)
- handler/space.go 空间端点 + 路由;agent.go 改空间作用域(删/覆盖他人需 admin)
- 计费零改动(Space 与 ResolveBillingTenantID 正交)
桌面端:
- api.ts space 接口;顶栏 SpaceSwitcher(含新建项目空间);StudioView 随空间切换
重拉编排 + viewer 禁保存;Agent 列表显示创建人 + 按 mine 控删除
验证:中间件6门控单测 + DB迁移(13个人空间/9 Agent全re-key/索引换新) + 后端HTTP全
场景(member见他人编排/删他人403、viewer存403、非成员切空间400+隔离、owner删他人200)
+ 浏览器实机(切换器3空间/Studio空间编排随切换隔离刷新/创建人显示/console无错)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 10:51:33 +08:00
Blizzard
bf5be08e96
feat(rbac): 租户角色门控落地(极简版) —— viewer/billing_admin 只读不可提交任务
...
角色此前能分配但不生效(viewer 也能烧租户积分)。本次让角色真正挡动作:
- store.RoleRank 角色层级:owner(4)>admin(3)>member(2)>viewer/billing_admin(1),
未知/非成员=0;billing_admin 定位为「财务只读」不跑任务
- 可复用中间件 RequireTenantRole(db, minRole),抽 MemberRoleResolver 小接口便于单测
- POST /tasks 挂 ≥member 门控:唯一会烧租户积分的入口(KB 入库不计租户),挡住即够
- 桌面端 StudioView 收 readOnly:viewer 时「运行」禁用+只读提示(UX 兜底,真闸在后端)
- 测试:RoleRank 纯逻辑 + 中间件 7 门控 case(owner/admin/member 放行,
viewer/billing_admin/非成员 403,未登录 401)
实机验证(gateway+docker):viewer 提交 403 → 升 member 202 → 降回 viewer 403 →
owner 自租户 202(solo 用户不受影响),三态可逆、角色驱动、中间件先于 handler。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-13 09:49:24 +08:00
Blizzard
484cc18664
chore(dispatcher): 升级 eino v0.9.9 → v0.9.12
...
v0.9.9→v0.9.12 共 3 个提交,仅 1 个与我们相关:
- fix(compose): checkpoint/runnable 的 typed-nil 归一化 —— 直接加固我们用的
compose.Interrupt + checkpoint 恢复路径(HITL 审批 resume),修 typed-nil gob 序列化边界。被动获益,无需改代码。
- refactor(adk FailoverProxyModel) / fix(summarization):我们未用 eino ADK/摘要组件,无影响。
build + eino 包测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-09 17:18:57 +08:00
Blizzard
632057f29b
docs: 状态机 & agent loop 三层嵌套图(独立 SVG)
...
任务状态机(NATS→PG) → Eino compose.Graph DAG 调度 → react.Agent ReAct 循环。
自包含 SVG(内联样式),任意浏览器/编辑器可开。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-09 17:12:55 +08:00
Blizzard
1ee1e87371
fix: 状态/用量回写升级 JetStream 持久 —— 堵住漏账(core NATS fire-and-forget)
...
问题:usage(计费)/status 回写走 core NATS,网关离线/慢消费者/NATS 抖动期间
dispatcher 发的事件直接丢——任务照跑照烧 token,但这次计费凭空消失(漏账),且零重试零对账。
(对比:提交/审批/入库本就 JetStream 持久,唯独回写是 best-effort。)
修复(照 tasks/approvals 套路):
- 新增 JetStream 流 SUNDYNIX_USAGE(MaxAge 72h) / SUNDYNIX_STATUS(24h),捕获 usage.task/status.task。
- PublishUsage/PublishTaskStatus 改 js.Publish(同步等 stream ack);dispatcher+gateway 启动各自 ensure 流。
- ConsumeUsage/ConsumeTaskStatus 持久消费者 + 显式 ack:落库成功 Ack、失败 Nak 重投自愈、脏数据 Term。
- 幂等保证 at-least-once 安全:usage_event.task_id 唯一 + 门控;SaveUsageEvent 返回 inserted,
仅新插入才累计 Redis 日计数(非幂等旁路,防重投重复累加);UpdateTaskStatus 按 task_id 覆盖幂等。
live 验证(复现原漏账场景):提交任务→立刻杀网关→dispatcher 跑完把 usage 发进持久流
(网关离线,usage_event=0 但流积压 1 条=钱没丢)→重启网关→自动补消费:任务 done、
usage_event 补上、公司A 余额扣 0.098、消费者 num_pending/ack_pending 归零。旧设计下这笔会永久丢失。
eval 回写仍 core NATS(仅观测,低价值,暂不改)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-09 17:07:31 +08:00
Blizzard
248267984c
feat(desktop): 顶栏租户切换器(多租户)—— 切换活跃租户即换工作区+计费
...
用户属多个租户时,顶栏显示租户下拉(Building2 图标):切换即调 POST /me/tenant
换活跃租户,随后刷新余额芯片(显示新计费租户的可花余额)。仅 >1 租户时出现。
api 加 myTenants/switchTenant。
live 验证(preview):demoB(属公司A+公司B) 切换器显两租户;从公司A(共享,余额15.84)
切到公司B(个人,余额6.38)→ 后端 active 持久化、芯片随之更新为 6.4(橙,偏低)。tsc+48 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 11:02:36 +08:00
Blizzard
b388915fff
feat(admin): 租户页加「共享计费」开关(每租户)
...
TenantRow 带 shared_billing;选定租户成员区上方加共享计费开关(PUT
/admin/tenants/:id/shared-billing):开=成员消耗计本租户池、关=各计个人池。
live 验证:公司A 开关随真实状态回显为勾选。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:56:21 +08:00
Blizzard
b98cf3c718
feat(gateway): 多租户增量2 —— 租户切换 + 每租户共享计费开关
...
用户可切活跃租户,消耗按"计费租户"扣(数据仍落活跃租户/工作区):
- User.active_tenant_id(切换持久化)+ Tenant.shared_billing(每租户开关,默认关)。
- 中间件 ActiveTenantForUser:active_tenant_id 若有效(仍是成员)则用之,否则默认租户。
- 计费目标解析 ResolveBillingTenantID:本人是活跃租户 owner 或该租户 shared_billing 开
→ 记活跃租户;否则记本人个人租户(各付各的)。SubmitTask 的硬拦截 + 用量都按它走。
- 接口:GET /me/tenants、POST /me/tenant(切换)、PUT /admin/tenants/:id/shared-billing;
/tenants/current 改显"可花余额"(计费租户) + billing_shared 标记。
live 验证(demoB 是公司A 成员、个人租户=公司B):切到公司A 后——
shared OFF:任务落公司A 工作区、用量扣公司B(个人),公司A 不变;
shared ON:用量扣公司A(共享),公司B 不变;/tenants/current 余额随之在 6.48↔15.92 切换。全过。
前端切换器 + admin 开关见后续。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:52:17 +08:00
Blizzard
aa10037511
feat(admin): 「租户 & 用户」页接真实成员管理(删掉 mock)
...
此前整页是硬编码 mock(假租户/假用户/假 API key/假日预算)。重写为真数据:
- 左:租户目录(listTenants:名称/slug/plan/成员数/积分余额)+ 建租户表单
(名称/slug/可选 owner 邮箱)。
- 右:选定租户成员表(listMembers)——按邮箱加成员+选角色、行内改角色、移除;
owner 受保护(不可改角色/移除)。api 加 listTenants/createTenant/listMembers/
addMember/setMemberRole/removeMember。
live 验证(preview):14 个真实租户带成员数/余额;UI 加 demoB→Dexter「2 成员」、
成员表 owner 保护 + 成员可改角色/移除、移除后回 1 成员,全过;tsc + 41 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:20:11 +08:00
Blizzard
0e47a827c0
feat(gateway): 多成员租户·成员管理后端 —— 租户目录 + 成员增改删
...
真·多租户地基(此前只有单人默认租户 + admin 页是 mock):
- store:ListTenants(含成员数+余额) / MyTenants / ListMembers(join user) /
AddMemberByEmail(邮箱归一化小写、幂等 upsert、复活已移除、未注册报友好错) /
SetMemberRole(禁改 owner、禁直接授 owner) / RemoveMember(软移除、禁移除 owner) / ValidRole。
- admin 接口:GET/POST /admin/tenants、GET/POST /admin/tenants/:id/members、
PUT/DELETE /admin/tenants/:id/members/:uid。
live 验证:加 demoB 入公司A→member、列表、改角色→admin、移除 owner 被拒、移除 member,全过。
admin 前端接线 + 租户切换 见后续。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 10:13:58 +08:00
Blizzard
a878e2dc89
feat(desktop): 「用量」视图 —— 我的积分余额/消耗趋势/最近消耗(P3 续)
...
给终端用户一个自己的计量观测面(守边界:只读自己租户):
- 新增 UsageView(左导航 MANAGE 组「用量」,Coins 图标):余额 hero 卡
(硬拦截+余额≤0→红/偏低→橙)、区间 KPI(积分/Token/成本/运行数)、
积分消耗按天趋势条形图(近7/近30天可切)、最近消耗明细表(任务/模型/token/积分/成本)。
读 /api/v1/me/usage。
- 顶栏积分余额芯片改为可点,进「用量」页(onOpenUsage → setView)。
live 验证(preview):视图显示 公司A 余额 15.92 + KPI + 趋势bar(落在实际消耗日) +
最近消耗 3 行真实数据;tsc + 48 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:31:12 +08:00
Blizzard
f6ea03b34b
feat(gateway): GET /me/usage —— 用户自己租户的用量明细(余额+趋势+最近消耗)
...
面向用户口径(非 admin,受插件按请求 ctx 租户自动隔离):返回积分余额 +
credit_enforce + 按天消耗趋势 + 区间合计 + 最近 10 条消耗明细。复用
UsageTrend/TenantBalance,新增 RecentUsage(按 tenant 取近 N 条 usage_event)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:31:12 +08:00
Blizzard
511ae199a4
feat(desktop): 顶栏积分余额芯片 + 提交遇余额不足透出友好文案(P3 租户感知)
...
让计费对终端用户可见(守 desktop/admin 边界:只读自己租户,不碰 admin 配置):
- TopBar 加积分余额芯片(Coins 图标):常态显余额;硬拦截开+余额≤0 → 红「余额不足」;
偏低 → 橙。tooltip 显租户名 + 是否拦截。api.tenantCurrent() 读 /tenants/current。
- App 登录后拉租户上下文,每 20s 轮询 + 运行结束即刷新(余额跟手)。
- submitTask 遇 402 解析后端 error 文案透出(如「租户积分余额不足,请充值后再试」),
在运行面板报错处显示,用户知道为何被拦。
live 验证(preview):芯片显示 公司A 余额 15.9 ✓;enforce 开+余额0 时 /tenants/current
标 enforce+余额0(芯片转「余额不足」)、提交 402 带友好文案 ✓;tsc + 48 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:18:30 +08:00
Blizzard
d5a4670c2f
feat(gateway): /tenants/current 带上积分余额 + 硬拦截开关(桌面端租户感知用)
...
面向用户自己的租户(非 admin 口径):返回 credit_balance_micro + credit_enforce,
供桌面端顶栏显余额、并据 enforce 判断是否会因余额不足被拦。复用已有 GetTenant/CreditEnforceEnabled。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-08 09:18:30 +08:00
Blizzard
b46382817c
feat(admin): 计费页加「余额硬拦截」开关 + 各租户「充值」按钮
...
闭合积分环的配置/操作端,接到 P4 后端:
- 计费规则区:加「余额硬拦截」开关(credit_enforce),与汇率合成一个「保存规则」。
- 用量观测·租户排行:每行加「充值」按钮(正=充值、负=校正),调 /admin/credits/grant,
充值后即时刷新余额。
- api:getBillingConfig/saveBillingConfig 带 credit_enforce;新增 grantCredits。
live 验证(preview):开关渲染+可存;点公司A「充值 10」→余额 5.92→15.92、账本落对租户;
tsc + 41 vitest 全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:53:52 +08:00
Blizzard
93741a5504
feat(gateway): 闭合积分环 —— 充值/发放 + 提交前余额硬拦截(默认关)
...
让预付费积分成为闭环(发放→消耗→见底拦住):
- POST /admin/credits/grant:给租户充值/发放(正=grant,负=adjust 校正),
复用 store.GrantCredits(账本分录 + 物化余额,一事务)。
- 提交门控:credit_enforce 开启且租户余额≤0 → 拒绝新任务 402;默认关=软扣不拦。
开关入 billing-config(sundynix_setting KV,后台可切)。
- 修 bug:GrantCredits 是跨租户管理操作,须 store.WithoutTenant——否则 tenant 插件
会把账本分录的 tenant_id 覆盖成 admin 自己的租户(余额记目标、分录记 admin,破坏对账)。
live 验证:enforce 关→余额0可提交;开→余额0拒 402;充值后可提交、余额递减;
修复后 grant 分录落到目标租户、balance==SUM(ledger) 不变量成立。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:53:43 +08:00
Blizzard
11d321f218
feat(admin): 计费 & 用量合成一页 —— 顶部配规则、下方看结果(闭环)
...
按"配置和观测放一个页面"收口:删掉重复的「计价 & 预算」页(其预算区块是 mock),
把真·计价配置并入「计费 & 用量」,形成 规则→扣费→观测 一页闭环。
- 新组件 BillingRules:全局 token→积分汇率 + 每模型 单价/币种/积分权重(读写
/admin/pricing + /admin/billing-config),改完对后续任务实时生效。
- UsagePage:顶部「计费规则」(配置端) + 下方「用量观测」(按规则折算后的实际消耗)。
- 删 PricingPage + /pricing 路由;api 加 credit_weight / getBillingConfig / saveBillingConfig。
live 验证(preview):改汇率保存→持久化→新任务按新汇率扣→用量观测反映;
tsc + 41 vitest 全过;无 PricingPage 残引。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:26:33 +08:00
Blizzard
e2fc2d366c
feat(gateway): 计费规则可后台配置 —— token→积分汇率(DB) + 每模型积分权重
...
让「扣费按规则扣」的规则真正可后台热调(此前汇率是 env、积分权重无接口):
- sundynix_setting KV 表 + GetSetting/SetSetting;TokensPerCredit 改为
DB 设置优先 → env 回退 → 1000,SaveUsageEvent 按 ctx 读,改完即对后续任务生效。
- Pricing.credit_weight 纳入 UpsertPricing + ListPricing/SavePricing API。
- GET/PUT /admin/billing-config(token→积分汇率)。
live 验证:UI 存汇率=500 → billing-config/DB=500 → 新任务 credits_micro=214000
=107tok/500×1e6,规则→扣费联动精确成立。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 13:26:20 +08:00
Blizzard
54de95aa53
feat(admin): 用量 & 计费页 —— SaaS P2 计量观测面(读 /admin/usage)
...
管理端「分析」组新增页:全平台 / 单租户的 token · 积分 · 成本口径。
- api.adminUsage():拉 /admin/usage(含 trend / totals / tenants 排行 / 单租户余额);
微单位(×10⁻⁶)前端 ÷1e6 展示。
- UsagePage:租户下拉 + 近7/30天切换;KPI 卡(积分/Token/成本/余额或活跃租户);
积分消耗按天条形趋势(补零连续);全平台口径下各租户排行表(点名下钻单租户)。
- routes 注册 /usage(HashRouter)。
live 验证(preview):全平台两租户排行 + KPI + 趋势条渲染正确;点租户下钻→
单租户口径 + 当前余额卡;数值与后端自洽;无 console 错误;tsc + 41 vitest 全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 12:38:33 +08:00
Blizzard
bf75bb8a62
feat(gateway): SaaS P2 计量·增量3 —— GET /admin/usage 用量/积分/成本口径
...
系统级(store.WithoutTenant,跨租户)用量观测:
- 无 tenant:全平台按天 SUM 趋势 + 各租户用量排行(含当前余额)。
- 有 tenant:该租户按天趋势 + 当前积分余额。
- from/to(YYYYMMDD)缺省近 30 天。UsageTrend/UsageByTenant/TenantBalance store 方法。
live 验证:两口径数值自洽(balance=seed−credits、credits=tok×汇率、totals=trend之和)。
至此 P2 计量后端(事实源)齐:明细/账本/余额/rollup/查询。前端页 + P4 硬拦截/充值待需求拉动。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 11:42:21 +08:00
Blizzard
d058a63434
feat(gateway): SaaS P2 计量·增量2 —— 积分账本 + 物化余额 + 用量 rollup
...
- credit_ledger(append-only):grant/usage/adjust 分录,扣量/充值的事实源。
- tenant.credit_balance_micro:物化余额(= 账本之和),用量扣、充值增。
- usage_rollup:租户/天 upsert 累加(配额/账单读一行,不扫明细)。
- 消费 usage_event 一个事务内:落明细 → applyUsageCredit(账本负分录+扣余额)
→ upsertRollup(累加)。幂等锚在 usage_event.task_id 唯一:RowsAffected==0(重投)
则跳过账本/余额/rollup,绝不重复计费。软扣:余额可为负(不拦,硬闸留 P4)。
- GrantCredits 充值/发放 API(记 grant 分录+增余额;增量3 admin 接)。
live 验证:ledger 负分录(ref=task_id) / 余额递减 / rollup 两任务累加(count=2,tok/credits求和)
/ **balance==seed+SUM(ledger) 不变量成立**(物化余额 == 账本真值)。
增量3(GET /admin/usage 趋势+余额)待做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 11:26:27 +08:00
Blizzard
d032c198c8
feat: SaaS P2 计量·增量1 —— 用量按租户落持久明细 + 积分/成本折算
...
链路打通 tenant → 计费事实源:
- 契约:Task.Meta 加 MetaTenantID;UsageEvent 加 TenantID。
- 提交:网关 task.Meta[MetaTenantID]=tenantID(c);dispatcher emitUsage 带租户。
- 明细表 sundynix_usage_event(追加式,task_id 唯一→幂等防重投重复计费):
tenant/owner/model/tokens + credits_micro + cost_micros。
- 折算:credits=total_tok/TOKENS_PER_CREDIT×credit_weight(token 基准,设 1 即 token 直计);
cost 按 Pricing 折算;Pricing 加 credit_weight 列(每模型积分权重,缺省 1)。
模型名空则回退激活 chat 模型(近似,忽略 failover 备用模型,已在设计标注)。
- 网关 SubscribeUsage 折算落明细(保留 Redis 日计数作快速配额校验)。
live 验证:提交任务→一行 usage_event,tenant 匹配用户租户、
credits=89tok/1000×2×1e6=178000 微积分、cost=45/1000×1+44/1000×2=133000 微元 CNY,
折算数学与幂等键均正确。
设计见 SAAS_P2_DESIGN.md。增量2(credit_ledger 余额软扣 + rollup)待做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 10:54:29 +08:00
Blizzard
22183cac51
docs: DEPTH_ROADMAP 增量2b 打勾(PG 核心表租户隔离闭合)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 10:18:55 +08:00
Blizzard
766eaab7a3
feat(gateway): 多租户2b-B —— Eval/Doc/DocLink 补 tenant_id(异步路径按源复制)
...
闭合 PG 核心表 tenant_id 全链路。这些表走 background ctx(NATS 回写 / 异步入库),
插件无请求租户可读,故在写入时按关联源显式补:
- Eval:SaveEval 从对应 task 复制 owner+tenant(+isTenantScoped)。
- Doc:SaveDoc 按 owner 查默认租户补 tenant(tenantIDForOwner 助手);覆盖路径不抹空。
- DocLink:ReplaceDocLinks 同法按 owner 补。
- BackfillRowTenants 扩到 doc/doc_link/task(owner→租户)+ eval(task join)。
live 验证:新任务→eval 复制 owner/tenant 与提交者一致 ✓;C 读己方 eval 有数据、
D 读同一 task eval 被租户过滤为空 ✓;异步入库的 note→doc 自动补对 tenant ✓;
vault C 见己方、D 空 ✓。存量回填:doc 47/51(4 条 owner 无租户=孤儿)。
至此 PG 核心表(KB/Agent/Task/Eval/Doc/DocLink)租户隔离闭合;
Redis/MinIO/Milvus/Neo4j 前缀留待 P1 后续。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 10:17:45 +08:00
Blizzard
ee1e9cfdca
feat(gateway): 多租户2b-A —— Task 加 owner/tenant 隔离 + 系统旁路修 admin 全局口径
...
- store.WithoutTenant(ctx):显式跨租户旁路,插件即使 ctx 带租户也不过滤。
修复增量2 引入的回归:admin SystemCounts 数 KB 时被 admin 自己租户误过滤。
AdminOverview 改用旁路 ctx → 任务/KB/Doc/Eval 恢复全平台口径。
- Task 加 TenantID(插件自动填) + Owner(提交者 user.id) + isTenantScoped()。
SaveTask 记录 owner;RecentTasks/RecentRuns 按 owner+租户过滤"我的运行"。
RecentRuns 是 raw Table 查询绕过插件,手动补 owner+tenant WHERE。
live 验证:A 提交任务 → 行 owner/tenant 自动填对 ✓;A 的 /runs 只见己方、
B 空、legacy 无 owner 行被排除 ✓;admin/overview tasks_total=39/kb_count=21
= DB 全局真值(旁路生效,回归已修)✓。
Eval/Doc/DocLink 的 tenant 传播见 2b-B。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-07 09:18:15 +08:00
Blizzard
30d667954b
feat(gateway): 多租户增量2 —— gorm 租户插件按上下文自动隔离查询(SaaS P1)
...
统一强制、别靠人肉:受租户模型标记 isTenantScoped() 后,store/tenant_scope.go
的 gorm 回调按请求 ctx 自动给查询加 WHERE tenant_id、创建自动填 tenant_id。
- KB / Agent 加 TenantID 字段 + isTenantScoped() 标记
- middleware.TenantContext 把 tenant 注入 request context 供 store 插件读取
- ctx 无租户(系统/回填/未登录)不过滤,保留跨租户操作能力
- 启动 BackfillRowTenants 回填存量行 tenant_id=owner 默认租户(幂等)
live 验证:创建自动写 tenant_id ✓;同 owner 不同 tenant 的行被查询过滤 ✓。
Doc/DocLink(异步入库)、Task/Eval(无 owner)留待增量2b。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 17:58:58 +08:00
Blizzard
f7d12cbf66
docs: DEPTH_ROADMAP T4.A 多租户增量1 打勾(租户身份地基)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 17:32:11 +08:00
Blizzard
693a8f09e9
feat(gateway): 多租户地基·增量1 —— 租户身份/成员/上下文(T4.A/SaaS P1)
...
按 SAAS_DESIGN.md P1 第一刀(真·多租户 + 桌面端为主):先立租户身份,不动查询。
- store.Tenant / TenantMember 表 + AutoMigrate
- store: CreateTenant / AddMember(幂等) / DefaultTenantForUser / EnsureDefaultTenant(幂等) /
BackfillDefaultTenants / GetTenant / MemberRole
- 注册即建单人默认租户(owner);启动回填给存量用户补建(幂等)
- middleware.TenantContext(挂 Auth 后)解析当前租户→注入 tenant_id;handler.tenantID(c) 助手
- GET /api/v1/tenants/current 验证端点(租户上下文 + 角色)
- live:存量用户(回填 7 租户)/tenants/current 返回默认租户+owner;新注册自动建租户
下一步(增量2):核心表加 tenant_id + 统一 gorm scope 强制隔离 + 存量行回填 + 重写查询。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 17:31:09 +08:00
Blizzard
2f19e322e3
docs: 多租户 SaaS 化设计(收敛版 SAAS_DESIGN.md)
...
前提:真·多租户商业化 + 桌面端仍是主产品(非 Web-first)。
对 GPT 重构方案的收敛替代:保留正确诊断(tenant_id前置/usage先落事件/subject规范/migration),
砍掉过度设计(Web重写/7服务拆分/Temporal/Remote MCP/独立Tool Policy)。
核心:保架构、穿一条多租户+计费脊柱、按需求拉动分阶段(P1地基tenant_id→P2用量→P3客户端租户化→P4enforcement→P5支付+硬化)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 16:47:11 +08:00
Blizzard
53f7e172c3
fix: KB 级联删「事务化」—— 失败不再留不可删孤儿(T4.F)
...
原为 best-effort:三库删失败只 log、MinIO 删错误全吞、PG 照删 → 删一半失败即在
向量/全文/图谱/MinIO 留下「PG 无记录、连 file_id 都查不到」的不可删孤儿。
改为「类事务」(跨库 2PC 不可行,退而求其次:不留不可恢复孤儿 + 失败可见可重试):
- milvus.deleteByFile / bleve.deleteDoc / blob.Delete 改返回 error(原 void 吞错)
- rag.DeleteDoc 三库全试一遍(最大化清理)+ 聚合错误(原只回 Neo4j 的错);三库删幂等
- gateway KbDeleteDoc 失败闭合:先删依赖存储(三库→MinIO)、PG 最后删;
任一存储删失败 → 不删 PG、返 502「未删除请重试」(保留 file_id 供幂等重试)
- 语义翻转:从「总能从列表删掉但留孤儿」→「有孤儿风险就不删、报错可重试」
live:杀 mcp-go→删→502+文档保留;mcp-go 活→删→200+清空(清场僵尸进程后验证)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 16:32:25 +08:00
Blizzard
55d50417a9
feat: 模型健康/熔断态 surface 到管理端(T4.F 可观测)
...
failover/熔断的运行时态原来只在 dispatcher 日志、admin 看不到 —— 本次接到概览可见:
- harness: CircuitBreaker.Snapshot() 只读观测访问器(state + fails,不动状态机)
- llm: Pool.ModelHealth() 上报主备链每模型 {provider,model,role,state,fails};
buildWithFallbacks 把模型名↔breaker 配对(同包直接读 failoverModel.breakers);
newFailoverModel 改返回具体类型以便读 breakers
- dispatcher 心跳 payload 加 models[]
- gateway /admin/overview 独立超时 Ping dispatcher,合并进 models.health
- admin 概览「模型路由」新增「运行时链路态(实时)」:逐模型状态点
(🟢 在线/🔴 熔断中+失败数/🟡 半开探测/单点)
- 单测:Snapshot、Pool.ModelHealth(名字↔态配对/单点/空)
- live:配坏主→提交任务打熔断→概览显示 broken-demo「熔断中·失败3」,备用在线
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 12:00:13 +08:00
Blizzard
6a258fc884
docs: 架构设计文档 ARCHITECTURE_DESIGN.md(当前完整版)
...
描述当前架构设计:分层总览/各组件/NATS 总线(5类通信)/Eino 编排引擎/
LLM接入(failover+熔断)/数据存储/关键数据流/安全治理/可观测/前端/部署配置/
当前能力矩阵(已建·进行中·规划)/技术栈。与 ARCHITECTURE_REVIEW(评审) 互补。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-06 10:25:42 +08:00
Blizzard
da04beadf5
docs: 架构评审报告 ARCHITECTURE_REVIEW.md
...
基于通读代码 + 真机验证的全栈架构评审:系统全景/核心决策取舍/关键数据流/
优缺点/技术债「税单」映射路线图/上生产前必修清单/总体判断。
一手依据:控制面热切换 live、failover+熔断 live、双 NATS 脑裂实测、审计误报核实。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 16:35:21 +08:00
Blizzard
c060000f64
docs: DEPTH_ROADMAP 记入「模型健康/熔断态 surface 到管理端」(T4.F 可观测)
...
failover demo 暴露的真实缺口:熔断/failover 运行时态只在 dispatcher 日志、admin UI 不可见。
待补:dispatcher 心跳上报每模型 breaker 态 → gateway 聚合 → 概览/状态页展示。后期一起做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 14:29:24 +08:00
Blizzard
23b8fa5e8a
feat(gateway): 安全收口 —— CORS 生产收紧 + 限流按用户(T4.F)
...
- CORS:开发期缺省仍放行 *(便利);生产(APP_ENV=prod/GIN_MODE=release)未显式配
CORS_ALLOW_ORIGIN 则不发 ACAO 头(浏览器按同源拦截),逼运维显式配置允许的源
- 限流键改「已认证按 uid、未认证按 IP」:企业网多人共享出口 IP 不再互相拖累,
单用户换 IP 也绕不过;中间件顺序调整 Auth 前置于 RateLimit(否则取不到 uid)
- isProd() 判定与 middleware.RequireAdmin 同口径
- live 冒烟:登录/认证请求正常(重排未破链),dev CORS 仍 *
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:55:20 +08:00
Blizzard
6095bc71d2
refactor(mcp-go): 拆除 search.Hybrid 残骸(T4.F)
...
- search.Hybrid 空转(NewHybrid 返空、Query 返 nil TODO)、构造后存进 Gateway
却从不被调用(真实 RAG 走 rag.Engine)→ 纯误导性死重量
- 删 internal/search 包 + gateway.go 字段/构造参数 + main.go 接线
- build/vet 干净;wiki_search 不受影响(本就用 g.rag)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 13:39:14 +08:00
Blizzard
7c211719d2
fix(dispatcher): 熔断器接回 failover —— 挂掉的主模型跳过而非每次白试(T4.E 收官)
...
- failoverModel 加每模型熔断器(阈值3/冷却20s,比编排层更紧):
主模型持续失败达阈值 → 熔断 → 后续请求直接跳过主、直连备用(省掉每次白试主的失败往返);
冷却到点半开放行探测打回主,成功即自动恢复走主(靠熔断器半开机制,无需外部通知)
- 全部模型都熔断时强制试主兜底(编排层 o.breaker 兜"全挂")
- WithTools 重包共享同一批 breakers(状态不清零)——否则每次 rewrap 熔断失效,关键坑
- harness 加 NewCircuitBreakerWith(threshold,cooldown,halfOpenMax) 参数化构造
- Generate/Stream 用泛型 runFailover 共用选路循环(去重)
- 3 新单测:熔断跳过主/WithTools 共享熔断状态/冷却后半开恢复(全三态)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:49:54 +08:00
Blizzard
65e939889e
feat(gateway): DSL 拓扑校验 —— 前置拦截坏图(T4.E)
...
- ParseAndAssemble 加 validateTopology:拦重复/空节点 id + 悬挂边
(source/target 指向不存在节点),避免坏图进编排后被 compose 静默跳过
- 保守策略:空图 / 报告任务 {topic} / 非标准载荷一律宽松放过,不误伤合法提交
- 单测覆盖合法/重复id/空id/悬挂边×2/空端点/放过场景
- live:悬挂边 POST /tasks → 422(错误指明具体边与缺失节点);合法图 → 202
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:14:31 +08:00
Blizzard
4bec95fde1
fix(dispatcher): Branch else 兜底 + 选路留痕(T4.E)
...
- branchNode 识别 default/else 边:主选择(true/false 或边序)未命中任何下游时,
走显式 default 边而非悄悄落 END
- trace 明确区分「走 default / 未匹配收口结束(END) / 正常选路」,
杜绝静默 fall-through 被误当 bug
- compose 层原有 len(chosen)==0 → END 收口保留(无 default 时的安全兜底)
- 3 断言单测:default 命中 / 条件真走 true / 无 default 未命中仍返回空
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:08:05 +08:00
Blizzard
f9c849b14b
fix(dispatcher): 多智能体专家超时 —— 卡死专家不再拖垮协调(T4.E)
...
- specialistTool 加 timeout 字段(默认 specialistTimeout=3min,专家可多轮 react+工具故给宽)
- InvokableRun 用 WithTimeout 包裹专家派发;超时(DeadlineExceeded)作为"观察"
跳过该专家(err=nil),lead 据其余专家继续综合,不中断整个协调
- 2 单测:卡死专家 ~50ms 跳过并返回超时观察 / 正常专家不受影响
- timeout=0 时不包裹(向后兼容既有 specialistTool 构造)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 10:03:29 +08:00
Blizzard
de36ed4cb3
fix(dispatcher): Map 节点错误传播 —— 并行子项失败不再静默(T4.E)
...
- writeSection 返回 (body, error):仅真·LLM 调用失败返 err;预算触顶/模型未配置
是主动降级(可见降级正文,err=nil)不计失败
- writeSections 返回 ([]section, failed):失败项 Body 带可见「撰写失败」标记 + 汇总失败数
- mapNode:全部子项失败 → 置 b.fatalErr(任务判 failed 而非静默 done-空);
部分失败 → trace span + 流式 ⚠️ 告警
- report handleReport:部分章节失败时流式提示,不再当全成功
- 3 单测:全失败/部分精确计数(=1 非 all-or-nothing)/mapNode 置 fatalErr
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:56:26 +08:00
Blizzard
a830ae2a04
feat(admin): 审计 & 安全事件页(T4.B 收尾)
...
- 新增「审计 & 安全」页(/audit,运维组):接 /admin/audit + /admin/guardrail-events
- 安全事件:护栏拦截/灰区放行(kind 徽标 + 原因 + method/path + actor/ip/时间)
- 操作审计:变更操作(方法配色徽标 + 路径 + 状态码着色 + actor/ip/时间)
- 顶栏统计(操作留痕/护栏拦截/灰区) + 30s 自刷 + 手动刷新
- api.ts 增 listAudit / listGuardrailEvents + 类型
- 风格对齐重做后的服务状态页(中性克制)
- T4.B 整组完成(剩 HITL 审批明细可选小项)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:43:41 +08:00
Blizzard
9e43d07428
feat(gateway): 护栏拦截事件落库 + 安全事件流(T4.B)
...
- store.GuardrailEvent 表(sundynix_guardrail_event) + AppendGuardrailEvent/ListGuardrailEvents
- middleware.Guardrail(db):命中 blocked/suspect 时 best-effort 落库
(actor/kind/reason/signals/method/path/ip,独立超时 ctx)
- GET /api/v1/admin/guardrail-events:安全事件流(倒序,翻页)
- store.clampPage 抽出分页归一(audit/guardrail 共用)
- live:注入 "ignore all previous instructions" → 422 硬拦 + 事件留痕(kind=blocked)
- DEPTH_ROADMAP T4.B 护栏事件打勾
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:31:31 +08:00
Blizzard
16c67dcb4f
feat(gateway): 敏感操作审计日志(T4.B)
...
- store.AuditLog 表(sundynix_audit_log) + AppendAudit/ListAudit
- middleware.Audit(db):只审计变更类(POST/PUT/DELETE/PATCH),收尾 best-effort
落库(独立超时 ctx,失败静默不拖垮主流程);挂管理组 + prompt 激活/撤销 + HITL 审批
- GET /api/v1/admin/audit:倒序审计流(limit/offset 翻页)
- live:PUT pricing / POST prompts/deactivate 留痕(actor/path/status/ip),GET 不记
- DEPTH_ROADMAP T4.B:overview✅ + audit✅ 打勾
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 09:19:52 +08:00
Blizzard
30e80c0eed
feat(admin): 提示词控制面页 + 概览/状态页重做 + 接入 vitest
...
- 新增「提示词」页(/prompts):接 /api/v1/prompts,建版本/激活热下发/
对比激活版行级 diff/撤销,挂到配置组
- 概览页重做为系统控制塔:吃 admin/overview(全平台口径)——平台任务/
评测/用户规模/服务在线 + 模型路由态 + 提示词覆盖态 + 健康拓扑 + 30s自刷
- 服务状态页重做:中性克制风 + 请求链路做成深色实时数据管道(流动光点/
节点辉光) + MCP 工具改能力域配色小卡片
- 接入 vitest(此前零测试):41 单测(api 控制面/lib diff/路由派生)
- api.ts 增 statsOverview/adminOverview/prompt 控制面四接口 + groupPrompts
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 08:59:40 +08:00
Blizzard
e2104efc64
feat(gateway): admin/overview 系统级聚合接口 + Tier4 后端路线图
...
- GET /api/v1/admin/overview (RequireAdmin):全平台口径聚合——
用户/KB/文档总数 + 全局任务(今日/累计/趋势/终态) + 全局评测均分
+ 模型配置态(主/备/各kind数) + 提示词覆盖态 + 服务健康
- store.SystemCounts:全局 users/kb/doc 计数(区别 owner 隔离的个人口径)
- 区别桌面端个人 stats/overview:管理端概览改吃此接口(系统控制塔)
- DEPTH_ROADMAP 新增 Tier4 后端做实(A–F 六组,三路 Explore 审计产出)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-07-02 08:59:28 +08:00
Blizzard
cb7dd86121
docs: DEPTH_ROADMAP T2.3 ✅ + T2 深化 4/4 全清(prompt DB 控制面热切换,live)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:59:52 +08:00
Blizzard
5736ad145e
feat(prompts): v2 DB 控制面热切换 —— 版本留存 + 不重启即生效
...
在 v1(注册表+文件覆盖)上加 DB 管理层与热切换,镜像 model-config 控制面:
- store: sundynix_prompt 表(key/version/content/active) + ActivePrompts/ListPrompts/
CreateVersion/Activate/Deactivate
- 控制面: ServePrompts/RequestActivePrompts(+Retry)/PublishPromptsUpdated/SubscribePromptsUpdated;
prompts.ApplyOverrides 整体替换覆盖集(DB 激活集为权威)
- gateway API: GET/POST /api/v1/prompts、version/activate/deactivate;激活/撤销即广播
- dispatcher/mcp-go: 启动拉激活集 + 订阅热更新(不重启)
- live: 建版本→激活→mcp-go 图谱抽取 2→0→回滚 2(全程不重启);deactivate 回退代码默认;版本可回溯
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:59:52 +08:00
Blizzard
aa61590b6c
Merge remote-tracking branch 'origin/dev' into dev
2026-06-30 14:37:07 +08:00
Blizzard
16c9298cf8
docs: DEPTH_ROADMAP T2.3 prompt 版本化地基 ✅ (文件覆盖,live A/B),v2待
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:36:04 +08:00
Blizzard
9a3a816c80
feat(prompts): prompt 版本化地基 —— 注册表 + 运行期文件覆盖
...
把散落各服务的硬编码 system prompt 收口为受管注册表,不重编译即可改/回滚/对比:
- shared/prompts:内置默认(随代码) + 运行期覆盖(PROMPTS_FILE) + Get/Keys,并发安全,含单测
- 接入 9 处:mcp-go(graph.extract);dispatcher(eval.quality/eval.refine/guard.jailbreak/
coordinator.lead/memory.extract,按引用登记默认、无文本重复)
- main 启动调 LoadFile 加载 PROMPTS_FILE 覆盖
- live A/B:覆盖 graph.extract → 图谱抽取 2 条→0 条、向量仍正常(覆盖生效、管道未坏)
- v2(DB 控制面热切换 + 灰度)留后续
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 14:36:04 +08:00
Blizzard
865a47eeea
docs: DEPTH_ROADMAP T2.2 ✅ 检索质量评测(混合稳健兜底,量化在案)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:56:39 +08:00
Blizzard
a17e25b6ba
feat(rag): 检索质量评测 —— 单路检索能力 + recall@k/MRR 评测台
...
- 引擎抽 searchPaths(三路召回) + SearchByMode(vector/fulltext/graph/hybrid,
纯检索不 rerank,公平对比);kb_search 加 mode 参数(空=生产含rerank),
gateway KbSearch 透传 mode
- scripts/rageval.py:标注语料+查询 → 四模式 recall@k/MRR 对比表(可复用)
- live 量化:纯语义改写让全文0.88/图谱0.75 漏召回,混合 1.00 兜回,
混合=各路上界的稳健组合
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:56:39 +08:00
Blizzard
4f52b30f95
docs: DEPTH_ROADMAP 新增 T2.4 大文件RAG生产化(三段/P0-P2 全勾,均 live 验证)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:38:03 +08:00
Blizzard
9a5ed75c05
feat(gateway): 入库队列接线 + 正文一律MinIO + file_id级联删端点
...
- 入库改走 JetStream 队列:claim-check 暂存 MinIO → 发作业 → 立即返 job_id;
worker 池(StartIngestWorkers)有界并发消费,崩溃重投续跑(幂等),优雅 drain
- 存储:正文一律落 MinIO(去 <8000字内联PG 阈值,仅 MinIO 挂时回退兜底);
sundynix_doc 删死字段 MD5
- 下游键传稳定 file_id(非展示名);新增 DELETE /api/v1/kb/doc 级联删
(三库 + MinIO 原文 + PG 元数据/双链,owner 隔离)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:38:03 +08:00
Blizzard
79e834e8e9
feat(rag): 大文件 RAG 做深 —— 并发入库/图谱窗口化 + Bleve落盘 + file_id治理
...
几十万字文件从"广而浅"到准生产级:
- 向量化串行→并发分批保序(embedAll);几十万字上千块快数倍
- 图谱整篇喂LLM(爆上下文只抽开头)→窗口化并发抽(extractGraphWindowed),
全覆盖;窗口/封顶/并发 env 可配;图谱可单配便宜模型(GRAPH_CHAT_*,未配回退主chat)
- Bleve 内存索引(重启即丢、三路退两路)→落盘 scorch(env BLEVE_PATH,失败退内存兜底)
- 下游键改稳定 file_id:Neo4j 关系打 file_id(实体仍 kb+name 共享);
新增 kb_delete 工具 + Engine.DeleteDoc 级联删 Milvus/Bleve/Neo4j
- 单测:窗口化/去重/env可配/落盘持久/图谱模型回退
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:37:45 +08:00
Blizzard
6f16062dfc
feat(bus): 入库 JetStream 持久工作队列 —— 崩溃重投/背压/幂等
...
把入库从网关裸 goroutine 升级为和任务流同级的 JetStream 持久工作队列:
- contract/ingest.go: IngestJob(claim-check 引用) + 流/消费者常量
- bus.go: EnsureIngestStream / PublishIngestJob / ConsumeIngestJobs
(durable consumer + MaxAckPending 背压 + AckWait 可配(env) + MaxDeliver 4
毒消息兜底 + lastAttempt 终态收尾 + 优雅 drain)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 13:37:24 +08:00
Blizzard
ce70388e52
docs: DEPTH_ROADMAP T2.2 三路做实(中文全文/图谱修活),质量评测待补
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 10:00:59 +08:00
Blizzard
a4852c3aef
fix(rag): T2.2 把"三路混合"做实 —— 中文全文/图谱两路从 0 贡献修活
...
基建拉起后实测发现"三路混合检索"对中文其实只有向量路在干活:
`hybrid: 向量=1 全文=0 图谱=0` —— 全文、图谱两路恒 0 贡献(典型"广而浅")。两个真因:
1. Bleve 全文:默认标准分词器不切中文 → 整段当一个 token → 中文查询永远 0 命中。
修:text 字段用 cjk 分词器(bigram),kb/doc 用 keyword(保 TermQuery 精确过滤)。
2. 图谱检索:`$q CONTAINS a.name` 要求实体名是查询子串,而 LLM 把实体抽成"星云一号卫星"
(带后缀),查询说"星云一号"→ CONTAINS 失败 → 0 命中。
修:加查询字符 n-gram(2..8)双向子串匹配,`星云一号` 即可命中 `星云一号卫星`。
live 验证(同一查询):修复前 `向量=1 全文=0 图谱=0` → 修复后 `向量=1 全文=1 图谱=9`,
三路全贡献。中文混合检索从 1/3 变真 3/3。
测试:Bleve 中文检索命中 + queryNgrams 子串/长度边界(CI 安全,无需基建)。
DEPTH_ROADMAP T2.2:三路做实✅ ;检索质量度量(离线评测集 recall@k/MRR)待补。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 10:00:25 +08:00
Blizzard
5d090304e5
docs: DEPTH_ROADMAP T2.3 输出缓存(半做,诚实标注命中率有限)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 09:40:45 +08:00
Blizzard
e80e481f9f
feat(llm): T2.3 输出缓存 —— 同输入命中跳过 LLM 调用(省成本+提速)
...
在 eino model 层加 cachingModel 装饰器,包在 failover 链最外层:命中直接跳过整条链。
- 只缓存 Generate(非流式):Stream 是用户可见的创作型输出、重复率低且回放复杂,透传不缓存。
- 键 = 模型名 + 绑定工具哈希 + 消息内容哈希,不同模型/工具集/输入互不串味。
- 默认 TTL 60s(env LLM_CACHE_TTL_S,0=关):只覆盖短窗内的重试/双发/重复点击 —— 这类
几乎一定同一意图,命中省成本+提速;又短到不让助手对同一问题长期"复读"。容量上限
LLM_CACHE_MAX(512) 满则随机淘汰。换激活模型 → 键含模型名自然失效。
测试:命中跳底层/不同输入不串味/TTL 过期重调/流式不缓存/工具集不同键/TTL=0 禁用。
诚实说明:本平台以创作型流式为主、且 Generate 输入(专家简报/评测)每次都变,**真实命中率
天然偏低**——主要吃"短窗内逐字相同"的重试/双发。机制正确、零风险(可 env 关),但不是大
成本杠杆;更大的省钱项(语义缓存/确定性工具结果缓存)是后续。Prompt 版本管理(T2.3 另一半)
未做。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 09:40:20 +08:00
Blizzard
3519570178
docs: DEPTH_ROADMAP T2.1 ✅ (模型 Fallback)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 09:26:47 +08:00
Blizzard
ecf4a80466
feat(llm): T2.1 模型路由 + Fallback —— 单 provider 抖动不再整体宕
...
现状:单 provider,一家 API 抖动/挂掉全平台不可用。加主备 failover:主模型调用失败
自动按序切备用,compose/ReAct/Chat 全路径透明白嫖。
- llm/failover.go: failoverModel 把多个 ToolCallingChatModel 串成主备链,按序调用、
遇错切下一个;它本身是 model.ToolCallingChatModel 故全路径透明。调用方主动取消
(ctx.Err()!=nil) 不切;模型自身请求超时走内部 ctx、不污染父 ctx 故仍 failover。
局限(v1):Stream 仅建流同步报错时切(已回流 token 的中途失败不切)。
- llm/pool.go: SetConfig 用激活配置(含 Fallbacks)重建——主+可用备用串成 failover 链,
无备用则直接用主;备用单个构建失败跳过不影响主链。
- contract.ModelConfig: 加 Fallbacks 字段(骑在主配置里下发,不改任何 bus/ServeConfig 签名)。
- gateway store.ActiveConfig: chat 把"其它已登记 chat 模型"按序填进 Fallbacks;
provide(main) + broadcast(admin) 共用 → 注册多个 chat 模型即自动成主备。
- bus.decryptConfig: 备用模型的 api_key(密文)一并解密。
测试:failover 单测(主可用不调备/主挂切备/全挂报错/取消不切/Stream 切备/WithTools 链)。
live 验证:active=死 ollama 主 + deepseek 备 → 任务连主拒连→自动切 deepseek→4s 完成。
DEPTH_ROADMAP T2.1(admin 注册多模型即主备,无需新 UI)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 09:26:18 +08:00
Blizzard
ef6f525a74
refactor(dispatcher): T1.1 退役 graph.go —— 编排引擎收成单一 compose
...
compose 已默认数天、HITL/多智能体/评测全在其上真实跑过,soak 充分。删掉自研拓扑
解释器这第二套引擎,消灭"双实现 drift"税:
- 删 runGraph(graph.go 的自研解释器)+ composeEnabled/EINO_COMPOSE 逃生舱开关
+ runConversation(仅 runGraph 用的死代码)。
- executeGraph 直接走 runComposeGraph;compose 编译失败兜底改单轮对话(不再回退
graph.go);清掉仅 runGraph 用的 import(otel attribute/trace/otelx)。
- 保留 board / 各节点执行器(retriever/tool/agent/branch/approval/map/render/aggregate)
/ 工具函数 —— 它们是 compose 各节点 lambda 复用的,非 graph.go 专属。
- 测试:8 处 runGraph→runComposeGraph;等价测试(对照两引擎)转为 compose 正确性测试;
runConversation 的开关测试转为「无 ChatModel 降级 runAgent」。
go test ./... 全绿 + vet 干净;冒烟 简单 agent/分支图 跑通。此后每个编排改动不再两边
对齐,成本减半。DEPTH_ROADMAP T1.1 ✅ 。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-30 08:53:02 +08:00
Blizzard
835a6d7432
fix(harness): 评测不再误判长答案「截断」—— 评审截断从 1500 抬到 6000 + 明确标注
...
T0.1 校准后 judge 变严,但被评测自己的截断坑了:judge 评分前把模型回答截到 1500 字
(evalTruncate(output,1500)),而报告/多智能体综合等长答案普遍 2000+ 字 → judge 只看到
前半截、误判「回答不完整/截断」,给出假阴性 warn。多智能体一跑就暴露(2278 字答案被
误判 0.55 warn「截断」)。
- evalReviewOutput: 评审长度上限 6000(覆盖绝大多数长答案);确被截断时明确标注
「已被评审系统截断,勿因结尾不完整扣分」,杜绝 judge 因自身截断而扣分。
- llmJudge / llmJudgeGrounded 改用 evalReviewOutput 喂模型回答。
live 验证:同款 2 专家协调任务,修复前 0.55 warn「截断」→ 修复后 0.85 ok,且 judge
挑出真实缺陷(推荐逻辑前后矛盾)而非假截断。既公平又严格。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 17:35:13 +08:00
Blizzard
cb6eec5614
feat(desktop): T0.2 多智能体进 Studio —— coordinator 节点 + 专家卡片 + 派发可观测
...
把已通的多智能体后端(MULTI_AGENT.md)接上 UI,用户可在画布拖出协调者图。
- nodeCatalog: 新增 `coordinator`「多智能体协调」节点(indigo) + 新字段类型
`agentList` + Specialist 接口({name,use,system,tools}),与后端 parseSpecialists 对齐。
- Inspector: AgentListField —— 可增删的子智能体卡片(名/用途/系统提示词/工具逗号分隔)。
- dsl 校验: agentList 需 ≥1 个有名字的专家、名字不重复。
- RunsView: 「工具调用」面板纳入专家派发(kind=agent),改名「工具/专家」,专家项
用 Users 图标 + indigo「专家」徽标区分(此前只筛 kind=tool,漏掉多智能体派发)。
- 导出: agents 数组经 exportDsl 原样透传进 DSL → 后端 parseSpecialists 直接消费。
tsc + vitest(19) 绿;UI 同款结构 DSL 后端可跑(协调链路 live 已验)。
DEPTH_ROADMAP T0.2 ✅ ,T0 激活 2/2 完成。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 17:14:14 +08:00
Blizzard
158fe094ab
fix(hitl): 审批复盘轨迹补全 —— 中断不关 exec 流,resume 事件续录
...
现象:已完成的审批任务,复盘轨迹停在「已中断等待审批」、审批节点永远转圈,看着
像卡住(实际任务已 done、有输出)。
根因:任务中断时 Handle 的 defer tr.done() 给 exec 流发了 CompleteExec → 网关 exec
录制器关闭。resume 是另一次独立调用,其 exec 事件(审批通过/拒绝、续跑节点)发到
同一主题时录制器已关 → 没录进去。(token 流中断时特意没关,所以输出录到了;exec
流却被关了,不对称。)
- exec.go: execTracer 加 suspended 标志,done() 挂起时不关流。
- orchestrator.go: 中断分支置 tr.suspended=true(与 token 流一致)。
- ExecTrace.tsx: 前端兜底——运行已完成(phase done)时把悬挂的 waiting/running 节点
收敛为 done,修旧任务(已 done、轨迹缺 resume 事件)的转圈假象。
live 验证:审批任务批准后复盘轨迹完整呈现 approval await→end「批准:放行」→
agent start→推理→end,审批节点不再转圈。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 16:57:11 +08:00
Blizzard
fcefecd10d
fix(desktop): 轮询命中终态时同步 phase —— 运行按钮不再永卡「运行中」
...
attachRun 的状态轮询此前只更新 lifecycle、不动 phase。token 流没收到 done 的场景
(如恢复的在途任务、或任务被外部置终态)→ phase 永卡 streaming → Studio 运行按钮
永远禁用「运行中…」,看似不能跑新编排。
修复:轮询命中终态(done/failed/timeout/rejected)时把 phase 也置 done/error,
释放运行按钮。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 16:48:50 +08:00
Blizzard
e8dc2e02c3
fix(desktop): 审批条提交后不再永卡「提交中」+ 按 taskId 重挂载
...
复现:登录恢复待审任务时若捞到一个无法续跑的任务(如早期 KV 冒号 bug 留下的无
resume 记录的孤儿),点批准 → 决定发出但消费者找不到记录 → 任务永远 waiting →
审批条 busy 状态成功后从不复位 → 永卡「提交中…」spinner,看似整个应用卡死。
- decide 成功后置 submitted 并在 finally 复位 busy(此前只在 catch 复位)。
- submitted 时显示「决定已发出,等待续跑…(若长时间无响应,该任务可能已失效)」
而非停在 spinner —— 诚实反映状态,不误导成 hang。
- Bar 加 key={taskId}:换审批任务时重挂载,避免上一个的 submitted 残留。
(孤儿任务本身已在 PG 标记 failed 清理;冒号 bug 早已修复,不再产生新孤儿。)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 16:40:07 +08:00
Blizzard
5fb6e3ffa8
fix(harness): T0.1 校准评测裁判 —— 让恒温器真触发(此前全 1.00 空转)
...
诊断:旧评测 LLM judge 恒给 0.94/1.00、从不判 poor → 低分自动纠偏闭环几乎从没
启动。根因两层:
1. 提示词软:只说"严格"但无评分基准、不强制挑毛病 → 模型恒锚定 4–5。
2. 数学更致命:归一 score/5 下限 0.2,叠加无来源 Overall=0.4*rule+0.6*s 的
0.4*rule≈0.4 底 → Overall 恒 ≥0.52、poor(<0.5)对"流畅但跑题/错误"永不可达。
修复:
- judge 提示词改对抗性+rubric:默认怀疑、先点缺陷再打分、给死 1–5 评分基准、
要求用满区间;grounded judge 忠实度按编造说法递减(一处编造≤2)。
- 归一 score/5 → normJudge=(v-1)/4(1→0),让低质能压到 poor 触发纠偏。
- 测试:normJudge 区间 + 流畅跑题(judge=1)可达 poor + 好坏区分度;更新两处旧
断言(4→0.75, 2→0.25)。
live 验证(deepseek):跑题→0.40 poor→自动纠偏(0.40→0.55);截断→0.55 warn;
好答案→1.00 ok。校准前三者全 1.00。评测+纠偏的投入由"摆设"变"在用"。
DEPTH_ROADMAP T0.1 ✅ 。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 16:23:30 +08:00
Blizzard
cc1a2c39aa
docs: 深度路线图 DEPTH_ROADMAP.md —— 现有广度下做深度的优先级清单
...
诊断深度不足分四类:A 建了没激活(评测裁判恒 1.00、多智能体无 UI)、B 重复税
(双引擎、前端测试薄)、C AI 核心还浅(单模型无 fallback、RAG 部分降级/桩)、
D 生产硬化(部署深度,无真实流量前暂缓)。
按"杠杆÷工作量"排:T0 激活(校准评测/多智能体进 Studio) → T1 收口(退役
graph.go/前端补齐) → T2 深化(模型 fallback/RAG/prompt 版本+缓存) → T3 硬化⏸。
完成一项勾一项。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 16:14:09 +08:00
Blizzard
66caeef35c
feat(agent): 多智能体协同 v1 —— coordinator 节点(Eino×Anthropic 融合)
...
LLM 自主在 agent 间路由/委派:orchestrator=ReAct(Eino 出机器),编排认知按 Anthropic
orchestrator-worker 配方(出脑子)。专家=包成工具的子 agent(agent-as-tool),lead 给
每个专家写定制简报(brief)后并行派发、综合。方案见 MULTI_AGENT.md。
为什么 agent-as-tool 而非 Eino host:host 的 specialist 拿原始输入(preHandler
return state.msgs),传不了 lead 写的定制简报,而定制简报正是 Anthropic 多智能体的
精髓。agent-as-tool 让 orchestrator 自己 emit 工具调用、参数 brief 即简报。
= OpenAI agent.as_tool() / Anthropic 研究系统的 orchestrator-worker。
- coordinator.go: specialistTool(react.Agent/ChatModel 包成 InvokableTool,入参 brief,
精炼返回) + parseSpecialists/buildSpecialists(带工具→react,不带→ChatModel,MCP 工具
按 spec.tools 过滤) + runCoordinator(lead 提示词=Anthropic 配方) + leadOrchestratorPrompt。
- 双路接入 execDSLNode(compose)+ runGraph(graph.go)的 case coordinator。
- 护栏:禁套娃(专家是内联叶子)/ MaxStep / 专家 I/O 计入共享 Budget / 降级(无
ToolCallingModel 或 0 专家 → runAgent)。
- streamAgentReply:抽出 runReactAgent 与 runCoordinator 共用的流式回流尾段。
- 复用即得:evaluator-optimizer=harness 低分纠偏;成本天花板=预算护栏;上下文隔离=
专家独立 react.Agent;观测=每次派发落 agent 轨迹。
测试:parseSpecialists / agent-as-tool 包装(brief 透传+精炼返回+失败作观察) / 降级。
live 验证(真 deepseek):两专家**并行派发**、lead 给各自写**不同定制简报**、最终
**综合**(非拼接)成稿,评测 1.00。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 15:37:23 +08:00
Blizzard
49d19d9b59
fix(desktop): 刷新/重开后恢复在途待审任务 —— HITL 审批条可跨会话重现
...
HITL 持久化中断后审批可跨重启、等数小时,但前端 ApprovalBar 只绑定内存里的
live run:用户一刷新页面/重开 app,run 清空 → 审批条消失 → 那个 waiting 任务
再也点不到批准(历史页只读回放、无审批入口)。这是 durable 模型下的真实 UX 洞。
- App: 抽出 attachRun(状态轮询 + exec 流 + token 流),新发起与恢复共用。
- 登录后探测 waiting 任务并 attachRun 挂回 → 全局审批条重现、可批准/拒绝,
续跑 token 经重订阅的流回显(网关从 Redis 回放 exec 补回审批摘要)。
- 坑:StrictMode(dev)双调用 effect,原 cancelled 守卫会把首次 async 的恢复误吞
(restoredRef 已保证只跑一次,App 是根组件不卸载)→ 去掉 cancelled。
真实 UI 验证(preview 驱动 :5173):提交 HITL 任务→waiting→刷新页面→审批条
自动重现→点批准→从 checkpoint 续跑→done,真实出稿 + 评测 1.00。tsc + vitest 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 14:50:47 +08:00
Blizzard
0378a770ca
fix(hitl): resume 记录 KV 键不可含冒号(NATS: invalid key)
...
live NATS 联调发现:resume 记录键用 "pending:"+taskID,冒号是 NATS JetStream KV
非法字符(仅允许 [-/_=.a-zA-Z0-9])→ 中断时 persistResume 的 Put 静默失败、决定
到达时 loadResume 报 "nats: invalid key",任务永卡 waiting、无法恢复。内存桩接受
任意键,故单测漏过——正是只有 live NATS 才暴露的那类。
- pendingKey: "pending:"+id → "pending_"+id(合法键)。
- persistResume: Put 失败改 log.Printf 大声告警(不止 exec 轨迹),关键失败可见。
- 回归测试 TestPendingKeyIsNATSValid:直接钉键形匹配 NATS KV 字符集,绕开内存桩盲区。
live 验证(devnats + 真链路):提交 HITL 任务→waiting→杀 dispatcher→离线批准→
重启→1s waiting→2s running→3s done,deepseek 真实出稿。证明 checkpoint 抗重启 +
决定经 JetStream 抗离线 + 断点恢复。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 14:25:47 +08:00
Blizzard
515cf7f87a
feat(hitl): 增量3b —— 持久决定投递 + 生产激活(HITL 中断/恢复上线)
...
把中断/恢复模型在 dispatcher 接线启用,并让审批决定持久化抗离线。至此 HITL
从「阻塞 goroutine 等 5min、core NATS 非持久、不抗重启」升级为「持久化中断 +
决定持久投递 + 从 checkpoint 恢复续跑」。
- contract: 新增审批决定流 StreamApprovals(SUNDYNIX_APPROVALS)/通配
SubjectApprovalAll/消费者 ConsumerApprovals + checkpoint 桶 BucketCheckpoints。
- bus: EnsureApprovalStream(JetStream 流持久捕获 sundynix.approval.>,MaxAge 24h;
gateway 现有 nc.Publish 的决定被本流自动捕获,无需改 gateway)+ ConsumeApprovals
(持久消费者,队列组多副本安全)。
- orchestrator: HandleApprovalDecision(据 task_id 取 resume 记录续跑;无记录则忽略,
兼容阻塞态任务的决定 + 决定重投幂等)+ finishResumed(收尾对齐 Handle 尾段:
中断/拒绝/预算/失败/成功+评测落历史)。
- main: 开 checkpoint 存储 + 审批流 + 起决定消费者 → SetCheckpoints 启用中断模型;
任一步失败优雅降级回阻塞模型;停机 drain 在途 resume。
决定经 JetStream 持久:dispatcher 在决定发出时离线,重连后仍消费到并续跑;任一
dispatcher 副本都能据共享 KV 的 checkpoint+记录恢复(HA)。
测试:决定驱动恢复(续跑下游+判 done+清记录)、无记录忽略(幂等/兼容)。
全模块 go build + go test 绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 13:08:41 +08:00
Blizzard
368938ce89
feat(hitl): 增量3a —— resume 重入闭环(从 checkpoint 续跑审批)
...
把 HITL 的恢复半边补上:人工决定到达后从 checkpoint 重入图、喂给审批节点续跑。
至此中断→恢复全闭环在 compose 路径打通(in-process 端到端测试钉死)。
- runComposeGraph 重构为统一入口 execComposeGraph(rc):rc==nil 全新执行、rc!=nil
从 checkpoint resume,两路共用建图/编译,仅三处分叉——①记忆注入仅 fresh(resume
时黑板由 checkpoint 还原,重注入会覆盖已积累态);②Invoke ctx(resume 经
ResumeWithData 注入决定);③终态黑板来源。
- live 捕获:resume 时 compose 用 checkpoint 还原的实例作 local state(非闭包 b),
故节点 ProcessState 内捕获 live 指针,Invoke 后据此读终态(fresh 仍读 b)。
- resume 记录:中断时把 {interruptID, Task} 落 KV(pending:task_id),供决定在另一
goroutine/重启进程独立重建任务并续跑;终态清记录 + checkpoint(幂等)。
- ResumeApproval(t, dec) 入口:载记录定位中断点 → execComposeGraph resume。续跑
语义同 fresh:再遇审批→errInterrupted、批准跑完→稿+refs、拒绝→errRejected。
测试:批准(黑板无损还原 + 下游执行 + 状态拨回 running + 清记录/checkpoint)、
拒绝(errRejected + 拒绝语 + 下游不跑 + 清 checkpoint)。这条用例也透过 eino 真实
checkpoint 路径端到端验证了 board 序列化(2a)。全量 go test ./... 绿。
下一步 3b:审批决定改 JetStream 持久投递 + orchestrator 持久消费者触发 ResumeApproval
+ main 接 Bus.Checkpoints 打开开关。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 12:57:07 +08:00
Blizzard
bc7600625d
feat(hitl): 增量2b —— 审批节点改 compose.Interrupt + orchestrator 识别中断
...
接了 checkpoint 后端时,审批节点从「阻塞 goroutine 等 5min」改为持久化中断:
首次执行发待审 + 置 waiting + compose.Interrupt → compose 把整图状态(含 board)
落进 checkpoint store 并返回中断错误 → Handle 释放 goroutine、任务停在 waiting,
不收尾不评测不判 done。抗 dispatcher 重启。
- orchestrator: 新增 errInterrupted 哨兵 + checkpoints 字段 + SetCheckpoints
setter(沿用 guard/usageSink 的 setter 注入,不动构造签名);Handle 识别
errInterrupted → 释放 goroutine、保留 waiting、SSE 流不关。
- compose_compiler: 编译挂 WithCheckPointStore + WithGraphName("root"),Invoke
带 WithCheckPointID(task_id);审批节点接 checkpoint 时改走专用
approvalInterruptLambda(须把中断错误作节点返回值上抛,泛型 lambda 会吞掉);
ExtractInterruptInfo 识别中断 → 上抛 errInterrupted。
- graph.go: 抽出 approvalSummary / applyApprovalDecision,阻塞式与中断式审批共用,
杜绝两路文案/语义漂移。
双路径并存:未接 checkpoint 后端(store=nil)维持阻塞模型,行为不变;main 暂不
接,生产保持阻塞,待增量3 resume 闭环补齐再打开(建在 flag 后)。
测试:中断半边端到端——errInterrupted + 置 waiting + checkpoint 落 KV(key=task_id)
+ 下游 agent 不执行。既有阻塞式审批/等价测试全绿。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 12:46:48 +08:00
Blizzard
5a1a994cc2
feat(hitl): 增量2a —— board 可序列化(compose checkpoint 的状态载体)
...
compose checkpoint 把图执行态(含 GenLocalState 的 board)序列化进 store,但
Eino 序列化器只认导出字段,而 board 字段全未导出 → 直接持久化会落成空、resume
丢全部状态。Eino 对同时实现 json.Marshaler+Unmarshaler 的类型改走自定义 JSON
(internal/serialization checkMarshaler),故给 *board 实现一对 JSON 方法映射到
导出 DTO,无需把 board 字段全导出(牵连几十处调用点)。
- board_serde.go: *board 的 MarshalJSON/UnmarshalJSON ↔ boardSnapshot;丢弃
fatalErr(transient error,中断点必为 nil);schema.RegisterName[*board] 注册
类型名供 checkpoint 的 State(any) 还原。
- 测试: 快照往返无损 + 编译期断言实现 json.Marshaler/Unmarshaler + fatalErr
不被持久化。
零行为变更(纯新增)。go test ./... 全绿。
下一步 2b:审批节点 compose.Interrupt + 编译挂 checkpoint store + orchestrator
识别 InterruptInfo 置 waiting 并释放 goroutine。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 12:38:23 +08:00
Blizzard
f3bf42c432
feat(hitl): 增量1 —— JetStream KV checkpoint store(持久化中断地基)
...
HITL 持久化中断/恢复的地基:compose checkpoint 需要一个可持久化、抗重启的
存储后端。dispatcher 是"只说 NATS、无 DB"的纯净设计,故复用既有 JetStream
(bus.js)开 KV 桶,不引 Redis、不破坏架构原则。
- shared/bus: 新增 KVHandle + Bus.Checkpoints(bucket, ttl)。薄封装把 NATS
细节(ErrKeyNotFound→ok=false、Delete 幂等)挡在 bus 内,对外是朴素
Get/Put/Delete;bus 无需反向依赖 eino。File 存储 + 桶级 TTL 兜底清理。
- dispatcher/eino: checkpointStore 把 CheckpointKV 适配成 compose.CheckPointStore
(Get/Set + 可选 Delete)。CheckpointKV 是最小接口,bus.KVHandle 结构化满足。
- 测试: 内存桩往返(Set→Get→Delete→miss)+ 编译期契约断言
`var _ CheckpointKV = (*bus.KVHandle)(nil)` 钉死 bus↔eino 隐式契约。
零爆炸半径(纯新增)。go test ./... 全绿。
下一步增量2:审批节点改 compose.Interrupt + Orchestrator 识别中断置 waiting。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 11:39:20 +08:00
Blizzard
03225e31a9
refactor(dispatcher): compose 翻默认 —— 补平三缺口后退役 graph.go 在望
...
EINO_COMPOSE 默认改为开(留 =0 逃生舱回退权威 graph.go)。翻默认前补平
compose 路径三个会静默吃掉治理能力的缺口:
- HITL 审批:execDSLNode 无 approval 分支 → 审批节点被当未识别跳过、下游照跑;
补 case 调 approvalNode。
- 忠实度评测:executeGraph 把 refs 硬写 nil → 评测静默失效;runComposeGraph
改签名回传 refsOf(b)。
- 终态传播:rejected/fatalErr 不传播也不阻断下游 → 任务误判 done-空;加节点
lambda 入口守卫 + branch cond 守卫 + 终态上抛 errRejected/fatalErr,并让
runComposeConversation 模型失败置 fatalErr(对齐 graph.go 的 break 语义)。
新增等价测试:审批拒绝停下游、refs 回流。go test ./... 全绿。
soak 无回归后即可物理退役 graph.go。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-29 11:13:46 +08:00
Blizzard
2d9695e940
feat: admin端UI
2026-06-27 12:06:29 +08:00
Blizzard
d2662a1f37
fix(history): 历史运行复盘读库持久化 —— 杜绝过 Redis TTL 后卡「流式中…」
...
问题:历史任务超 Redis 流 10min TTL 后,SSE 回放在空流上永久阻塞 → 运行页卡「流式中…」、
轨迹/工具/输出全空。
修复:收尾把最终输出 + 执行轨迹持久化到 PG,历史复盘改读库(不依赖 Redis TTL):
- store:Task 加 output/trace 两列;SaveTaskOutput/SaveTaskTrace/GetRunDetail。
trace 用 type:text(不是 jsonb)——否则提交时空串 "" 入 jsonb 列会 INSERT 失败、整条任务不落库。
(已 ALTER 既有 trace 列 jsonb→text。)
- gateway:token/exec 录制器在 done 时把累计的输出/轨迹快照落库。
- 新增 GET /tasks/:id/replay 返回持久化的 {output, exec}。
- RunsView:选中历史运行改 runReplay() 读库(秒回、phase 立即 done/error),不再 SSE 回放。
即便旧任务无持久化数据,也是 done+空态,绝不再卡「流式中…」。
live:新任务落库 output 305 字(含表格) + 轨迹 5 事件,/replay 正确返回;tsc+vite、gateway 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 17:52:26 +08:00
Blizzard
594be6a317
fix(desktop): 模型输出渲染图表 + Markdown 支持表格/代码围栏;ReAct 步数提至可配 12
...
针对运行页输出的两个渲染缺口 + 一个节点报错:
- 图表:模型输出走 chart 块分段渲染(文本 Markdown,```chart 块用 ChartView 出图),
不再把图表 spec 显示成原始代码。
- 表格:轻量 Markdown 组件原本不支持 GFM 表格(| … | 显示成原始竖线)与 ``` 围栏代码,
现补上:表格解析成 <table>、围栏渲染成 <pre>。
- ReAct 步数:reactMaxStep 由常量 8 改 env 可配(REACT_MAX_STEP,默认 12)。研究型任务
(搜索→抓取→推理多轮)8 步偏紧易触顶报 exceeds max steps;提到 12 覆盖多数。
tsc+vite 通过,dispatcher build/vet 绿;wails 重启加载。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 17:33:58 +08:00
Blizzard
101d3f9bc9
refactor(desktop): 运行页改三栏 —— 模型输出固定右侧(Markdown),中间 tab 只留轨迹/工具/评测
...
按反馈调整运行·观测布局:模型输出不进 tab,恢复成独立右侧面板用 Markdown 渲染(与旧版一致);
中间 tab 切换 执行轨迹/工具调用/评测,标签行右上保留运行状态指示(完成✓/流式中…/出错)。
三栏:运行历史 | tab详情+状态 | 模型输出。
tsc+vite 通过;预览自查布局正确(左历史/中tab+状态/右Markdown输出),wails 重启加载新版。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 17:12:48 +08:00
Blizzard
7cc7f5fd26
refactor(desktop): 观测收敛进「运行」页(tab 切换),删全局底部抽屉
...
底部抽屉与新版运行页重复,且默认展开常驻占 ~176px、引用/评测还是空壳。按高内聚收敛:
- 运行·观测 详情区改 tab 切换:执行轨迹 / 模型输出 / 工具调用 / 评测(去掉没接的「引用」空标签);
OutputView(含 chart 渲染) + ToolCalls 从抽屉并入运行页;评测标签接 taskEval 全量展示。
- 删除全局 BottomDrawer,释放底部空间。
- HITL 审批条抽成独立 shell/ApprovalBar,全局常驻于 TopBar 下(审批中断必须随处可见可操作)。
- 发起运行自动跳「运行」页 + 新运行自动切回「当前运行」,实时观测不丢。
tsc+vite 构建通过;wails HMR 热加载生效。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 16:56:52 +08:00
Blizzard
3cf3c0b070
feat(desktop): 运行·观测 重做为运行历史 + 复盘(Tier2,用上 exec Redis 回放)
...
RunsView 原本只能看「最近一次」实时运行;现做成完整的运行历史复盘:
后端 GET /api/v1/runs?limit=(store.RecentRuns):任务 LEFT JOIN 评测,返回
task_id/status/time + eval level/overall,供历史列表。
前端 RunsView:左侧运行历史列表(状态点 + 相对时间 + 评测分级徽标),点选任一历史运行
→ 经 streamExec/streamTokens 从 Redis 回放该次执行轨迹 + 模型输出(对已完成任务流即回放),
并取 /tasks/:id/eval 显示评测(分级/忠实度/纠偏/评语/flags)。「当前运行」固定置顶沿用实时订阅。
api 补 listRuns + taskEval。
这正是之前 exec 轨迹 Redis 回放的消费场景。live(vite + 预览):提一新任务 → 历史列表出现 →
点选 → 完整回放轨迹(2 节点/2518ms/含推理过程)+ 答案全文 + 评测 ok·1.00,无控制台报错。
tsc+vite 构建通过,gateway 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 16:34:07 +08:00
Blizzard
1344bf98a0
feat(desktop): 工作台重做为实时仪表盘 + /stats/overview 聚合端点(Tier1 UI 升级)
...
桌面端首屏原是静态宣传页(stat 全硬编码、唯一活数据是网关在线),"太单调"。
重做为活的驾驶舱:
后端 GET /api/v1/stats/overview(聚合,几条轻量查询):
- 任务今日/累计、7 日趋势、近 7 天终态分布(实例级,Task 无 owner)
- 评测均分 + 忠实度均值 + 计数;知识库文档/库数(owner 级)
- token 今日 + 7 日趋势(Redis 日计数)、近期运行 feed、服务健康(复用 health 口径)
- store.StatsOverview + RecentTasks。
前端 Home 重写为仪表盘:4 指标卡(今日任务/Token/评测均分/知识库)带 SVG 火花线、
近期运行 feed(状态点+相对时间,点进运行观测)、7 日任务量柱图、服务健康灯带、能力入口、
快捷动作。5s 轮询刷新。配色沿用现有 ink 暗底 + brand/accent。
live(vite dev + 预览截图):登录后仪表盘渲染真实活数据(今日任务/Token/评测 1.00/近期运行/
服务全绿),无控制台报错。tsc+vite 构建通过,gateway 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 16:13:54 +08:00
Blizzard
a5fd251fc0
feat(deploy): 容器化 + 一键独立部署 —— 5 服务镜像 + prod compose + 安装/备份/许可
...
此前应用服务只是本机裸二进制、无 Dockerfile、无法交付。本提交把项目从"只能在我笔记本
跑"变成"任何人一条命令起一整套",是「演示 / 拉投资 / 卖给客户独立部署」的可交付基础。
- Dockerfile ×5:gateway/dispatcher/mcp-go 多阶段(distroless static, 36–55MB);
mcp-py(python-slim, 268MB);admin(node 构建→nginx 托管 SPA + 反代 /api,76MB)。
Go 构建上下文为仓库根以兜住 replace ../sundynix-shared;.dockerignore 瘦上下文。
- docker-compose.prod.yml:应用 + 基建一把起;经服务名互连(顺带避开 localhost DNS 坑);
基建端口不对宿主暴露;depends_on 健康检查保序(mcp-go 待 Milvus healthy);
APP_ENV=production 强制密钥校验 + 锁 CORS + 管理员白名单。
- .env.example + DEPLOY.md:两类密钥分层(引导密钥走 .env,模型 key 控制台加密存库);
首次管理员流程;安全建议。LICENSE 专有(授权模型可后定)。
- scripts/backup.sh + restore.sh:PG 逻辑备份 + 各数据卷快照 / 恢复。
- .gitignore 挡 .env*(密钥不入库)。
live:5 镜像全构建通过;独立 project 起整套 → nginx→gateway→NATS→dispatcher→mcp 全链路
注册+提任务 running→done,depends_on 保序生效。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 15:23:36 +08:00
Blizzard
165ecb4ec6
test(dispatcher): RAG 管线集成测试 —— 检索→注入→生成→refs→忠实度评测整条链
...
补全核心链路最后一块。此前只测了 retriever 组件的解析,整条 RAG 管线没被集成覆盖。
新增 rag_integration_test.go 4 例:
- ConversationInjectsAndReturnsRefs:input→retriever→agent,断言 kb_search 被调、
kb 按 owner 作用域(u42/travel)、检索片段注入 agent system prompt、refs 经 runGraph 回流。
- RefsDriveGroundedEval:有 refs 时 evaluate 走「含检索资料」的 grounded judge 路径,
记录忠实度分(而非无来源路径)。
- ReportSectionInjectsRefs:报告 writeSection 检索命中注入撰写 prompt。
- DegradesWhenRetrieverDown:kb_search 失败 → 空 refs,agent 仍正常出答案、整图不失败。
go test -race ./internal/eino 干净,四模块全绿。至此核心编排链 + RAG 管线均有集成兜底。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 14:21:11 +08:00
Blizzard
32f50cd983
test(dispatcher): Handle 入口级集成测试 —— 钉死核心编排链 + harness 治理栈协同
...
此前测试都直打 runGraph/evaluate,绕过真正的任务入口 Handle()——熔断、输入护栏门控、
状态机流转、token 预算、异步评测/用量这些治理逻辑的协同从未在入口级被覆盖。
新增 handle_test.go 6 例(全 fake 依赖,可断言各出口):
- HappyPath:running→done + token 流出/收尾 + 异步评测落库(ok)
- ToolFeedsAgent:图执行→工具→agent 全程经 Handle,工具被调、产出注入
- CircuitBreakerOpen:熔断开 → 快速 failed、不执行图
- BudgetExceeded:meta token_budget=3 → failed + 用量回写带 Exceeded
- GuardrailBlocks:灰区 + LLM 分类器命中 → rejected、不进 running
- EmptyTaskDropped:空 id 直接丢弃、无状态回写
配套 fakeStatus/fakeUsageSink;fakeEvalSink 加锁(Handle 异步评测在 goroutine 写)。
go test -race ./internal/eino 干净,四模块全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 14:10:53 +08:00
Blizzard
f926f6fd41
feat(obs): exec 执行轨迹 Redis 回放 —— 连晚/重连不再丢轨迹事件
...
执行轨迹原本只走瞬时 core NATS(sundynix.exec.<id>),SSE 连晚或刷新重连就丢掉
已发生的节点点亮/工具调用/推理过程事件。本提交把它做成与 token 流同构的可回放流:
- store: Redis Stream 函数加 channel 维度(ChannelToken="stream" / ChannelExec="exec"),
同一套 XADD/XREAD/TTL 复用;key 按 channel 分命名空间互不串扰。
- gateway: 提交即启 startExecRecorder 后台订阅轨迹落 Redis(与 SSE 是否在线无关,
12min 兜底含 HITL 审批等待);StreamExec 改为优先 Redis 回放 + Last-Event-ID 断点续传,
Redis 降级回退 live NATS(streamExecLive)。
单测 streamKey channel 隔离;live:任务 done 后再连 /exec,仍从 Redis 完整回放
全程轨迹(含推理过程),Redis XLEN 对账一致。
至此「可靠性细节」两项(优雅停机 + 轨迹回放)补齐。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 14:03:31 +08:00
Blizzard
19df6f3a94
feat(ha): 网关多副本安全 —— 事件订阅改队列组,杜绝重复落库/重复计费
...
去单点的代码层基础:dispatcher/mcp-go 本就靠队列组可多副本,但网关侧的 eval/usage/
status/config 订阅是广播(nc.Subscribe),多网关副本下每条会被每个副本各处理一遍 →
评测/状态重复写 PG、token 用量重复累加(日预算翻倍)、config 请求多份重复应答。
改为 QueueSubscribe + contract.QueueGateway 队列组:组内每条事件/请求只一个副本处理。
(dispatcher/mcp-go 的 config 变更广播订阅保持 nc.Subscribe 不动——每副本都要热更新。)
验证:
- 单测 TestGatewayQueueDedup:2 网关副本订阅,发 50 条 eval,合计处理 50 次(非 100)。
- live:2 dispatcher 副本提 8 任务,队列组自动 4/4 分摊。
至此进程级全部可水平复制。剩 NATS 集群 / 网关 LB / PG·Redis·Milvus 基础设施 HA 属部署期。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 13:10:49 +08:00
Blizzard
2e3cb8105d
fix(store): PG DSN 默认用 127.0.0.1 免每连接 DNS 解析;订正压测报告 C=256 归因
...
DSN 由 localhost 改 127.0.0.1(gateway + mcp-go):pgx 每新建连接解析一次主机名,
高并发连接池扩容时省掉这层 DNS 开销与一类失败模式。
诚实订正:复测发现 DSN 改完 C=256 仍 0 完成(错误从 lookup localhost 变 dial 127.0.0.1 canceled)。
故 C=256 的「停摆」根因不是 DNS,而是单节点在 256 并发下整体饱和(任务排队 + 每任务 3 次 PG
状态写 + 网关 handler 顶不住),属单节点容量上限、非 bug;C≤128 优雅可用,正解是横向扩。
LOAD_TEST_REPORT §4.3/§6 与 project_analysis 容量实测已据实更新。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 12:49:35 +08:00
Blizzard
468107ea7a
docs: 容量压测报告 + 架构拆分验证(LOAD_TEST_REPORT.md)
...
把本次压测的方法/数据/结论独立成报告落项目根目录:单节点平台天花板 ~110 任务/s、
单任务开销 ~42ms、瓶颈是多跳管线而非 DB 连接、C=256 崩于 DSN localhost DNS 抖动(易修);
正面验证'平台不是瓶颈 GPU 才是'→ 横向拆服务+队列组喂满 GPU 集群的设计成立,并列出
网关 HA / PG 状态写 / 集群复测三个待补点。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 11:57:57 +08:00
Blizzard
7bfea74cc0
feat(perf): 容量压测器 + 平台天花板实测曲线
...
cmd/loadtest:闭环加压器,阶梯并发,经 SSE 流检测完成(非轮询,避免轮询放大把网关
压成假瓶颈),输出吞吐 + 延迟分位。配套两个 benchmark 开关:
- dispatcher LLM_FORCE_STUB=1 + LLM_STUB_TTFT_MS/INTERTOKEN_MS=0:绕开真实 LLM 推理,
量平台自身全链路天花板(不烧 token、不被模型节奏掩盖)。
- gateway RATE_LIMIT_PER_MIN 可配(缺省 120):压测放开限流。
实测(单 dispatcher、并发64、stub):
- 单任务纯平台开销 ~42ms;吞吐峰值 ~110 全链路任务/秒(饱和点 并发32–64);
并发128 优雅降速,256 硬崩。
- 吞吐瓶颈不是 DB 连接数(池 25→80 吞吐不变),是每任务多跳管线综合成本;
256 崩根因为 DSN 用 localhost、pgx 每连接解析 → 连接churn致 DNS 取消(易修)。
- 关键判断:平台 42ms ≪ LLM 秒级出答案,平台不是瓶颈、GPU 才是;横向拆服务喂满 GPU 集群
的意义成立。细节与曲线见 project_analysis「容量实测」。
stub 延迟改 env 可配(默认值不变);不影响线上路径。dispatcher+gateway build/vet/test 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 11:46:50 +08:00
Blizzard
075d41f5b3
feat(llm): 本地模型接入 vLLM / Ollama + reasoning 思考过程入轨迹
...
vLLM 与 Ollama 都暴露 OpenAI 兼容 API(底层 go-openai 请求 {base}/chat/completions),
故统一走 openai 客户端,按 provider 归一化连接参数:
- Ollama/vLLM 的 OpenAI 端点固定在 /v1,BaseURL 漏写自动补全(否则打到 /chat/completions 404)
- 本地后端默认不校验 api_key → 缺省补占位(ollama→"ollama",vllm→"EMPTY";openai 客户端要求非空)
- 显式 key 一律尊重(vLLM --api-key 启动);在线 provider 原样不动(DeepSeek 两种都收)
reasoning_content 适配:ChatStream 增 onReasoning 回调,捕获 reasoning 模型
(本地 Qwen3 思考 / DeepSeek-R1 / QwQ、在线 deepseek-v4-pro)的思考分片。思考阶段分片
Content 为空本就不污染答案;runAgent 把思考累计后 surface 到 exec 轨迹「推理过程」事件。
控制台 ModelManager 加 ollama 选项;llm 包补 normalizeBaseURL / apiKeyOrPlaceholder 单测。
四模块全绿。live:经 admin 配 ollama qwen2.5:0.5b → dispatcher 热切(日志 base 自动 .../v1)
→ POST /v1/chat/completions 200 端到端出答案;切回 deepseek-v4-pro → exec 轨迹现「推理过程:思考26字…」。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 11:10:18 +08:00
Blizzard
05c25d7099
feat(ops): 优雅停机 drain —— 三 Go 服务 SIGTERM 后排空在途,不硬切
...
滚动更新/重启时旧的"信号到→进程退"会硬切在途工作:dispatcher 在途任务被掐断、
mcp-go 在途工具调用让 dispatcher 干等超时、gateway 在途 HTTP 请求被截断。
共享 bus 加在途追踪 + drain:
- ConsumeTasks/ServeTool 各加 sync.WaitGroup 跟踪在途 goroutine,返回 drain(ctx):
先停止接新活(cc.Stop / Unsubscribe),再等在途跑完至 drain 超时。
- 关键修复:任务 handler 的 ctx 改为派生自 context.Background()(而非信号 ctx),
否则 SIGTERM 会立即取消在途任务的 ctx,drain 形同虚设。超时未跑完才由 JetStream
AckWait 重投兜底(不丢任务)。
- DrainTimeout():SHUTDOWN_DRAIN_TIMEOUT 秒,默认 30s。
各服务收尾:
- gateway:r.Run → http.Server + signal.NotifyContext + srv.Shutdown(drain 在途请求),
随后 defer 关 db/redis/bus(HTTP 排空后才断后端连接)。
- dispatcher:收到信号 → drain 在途任务跑完再退。
- mcp-go:收到信号 → drain 在途工具调用回完再退(dispatcher 拿到结果而非超时)。
bus 加 TestGracefulDrain(drain 等满在途任务 + 验证在途 ctx 不被取消),e2e 测试适配
新签名,四模块全绿。live:在途任务执行中 kill -TERM dispatcher → 日志「drain 在途任务」
→ 11s 后 task done(511字完整生成) → 「drain 完成,退出」,任务终态 done 非 failed;
gateway/mcp-go 同样优雅退出。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 10:29:23 +08:00
Blizzard
faa1871760
feat(harness): 成本/Token 预算护栏 —— 单任务硬上限 + 单用户日预算(恒温器最后一环)
...
token 用量估算计量(CJK≈1/字、ASCII≈1/4字,无需分词器,护栏够用)。
单任务硬上限(dispatcher):Budget 挂 ctx 沿图透传,各 LLM 节点(对话/ReAct/compose/
报告)入口计输入 token、出口计输出,触顶即中止整图——防失控成本(死循环/超大报告)。
报告路径优雅降级:触顶跳过剩余章节出部分稿,不整体失败。预算来源 Meta.token_budget
或 env TASK_TOKEN_BUDGET(默认 20 万)。
单用户日预算(gateway):dispatcher 收尾经 NATS 回写 UsageEvent → 网关按用户按天累计
Redis(48h 过期自滚动)→ 提交前门控 USER_DAILY_TOKEN_BUDGET(0=不限,超额 402)。
/billing 升级为真实用量:当日已用 / 日预算 / 余额。
契约 UsageEvent + MetaTokenBudget + SubjectUsage;bus Publish/SubscribeUsage;
orchestrator SetUsageSink + 预算触顶 failed(不计熔断)。harness budget 5 单测,三模块全绿。
live:单任务 budget=30 → failed(已用约689);用户日 budget=200 → /billing remaining=0 → 402。
至此 harness 由「测温计」完成向「恒温器」的演进(评测闭环/纠偏/忠实度/脱敏/输入护栏/预算六项)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 10:00:51 +08:00
Blizzard
2f78fc565e
feat(harness): 输入护栏升级 —— 归一化反绕过(Tier1) + LLM 越狱分类器(Tier2)
...
原输入护栏纯正则,空格/编码/同形字一改写即漏,且 bannedTerms 空置。升级为两层:
Tier1(网关同步、无 LLM):先归一化再匹配,干掉绕过——
- 小写 + 去零宽字符 + 去变音符 + 同形字折叠(西里尔/希腊→拉丁) +
拆字间隔还原(i g n o r e / i.g.n.o.r.e → ignore) + base64 解码回扫
- 多视图(原文/归一化/紧凑/解码)匹配高精度注入正则,无需穷举变体
- bannedTerms 经 GUARDRAIL_BANNED_TERMS env 落地
- 软信号(jailbreak/developer mode/无限制…)→ 灰区,放行但打 safety_check 标志
Tier2(dispatcher harness LLM 分类器,escalation):
- 仅对灰区任务执行前调 LLM 裁决 jailbreak+severity,≥0.7 → rejected
- 明确干净/恶意的不付 LLM 成本;模型抖动/解析失败 fail-open 不误锁正常用户
契约新增 MetaSafetyCheck 透传灰区标志;orchestrator 加执行前护栏门控 + SetGuardian。
网关 6 单测 + dispatcher 4 单测,三模块全绿。live:拆字/base64/西里尔同形字均 422 拦,
恶意灰区被 LLM 拒(severity 1)、良性灰区(海盗 roleplay)放行完成。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-26 09:38:34 +08:00
Blizzard
9506a82be9
feat(harness): 输出脱敏增强 —— 跨分片 StreamRedactor + PII,杜绝密钥碎片泄漏
...
原逐片脱敏有两个漏:①密钥被切成两片("sk-912cf85b"|"16d0...")逐片都不命中正则而漏检;
②贪婪正则在缓冲末尾凑够最短长度就把半截密钥提前脱敏发走、剩余字符随后明文流出(碎片泄漏)。
有状态 StreamRedactor 跨分片缓冲,切点在「原文」上定且绝不切断任何完整匹配:
- opener 暂留末尾仍在增长的疑似密钥(sk/AKIA/JWT/Bearer/手机/邮箱/长数字)
- 始终留 16B 尾窗兜底 opener 未覆盖的短模式;勿切断完整匹配(循环至稳定)
- rune 边界安全:cut 退到最近 rune 起点,中文不被切成半个发出乱码
- 暂留封顶 256B,防对抗性长串无限暂留 / O(n²)
- 新增 PII:手机号 / 邮箱 / 身份证(18 位)
3 个流式点(graph/react_agent/compose_graph)统一接入,逐片 Push + 收尾 Flush。
7 单测(跨片/逐字符 JWT/碎片回归/尾窗内匹配/干净重建/PII/无误伤),-race 干净。
live 实测:26 位密钥(曾泄漏 ijkl90mnop 碎片)与邮箱整条 [已脱敏]。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 16:46:08 +08:00
Blizzard
93e9d3b195
feat(harness): 低分自动纠偏 —— poor 触发评语驱动重生成,取更优者(测温计→恒温器)
...
评测闭环延伸出自愈:当自动评测判定输出为 poor(综合<0.5),dispatcher 在热路径外
自动用「原问题+初版回答+评审短板(flags/评语)」(有来源则连来源一并喂回、要求严格基于来源)
让模型重写,重评后仅当新分严格更高才采纳(绝不退步);采纳的修订版落会话历史,
保证多轮上下文用的是好答案而非被判低分的初版。评测终值带 corrected 标记经 NATS→网关落库。
- maxRefineRounds=1:poor 稀少,1 轮重写+重评够用,防成本失控
- canRefine 门控:模型就绪且熔断未开才纠偏,避免后端抖时雪上加霜
- 单 goroutine 串 评测→纠偏→落历史,杜绝原两 goroutine 对答案版本的竞态
- 契约 EvalEvent / Eval 表 / upsert / GET /tasks/:id/eval 均加 corrected 字段
- refine_test.go:采纳更优 / 不退步 / 非低分不触发 三测;live 验证好答案不误触发
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 16:22:45 +08:00
Blizzard
10f08ffb14
feat(harness): 评测闭环 —— 评测结果落库 + 分级 + 告警 + 可查(测温计→恒温器)
...
此前 eval 只打日志、不闭环。现在:
- 分级:evalLevel 据综合分+忠实度 → ok(≥0.75) / warn(≥0.5 或忠实<0.6) / poor(<0.5);poor 出 slog.Warn 告警。
- 落库:dispatcher 评完经 NATS(SubjectEval) 广播 EvalEvent → 网关订阅写 PG(新表 sundynix_eval,
按 task_id upsert)。沿用任务状态回写那套(dispatcher 无 DB,经 bus→gateway 落库)。
- 可查:GET /api/v1/tasks/:id/eval 返回 overall/rule/llm/faithful/level/flags/reason/sources。
- 契约 EvalEvent + EvalOK/Warn/Poor;bus PublishEval/SubscribeEval;dispatcher EvalSink(NewOrchestrator 第9参)。
验证:三模块 build+vet+test 全绿;live RAG 任务评测落库,端点返回 overall~1.0 / level=ok / faithful=1 / sources=1。
剩:桌面端质量面板、低分自动重试(P3)。project_analysis 勾掉该项。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 15:26:19 +08:00
Blizzard
446b784fc6
feat(harness): RAG 忠实度评测 —— judge 拿检索原文评幻觉
...
补 Harness 已知洞:此前 LLM-judge 只看 input+output,看不到检索来源,幻觉其实没评。
- Evaluator.Score 增 sources 参数;有来源时走 llmJudgeGrounded:一次调用同时评 quality 质量 +
faithfulness 忠实度,并列出 unsupported(未被来源支持的说法)→ 进 Flags。
综合分(有来源)=0.3规则+0.35质量+0.35忠实;无来源时维持原 0.4规则+0.6质量。Result 增 Faithful 字段。
- 透传检索来源:runGraph 返回 (answer, refs, err),executeGraph 同步;Handle→evaluate(input,output,refs);
refsOf(board)=检索资料+工具产出。compose 路径暂返回 nil refs(不评忠实度)。
- eval 日志增「忠实 X.XX,来源 N」。
测试:单测覆盖 grounded(quality/faithfulness/unsupported 解析 + 加权 + flags)与无来源跳过;
3 处测试 runGraph 三返回值更新。live 实测 RAG 任务忠实 1.00/来源 1,judge 正确判定无编造。
project_analysis Harness 清单勾掉该项。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 15:03:17 +08:00
Blizzard
82b1d3e802
Revert "feat(desktop): 底部抽屉可拖拽调高/最大化 + 输出渲染 Markdown"
...
This reverts commit 429b04d64a .
2026-06-25 14:12:25 +08:00
Blizzard
429b04d64a
feat(desktop): 底部抽屉可拖拽调高/最大化 + 输出渲染 Markdown
...
解决两个真实体验问题(编排页底部输出区):
- 抽屉太矮(176px)长输出放不下:改为可拖拽顶边调高(140px~85vh) + 一键最大化(82vh)/还原。
- 输出原样显示 markdown 未渲染:接入现有轻量 Markdown 组件(标题/加粗/斜/码/列表/引用/分隔/双链,
随主题),替换 <pre>;含 ```chart 块时文本段渲 Markdown、图表段渲 SVG,保持顺序。
复用现成组件,未引重依赖(react-markdown 试装后回退),bundle 不涨。
tsc + 生产构建 + vitest 48 全过;桌面端已 HMR。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 12:57:51 +08:00
Blizzard
760ca06667
feat(desktop/studio): Tier1 编排画布主题化 + 节点精修(产品灵魂打磨)
...
react-flow 此前 colorMode 写死 dark、背景/连线/控件全用库默认,换肤下不协调。改为:
- colorMode 绑定当前主题 → 控件/连线/手柄/选区自动随亮暗切换。
- Background 点阵色、MiniMap 遮罩随主题;隐藏库水印(proOptions.hideAttribution,更干净)。
- index.css 精修 react-flow:控件(细边/圆角/themed hover)、连线(中性描边+选中紫)、迷你图边框,对齐设计系统。
- TypedNode:卡面落到表面色(ink-850)、选中环用 brand 令牌(替硬编码紫)、加 hover 描边、手柄themed。
- 工具栏/调色板/检查器已用新 Button + 令牌类,自动一致。
tsc + 生产构建通过;桌面端已 HMR。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 12:47:48 +08:00
Blizzard
94f04bd204
refactor(desktop/ui): 组件库对齐 shadcn 画廊基调
...
按已定稿的组件画廊精修 ui/ 基础组件(均走主题令牌,亮/暗自适应):
- Button:主按钮去掉霓虹 shadow-glow(玩具感残留)→ 扁平 + active 微缩;
次按钮落到 ink-850 表面。
- Input/Textarea/Select:底色对齐表面(ink-900) + 焦点环改 ring-2 ring-brand/25(更柔的聚焦)。
- 新增 Table/Tr/Td:细分隔线 + 弱化表头 + 行 hover,运维/数据场景标准呈现。
- EmptyState/Skeleton/Badge/Tabs/Card 已主题化,沿用。
页面经 ui/ 桶引入,组件升级即全页生效。tsc + 生产构建 + vitest 48 全过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 11:23:59 +08:00
Blizzard
ca7ce4c26f
feat(desktop): 亮/暗主题切换 —— 默认亮色,一处令牌驱动全 app 换肤
...
把高频硬编码色(ink 表面 / line 边框 / slate 文字)重定义为 CSS 变量(RGB 三元组,
保留 /透明度 修饰符),:root(亮) 与 .dark(暗) 两套值切换——改 tailwind.config + index.css
两处即让全 app 换肤,无需逐文件迁移。
- 亮色走 shadcn 中性灰(zinc:zinc-50 底/白卡/zinc-200 边/zinc-900 字);
暗色精炼为中性 zinc 暗(替代原偏蓝 ink),顺带提质感。
- lib/theme.ts:useTheme + applyInitialTheme(main 启动前设类,避免首屏闪烁)+ localStorage 持久化,默认亮色。
- TopBar 加 Sun/Moon 切换钮;滚动条/输入控件 color-scheme 随主题。
- 修亮色下会失效的 bg-white/5 → bg-ink-800(Tabs/Badge/ExecTrace 的中性微底);
模态遮罩 bg-black/55 两模式皆宜,保留。
验证:tsc + vitest 48 过 + 生产构建通过(var 色全解析)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 10:39:36 +08:00
Blizzard
aea04e2e8d
docs: project_analysis 增补 Harness 治理层专项 + 逐项优化清单
...
记录 Harness 四组件真实成熟度(熔断器生产级 / 评测·脱敏·输入护栏偏启发式),
定性「测温计而非恒温器」,并列出可逐项推进的优化清单(评测闭环 / RAG 忠实度评测 /
脱敏跨片+PII / 输入护栏升级 / 坏输出自动纠偏 / 成本预算护栏),便于一个个优化。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 10:13:56 +08:00
Blizzard
4d867ce460
docs: 重写 project_analysis.md —— Opus 4.8 全程搭建后的实测版
...
整体覆盖旧自动分析版。要点:
- 真实数据实测(~19,200 行 / 32 测试文件 / dev 119 提交),订正旧版"基于 main"
及我此前口头"14 未推送"的口误(实测 dev 仅领先 origin/dev 4 个、origin/main 基本同步)。
- 据实盘点功能 + 本会话端到端验证标注(HITL/6 工具/AES/OTel/并发等均实测确认)。
- 严格区分"功能存在"与"成熟/被验证":对标矩阵加口径警示,不再喊"超越数万 star/安全最强"。
- 正视生产硬门槛:单点无 HA、无备份灾备、未审计、无配额、规模未验证、推理模型未适配、
exec 轨迹丢事件、停机不 drain。
- 评分 3.7/5(功能完成度高、运维成熟度低,分列说明)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 10:05:52 +08:00
Blizzard
db77cd687d
docs: 订正 project_analysis.md —— 实事求是,挤掉自评水分
...
昨晚版(opus 4.6 生成)功能盘点准确,但价值判断系统性虚高,订正:
- 范围:标称"main 分支"实为 dev 工作树(HITL/6 新工具/AES 线缆加密/并发等 14 提交
未推送合并到 main),头部加范围与口径说明。
- 去过誉:"业界顶级""超越数万 star 主流项目""开源安全最强""均不具备" → 改为
"功能覆盖面广但未经审计/规模验证",强调勾选≠成熟度。
- 对比矩阵加读法警示(功能存在≠成熟度对等;AutoGen 未纳入;竞品 ❌ 多为实现方式不同);
修正 Dify 多智能体/工具发现等不公平 ❌ 。
- 补真实短板:单点无 HA、规模未验证、无备份灾备、exec 轨迹丢事件、推理模型未适配、
优雅停机不 drain、安全未审计。
- 重校评分 4.5→3.6,"准生产+"→"工程原型→准生产",各维度加成熟度折扣说明。
- 优先级补 HA / 压测 / 备份灾备 / 安全审计 / dev→main 推送。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-25 09:52:27 +08:00
Blizzard
36aa298410
docs: 项目分析6.24
2026-06-24 21:32:48 +08:00
Blizzard
17955f0088
Dev ( #4 )
...
mcp tools 完善
2026-06-24 20:57:14 +08:00
Blizzard
592b2a3d97
feat(tools): chart 图表工具 —— 工具产出 JSON spec,前端 SVG 渲染(职责分离)
...
按「工具只产数据、渲染交前端」设计:
- 后端 chart 工具(mcp-go):校验并返回规范化图表 JSON(type=bar/line/pie + labels + series,
校验类型/长度一致/pie 取首系列)。工具说明指示 agent 用 ```chart 围栏原样包裹返回的 JSON。
- 前端:lib/chartspec.ts 从输出抽取 ```chart 块(解析失败回退为文本不丢内容);
components/ChartView.tsx 自绘 SVG 柱/线/饼图(无第三方图表依赖);
BottomDrawer 输出区含图表块时分段渲染(文本 + SVG),否则纯文本。
测试:前端 chartspec 单测 12 例(isChartSpec 校验、分段抽取、非法块回退、多块、hasChart);
tsc 干净,vitest 48 过。live 自主 agent:chart 工具产出 {"type":"bar",...},
agent 正确用 ```chart 围栏嵌入答复,前端据此渲染。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 17:24:41 +08:00
Blizzard
ca38dcd0c9
feat(tools): 新增 sql_query 只读 SQL 查询工具(agent 可见)
...
agent 可对数据库执行只读查询。三重防护:
- 静态校验:仅单条 SELECT/WITH,词边界拒 insert/update/delete/drop/alter/create/
truncate/grant 等写/DDL 关键字(不误伤 created_at 之类列名)。
- 只读事务(sql.TxOptions{ReadOnly:true}):Postgres 引擎级强制只读,硬兜底。
- 行数(100)+超时(10s)+单元格(200 rune)上限。
连接:SQL_QUERY_DSN 优先(生产应指向专用只读库/账号),未设回退服务已解析的平台 PG DSN
(经 NewGateway 传入 g.pgDSN,不再裸读 env)。独立小连接池(8)。
测试:单测 validateReadOnlySQL(放行 SELECT/WITH/列名含 created;拒写/DDL/多语句)。
live 自主 agent 实测:查 sundynix_task=157 行、sundynix_model=2 行。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 17:07:34 +08:00
Blizzard
e503965a83
feat(tools): 新增 4 个 agent 工具 —— web_search / web_fetch / calculator / current_datetime
...
均在 mcp-go,注册即 agent 可见(dispatcher 经 list_tools 动态发现,零改调度代码):
- web_search 联网搜索:Tavily(有 TAVILY_API_KEY 则用,干净 JSON)/ DuckDuckGo HTML(免 key 兜底)。
- web_fetch 网页抓取:取 URL → 去脚本样式 → 去标签 → 解实体 → 收敛空白,限 8000 rune;
复用 external_api 的 SSRF 防护(拒环回/内网/元数据 + 重定向校验)。
- calculator 计算器:自研调度场算法求值(+ - * / % ^ 括号、一元负号),杜绝任意代码执行。
- current_datetime 当前时间:含星期与时区(可传 tz)。
测试:单测覆盖 evalArith(含右结合/一元负号/错误用例)、htmlToText(剔脚本样式+解实体)、
ddgUnwrap。live 自主 agent 实测:calculator (123+456)*7→4053、current_datetime 周三、
web_fetch example.com→200、web_search DDG 返回真实结果,全部端到端通过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 16:47:15 +08:00
Blizzard
700845d64a
feat(prod): DB 连接池上限 + LLM 失败暴露为 failed(生产级并发收尾)
...
为高并发生产做的三项收尾(配合已有的任务/工具并发消费):
1. DB 连接池上限(pgsql.go / memory/store.go):SetMaxOpenConns(默认 25,
DB_MAX_OPEN_CONNS 可调)+ MaxIdleConns 5 + ConnMaxLifetime 1h。
防高并发无限开连接打爆 PG(max_connections 默认 100)。
2. LLM 失败暴露为 failed(graph.go):board.fatalErr —— agent 模型调用出错即上抛,
runGraph 中止后续节点并返回错误 → Handle 判 failed(带原因),不再静默 done-空。
可观测/可告警,生产排障必需。
压测验证(dispatcher 并发=50, 池=25, deepseek-v4-pro 推理):
- 平台同一秒并发收下 40 任务,全程零 DB/连接错误,平台开销≈0(裸 LLM 1.8s vs 平台 P50 1.7s)。
- 并发 10 健康 4.7/s;20+ 延迟暴涨 = DeepSeek 开发账号并发限流(外部),非平台。
- 失败注入(错误模型名)→ 任务正确判 failed 并回传原因。
结论:平台并发机制达生产级;真实吞吐上限 = 自托管模型容量(生产 Qwen,可加卡线性扩)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 16:14:19 +08:00
Blizzard
38a74e3911
fix(history): 空答复不落历史 + 发送前过滤空消息(根治会话毒化)
...
现象:某轮 LLM 返回空(余额不足/失败/降级)→ 空 assistant 消息被写进会话历史
→ 此后该 session 每次请求都带一条空 content 的 assistant 消息 → DeepSeek 400
"Invalid assistant message: content or tool_calls must be set" → 又空 → 又写空 → 自我循环毒化。
- Fix A(断源):orchestrator.memorize 对空答复直接 return,不落历史(失败的一轮不留痕)。
- Fix B(兜底):buildMessages 发送前剔除 content 与 tool_calls 均空的历史消息,
防御任何来源的脏历史(含存量)。
验证:清理被污染的 default 会话后恢复正常;新逻辑下空答复不再写入。dispatcher
build+vet+test 全绿。
注:诊断中发现激活模型 deepseek-v4-pro 是推理模型(答案在 reasoning_content,content 为空),
已在管理端切回 deepseek-chat。仍待办:LLM 调用失败应暴露为 failed 而非 done-空输出。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 15:53:30 +08:00
Blizzard
53669437d5
perf(tools): 工具服务单实例并发(ServeTool 协程化)+ mcp-py 同改
...
垂直扩:单个 mcp 实例内每个工具调用分发到独立 goroutine/task 并发处理,
不再受 NATS 单订阅回调串行所限;配合队列组多副本即「单实例并发 × 副本数」横向扩。
- bus.ServeTool:回调立即起 goroutine 并返回(不阻塞 NATS 投递),信号量限并发
(MCP_TOOL_CONCURRENCY,默认 16),handler panic → 回错误结果避免调用方干等超时。
- mcp-py mcp_gateway:_on_call 改 asyncio.create_task 派发 + Semaphore 限并发(同默认 16)。
- 测试 TestConcurrentToolServe:slow 工具阻塞时 fast 工具仍返回(旧串行下会超时)。
验证:单测通过;mcp-go/mcp-py live 重启工具就绪,工具往返正常(memory/history/kb_search
均正常响应,无 panic)。全模块 build+vet+test 全绿。
注:下游共享后端(Milvus/Neo4j/PG/嵌入端点)是横向扩的最终上限,届时扩这些而非 mcp 实例。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 15:40:50 +08:00
Blizzard
e3b0a4f81c
perf(dispatcher): 任务并发消费 —— 待审/慢任务不再阻塞其他任务
...
bus.ConsumeTasks 由单条串行改为限并发分发:每个任务进独立 worker goroutine,
并发上限 = 信号量 + 消费者 MaxAckPending(DISPATCHER_CONCURRENCY,默认 8)。
- 背压:并发满则在 select{sem, ctx.Done} 处等空位;关停时留消息不 ack 待重投。
- 健壮:worker 内 recover panic → Term(避免崩溃循环);ack/nak/span 收口在 worker。
- 并发安全已核:CircuitBreaker 有锁、Orchestrator.turns 有 turnMu、pool RWMutex、
evaluate 本就异步。
效果:一个 HITL 待审任务(Handle 阻塞至多 5min)或长 LLM 生成不再冻结后续任务。
验证:单测 TestConcurrentConsume(A 阻塞时 B 完成);live 实测 HITL 停 waiting 期间
普通任务 3s 跑完且 HITL 不受影响。全模块 build+vet+test 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 15:03:00 +08:00
Blizzard
d7c6fab933
fix(hitl): 审批弹窗可靠化(状态轮询兜底)+ 审批节点入调色板并改橙色
...
两个实测发现的问题:
1. 审批条偶发不弹、任务卡「运行中」:exec 的 await 事件走实时 core NATS(无回放),
UI 的 EventSource 晚连一拍就漏掉 → 永远不弹审批。改为轮询持久化的任务状态
(GET /tasks/:id,每 1.5s),status==waiting 即弹审批条,不再依赖易抢跑的 exec 事件;
exec 事件仅用于丰富摘要。新增 api.taskStatus + RunState.lifecycle/detail +
App.tsx 轮询 + 终态停轮询;BottomDrawer 审批条触发改以 lifecycle==waiting 为准。
2. Studio 左侧调色板没有「人工审批」节点(只加了 NODE_KINDS,漏了 NODE_ORDER),
且其配色与「输入」同为 amber 难以区分:补进 NODE_ORDER(分支与并行之间),
配色改 orange(橙)与输入的 amber(琥珀金)区分。
验证:桌面端 live 重启确认审批条可靠弹出 + 批准/拒绝两条路径正常;左侧节点列表与
画布配色区分清晰。tsc 干净 + vitest 36 过。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 14:51:37 +08:00
Blizzard
16a6c4b1aa
feat(hitl): 人工审批中断(Eino Phase D)—— 审批节点暂停→批准续跑/拒绝中止
...
专用「审批」节点方案,全栈打通。
后端:
- contract:TaskWaiting/TaskRejected 状态 + ApprovalSubject/ApprovalDecision。
- bus:PublishApproval + WaitApproval(订阅决定主题,带超时);消费者 AckWait
30s→15min(阻塞等人审期间消息未 ack,否则重投成重复任务)。
- dispatcher:ApprovalWaiter 接口 + approvalNode——执行到审批节点发 await 事件 +
置 waiting,阻塞等决定。批准→回 running 放行下游;拒绝/超时→errRejected 哨兵→
剪下游→rejected,优雅收尾不计熔断。超时安全默认拒绝。
taskExecTimeout 3→10min(审批5 < 执行10 < AckWait15)。
- 网关:POST /tasks/:id/approve(仅 waiting 态受理,幂等)。
桌面端:
- Studio 新增「人工审批」节点(nodeCatalog,可填标题/说明)。
- run.ts pendingApproval() 从 exec 流派生待审中断 + waiting 节点状态。
- BottomDrawer ApprovalBar:琥珀审批条(摘要 + 批准/拒绝 + 备注,调 api.approveTask)。
- ExecTrace waiting 状态灯。
验证:后端 curl 实测 waiting→批准→running→done;waiting→拒绝→rejected(下游未跑)。
前端 tsc + vitest 36 过(pendingApproval 4 例 + waiting 状态)。全模块 build+vet+test 全绿。
EINO_ADOPTION Phase D 标记 HITL 完成。
未覆盖:compose 路径(EINO_COMPOSE,默认关)的 approval 节点;桌面端审批条未在 GUI 实点。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 13:18:47 +08:00
Blizzard
92a2ad2136
dev ( #3 )
...
高价值项优化
2026-06-24 12:56:43 +08:00
Blizzard
1d23bdf0a3
feat(rag): Markdown 标题感知切块(块带章节面包屑、不跨章节、跳过代码围栏)
...
chunk() 分流:检测到 ATX 标题(#~######)走 chunkMarkdown,否则走原纯语义
切块(packSection)——纯文本行为不变,向后兼容。
- splitMarkdownSections:按标题层级切段,维护标题栈生成面包屑路径
(如「部署指南 > 环境要求 > 端口」);块绝不跨章节边界。
- 正确跳过代码围栏(``` / ~~~)内的 #,避免 #define、bash 注释被误判为标题。
- 每块前缀完整标题路径 → 检索到的块自带章节语境,提升命中质量与 LLM 理解。
- 节内仍复用原语义打包(句界收口)+ 块间重叠,rune 安全不变。
测试 12/12(7 原有 + 5 新):面包屑、不跨章节、层级出栈重置、代码围栏忽略、
纯文本与语义切块一致。实测 ingest 一篇 Markdown 切出正确三级面包屑,
kb_search「Gateway 监听哪个端口」top 命中即带面包屑的块。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 12:49:41 +08:00
Blizzard
218fba559c
feat(observability): slog 日志带 trace_id,与链路双向互跳
...
新增 sundynix-shared/otelx/slog.go:traceHandler 包装 slog.Handler,凡 ctx 有
活跃 span 的 slog.InfoContext(ctx,...) 自动注入 trace_id/span_id;SetupSlog(服务名)
装全局 JSON slog(service 标签 + LOG_LEVEL 控级)并设默认;TraceID(ctx) 辅助取 hex。
- 三个服务 main 启动调 otelx.SetupSlog。
- gateway 访问日志 Observe() 改用 slog.InfoContext(c.Request.Context(),...)(删旧
accessLogger)→ 每条 HTTP 日志带 trace_id。
- dispatcher orchestrator.Handle 的 received/done/error 改 ctx-aware slog → 任务
执行日志带 trace_id。
- otelx 单测 5 例(注入/无 span 不注入/With() 后仍生效/级别解析)。
- production_readiness.md 1.1:可观测性三件套(metrics+logs带trace_id+traces)闭环。
验证:dispatcher「task done」日志 trace_id 拿去 Jaeger /api/traces/<id> 命中同一条
11 span/3 服务链路;gateway 访问日志亦带同一 trace_id。四模块 build+vet+test 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 12:41:33 +08:00
Blizzard
f37d046f2c
test(desktop): 前端单元 + 关键组件测试(Vitest + RTL,31 例)
...
测试基建:vite.config.ts 加 test 配置(jsdom + setup),package.json 加
test/test:watch 脚本,src/test/setup.ts 引 jest-dom;tsconfig 把测试文件 exclude,
使 CI 现有 tsc --noEmit 不检查测试文件(仍绿),测试由 Vitest 自跑(先不接 CI)。
覆盖(31 例 / 4 文件):
- dsl:exportDsl 节点/边映射 + config 兜底 + branch 真假边 sourceHandle 条件;
validate 空画布/孤立节点/必填项缺失。
- version:isNewer 版本比较矩阵;checkUpdate 有更新/同版/旧版/404限流/离线异常;
openExternal Wails / 浏览器双路径。
- run:deriveNodes start→running、end→done+耗时、error、error 不被 start 覆盖、
info 累计 notes、首现顺序、label 更新。
- UpdateBanner(RTL):无更新不渲染、有更新显版本+点下载调 openExternal、点忽略消失。
小改:version.ts 导出 isNewer(原私有)以便直接测版本判定矩阵。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 11:53:54 +08:00
Blizzard
a2d184b7ec
feat(observability): OpenTelemetry 全链路追踪(Jaeger + NATS 跨总线传播)
...
新增 sundynix-shared/otelx:otelx.Init(ctx,服务名) 注册 W3C 传播器 + OTLP/HTTP
批量导出(默认 localhost:4318,docker 里的 Jaeger);OTEL_SDK_DISABLED=true 关导出。
Jaeger 不在线 / 导出器构建失败都不阻断启动(可观测性是增益而非依赖)。
- NATS 跨进程传播(无现成中间件):bus/trace.go 的 natsHeaderCarrier + inject/extract,
PublishTask/CallTool 注入 traceparent,ConsumeTasks/ServeTool 抽出续上 → 链路跨总线连成一棵树。
- 埋点:gateway 挂 otelgin(HTTP server span,链路根);dispatcher task.execute →
node.<kind>(每节点,nctx 下传使工具/LLM 挂到节点下)→ llm.stream/llm.generate;
bus 自动出 tool.call(client)↔tool.serve(server) 成对跨服务 span。
- docker-compose 加 jaeger all-in-one(UI :16686,OTLP :4318)。
- 依赖修复:otlptracehttp 触发 genproto 单体(旧)vs 拆分模块 ambiguous import(milvus 拉旧版),
pin genproto 至后拆分版(go.work 工作区全局生效)。
- production_readiness.md 1.1 更新为「已实现」。
验证:真实 input→retriever→agent 任务在 Jaeger 出 14 span / 3 服务的完整树,
跨 NATS(publish→consume)、跨服务(tool.call→tool.serve)均连通,
瓶颈 kb_search 692ms、llm 1597ms 一眼可见;四模块 build+vet+test 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 11:35:08 +08:00
Blizzard
46ef3df221
feat(security): LLM api_key 端到端加密(AES-256-GCM,磁盘+线缆均密文)
...
新增 sundynix-shared/secrets:AES-256-GCM,密钥由 SUNDYNIX_SECRET_KEY 经
SHA-256 派生;密文带 enc:1: 版本前缀,历史明文行自动透传(下次保存升级为密文)。
- 网关 SaveModel 加密落库;ListModels/TestModel 解密后脱敏/探测;
空或脱敏占位的 api_key 视为「未改」→ 沿用库内既有密文(不二次加密)。
- 密文经 NATS 原样下发;消费方解密集中在 bus 层 decryptConfig
(RequestConfig + SubscribeConfigUpdated)→ dispatcher/mcp-go 零改动。
- secrets.MustHaveKeyInProd():生产未设 SUNDYNIX_SECRET_KEY 直接 fatal,
gateway/dispatcher/mcp-go 启动各调一次(三服务须配相同密钥)。
- 修复 store.SaveModel 整行 Save 把 active 清零的旧 bug:改 Select(...).Updates
只覆盖可编辑列,改 key 不再顺手取消模型激活。
- secrets 单测:往返/空串/随机 nonce/错密钥 fail-closed/历史明文透传。
- production_readiness.md 2.1 更新为「已落地」。
验证:DB 列由明文 sk-…(35) → 密文 enc:1:…(90);dispatcher 从密文广播解密后
model config set;真实任务 √256→16、12×12→144 打通 DeepSeek(非降级桩)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 10:36:34 +08:00
Blizzard
5cfed55e91
fix(dispatcher,mcp-go): 配置拉取改为后台重试,根治启动竞态
...
此前 dispatcher(chat)/mcp-go(embedding) 启动时一次性请求控制面配置,3s 扑空即
降级,且只能干等热更新广播——若消费方早于 gateway 启动,会全程降级(LLM 跑桩、
RAG 无向量),必须手动重启才恢复。
改为:先订阅热更新,再后台 RequestConfigWithRetry(重试至拿到配置,容忍 gateway
晚启)。新增 shared/bus.RequestConfigWithRetry + dispatcher Subscriber 包装。
验收:故意先起 dispatcher/mcp-go、后起 gateway,二者自动重试拿到 chat/embedding
配置,无需手动重启;make test-go 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 09:57:06 +08:00
Blizzard
56eee90705
docs: 订正 project_analysis.md(经源码核实 + 跟进最新进度)
...
修 5 处事实硬伤(原分析靠文件名/印象、且写于近期改动之前):
- 桌面端 UI:shadcn/ui → 自建 UI 原语(src/ui)
- Word 渲染:UniOffice → 自建零依赖 OOXML(unioffice.go 注释明确不用商业库)
- CORS:「未见配置」→ 实有 cors() + CORS_ALLOW_ORIGIN
- 限流:「实现程度不明」→ 实有 Redis 滑动窗口按 IP 限流
- CI/CD:「无」→ 已加 ci.yml + release.yml
另:安全沙箱被误判为「桩」→ 实已可用(run_code 已接入自主 agent)。
补录近期新增功能:Token 流可回放+断点续传(Redis Stream)、
GitHub Releases 分发 + 桌面端自动检查更新。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-24 09:31:06 +08:00
Blizzard
9f2a059ce2
docs: 项目分析与生产演进蓝图
2026-06-23 20:52:41 +08:00
Blizzard
fc1494b634
Merge pull request #2 from blizzardzhang/dev
...
Dev
2026-06-23 17:25:45 +08:00
Blizzard
6d839dbb30
fix(ci): release.yml 的 GOWORK 加引号(裸 off 被 YAML 解析为布尔致 workflow 不触发)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 17:23:44 +08:00
Blizzard
616ba950b6
test: test
2026-06-23 17:19:18 +08:00
Blizzard
036ff00e4c
docs: 发版清单 RELEASE.md(版本号对齐 + tag 发布 + 验证 + 回滚)
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 16:47:51 +08:00
Blizzard
8c980d2b37
feat(desktop,ci): GitHub Releases 分发 + 桌面端检查更新
...
让桌面端像开源项目一样用 GitHub Releases 分发,旧版用户自动获知更新:
- CD:.github/workflows/release.yml —— 打 vX.Y.Z 标签触发,macOS(universal)/
Windows(amd64) 用 wails build 出安装包,softprops/action-gh-release 发布到
GitHub Release(GOWORK=off,因 desktop 不在工作区)。
- 桌面端检查更新:lib/version.ts(APP_VERSION + 查 GitHub /releases/latest +
语义版本比较 + openExternal 走 Wails BrowserOpenURL);UpdateBanner 启动时
检查,有新版顶部横幅「前往下载」。404/限流/离线均静默不打扰。
注:未签名/公证(macOS Gatekeeper / Win SmartScreen 需后续证书);release
workflow 需真实 tag push 才能验证(GitHub 侧运行)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 16:26:02 +08:00
Blizzard
9fc3597317
ci: 加 GitHub Actions —— Go/前端/mcp-py 自动化测试
...
工程保障地基:每次 push / PR 自动跑回归,防止改动悄悄破坏现有功能。
- go:4 模块 build + vet + test(bus 用内嵌 NATS,无需外部服务)
- web:desktop/frontend + admin 各跑 npm ci + tsc --noEmit(矩阵并行)
- py:mcp-py pip install -e . + sandbox 守卫测试(pytest,缺则 fallback harness)
带 concurrency 取消旧运行、npm/pip/go 缓存。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 16:11:17 +08:00
Blizzard
f238ae1455
feat(dispatcher): 编排式多智能体接力 —— 上游 agent 产出沿图传给下游
...
让画布上串联的多个 agent 真正协作完成一件事(如 检索→撰写→审查 出报告),
而非各自对原 query 重答:
- board 增 agentOut(各上游 agent 产出按序);RunCtx 增 Upstream,buildMessages
注入"前序协作 agent 的产出,请在此基础上继续"。
- runAgent / runReactAgent / runComposeConversation 三条路径统一:注入 b.agentOut
作上下文,产出经 recordAgentOutput 入黑板(append agentOut + 设为当前成稿 answer,
多 agent 时最后一个 agent 产出即最终成品)。
不需要 eino multiagent 组件——编排式协作由现有图引擎 + 上下文传递实现。
测试 TestAgentCollaborationPassesOutput:下游 agent 确见上游产出、成稿=下游产出;
make test-go 全绿。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 15:42:58 +08:00
Blizzard
ddc2265c5b
feat(mcp-go): RAG 语义切块 —— 递归 + 句界 + 重叠 + rune 安全
...
替换朴素切块(按行切 + 字节硬截)为工业级语义切块:
- 修真 bug:旧版 s[:2000] 按字节切,中文 UTF-8(3 字节/字)会被切碎成乱码;
新版全程按 rune 操作。
- 算法:splitToAtoms(换行/中英句末标点切原子,超大无标点原子按 rune 窗口兜底)
→ packAtoms(贪心打包到 target=500 字、句末收口,尾块 <100 字并入相邻)
→ addOverlap(块间 80 字重叠,保跨块上下文)。硬上限 1000。
- chunk.go 独立成文件 + chunk_test.go(空/短/rune安全/大小上界/句界/重叠/超大无标点)。
收益:检索片段语义完整(不再断句)、中文不乱码、跨块上下文不丢 → RAG 召回质量。
make test-go 全绿。后续可加 Markdown 标题路径前缀(结构化文档增强)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 15:21:17 +08:00
Blizzard
529cd0fdd6
feat(gateway): SSE token 流可回放 + 断点续传(Redis Stream)
...
根治 core NATS 不回放导致的"连晚/刷新/抖动重连丢实时 token":
- store/redis:token 流落 Redis Stream(XADD,带 TTL)——StreamAppend /
StreamRead(XREAD BLOCK,支持从任意 ID 续读)。
- gateway:SubmitTask 起即启动 token 录制器(订阅早于 dispatcher 产 token,
全量捕获,与 SSE 客户端是否在线无关);StreamTask 改从 Redis Stream 读,
发 SSE 时带 id(= Redis entry ID),浏览器重连自动带 Last-Event-ID 续传。
Redis 降级时回退原 live-NATS 路径(streamTaskLive)。
实测:任务跑完后才连 SSE → 完整回放;带 Last-Event-ID 重连 → 断点续传不重不漏;
make test-go 全绿。最终答复本就持久在 Redis 历史,此改让"实时流"也无损。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 15:05:53 +08:00
Blizzard
79437e1c1c
feat(mcp-py,dispatcher): 自主 agent 工具动态发现覆盖 mcp-py
...
把动态工具发现从 mcp-go 扩到两台 MCP:
- mcp-py:TOOL_META 改成与 mcp-go 同形(cn/desc/agent/params/inject),
list_tools 上报这些;run_code 标 agent 可用(params: code)→ 自主 agent
可执行 Python 代码做计算/数据处理。
- dispatcher:agentTools 拆出 discoverTools(subject),分别探 mcp-go / mcp-py
的 list_tools 并合并;某台 MCP 离线即跳过(降级,不阻断)。
验收:mcp-py 未启动时自主 agent 仍正常(探测秒跳过),一次自主调用了
history_get + memory_get 两个动态发现的 go 工具(注入 session_id/user_id);
go 全绿、mcp-py py_compile 通过。mcp-py 起着时 run_code 即自动入 agent 菜单。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 14:34:07 +08:00
Blizzard
95f4752d9e
feat(desktop): Agent 节点加「自主工具 (ReAct)」开关
...
前端补上 Phase B 的 UI 接缝:agent 节点 nodeCatalog 增 autonomous checkbox
(默认 false)。勾选 → exportDsl 带 config.autonomous=true → dispatcher
走 ReAct 自主调工具。此前能力在后端但画布点不到。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 14:29:37 +08:00
Blizzard
7388f2741d
feat(mcp-go,dispatcher): 自主 agent 工具集动态化 —— list_tools 自描述 + 动态发现
...
杜绝硬编码:自主 agent 的工具菜单不再写死在 dispatcher,而是从 mcp-go
注册表(单一事实源)动态发现。加工具只改 mcp-go 一处,dispatcher 零改动。
- mcp-go:toolDef 增 agent/agentName/params/inject 元信息(paramSpec 声明
模型可填参数;inject 声明服务端注入、不暴露给模型的参数如 user_id);
list_tools 上报这些。当前标 agent 的 4 个:wiki_search / recall_user_memory
/ remember_user_fact / history_get。
- dispatcher:agentTools() 改为调 list_tools → 取 agent_exposed → 按上报的
params 建 schema.ToolInfo → 生成 mcpTool;inject 参数(user_id/session_id/
kb/task_id)运行时绑定。删除硬编码的 2 个工具。
验收:实测自主 agent 调用新暴露的 remember_user_fact(memory_upsert)成功——
参数由模型按 schema 自生成(key/value),user_id 服务端注入(map 带 task_id
佐证);make test-go 全绿;管理端状态面板兼容(忽略多余 JSON 字段)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 14:29:20 +08:00
Blizzard
45ad852806
feat(dispatcher): Eino 组件化补完 —— 检索 Retriever + 提示词 ChatTemplate
...
补完终态架构剩余两层(A 收尾):
- 检索:新增 ragRetriever(eino_components.go)实现 components/retriever.Retriever,
包 mcp-go kb_search(NATS)、命中转 *schema.Document;retrieve() 改经组件,
retriever 节点与报告分章检索统一走它。
- 提示词:buildMessages 改用 prompt.FromMessages + MessagesPlaceholder
(FString 仅解析模板串、值原样注入 → JSON 花括号安全),产出消息序列与旧手拼等价。
- 工具:mcpTool 即 InvokableTool(Phase B),用于模型自主调用;静态 tool 节点
确定性裸调用 by design。
测试:buildMessages 形状(含花括号)+ ragRetriever 文档解析单测;make test-go 全绿。
性能:compose 每任务编译实测 ~13µs(基准),相对 LLM 秒级可忽略 → 编译图缓存
判为 premature optimization 暂不做;并行效率已由 Phase C DAG 调度拿到。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 11:20:52 +08:00
Blizzard
2e927eca0a
feat(dispatcher): Eino 采纳 Phase C 完成 —— 全图 DSL→compose.Graph 编译器
...
把整张 DSL 图编译为 Eino compose.Graph 执行(编排归一):
- compose_compiler.go:每节点=Lambda,节点体复用 execDSLNode(全节点类型);
黑板进 compose 本地状态(WithGenLocalState + ProcessState);branch 走
AddBranch + 状态感知条件(复用 branchNode);边载荷 flowSignal(注册 no-op
合并支持 fan-in,真实数据全走黑板)。
- WithNodeTriggerMode(AllPredecessor) DAG 模式:无依赖节点并行调度(效率)。
- Handle→executeGraph 按 EINO_COMPOSE 开关选 compose/graph.go;compose 编译
失败自动降级回 graph.go(安全网)。默认关,graph.go 仍权威。
等价回归:线性图 + 分支图经解释器与 compose 两路径产出逐字一致(单测);
live 多节点分支图 compose 路径 2800 字答复 eval 1.00、FSM done、0 幽灵;
make test-go 全绿。
过渡期 soak 后翻默认到 compose、退役 graph.go。性能后续:编译图按 DSL-hash 缓存。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 11:02:32 +08:00
Blizzard
2ee16d1f99
feat(dispatcher): Eino 采纳 Phase C —— 对话主流程跑 compose.Graph + callbacks 归一
...
按"并存 + 等价回归"策略,对话主流程可跑在 Eino compose.Graph 上:
- compose_graph.go:runConversation 按 EINO_COMPOSE 灰度开关分流;
runComposeConversation 建图 START→ChatModel→END,Compile→Stream 回流 token;
模型未就绪/编译失败降级回 runAgent。默认关,graph.go 仍是默认且权威。
- compose_callbacks.go:composeTracer 用 utils/callbacks 把 ChatModel/Tool 的
start/end/error 桥到 ExecEvent(可观测归一,不再各处手写 emit)。
- LLM 接口 + Pool 增 ChatModel();fakeLLM 加 cm 字段 + stub Eino 模型。
- 测试:compose 图编译运行 / compose 对话流式 / 开关关→走 runAgent。
顺带修真 bug:SubjectTaskStatus 原 sundynix.tasks.status 落在任务流通配
sundynix.tasks.> 内 → 状态事件被当成"幽灵任务"自我放大(实测污染 2300+ 条)。
挪到 sundynix.status.task + dispatcher 加空任务护栏。
验收:make test-go 全绿;live compose 路径 54 字答复 eval 1.00、默认路径
eval 1.00、幽灵任务 0 复发。剩余 branch/map/render 等节点逐步迁移后 graph.go 退役。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 10:42:50 +08:00
Blizzard
71102d2424
feat(dispatcher,gateway): Eino 采纳 Phase D —— 任务生命周期状态机
...
submitted → running → done / failed / timeout 显式状态机,根治"卡运行中
看不出来"(此前 Task.Status 只写死 submitted、从不流转)。
- contract:新增 SubjectTaskStatus 主题 + TaskStatusEvent + 状态常量
- shared/bus:PublishTaskStatus / SubscribeTaskStatus(core NATS pub-sub)
- dispatcher:StatusSink 接口 + Orchestrator.Handle 状态钩子——进入执行
→running、收尾 finishStatus→done/failed、整体超时上限 taskExecTimeout
=3min→timeout;经 sub 回写
- gateway:SubscribeTaskStatus 落 PG(Task 增 Detail 字段,AutoMigrate);
新增 GET /api/v1/tasks/:id 供 UI 轮询状态
验收:实测 submitted→running→done 流转 + PG 持久化 + 端点查询闭环;
make test-go 全绿。HITL 中断恢复 / 多智能体仍按场景待做。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-23 09:48:44 +08:00
Blizzard
dfa1e163bf
docs: 项目总体分析
2026-06-22 23:39:21 +08:00
Blizzard
fa2e2ebeeb
feat(dispatcher): Eino 采纳 Phase B —— ReAct 智能体 + MCP 工具自主调用
...
agent 节点带 autonomous:true 时走 ReAct(flow/agent/react),模型在
推理-工具循环里自行决定调哪些 MCP 工具,而非只跑画死的工具节点。
- 新增 eino/react_agent.go:mcpTool 把 MCP 工具(NATS)适配成
components/tool.InvokableTool;agentTools 暴露 wiki_search +
recall_user_memory(context 参数 user_id 服务端注入,不暴露给模型);
runReactAgent 流式回流答复,工具调用经适配器落 ExecEvent 轨迹。
- LLM 接口 + *llm.Pool 增 ToolCallingModel()(openai 组件实现
model.ToolCallingChatModel);fakeLLM 同步桩(返回 nil → 降级普通对话)。
- graph.go:agent 节点按 autonomous 开关分流 ReAct / 普通;自主 agent
不预注入画像(让其经 recall_user_memory 工具按需自取)。
关键修复:默认 StreamToolCallChecker 只看首个流片段,deepseek 常先吐
文本再给 tool call 致漏判 → 改 streamHasToolCall 扫整段流(命中率 ~0→7/7)。
验收:自主工具调用 7/7 命中,args={} 证明注入生效,完整闭环跑通;
make test-go 全绿。已知 eval 看不到工具结果会误判 agent(Phase C 修)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-22 17:35:19 +08:00
Blizzard
521902226c
docs(eino): 标记 Phase A 已完成
...
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-22 16:55:03 +08:00
Blizzard
d84b1eceb5
feat(dispatcher): Eino 采纳 Phase A —— llm.Pool 内部换成 Eino ChatModel 组件
...
把手写的 OpenAI HTTP/SSE 客户端换成 eino-ext/openai 的 ChatModel 组件:
- SetConfig 用激活配置构建 ChatModel(热更新=重建实例,读写锁保护)
- ChatStream → cm.Stream(StreamReader 逐 chunk);Chat → cm.Generate
- 对外签名零变更(Chat/ChatStream/StreamText/Ready/ModelName),
graph.go / report.go / memory_extract.go 不动
- Ready() 语义升级为「ChatModel 构建成功」,更准
- StreamText 降级桩保留
验收:make test-go 全模块绿;真实链路提交任务经新组件流式出答复
(28 字 + eval 1.00,Stream/Generate 两条路径都过)。
为 Phase B(BindTools 函数调用 + ADK)铺好底层组件。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-22 16:54:31 +08:00
Blizzard
763646d5c5
chore(dispatcher): 升级 eino v0.9.5 → v0.9.9 + 全面采纳迁移方案
...
- dispatcher 升级 cloudwego/eino 到 v0.9.9(go work sync 同步各模块
transitive 依赖,如 golang.org/x/term v0.44.0);编译 + eino 链路测试通过
- 新增 EINO_ADOPTION.md:从"只借类型"演进为 Eino-native 编排核心的分阶段
方案(A 地基 ChatModel 组件 → B ADK 函数调用 → C compose 编排归一 →
D 状态化执行:任务生命周期 FSM / HITL 中断恢复 / 多智能体)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-22 16:38:24 +08:00
Blizzard
8bcb90cdb2
feat(admin): 服务状态面板(基建/服务探活 + MCP 工具注册)+ mcp-go 工具注册表
...
管理端新增「运维 › 服务状态」:总览横幅 + 摘要数字 + 请求链路拓扑
(客户端→网关→NATS→调度→MCP,按健康三态着色)+ 应用服务卡(含探针
延迟)+ 基建磁贴 + MCP 工具按能力域分组(中文名/作用)。
探活机制(全走 NATS,无 HTTP):
- mcp-go/mcp-py 新增 list_tools 自省工具,能应答=在线 + 上报工具清单
- dispatcher 无端点 → 新增 NATS 心跳主题 sundynix.health.dispatcher
(ServeHealth 应答 model/ready/uptime),网关用 bus.Ping 探
- 网关 GET /api/v1/admin/status 并发聚合四探针 + 各项延迟
mcp-go 重构:switch → map 注册表(buildRegistry),dispatch 与 list_tools
共用单一事实源,杜绝漂移;每个工具带中文名 + 作用描述。mcp-py 同样补元信息。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com >
2026-06-22 16:38:10 +08:00
Blizzard
0edfc948ba
feat(memory): P2 读路径打分 + 桌面端记忆面板(看/改/删)
...
读路径(Generative Agents 公式的 Recency+Importance 两项,Relevance 待 P3):
- memory.Get 改为 rankProfiles:Score=0.4·Recency+0.6·Importance,按分降序、截断 top-30。
Recency=0.98^天 指数衰减;未评分行用兜底 importance=5(不被不公平遗忘)。纯函数 + 单测。
- 新增 Store.List(结构化、不截断)+ memory_list 工具。
桌面端记忆面板:
- gateway GET /memory(列表) + DELETE /memory?key=(软删),受保护组。
- api listMemory/deleteMemory;MemoryView 右侧从占位 → 真列表:按分排序展示
key/value/重要度/最近时间,可内联编辑(PUT)与删除(软删);左侧登记后自动刷新。
实测:PUT 两条 → GET 返回带 importance/last_seen 的有序列表(较新者靠前)→ DELETE 一条
(软删)→ 再 GET 已消失。rank/recency/兜底 importance 单测过;前端 tsc+构建通过。
至此记忆:召回(打分) + 历史 + 自动对账(P1) + 用户可管控(面板) 闭环。Relevance(Milvus) 留 P3。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-22 14:48:11 +08:00
Blizzard
1674252d81
feat(memory): P1 长期记忆升级 —— 异步攒批 Consolidate + 软删 + importance/last_seen
...
把"逐轮盲写抽取"升级为 Mem0 式对账(方案见 memory_industry_analysis.md 落地节):
mcp-go:
- Profile 加 Importance(1~10, poignancy) + LastSeenAt(为 Generative Agents 读路径
Score=w1·Relevance+w2·Recency+w3·Importance 铺路)。
- Upsert 收 importance + 每次置 last_seen(印证);新增 Delete(软删,BaseModel.DeletedAt
已具备,失效不物删可审计)+ Touch;memory_upsert 透传 importance、新增 memory_delete 工具。
dispatcher:
- extractMemory → consolidateMemory:一次 LLM 调用同时做 抽取+对账,输出
[{op:ADD|UPDATE|DELETE|NOOP,key,value,importance}];ADD/UPDATE→upsert、DELETE→软删;
sanitizeOps 防幻删(DELETE 须命中已有)/夹 importance[1,10]/同key保末个/丢 NOOP。
- 攒批:每 3 轮(per-session 计数)才 consolidate 一次,省成本,对齐 ChatGPT 周期整理。
从根上解决 exact-key 盲写的记忆腐烂。
验证:parseOps/sanitizeOps/parseProfile 纯逻辑单测;store 集成测试(真 PG)覆盖
importance/last_seen 写入 + 软删(live 0 / 物理 1);dispatcher -race 全过。
(注:完整多轮 LLM consolidate 未做实跑,属构造性验证 + 沿用已证 pool.Chat 模式。)
P2 待做:读路径按 Score(Recency+Importance) 排序/衰减/截断 + 桌面端记忆面板。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-22 14:27:16 +08:00