Commit Graph

168 Commits

Author SHA1 Message Date
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 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