Commit Graph

36 Commits

Author SHA1 Message Date
Blizzard f8f7359723 feat(admin): 订阅管理页 + 修「余额列为 NULL 导致充值永不到账」
管理端「支付 → 订阅」:套餐配置 + 全平台订阅观测。配置时直接算出「一个周期
发几次、合计多少积分」,时长不能被间隔整除时橙字提示到期前会有空档 —— 让人在
配的时候就看见后果,而不是上线后才发现只发了一次。

顺带修了个真 bug,是拿真库验订阅时撞出来的(本地 42 个租户里 11 个中招):

  credit_balance_micro 是后加的列,早于它创建的租户行值为 NULL。而入账语句是
  「余额 + N」—— SQL 里 NULL + N 仍是 NULL,于是这些租户**充值永远不到账**:
  分录照写、余额不动、不报错。这条路径是充值/兑换码/退款/扣费/订阅发放共用的,
  不是订阅引入的问题。

三处修:
  - 5 处余额增减一律改 coalesce(credit_balance_micro, 0),新写入自愈;
  - 启动迁移回填存量 NULL(按账本求和,让「余额 = SUM(ledger)」重新成立);
  - 模型只加 default:0,**刻意不加 not null** —— 存量库有 NULL 行,AutoMigrate
    尝试 SET NOT NULL 会直接失败,而且它在回填之前跑,等于把部署搞挂。

回归测试先证明能失败(去掉 coalesce → 余额 0)再确认修复。第一版测试因为我给
模型加了 not null 而无法造出 NULL,恰好暴露了上面那个部署风险。

真库验证:回填后 42 个租户 0 个 NULL;那个"有分录但余额 NULL"的租户余额
2981 = 账本合计 2981.34。管理端页面显示真实订阅(已发放 3 次 = 首笔 + 补发 2 笔)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 16:28:32 +08:00
Blizzard 80d4aaf4ba feat(billing): 订阅制后端 —— 手动购买 + 周期发放积分 + 到期失效
规格(按需求):用户扫码买一个订阅周期,有效期内每 N 天发一次积分,到期即失效,
不自动续费。N 与每次发放额度都在套餐里配,后台可改。

为什么不做自动续费:微信 Native 扫码支付没有代扣能力,真自动续费要走「委托代扣」
——另一套产品与资质。与其假装有,不如把"到期即失效"这个语义做扎实。

两个决定,都写进了代码注释:
- **发放语义是累加而非重置**。每次刷新写一条 grant 分录、余额累加。重置型
  (月度配额清零)会让「余额 = SUM(ledger)」这条对账不变量变复杂,且有误清
  用户自费积分的风险。
- **订阅开通放在 store.MarkOrderPaid 内**,而不是各调用方。回调与掉单补偿两条
  路都经过它,放这一处才没人能漏掉;按 orderID 幂等,重复调用无害。

复用而非另造:订阅单与积分包单走同一条支付链路(下单/回调/查单/掉单补偿),
只是 kind=sub 且 credits_micro=0——积分不在付款时给,由订阅按周期发。

定时器每 10 分钟扫一轮,语义与幂等都在 store.TickSubscription 里,与手动触发
共用,不会两处漂移。停机期间欠下的发放会一次性补齐。

8 组测试。其中一条当场抓到真 bug:开通时原本无条件发一笔,同一订单重复开通
(回调重推/查单赛跑)会因序号自增绕过幂等索引,白送积分。改为开通也走与定时器
同一套排期判断——排期天然幂等。教训:幂等键要锚在业务时间轴上,不能靠自增序号。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 16:11:34 +08:00
Blizzard 373167b705 feat(admin): 补全平台任务观测 + 空间管理 + 基建加 MinIO/实时探针
对照已实现系统功能补 admin 缺失模块(feat/site):

- 全平台任务/运行观测:GET /admin/tasks(跨租户查 sundynix_task,join 租户名/提交人邮箱/评测,
  按状态/租户筛 + 状态计数;含 HITL 待审批=筛 waiting)+ TasksPage(状态卡+筛+表)。
- 空间(Space)管理:GET /admin/spaces(跨租户列 Space + 成员数子查询 + kind/归档态)+ SpacesPage。
- 基建观测:infra 加 MinIO(126 对象存储);postgres/redis/minio 从启动标志升级为实时 ping
  (blob.Ping/Postgres.Ping/Redis.Ping),能反映中途掉线。StatusPage 加 minio 标签、6 个依赖。
- 模型健康/熔断:确认 DashboardPage 已有「运行时链路态」渲染 m.health,无需重做。

验证:admin tsc + vitest 41 过、gateway build/vet/test 过;两新页浏览器实测渲染+优雅错误处理;
两新端点起临时 gateway 打真 PG 实测——tasks(counts+3路join)、spaces(成员数子查询)均返真数据。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 17:06:25 +08:00
Blizzard 5e5f6e9610 feat(billing): 人工退款入口 —— paid 单冲销积分+置 refunded(幂等)
PAYMENT_DESIGN §5 承诺却只留了 OrderRefunded 常量、无入口。补齐全链路:

- store.RefundOrder:订单 CAS(paid→refunded) 为主闸(重复退 changed=false 幂等),
  同事务记 adjust 负分录(ref=订单号)+ 回退物化余额。照抄 MarkOrderPaid 双闸范式;
  新增 idx_ledger_refund_ref 部分唯一索引(kind='adjust' AND ref<>'')做账本级兜底,
  与 grant 索引对称、不与 admin 手工校正(ref 空)冲突。
- 积分若已消费,回退后余额可为负(人工退款预期,账本仍自洽,后续消费被硬拦截)。
- handler AdminRefundOrder + POST /admin/orders/:id/refund(admin 组已挂 Audit 留痕);
  真渠道钱款原路退回需 admin 另在商户后台操作,本地仅冲销积分与订单态(不接自动退款 API)。
- admin 订单流加「退款」按钮(仅 paid 单可见,二次确认+填原因)。
- 测试:RefundOrder 冲销+幂等、只退 paid 两个不变量测试(sqlite 真 DB,余额=账本之和)。
  gateway build/vet/test 全绿,admin tsc 干净。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 13:35:31 +08:00
Blizzard 8e05f4b3fe feat(billing): 支付 P5.1 —— 订单骨架 + 兑换码渠道全闭环
按 PAYMENT_DESIGN.md 施工。零资质渠道先把「订单→入账→对账」的骨架跑真,
微信 Native(P5.2)进来只是多一个 adapter。

- store/payment.go:CreditPack(admin 可配包)/PaymentOrder(支付侧事实源,兑换
  也写单,全部充值一个查法)/RedeemCode(SDX-XXXX-XXXX-XXXX,crypto/rand,剔除
  易混字符)。三模型都不标 isTenantScoped——订单归计费租户,与活跃租户可能
  分叉,插件自动注入会写错归属(RecentRuns 同款教训),显式赋值+显式过滤。
- Redeem 单事务:码 CAS 占用(unused→used 只成功一次,幂等主闸)→建已支付
  订单→账本分录+物化余额。credit_ledger 加 (kind,ref) 部分唯一索引兜底
  ——GrantCredits 此前对 ref 零约束,回调 at-least-once 就是重复入账事故。
- 路由:/billing/packs|orders(查,全员) + /billing/redeem(≥member+审计);
  admin /redeem-codes(生成/列表) + /packs(配包)。
- Web 面账单页:兑换码输入(viewer 不摆输入框,真闸在后端)+最近充值订单流。
- 测试:码形态/200 样本无撞码/参数边界;go+tsc+vitest 全绿。
  live 13 项:生成→核销余额精确+100→重core 400(主闸)→DB 直插重复 ref 被唯一
  索引打回(兜底闸实证)→viewer 403→balance==SUM(ledger) 不变量→审计留痕→
  浏览器 UI 兑换 100→200+订单流展示。

已知余项:admin 生成码暂只有 API(admin 页 UI 下一刀);微信 adapter=P5.2。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 10:18:14 +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 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 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 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 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 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 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 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 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 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 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 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 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 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 597665f3c8 feat(admin): 计价配置(按模型·分输入/输出单价)—— 计费比率配置落地
计费需 token↔真钱比率,配置归管理端。本次落地"按模型·分输入/输出"粒度:

后端(gateway):
- store.Pricing 模型(BaseModel + model_id 唯一 + input_per_1k/output_per_1k + currency),
  AutoMigrate 建 sundynix_pricing;ListPricing/UpsertPricing(OnConflict model_id 覆盖)。
- admin handler:GET /admin/pricing 列表、PUT /admin/pricing 设置(校验非负,币种默认 CNY),
  挂在 RequireAdmin 组下。

前端(admin):
- api:listPricing/savePricing(带 Bearer)。
- PricingPage:列出所有已登记模型(chat+embedding),每行可编辑 输入/输出每1K单价 + 币种,逐行保存。
- routes 新增「计价」页(配置组)。

实测:PUT→ok;GET 返回正确行;重复 PUT 同 model_id 仍 1 行且值更新(upsert 生效);表自动迁移。
前端 tsc 干净。下一步可做用量计量 × 单价折算(真正计费)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 11:25:24 +08:00
Blizzard 5d76652bff refactor(kb): 文件主表 + 文档关联改用雪花ID(弃用按名关联)
把 sundynix_doc 明确为"文件主表",补齐文件基础信息字段;
文档间 [[双链]] 改为以 Doc.ID 关联,查询/渲染一律按文件 ID。

store:
- Doc 增 Ext(后缀)/MD5(内容指纹) 字段;ObjectKey 即"存放链接"
- DocLink 由 (FromName,ToName) 改为 (FromID,ToID,ToName)
  · FromID/ToID 关联 Doc.ID;ToName 保留用于悬空链接展示与回填
- SaveDoc 返回新建/更新文件的雪花 ID(供建链)
- 新增 GetDocByID(按 ID + owner 取正文,防越权)
- ReplaceDocLinks 以 fromID 重建出链,按 [[名称]] 解析目标 ID
- 新增 ResolveInboundLinks:目标入库后回填指向它的悬空链接
- ListLinks 只返回已解析(to_id 非空)的 ID→ID 边
- migrateDocLinkToID:旧按名双链表无 from_id 列则重建为按 ID 关联

gateway/handler:
- runIngest 计算 ext/md5,SaveDoc 取回 ID 后建链 + 回填悬空
- KbDoc 改为 GET ?id=(按文件 ID 取全文)
- KbVault 返回 id+ext;KbLinks 返回 from/to 为 ID

desktop:
- VaultDoc 增 id/ext;getDoc(docId) 按 ID 取正文
- VaultPanel 选中态/正文/反链/关系图改用 ID,名↔ID 双向映射
  渲染;保存笔记后按名定位回其新 ID

验证(gateway+PG+MinIO 实测):vault 带 id+ext;双链 ID→ID 且
A→B 悬空链接在 B 入库后成功回填;按 ID 取大文档(15006字)从
MinIO 完整取回;跨 owner 按 ID 取文档 404(隔离生效)。桌面端
文库 Tab 按 ID 选中/载入/反链渲染正常,无控制台报错。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 09:38:02 +08:00
Blizzard 4fd44380aa refactor(store): DB 规约统一 —— 雪花字符串 id + created/updated/deleted_at 软删
所有数据库映射结构体收敛到统一基类,清理混乱。

- store/base.go:BaseModel{ID string(雪花,bwmarrin/snowflake) PK, CreatedAt, UpdatedAt,
  DeletedAt gorm软删index} + BeforeCreate 生成 id + NewID()。
- 全模型嵌入 BaseModel:User/Task/LLMModel/KB/Doc/Agent(去掉各自 ID uint/CreatedAt);
  Task 业务 id(task_xxx)挪到 TaskID 唯一列,主键统一雪花。
- 模型 id uint→string:admin :id 路由、SetActiveModel/DeleteModel/SaveModel、modelBody.ID。
- 一次性迁移 migrateLegacyIntIDs:检测旧整型 id(AutoMigrate 不改主键类型)→ 备份
  sundynix_model 行(唯一不可再生的 API 密钥)→ 删旧表 → 按新规约重建 → 回灌模型(新雪花 id)。
  其它表(User/Task/KB/Doc/Agent)为可重建测试数据,重置。
- Doc 预埋 Size/Preview/ObjectKey 字段,DocLink 表(为后续 B/C)。

验证:重启 gateway → 日志"回灌 2 条模型配置";PG sundynix_model.id=varchar、有
created_at/updated_at/deleted_at;DeepSeek/百炼 密钥保留(keylen 35);admin 列表返回
雪花 string id + 脱敏 key;健康五灯全绿。gateway build 通过。

注:mcp-go 的 sundynix_user_profile(Profile) 模型尚未套同规约,待跟进对齐。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:45:52 +08:00
Blizzard 4bf614a07c feat(studio): Agent 编排服务端保存 + 我的编排列表(owner 隔离)
编排好的 Agent 现在可命名保存到服务端、跨会话可见;左侧「我的编排」列出本人全部。

- store: sundynix_agent 表(owner+name 唯一,Graph=React Flow {nodes,edges} JSON 含布局,
  UpdatedAt);ListAgents(最近在前)/SaveAgent(OnConflict 覆盖图+时间)/DeleteAgent。AutoMigrate +Agent。
- gateway: GET/POST/DELETE /api/v1/agents(owner 隔离,身份取自 X-User-ID)。
- 前端:api listAgents/saveAgent/deleteAgent;StudioView 左面板下半区「我的编排(N)」列出本人编排,
  点击载入(含布局)、悬停删除;工具栏 编排名+保存(服务端),去掉 localStorage 模板。

验证:curl 保存「合同审查流程」→ wt 列表含之,alice 列表为空(隔离)。Preview:示例图填名「尽调问答
Agent」保存 → 左「我的编排(2)」即时出现两条、可点载入。tsc+vite+gateway build 通过;重建 .app。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:23:03 +08:00
Blizzard 55c85302b6 feat(kb): Obsidian 式文库 —— 笔记浏览 + [[双链]] + 反向链接(Tab 化)
把知识库做出 Obsidian 感:入库的每份文件/笔记留原文,可浏览、可读、可互链。

- store: sundynix_doc(owner+kb+name 唯一,存原文),SaveDoc(OnConflict 覆盖)/ListVault。
- gateway: runIngest 留存原文(文件用文件名、文本用首行作笔记名);GET /kb/vault?kb= 取文库(owner 隔离)。
- Markdown 组件:解析 [[名称]] / [[名称|别名]] → onLink 可点(Obsidian 双链)。
- KbView 改 Tab(入库 / 文库 / 检索 / 图谱):
  - 文库 = 左文档列表 + 右 Markdown 笔记([[双链]]点击跳转)+ 反向链接面板(扫全库 [[本笔记]])。
  - 检索、图谱各占整页;图谱放大到 460。

验证(Preview):入两条带 [[双链]] 的笔记 → 文库列出 2 篇 → 打开「项目A概述」渲染出可点的
[[模块X]][[模块Y]] + 反向链接显示「模块X」→ 点 [[模块X]] 跳转到该笔记、其 [[项目A概述]] 亦可点。
curl 证隔离:alice 取 wt 的 vault → 空。tsc+vite+gateway build 通过;重建 .app 重启窗口。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 15:06:31 +08:00
Blizzard 3a175e46f3 feat(kb): 批量文件入库(文件列表) + 项目/案件知识库 + owner 作用域隔离
回应三点诉求:一次入一批文件、按文件夹/项目/案件组织、且只有我能查我的库。

隔离(核心):知识库实际分区键 = "owner/name",owner 由网关从 X-User-ID 注入,
客户端只发库名、发不了 owner —— 故任何人都只能查到自己 owner 前缀下的数据。
- gateway: scopedKB(owner/kb) 注入 ingest/search/graph;ingest/search/graph 全部带身份头。
- store: sundynix_kb 注册表(owner+name 唯一 + kind),ListKB/EnsureKB(OnConflict DoNothing)。

项目/案件组织:
- gateway: GET /kb/list(owner 隔离列表)、POST /kb/create(folder/project/case/general);
  入库时 EnsureKB 自动登记。
- 前端: KbView 顶部知识库下拉 + 新建(项目/案件/文件夹/通用),检索/图谱/入库都绑定所选库。

批量文件:
- 前端: 选择文件(multiple) + 选择文件夹(webkitdirectory) + 拖拽一批 → 每文件一个 job,
  文件列表实时显示各自状态(排队/解析/向量化/写入/抽取/完成/失败)+ 完成/失败计数。

验证:curl 证隔离 —— wt 入 default→可检索;alice 查同名 default→[] 空;alice 列表不含 wt 案件库。
Preview 证 UI —— 知识库下拉含 案件-2024-001(案件)+default(通用)、owner 隔离徽标、批量/文件夹按钮。
tsc+vite+gateway build 通过;重建 .app 重启窗口。

注:身份目前来自 X-User-ID 头(可信前端),生产应换 JWT 鉴权中间件——隔离机制(owner 前缀)已就位。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 14:50:33 +08:00
Blizzard 3b54e59ecf feat: embedding 配置搬上控制面 — 数据源页可视化配置 + 热更新
embedding 从 env 改为控制面驱动(持久化+可视化),复用 chat 模型同套范式:
配置控制面泛化为按 kind(chat/embedding),加 embedding kind。

- shared: 配置 subjects 泛化 sundynix.config.<kind>.get/.updated;bus 方法改 kind 参数
  (RequestConfig/ServeConfig/PublishConfigUpdated/SubscribeConfigUpdated)
- gateway: sundynix_model 加 kind 列(每 kind 唯一激活)+旧行回填 chat;admin 按 kind
  增删改/激活/列表,测试连接 embedding 走 POST /embeddings;main 按 kind ServeConfig;
  变更广播各 kind
- dispatcher: 取 chat 配置(kind 化)
- mcp-go: rag.Engine.SetEmbedding 热更新(RWMutex);main 取/订阅 embedding 控制面配置
  (覆盖 env)
- admin 控制台: api 按 kind;抽出复用 ModelManager;ModelsPage(chat)+新 DatasourcesPage
  (embedding + 向量/图库占位);routes 数据源页就绪
- 验证: 全模块 build✓ + e2e PASS + 控制台 npm build✓;live 全跑通——chat(DeepSeek 回填
  kind 仍工作);mcp-go 不带 EMBED env 启动→控制台配 embedding(百炼)→测试连接✓→激活
  →NATS 热更新 mcp-go→入库+语义检索'存向量的数据库'→Milvus;浏览器数据源页拉到激活配置

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 17:25:54 +08:00
Blizzard 3c65189f30 feat: 配置控制面 + LLM Pool 接第三方在线 API (OpenAI 兼容)
后端从占位回显变为真实生成:管理员经控制面登记/激活模型,Gateway 经 NATS
下发,Dispatcher 热更新 LLM Pool,Eino 图用 OpenAI 兼容流式真实推理。

- shared: contract.ModelConfig(provider/base_url/api_key/model) + 配置 subjects;
  bus.RequestModelConfig/ServeModelConfig/Publish/Subscribe ModelConfigUpdated
- gateway: store.LLMModel→sundynix_model(AutoMigrate,唯一激活) + admin REST
  (GET/POST/active/delete/test models, api_key 脱敏) + main ServeModelConfig +
  变更广播; 路由 /api/v1/admin/models*
- dispatcher: llm.Pool OpenAI 兼容 SSE 流式客户端(ChatStream) + 热更新配置 +
  未配置则降级桩; poolModel.Ready()?真实流式:注入记忆的桩; main 取配置+订阅
- 开发期接在线 API 不拉本地模型(见 llm-provider-strategy memory)
- 验证: 4 模块 build✓ + e2e PASS; mock OpenAI 服务 live 跑通——登记/测试连接✓/
  激活→NATS 热更新→提交→真实 SSE 流出 mock 回复, mock 日志证明端点被调用且
  注入画像(老王)进了模型上下文

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:41:39 +08:00
Blizzard e5fa0ae36c feat: Gateway store 桩换真实 GORM/Postgres + go-redis (含自动迁移与优雅降级)
第 2 层网关持久层落地,遵守 sundynix_ 表名前缀 + AutoMigrate 约定。

- store: GORM(NamingStrategy 前缀 sundynix_/单数) → User=sundynix_user, Task=sundynix_task
  启动 AutoMigrate;go-redis/v9 滑动窗口限流(Incr+Expire,按 IP)
- 优雅降级:连不上库则 warn 继续(不 fatal),保证无 Docker 的 make demo 仍跑通
- handler: SubmitTask 持久化任务(best-effort),Billing 真实读库返回 tasks_submitted
- main: OpenPostgres/OpenRedis 读 POSTGRES_DSN/REDIS_ADDR 环境变量
- 验证: 4 模块 build ✓;e2e 3 测试 PASS;live 双路径(真实库持久化 + 坏DSN降级)实测通过

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 11:43:53 +08:00
Blizzard c7a02c3905 feat: 初始化 sundynix-agentix 分层式 AI Agent 平台脚手架
5 层 + 1 条 NATS 零拷贝消息总线的 monorepo(Monolith First → Microservices Morph B)。
纵向主干(任务流 + Token 流回流)已真实跑通,横向各层能力为带注释的桩。

已贯通(real code):
- sundynix-shared: 共享契约 + JetStream/core NATS 真实收发(bus) + 内嵌 NATS(devnats) + e2e 测试
- sundynix-gateway: Gin 接入 + DSL 解析组装 + NATS Publish + SSE 流式输出
- sundynix-dispatcher: NATS 消费 + Eino Orchestrator 流式回流 + 熔断器 + LLM Pool 占位流式
- 链路: HTTP POST → DSL → sundynix.tasks.* → Dispatcher → Token 经 sundynix.streams.<id> 回流 → SSE
- 基础设施: docker-compose(nats/postgres/redis/neo4j/milvus) + Makefile(make demo/e2e)

待填(桩):
- Eino 图编排 compose.NewGraph、LLM Pool 接 vLLM/Ollama
- Gateway store 换真实 pgx/redis
- sundynix-mcp-go: Bleve+Milvus+Neo4j 混合检索 / UniOffice / 外部 API
- sundynix-mcp-py: gVisor 沙箱 / MinerU(PaddleOCR) / Docker 解释器
- sundynix-desktop: React Flow 画布 → DSL 导出 → SSE 展示
2026-06-10 11:00:29 +08:00