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