Commit Graph

48 Commits

Author SHA1 Message Date
ThearchyHelios 2c2672cc04 时间线认得核查模型的输出被截断(verify_truncated):标题说「核查模型的输出达到上限(N token)被截断」,副标题说这次核查未能完成或已按截断前查到的结果收尾,下一步指到换不带推理的模型,入口落到节点的模型
decode.ts 按结构化字段(max_tokens、delivered)解读,不从原话里认,缺字段时只给不带数的标题;文案放在 terms.ts 新的 VERIFY_TRUNCATED_TEXT。check-decode 加一节:先核对 verify_agent.py 还是这么发的,再核对三种取值(没交出结果、退回 4096 后交了结果、缺字段)的标题、副标题、下一步、入口和原话。

跑完之后的复核层(engine/review.py 的 WARN_CODES)登记 verify_truncated:同 verify_limit 记成它自己的 kind、degraded,不落成笼统的 warn,也不触发「重跑一次大概率能好」。
2026-10-06 13:06:18 +08:00
ThearchyHelios cc166fefb8 合入期 4 审批质询(前端):报告句末与划选原文浮出「质询」,问答串与提问框挂在审批卡和运行详情,回答按片段与出处渲染;同一张卡挂两处、换了审批、报告改写前的问答各归其位;时间线解读 approval.inquiry,等待中来质询阶段仍是等待 2026-10-06 11:32:20 +08:00
ThearchyHelios 4c3ba8bc1b 期 4 复核修正(第 1 轮,前端):核查缺口分「核查未通过」「核查未完成」两组并补上证据页、印章的检查,写目录预告推断时算上被挡下的同项指标,反方审稿概况的说法与排布改准
- 出具缺口:双盲复算的「指标无法复算」是没核成,不再和两路不一致、结论被推翻一起叫「核查未通过」——splitIssuanceGaps 分成 failed「核查未通过」、unfinished「核查未完成」两组,时间线那一行、证据页、成果节点印章的悬停说明都按先未通过、再未完成、最后校验不完整排。check-decode 按服务端 GAP_* 和 _refuted_gap 的原话加出具契约的前缀拼出缺口再分组,服务端改了说法先变红;新预览用例 verify-gaps 画真的 EvidencePane 和成果节点卡片,check-verify 断言两组各列各的、横幅的「校验未全部完成」里没有它们、印章说明的先后,以及 390px 深浅色不溢出、可读(以前去掉这两处的实现所有检查照样全绿)
- 时间线:无法复算那一行只说「已记为缺口」,不再一律说「按缺口计入出具」(探索运行不出具,接在侧枝上的缺口进不了出具);dual_check_catalog 带值的写法补上检查,和审批卡「会记为」用同一个函数、一字不差
- 双盲复算审批卡:工件里没有合并预告时(老工件、组合超上限、还没取到),被目录挡下、带着写到哪的同项指标也算进来——业务日期、去重规则说法不同就说都不记(服务端先归拢、轮不到对目录),有效记录条件同项上有被挡下的只说可能记、也可能不记;推出来的条目标 data-inferred,结尾换成「实际写入以时间线上的记录为准」。check-verify 用新夹具 time_rejected 断言销售额不再说「会记为」
- 反方审稿审批卡:「没能」改「未能」,提示不再说「没有真正查询数据」(触顶的查过),原因补上报告未引用数据库查询,时间线的下一步不再建议调高上限(这一条不含触顶的);「N 句未能反查」挪到降档说明后面,不再插在「出具会降档」标记和它的说明中间,句数不超过「无法判断」;不在出具路径上的那句写明是反方审稿节点(审批卡上的「本节点」读着像审批节点)。ChallengeSummary 收 runClass,给了就照它、不另取;check-challenge 的夹具改成和服务端约定一致(未能反查 2 句、无法判断 3 句),另断言给了运行类别时另取失败也照样写降档、不发请求
2026-10-06 09:31:28 +08:00
ThearchyHelios e35d19d014 出具缺口里核查报上来的另起一组「核查未通过」,时间线认得期 3 留下的三个核查日志代码(前端)
- 时间线的出具那一行、成果节点卡片的出具印章、证据页的出具横幅:双盲复算、反方审稿报上来的缺口(「双盲复算「节点名」:」「反方审稿:」开头,按节点类型的前缀认,名字里带这几个字的别的节点不算)归在「核查未通过」下,不再和「校验不完整」混在一起——它们是核查查出来的问题(两路结果不一致、结论被推翻、指标无法复算),不是校验没做完。分组只有 run/decode.ts 的 splitIssuanceGaps 一处,三个地方共用
- 时间线解读 dual_check_unchecked(哪几个指标无法复算、各自的原因)、challenge_unreviewed(几句没能反查、第一句的原因)、challenge_sources_lost(几句只查了部分数据源、没查成的源):以前认不得,只能照原话显示;记入数据目录那一行带上写进去的值,写法同审批卡的预告
- 反方审稿的时间线文案从 DUAL_CHECK_TEXT.challengeLog 挪到 CHALLENGE_TEXT.log(各节点的文案放在各自的块里),decode.ts 跟着改引用
- 工件种类叫法补上 dual_check(双盲复算对照)、challenge(反方审稿记录),工件列表不再显示键名
- check-decode 补一节:三个日志代码、出具缺口的分组和顺序、前缀不重复节点类型、缺字段的老日志不崩
2026-10-06 07:53:18 +08:00
ThearchyHelios 23cdc986fc 审批质询(前端)第一步:质询的类型与接口、时间线解读 approval.inquiry
- types.ts 末尾按前后端约定加 Inquiry、InquiryRequest/Response、查询与快照的类型,字段名一个不改
- api/inquiry.ts:提问(POST,不设超时,服务端自有时长上限)、列出问答、取质询引用过的查询快照(外层包不包一层都交给界面认)
- decode.ts:approval.inquiry 不再落进「未识别的事件」,解读成顶层一行「审批人质询」:标题带问题前 40 字,副标题说回答完成 / 不完整(写明哪个上限)/ 未能回答、查了几次,封存之后问的写明封存后追加;缺字段、类型不对的照实少说,不拿 0 顶查询次数
- check-decode 断言上述说法与缺字段的兜底;check-trace 加「运行停在审批时来了一条质询,相位仍是等待」:等待段不被收掉、执行时长不涨、恢复后照常跑完、每个前缀上 trace 与 decodePhase 一致
2026-10-05 18:46:03 +08:00
ThearchyHelios 52baa0d65c 双盲复算前端复核修正(第 1 轮):高亮按服务端真实的差异输出做,「记入数据目录」只在服务端真会写时有条件地说,后果和提示按节点的处置说
审批卡
- 对照两条 SQL:text_diff 是规范化之后的行(列名带表名、条件并成一行),和原文逐行对不上,原来按「两边逐行相等」高亮在真实输出上一处都标不出。改成两段原文按行做一次 LCS(不看空白、大小写和行尾逗号分号)高亮不同的行,一行都对不上时不乱标;text_diff 分原流程、盲算两栏,每行高亮(窄栏上下排)。
- 「只差一个方面、会记入数据目录」:照服务端 dual_check_facts 的门槛收紧——不等价、没用兜底、差异恰好一条(几个输入都对不上时合并后至少两条,所以也就是只有一个输入对不上),且是时间列、筛选条件或聚合;按这三种分别有条件地说会记成哪一项,选「两边都不对」不记、已有人工确认的不覆盖、以时间线为准。时间范围、两条筛选、关联、解析失败、多个输入合并都不再说。
- 后果和提交后的提示:payload 不带 on_reject,从这次运行的图快照里按节点 id 读「未采用原流程时」;判为失败时写明采用盲算或都不对会终止运行、提示也照实说,缺省时不再挂那句,读不到时有条件地说、提示不下结论。
- 只有解析失败时和摘要阶段一样按兜底说(固定那句 + 解析的原话),默认展开对照;兜底的原话另有原因(比较超时)时附在下面,一边没有 SQL 时不说「两条 SQL 的写法不同」。
- 认不出任何指标时留一个「驳回并终止」(approved 为假),运行不再一直挂着。

时间线
- dual_check_catalog 的 item 是目录项的键(business_date 等),按数据目录的叫法写成「「orders」(shop)的业务日期」,认不出的键不显示。
- dual_check_limit 的 metric 是指标 ID,不再当名字加引号。
- challenge_skipped 的 no_report 是报告文档读不出来(报告节点找不到时节点直接报错),说法和下一步改成去查报告节点有没有执行成功、在不在执行到的分支上。

检查器
- 字段名和服务端校验报错的叫法(schema.py 的 DUAL_CHECK_FIELD_LABEL)对齐,处置选项以服务端的短说法开头;check-verify 在服务端合入后逐项比对。
- 盲算模型移出高级选项:时间线「模型相同」的定位要找得到它。
- 上游有几张口径卡时写明必须选(服务端校验会挡住发起运行),不再说「建议指定」。
- 「跟随设置」的当前值每次打开检查器都重取,改了设置回到画布不再是旧值。

测试
- 夹具里的差异说明换成 explain_sql_diff 对同一对 SQL 的真实输出,新增几对 SQL 的变体;backend/tests/test_dual_check_fixture.py 核对夹具和真实输出一致,并可重新生成。
- check-verify 补真实形状下的高亮、各种差异下「记入数据目录」说不说、只有解析失败和兜底原因、按处置说后果和提示、认不出指标时的驳回、叫法比对、多张口径卡、设置重取;check-decode 改用服务端真实的日志形状。check-verify 的耗时估计按实测改成 45 秒。
2026-10-05 16:10:55 +08:00
ThearchyHelios 4d29b67cef 双盲复算前端(一):时间线解读双盲复算和反方审稿的全部日志代码,补类型和文案
- decode.ts:约定里的 12 个日志代码(dual_check_* 7 个、challenge_* 5 个)按 code 和结构化字段说成标题、副标题和下一步,
  不从原话里认;后端发成 info 的(一致、裁决、记入数据目录、审稿完成)也上时间线,否则这两种节点在时间线上什么都不说;
  有缺口、有推翻时用提醒色,完整对照和审稿记录的工件可下钻;老日志缺字段、类型不对时不崩、不瞎填数
- check-decode:每个代码一条断言,另有换一种取值和缺字段两组
- types.ts 末尾加双盲复算的审批 payload、对照工件、节点配置和设置里核查一组的类型
- terms.ts:DUAL_CHECK_TEXT 写全检查器、节点摘要、审批卡、时间线的文案(反方审稿日志的说法暂放 challengeLog);
  VERIFY_SETTING_TEXT 顶层和服务端 VERIFY_SETTING_LABEL 的键和叫法一字不差,其余放 section
- 去掉双盲复算节点说明里的「功能开发中」和节点卡片上的占位摘要
2026-10-05 13:54:45 +08:00
ThearchyHelios 536c9de116 证据目录的省略警告照实说省略了什么:放不下不一定省略了查询。真实运行里查询全部列出、只是有的只列前几行,警告却写「0 次只列编号和摘要()。写作模型看不到这些查询的明细」。现在分三种写:有查询只列编号和摘要的照旧;查询都列出、有的只列前几行的写「N 次查询都列出了数据,其中 M 次只列前 3 行」并按来源列次数;只省略了表和字段说明的照实说。事件多带 compact、compact_rows,各来源带 compact;时间线按同样三种写标题。另外把测试夹具、注释和 check-decode 里沿用的那次运行的真实查询次数换成虚构的次数或概括说法 2026-10-04 12:28:07 +08:00
ThearchyHelios ee0700eca6 合入 FA:结构化抽取截断重试、写作清单按模型窗口定预算并按来源均衡、缺失断言交回重写、日期时间按可读格式显示 2026-10-04 11:43:21 +08:00
ThearchyHelios 546d0e9326 没有数据目录的大库:助手「参考了 N 张表」那一行提示先起草目录。真实运行里的大库一张目录都没有,挑表和 SQL 检查都用不上目录,用户也不知道该先去起草。服务端 context 操作的每个数据源多带 source_id 和 catalog_tables(去掉驳回项后还有内容的表数);前端 copilotContext 在要挑表的大库 catalog_tables 为 0 时给出下一步「这个数据源还没有数据目录,起草后挑表和 SQL 检查会更准」,入口直达 /data/catalog/:sourceId(几个库时点名,老服务端不给目录表数时不提示)。check-decode、check-stream、check-chat 补断言 2026-10-04 11:28:49 +08:00
ThearchyHelios 799c80b97e 清单有省略时不许断言「没有数据」:写作规则加一条,核对认出缺失断言交回重写一次,仍有的只标注
一次真实运行里写作者只看到上百次查询里的前二十来次,却写出「某类数据缺失、本轮没有查到任何结果」这样的断言,可那个 Agent 实际查到了上百条记录。

- 写作规则:清单有省略、或有来源的结构化结论为空时,不得断言没有查到、不存在、缺失、没有数据,只能写「本报告未覆盖」并说明依据不足
- 组装文档:清单有省略时按一组明确的说法认出缺失断言(没有查到、未查询到、查不到、没有任何记录、数据为空、数据缺失、不存在相关记录、暂无数据等;「不存在明显差异」「不代表没有数据」这类不算,代码块不查),记新违规 absence_claim,带 span、所在片段和原句
- 和句中的 [[see:]] 一样归入 REWRITE_ONLY:交回重写一次,重写后仍有的只标注、不判失败;只剩这几类时不再按最多重写次数反复重写。仍有时报告节点发 report_absence_claim(带 count、texts),运行复核算作缺口
- 证据面板的违规清单标「缺失断言」并写明为什么依据不足,时间线给 report_absence_claim 专门的一行,文案在 terms.ts;check-decode、check-evidence 补断言
- 没展开的查询的摘要在没有表结构快照时从查询快照里的 SQL 认表名
- 回归测试:合成的门店、会员、卡券、活动库真跑「4 个 Agent 并行查库 → 报告」(查询次数虚构、分布悬殊),覆盖抽取截断重试、两次截断记空值、清单均衡和缺失断言的重写
2026-10-04 11:28:23 +08:00
ThearchyHelios 4e876ff9d8 写作者的出处清单按写作模型的窗口定篇幅,放不下时分优先级、按来源均衡,省略了什么照实说
一次真实运行里证据台账有上百次查询(4 个 Agent 查询次数悬殊),清单写死 12000 字,十万字以上的目录被截到一万两千字以内,只有前二十来次查询进了写作者的视野;查询按编号排,前面的 Agent 占满了预算,有的 Agent 一次都没露面。

- 预算:写作模型上下文窗口的 40%(字数按 1 字约 1 token 保守算),窗口先看模型接入里给这个模型登记的 context,再看模型目录;拿不到按 128K(和 Agent 护栏同一个保守值),即 51200 字;下限 12000、上限 160000
- 第一优先:各 Agent 经按出处核对交来的结构化字段(换成报告目录里的全局编号,值按单元格引用的渲染规则写,给出可照抄的引用标记)、口径卡指标和运行输入,一律列出;结构化结论为空的来源写明不代表没有数据
- 查询按产出它的节点分组、轮流展开:字段引用到的查询先展开,再给每次查询列前 3 行,有余量再补到 20 行;表和字段、知识库检索最多占预算的四分之一
- 没展开的查询列出编号和一行摘要(表、行数);约定只能在句末写 [[see:Qn]] 作依据,不猜行列、不凭摘要写数,数字照样逐字核对
- 清单开头明说目录有省略、各来源各省略了几次;报告节点发 report_catalog_trimmed 警告(带 queries、omitted、nodes),时间线给它专门的标题和下一步,运行复核算作缺口
- 放得下时清单和以前一字不差(多出的只有结构化字段那一节)
2026-10-04 11:17:13 +08:00
ThearchyHelios a43c530fb8 结构化抽取被截断时加大额度重试,仍截断就照实说是额度在推理阶段用完了
一次真实运行里 4 个开了按出处核对字段的 Agent 抽取全部失败:抽取关掉思考、额度 4096,可经 OpenAI 兼容接口接入的推理模型关不掉思考,额度在推理阶段就用完了(openai 抛 LengthFinishReasonError);日志却叫人「稍后重试、去设置测试该模型」,两样都不解决问题。

- 额度默认至少 16384,节点配了更大的用节点的
- 认出输出被截断:openai 的 length limit 类错误、finish_reason=length、Anthropic 的 stop_reason=max_tokens;被截断时额度翻倍(最多 32768)重试一次,每次调用各记一行,被截断那次的用量照样记账
- 服务方嫌额度太大(400,报错里有 max_tokens 一类字样)时退回 8192、4096,不能因为额度提高反而抽不成
- 两次都截断:新日志 code agent_field_truncated(带 fields、max_tokens),原话说清额度在推理阶段用完、结构化结论没能写出来,下一步是换非推理模型或调大最大输出 token;字段照旧记空值、不以 0 代替。非截断类的失败照旧是 agent_field_unverified
- 时间线给 agent_field_truncated 专门的标题、副标题和下一步,入口落到该 Agent 的最大输出 token;check-decode 补断言
2026-10-04 11:02:06 +08:00
ThearchyHelios 15d5caf19f 运行时间线:查询那一行用 SQL 检查清单列出 tool.end 带来的检查结果,副标题数一下错误和提醒;并行的两个查询节点用同一个工具时按「节点 + 工具名」配对,结果不再配错节点(A1);指标所依据的查询没通过检查那一行读结构化字段(受影响的指标、问题、检查结果),「打开设置」改为「打开「取数」的 SQL」,落到来源查询节点的 SQL 那一栏而不是口径卡(B2);口径卡不完整、合并查询的警告也改读字段,不再按中文原话解析;助手自查的 SQL 检查问题和证据面板共用 lib/sqlcheck 的读法,关系编号不再丢失 2026-10-04 02:18:45 +08:00
ThearchyHelios adcde155e1 合并查询的预览不再被标成「查询已达行数上限」:合并结果没有截断、只是预览存了前几行时记为「此处仅为预览」,表格脚注写「显示前 5 / 6 行」;只有合并结果本身超过上限才说截断 2026-10-04 02:18:32 +08:00
ThearchyHelios 0c65b4bea6 助手「参考了哪些表」展开后,按需求挑过表的数据源排在最前面(请求里点名的库不再压在一串小库后面);带字段的表和只提供了表名的表分开数:标题写「参考了 26 张表的字段」,副标题写「另有 206 张表只提供了表名」 2026-10-04 02:18:14 +08:00
ThearchyHelios 9c02833544 数据目录 F3 / B4 界面:目录页顶部说明「这个数据源共有 N 张表,只探查了前 200 张」,写明为什么只取了这些、其余的助手也看不到,下一步回到数据源卡片换 schema 或缩小范围后重新探查;助手「参考了哪些表」那一行截断了的库写「共 N 张,只探查了 200 张」;补检查脚本的断言 2026-10-04 01:25:19 +08:00
ThearchyHelios 3ece89872b 数据目录 F3 / B1 界面:运行横幅、时间线和版本历史把 Agent 可能查询的表和直接引用分开说——直接引用写「有变化」,可能涉及写「Agent 可能查询的表有变化」并另起一组;版本历史按新的发布记录两组列出,发布之后才建目录的表也列;补发布记录两种写法的单测和三个检查脚本的断言 2026-10-04 01:24:50 +08:00
ThearchyHelios cece781b5a 合入集成分支(阶段 2A 数据剖析、4B 剖析与 SQL 检查界面及修正)到 4A:两边功能都保留
冲突的解法:
- 后端 api/catalog.py:同时导入 catalog_impact 和 catalog_profile、SnapshotTampered;data/catalog.py:__all__ 取两边的并集重排。
- 2A 放宽了码值校验(含义可以为空,剖析写进来的是「含义待填写」):目录修改提案的码值补充改为含义为空的码不覆盖已有含义、
  只在原来没有这个码时加进去,补了测试;卡片和过程里的码值把空含义写成「含义待填写」,和目录页同一个说法。
- 前端 types.ts、terms.ts:两边各在同一处追加了新段落,按顺序都留下(提案、影响面、目录版本在前,剖析、码值、SQL 检查在后),
  没有重复的键;client.ts、decode.ts、CatalogPage.tsx、TableDetail.tsx 的导入取并集。
- check-catalog.mjs:影响面和剖析、保存数据源三条假路由都留下,各节和最后核对真库数量的逻辑不变。
- 4A 的测试数据源改用 tests/fixtures/sources.py 的 drop_source 删除,顺带作废连接池。
2026-10-03 12:35:47 +08:00
ThearchyHelios 71cc07e31a 检查跟上数据剖析和 SQL 检查:数据目录覆盖剖析设置、确认与进度、报告、各类 409、含义待填写;证据、解码、助手面板和发布前检查各加 SQL 检查的展示与定位;文案补齐 409 的下一步,码值的填写入口收窄 2026-10-03 12:21:42 +08:00
ThearchyHelios bfa391e344 数据目录阶段 4A 发布与运行:发布时记下模板 SQL 用到的表的目录版本,正式运行时目录有变化就发提醒事件、不拦运行,运行详情和时间线显示提醒,版本历史显示发布时的目录版本及之后的变化 2026-10-03 12:11:14 +08:00
ThearchyHelios 1c806fa3a0 数据目录阶段 4A 前端:画布和问数据页的回答下显示「建议更新数据目录」卡片,逐项列出改前、改后和理由,保存带版本记为人工确认,遇到 409 可重新载入后再保存,也可忽略 2026-10-03 11:40:37 +08:00
ThearchyHelios 5df10ac260 合入数据目录阶段 1B:助手建图按任务挑表、沿关系补连接路径、只给相关表全字段,流里告知参考了哪些表,挑不出时回退到只列表名
# Conflicts:
#	backend/app/api/copilot.py
#	frontend/src/dev/preview.tsx
#	scripts/check-stream.mjs
2026-10-03 11:02:08 +08:00
ThearchyHelios 1294095d1e 数据目录阶段 1B 前端:画布和问数据页的过程里显示「参考了 N 张表」,展开按数据源列出表名,挑表失败时写明只提供了表名及原因 2026-10-03 10:56:25 +08:00
ThearchyHelios 98ebba384f 合入数据目录阶段 3:合并查询节点——跨库按键合并,截断输入拒绝,类型不一致与行数放大告警,证据可下钻到两边原始查询 2026-10-03 10:27:16 +08:00
ThearchyHelios cd9bf0aedb 合并查询节点(四):前端——节点库、卡片摘要和类型色,检查器从上游查询节点里选输入并填别名,运行详情展示输入、合并 SQL 和警告,证据面板展示合并的来源和逐格来历,推断来源写明经过的合并;补上解码、回答流、证据面板、编排页的检查 2026-10-03 10:22:46 +08:00
ThearchyHelios a007270536 数据目录阶段 0 前端:证据面板给不完整的指标标「结果不完整」并写明原因,截断切开的 Agent 字段标「已截断」,运行时间线说清指标不完整的原因和下一步 2026-10-03 09:36:40 +08:00
ThearchyHelios 03c18c4d78 界面文案整改:口语改书面、术语统一、不再露出实现细节;修掉几处说错的提示;新增文案检查防回潮
一、文案(前端约 1600 处,后端传到界面的约 880 处)
- 术语统一:工作流层面用「运行」(继续运行、重新运行、试运行),节点和工具层面用「执行」,不再用「跑」;
  总时长 / 执行时长 / 等待审批;服务端;助手;Agent;token;工作流;固定版本;模型接入;系统提示;批准 / 驳回;受管级别
- 口语改书面:取不到、没取回来、读不懂、连不上、多半、照样、兜底、揉成这类说法改成「无法 / 未能 / 失败 / 请」;去掉拟人写法
- 不再露出实现细节:界面上不写 checkpoint、同一拍、哈希(凭证处除外),不写改版历史(「以前…」「老数据」);
  报错、校验、门禁、自动修复里裸写的配置键名和枚举值换成界面上的中文标签,由新的 backend/app/engine/labels.py 统一提供;
  发布弹窗里结论句的选项不再是 off / require_citation
- 同一件事只留一种说法:生成阶段文案、出具档位说明、画布锁定提示各合成一份
- 去掉界面上的工业场景示例(MES、ERP、「张工」「王工」)和真实产品名,换成通用示例

二、说错的提示(都有测试证明)
- 数据库测连接失败时的专用提示以前从不出现(按报错文字全等比较,永远比不上):改为按错误类型判断,不再拼出重复的前缀
- 「设置 → 供应商」改指「设置 → 模型接入」;「在设置里建数据源」改指「数据」页
- 缺少 API Key、模型接入已停用的报错不再夹英文,写明去哪里填
- 两处原因说错(「这种引用在后续版本支持」「这一版还不支持」)改成真实原因
- 节点说明里不准确的说法改正:代码节点在 microVM 或系统沙箱中执行;报告撰写、口径卡的说明不再说过头
- 导出的证据 CSV 里两列同名,第三列改为「片段编号」

三、前后端对齐
- 前端按后端原文识别日志、报错的地方,新旧两种写法都认得(历史运行里存的是旧文字),逐条补了新旧原文的核对
- 后端交回模型的原指令放进单独的 for_model 字段,前端在解析响应时去掉,不会显示到界面上

四、检查
- 检查脚本里按原文匹配的断言改成新文案;改完会变成永远成立的反向断言逐条修正,并做了变异验证
- 新增 check-copy(静态扫描前端界面字符串和后端上界面的文案):再出现「跑」「后端」「多半」「令牌」、
  界面上的 Copilot、裸写的配置键名、半角标点等就报错;确需保留的写进 scripts/copy-allow.json 并注明理由
2026-09-30 08:05:15 +08:00
ThearchyHelios 9727c21d8d 可点击证据第四期:结论句由模型裁判;只引单元格的问数据图能发成受管
一、结论句裁判

- 报告节点新增 claims: judge:写完、核对完之后,把结论句和它引用的证据摘录交给裁判模型,
  判为支持 / 部分支持 / 不支持 / 不是结论,附一句理由;判定只标注,不改写正文
- 送裁判的句子按固定规则预筛:带引用的、含方向词或因果词的送去判,短句、过渡句不送
- 证据摘录:指标写名称、值和原式;查询给 SQL 前 300 字和被引用的行,被引用的列一定给到;遮罩列一律不给;
  规则里写明「数字已经由系统核对过,不要复算」
- 一份报告合并成一次调用,超出句数就分批;结构化输出失败时退回 JSON 解析,再失败记为未裁判
- 裁判模型的优先级:节点上写的 > 设置里的「证据裁判模型」> Copilot 的模型;思考关闭;
  和写作模型相同时给警告(等于自己审自己)
- rewrite_once(默认关):把不支持的句子和理由交回写作者只改这几句,再裁一次;
  改写稿冒出原稿没有的问题就不采用,保留原稿和原来的判定

二、成本上限

- 每份报告的句数、金额、时长,探索运行每次点击的金额,全局每日金额,每一项都可以自定义,也都可以设成不限;
  默认 40 句、$0.05、30 秒,每次点击 $0.01,每天 $2
- 调用前先估算,超出上限按优先级截断:先判带引用的,再判含方向词、因果词的,其余记为未裁判并写明「已到上限」;
  不报错,也不让报告失败
- 每日花费按本地日期计数,持久化在设置表里;重跑同一次运行不重复计费;价格目录里没有的模型只计次数,并写明估不出金额

三、正式运行与探索运行

- 正式运行在报告节点内裁判,判定写进报告文档、随文档封存;不支持的按 on_unsupported(degrade / withhold)降档或不予出具,
  裁判没跑成记缺口,不能判完整出具
- 探索运行按需裁判:POST /api/runs/{id}/evidence/judge,点开哪句判哪句;判定作为 evidence.judged 事件追加在封存之后,
  标「封存后追加」,封存核对仍然一致;只读封存范围内的工件;同一句不重复判;运行在裁判期间被接着跑时不记录
- 正式运行调这个接口返回 409,写明「正式运行的裁判在节点内完成」

四、治理与自动修复

- G3 放开 judge:受管级别写 judge 必须写预算(judge.max_cost_usd,或显式写 null 表示不限)
- 发布前自动修复:缺预算时给选项(默认金额 / 不限),不替人选;
  从 require_citation 改成 judge 不算降低要求,judge 改回 off、withhold 放宽成 degrade 算降低要求
- Copilot 手册:受管模板可以写 judge 并带预算;问数据按需裁判,不替人打开结论句策略

五、界面

- 结论句句末放徽标:支持 ◆、部分支持 ◇、不支持 ! 整句浅底、未裁判 ?;不用表示「已核对」的绿色;n / N 也跳到不支持的句子
- 证据面板里「模型的解释」:带「模型判断 · 模型名 · 非确定」,封存后追加的另外标出;
  「请模型判断这句」只在探索运行、还没判过时出现,触顶时写明已到上限和怎么调
- 出具横幅多一段结论句计数;设置页新增「证据裁判」一组(裁判模型、各项上限、今日花费),设成不限时写明费用受什么约束;
  报告节点可以配 judge;运行轨迹里裁判相关的事件都有中文标题

六、门禁修正

- 引用模式下,图里没有口径卡(只引查询单元格)的,出具契约不再要求 metrics_from,受管门禁也不再要求 required:
  strict 加 cells 已经保证每个数都点得开、对不上就不予出具;有口径卡的照旧要求。
  以前问数据的图怎么都发不成受管,Copilot 补上契约也会被判「冒出新的问题」作废
2026-09-29 11:10:53 +08:00
ThearchyHelios fcdcc30d4e 可点击证据第二期:取数链路打通、问数据的答案逐段可点;发布前自动修复
一、可点击证据第二期

取数链路
- 智能体、工具节点、代码节点、知识检索取数时都记进证据台账;报告目录按台账编出 Q1… / K1…
- 报告可以引用快照里的一格([[v:Q1.r0.gmv]]),也可以由系统从快照生成整张表([[table:Q1 cols=… rows=0-4]]),
  表里每一格都是可点的片段;取值一律以快照为准,取回快照时复验哈希
- 口径卡表达式新增 cell(x, row, column),lineage 精确到单元格
- 智能体开 cite_fields(配合 output_schema)后,循环结束做一次结构化抽取,每个字段标出取自哪次查询的哪一格,
  再到快照里核对:一致记 verified;不一致以快照为准并告警;查不到的记为空值,永远不兜底成 0
- 代码节点新增 evidence_role(source / compute)和 code_sha;沙箱算出来的数不能被报告直接引用,要进口径卡
- 查询快照记下列类型,文本列按文本渲染(年份 "2026" 不再显示成「2,026」)
- 升级前发起的运行(停在审批上的)一律走旧逻辑:新的 task 只追加在循环之后,恢复时不会错号

证据接口与面板
- 片段链接上查询步骤:SQL、被引用的行加前后各 2 行、高亮到格;数据源可设 mask_columns 遮罩列,
  遮罩在查询时记进快照,数据源事后改名或删除也照样遮罩(在有身份体系之前,遮罩只减少暴露,不是安全边界)
- 输入来源写明核对结果(与快照一致 / 模型报的值和快照不一致 / 没查到记为空);口径卡缺输入时也给出完整的链
- 受管出具要在契约里声明 cells 才能引用单元格;受管与否记在所钉版本上,改画布不再让受管版本的正式运行降级
- 口径卡可以用 caliber_from 钉住另一个已发布工作流里的口径卡,上游有新版本时按 upgrade_policy 处置,
  和子工作流同一套规则;子工作流节点也补上了升版处置的配置项

Copilot、校验与报错
- 问数据的答案里有数字时,搭 input → 智能体 → 报告 → 出口,报告直接引用查过的单元格
- 写 SQL 前核对数据源的表结构,SQL 里出现表结构里没有的列会被打回
- 不再搭「智能体文字 → 转换节点解析 JSON」这种图;转换节点的 JSON 报错分清是上游模型写坏了、
  模板里裸插了模型文字(给出 | json 的改法),还是模板本身写错了
- 工具节点遇到「查询失败」「SQL 被拒绝」判为失败;发起运行前检查绑定的工具是否存在,不存在时直接拒绝并给出入口

二、发布前自动修复

- 问题带上机器编号(code)和修复 id;新增只读接口 POST /api/workflows/{id}/publish-check 和 /autofix
- 修复分三类:答案唯一、不降低要求的一键修复;要人拿主意的给选项(不替人选);结构性问题交给 Copilot
- 一律先预览,不自动保存、不自动发布;删节点、删契约、放宽审批、关掉 strict、改级别等降低要求的改动一律拒绝,
  Copilot 的方案也要过这一关;应用后重跑检查,错误必须变少且不能冒出新的
- 发布弹窗打开就先检查;问题面板新增「发布前检查」,平时就能看到、就地修掉
- 门禁修正:契约有 report_from 时不再要求 narrative(模板⑨可以发成受管);全图默认审批为「全部放行」时同样拦下
- 修掉画布保存会抹掉全图默认(defaults)的老问题:以前任何一次保存都会把它清空,审批 always 因此被悄悄放宽

验证(沙箱):tsc 干净,生产构建通过,pytest 2268 passed(两遍),check-all 17 项检查、3915 项全过
(新增 check-publish);关键行为做了变异验证、全部抓到。
2026-09-28 21:20:04 +08:00
ThearchyHelios 62aac187d8 工具审批三档、智能体护栏取代固定 12 步、检查脚本分道并行
MCP / 自定义工具的审批三档(tools/trust.py)
- 平台看不出这两类工具做什么,以前审批关卡一律当它们安全、照跑不误。现在默认每次调用都等审批;
  工具页可以逐个改成「始终允许 · 门控把关」或「始终允许」,审批卡上的「始终允许」等于前者
- 门控把关:每次调用先由门控模型审一遍工具说明、参数和节点任务;判可疑、出错、超时、答不成格式,
  一律交还给人,不会变成放行。判定包进 checkpoint,审批恢复重放时不再问一遍
- agent 节点、工具节点、协作团队三处关卡都接上;团队成员停不下来等人,门控没放行的调用不执行
- 每次运行在发起时快照档位(runs.tool_trust),恢复和接着跑沿用同一份;正式运行不看档位,一律等审批;
  升级前发起的运行照旧不问——它们可能正停在某个审批上,多问一次答复就会对错号
- 接口:GET /api/tools 带 trust / trust_key,PUT /api/tools/trust;设置新增门控模型;新事件 tool.gated
- 界面:工具页三选一、审批卡「始终允许」、设置页门控模型、运行记录解码 tool.gated;
  顺手修了 MCP 工具详情 404、工具审批被驳回却显示成放行

智能体护栏(engine/guards.py),取代固定的「最多 12 步」
- 步数只留兜底:默认 100,设置里可改;以前画布自动写进节点的 12 按「没填」处理
- 同一个工具、同样的参数第二次不再执行,把上次的结果交还给它;连续 3 步拿不到新信息就收尾
- 每个节点的令牌预算(默认 200 万)和金额预算(默认不限),都可以在设置里改成不限
- 上下文用到窗口的 60% 先压缩早期的工具结果,到 85% 就收尾;临近任何一个上限先提醒模型一次
- 下游结构化校验失败时写明是上游哪个 agent 提前收了尾;复核信号带上收尾原因,
  问数据页只在真是步数用满时才给「放宽步数重跑」
- 上限在发起时快照(runs.agent_limits);升级前发起的运行一字不改走旧逻辑

检查脚本
- check-all 默认 4 道并行:量时间最多的两项互不重叠;并行时没过的最后单独重跑一遍,重跑才过的
  单列成「偶发」。16 项全量从约 21 分钟降到约 7 分钟(加上一次重跑约 10 分钟);--lanes 1 回到串行

顺带修的
- 设置接口里的 limits 改为只读:库里存着旧版设置页写进去的一份(max_agent_steps: 25),从不生效,
  却盖住了真实上限,问数据页照着错的数许诺
- test_kb 里嵌入模型那条测试不再真连 api.openai.com(代理慢时一条要等近 4 分钟)
- check-stream 读滚动容器前先等它挂上,并行跑时不再偶发

验证(沙箱):tsc 干净,生产构建通过,pytest 1655 passed(两遍),check-all 16 项检查、3562 项全过;
新增后端测试 53 条,关键逻辑做了 22 处变异验证、全部抓到。
2026-09-28 11:47:45 +08:00
ThearchyHelios e4f2693918 可点击证据第一期:报告里的每个数字都点得开出处
「引用即渲染」:写报告的模型只写引用标记([[m:指标id]]、[[i:输入字段]]、[[see:…]]),数值由系统按口径卡
填进去,每个数字都挂着一条能复算的证据链。本期是数字层闭环;实体、结论句的概率性证据放在后续几期。

内核(engine/evidence.py,口径卡、出具契约、表达式等)
- 口径卡记录每个指标的来源:原式、代入式(每个输入写成「值 ← 路径」)、复算结果、口径与版本
- 出具契约的数值匹配改为按位置标注;同一个值有几个出处时记为「出处不唯一」并列出候选,不再随便挑一个
- 证据 id 由内容派生,事后对不上就是被改过;零值、结构性数字(列表序号、代码块标签)单独处理
- 口径卡新增 on_missing:缺输入时默认仍是整个节点失败,可以改成记为空值、交给契约判档

报告撰写节点(report)与引用模式的出具契约
- 流式生成时 token 里就是真实数字,不会漏出原始标记;裸数字、不存在的引用当场判违规,交回去重写
  (max_repairs,默认 1 次);仍不过时按 on_violation 失败(正式运行默认)或把违规标在报告里(探索运行默认)
- report.checked 事件记下核对结果,落在封存范围内;fail 模式下仍不过时带 failed,运行记录里画成失败
- 出具契约写了 report_from 就按引用核对:取回文档时复验哈希,独立重算一遍,全部带引用才判完整出具
- 成果字段逐字等于报告正文时标注 _evidence;复核只能加说明,不能改写带证据的答案(前后端各拦一道)

证据接口
- GET /api/runs/{id}/evidence 返回证据图;GET …/evidence/segments/{id} 返回一个片段的证据链
- 只认封存范围内的事件能追到的工件,不读 artifacts 表;封存被改过时不把任何一项标成已封存
- 没有报告的旧运行降级为 none 或 legacy_contract

界面
- EvidenceDoc 按块、句、片段渲染报告,片段是和正文一样的按钮;整份报告只占一个 Tab 位,
  ←/→ 在片段间移动、↑/↓ 按句、n/N 跳到下一处「无证据」、Enter 打开面板、Esc 关闭并把焦点还回去
- EvidencePanel:宽屏侧边、窄屏底部抽屉、360px 画布右栏里栏内展开;显示指标步骤、代入式、复算结果、
  封存状态。正文不是封存的那份报告时明确说出来,不拿别的报告的封存画绿
- 出具横幅多一行「N/N 数字有出处 · 无证据 M」和「定位下一处」;没有 _evidence 的旧回答渲染一字不差
- 画布认得报告撰写节点;Copilot 手册补上口径卡、报告节点、出具契约的写法;新增模板「⑨ 可追溯周报」

验证(沙箱):tsc 干净,生产构建通过,pytest 1601 passed,check-all 16 项检查全绿(新增 check-evidence)。
2026-09-28 11:47:23 +08:00
ThearchyHelios b418c0d951 最终验收:148 项逐条复核(原审查 142 条 + 平台问题 6 条),返工后 145 项解决
四组验收员拿审查原文在沙箱里逐条复现:133 项已解决、15 项部分解决、没有未解决或回退的;
另发现 10 个新问题。按归属返工后,剩 3 项低严重度:MCP / 自定义工具的危险标记(待用户拍板)、
选中环与运行态同色(需跨三处协调改)、「接着跑」这个说法是否更换(多处引用原文,暂留)。

这次返工改了
- 右栏:老运行没有 active_ms 时执行时长改按航迹算,不再显示最后一段的「22 ms」;循环出口行不再差一;
  取消在一轮中途时泳道脚注不写「0 ms」;分支、循环出口显示 case 的说明而不是 key;
  展开已完成轮次不再误报「N 条新进展」;旧数据里已完成的模型调用不再一律标成「已取消」
- 后端:跳过事件带节点名和类型;restore 版本时把已发布 / 受管退回草稿;只是打开旧会话不再把它顶到最前、
  标成「刚刚」;协作团队全程没有成员交付成果、调度者却判完成时按失败或降档处理,不再当成功交付
- 画布:循环体有「循环体 · 最多 N 轮」的底框,出口标签不再被自己的走线穿过;运行中校验角标退让;
  768 宽下工具栏不溢出;运行被拦时点名是哪个节点;正式运行弹层写明以谁的名义发起
- 问数据:离线时能力条不把「没取回来」说成 0;窄屏下会话列表收起;check-chat 在零点前后不再误报
- 公共层:FastAPI 422 校验错误翻成中文,不再把英文原文透给用户

验证(沙箱):tsc 干净,pytest 878 passed(两遍),check-all 15 项检查全绿。
2026-09-28 02:42:40 +08:00
ThearchyHelios 3e311ec551 第三波收尾:跨模块整合、全站扫尾、检查脚本整合;网页工具认得本机代理的 fake-ip
整合(3c-integrate)
- 公共层:跨天时长(formatSpan / formatOffset / formatLapse)、本机署名(lib/actor)、检索分类色令牌、
  数据源目录(useDatasources)收进公共层;失败说明能一次列出多个写坏的自定义工具,查询超时指到数据源的查询时限
- 管理页收尾:设置页接入统一的离开前确认;检查脚本不再把沙箱里的真实值打进输出
- 画布 ↔ 右栏:点出具印章、点节点能跳到右栏对应位置;「定位」直接聚焦到出问题的字段
- 回答流 / 问数据 / 记录页:失败信息带「去哪修」且不再被翻译两遍;协作团队的结局按执行截取;
  等审批时断流重连、而运行已在空档里跑完的,补全事件

扫尾(3c-sweep)
- 术语统一:开始运行 / 继续运行、工作流最大步数;未保存的运行统一叫「未保存的工作流」(后端新运行同步)
- 沙箱报错不再带 Python 异常类名;新增 check-tokens,未定义的 CSS 变量为 0,两个旧别名迁移后删除

检查脚本(3c-checks)
- check-ui 跟上新界面(运行列表按 data 属性找行),加上令牌与选中态的回归断言,输出里的名字打码
- 新增 check-all:按顺序跑全部检查、汇总退出码,--repeat 列出偶发项
- 单节出错只记这一节失败、不再带崩整个脚本;外壳遥测浮层「重新检测」时焦点不再掉到 body

网页工具与 fake-ip
- Clash / mihomo 一类代理开 TUN + fake-ip 时,查什么都先拿到 198.18 段的假地址,被当成内网拦截。
  新增 AGENTLAB_HTTP_TOOL_FAKE_IP_RANGES(默认空):落在段里的解析结果改向公共 DNS(DoH)核实真实地址,
  公网放行、内网照拦、核实不了按拦截处理。DoH 默认先问境外的,境内公共 DNS 会对被封锁的域名返回污染结果
- 列表型环境变量接受逗号分隔(原来注释说逗号分隔、实际只认 JSON 数组);域名解析改为异步,不再阻塞事件循环

验证(沙箱):tsc 干净,pytest 866 passed(两遍),检查脚本全绿。
2026-09-27 23:37:32 +08:00
ThearchyHelios 587bf7e27c 第三波前两批:六个「看着成功其实没干活」的平台漏洞堵上,记录页能回放,管理页与画布收尾
起因是一次探索运行:Copilot 改节点时把工具绑定整份抹掉,agent 没有工具只能写「假设调用」,
校验节点的自动修复编出 total_count=0 让 schema 通过,协作成员把工具调用标记当文字输出了四轮,
轮数用完后节点把原话当成功结果交出去——运行标成已完成,数据库一次都没查。

平台可信度(先写失败测试)
- Copilot 修改节点改为合并:没写的字段保留,null 才删除;协作成员按名字合并。前后端同一套规则,
  工具集合变小时自查报警、改图回执列出变化
- 新校验规则:提示词点名了某个工具而节点没有绑定它,判错误,Copilot 自查会交回去修
- 校验修复只许改格式:冒出原文没有的值就判修复无效;修复这次模型调用计入用量
- 识别模型把工具调用写成文字的情况(DSML、<tool_call> 等标记):纠正一次,仍然如此就判失败并说明原因
- 协作团队用完轮数不再按成功交付:默认失败,可选降档,点出一次都没派到的成员
- 审批恢复后,同一节点里已经执行过的工具调用复用结果,不再重复执行

后端
- 查询时限交给数据库执行(Oracle call_timeout、PG statement_timeout、MySQL max_execution_time、
  SQLite progress handler),可按数据源配置;超时报错与实际耗时一致
- 等审批的运行可以真正取消;会话列表带最后一轮状态;测连接结果持久化;探查结构可只看不存;
  自定义工具可草稿试跑、写坏的参数定义保存时拒绝;MCP 服务改动后作废旧工具缓存;
  检索命中带贡献分解;重建索引在后台跑并报进度;报错统一收进 core/errors

前端
- 公共层:管理页共用件、失败说明(lib/explain)、健康状态进公共库;目录按表各自加载并有超时,
  一张表卡住不再拖垮整站;上传显示真实字节进度;离线横幅只播报不变的句子
- 画布:卡片只说游标所在的那一次执行;等审批时可放弃运行;团队收尾判定与未完成说清楚;
  回放用量取那一刻的值
- 编排页:Copilot 合并语义与工具变化回执;「用完轮数时」选项;正式运行期间锁住所有改图入口
- 回答流 / 问数据:新失败类型说人话并给下一步;画布与右栏互相定位;数据源范围、回收站、重新核对
- 记录页:「航迹」页签复用画布时间轴,可拖动回放、列出关键时刻;列表行带执行 / 等人 / 失败缩略条
- 外壳:统一的离开前确认(data router),导航、⌘K、快捷键、后退都会先问

验证(沙箱):tsc 干净,pytest 838 passed(两遍),14 个检查脚本中 13 个全绿;
check-ui 余 1 项旧断言(按旧日期格式找运行行),3c 更新。
2026-09-27 10:16:32 +08:00
ThearchyHelios e10c4f23fc 界面与交互全面翻修(前两波):运行态有了 HUD 和时间轴,终态会收尾,全站令牌、组件、文案统一
来自 2026-09 的 UI/UX 审查(142 条)。本提交是前两波,第三波(记录页接入时间轴、跨页互链、check-ui 更新、全站扫尾)另行提交。

地基
- 设计令牌:全局规则和 .btn/.field/.chip 放进 CSS 层,Tailwind 工具类重新生效,选中态、开启态不再被吞掉;字号阶梯;
  辅助文字在所有底色上 >= 4.5:1;状态色专用,节点类型色与之拉开色相;「跟随系统」真的跟随,首帧不闪深色;新字标与 favicon
- 基础组件:confirmDialog / promptDialog 取代原生对话框;toast 出错不自动消失、可复制、读屏可播报;离线横幅与断线恢复;
  九种剪影的状态徽标;唯一一份状态文案(lib/status)、格式化(lib/format)、术语(lib/terms)、报错人话(lib/errors)
- 运行态内核:事件折成航迹(run/trace.ts),可投影到任意时刻;取消 / 失败 / 服务重启后统一收尾,不再永远转圈;
  run.status 随事件回写;token 事件不再堆进 events
- 后端:协作成员逐个完成即发事件,调度者决策有事件;时长分执行 / 墙钟 / 等人;API 时间带时区;
  中断的运行也发 stream.end;运行列表可筛选翻页;删除运行 / 工作流有保护;运行默认值接通;可测未保存的配置;
  报错说清发生了什么、为什么、怎么办

功能
- 画布:状态四重编码、固定高度遥测槽、按缩放三档、运行胶囊与 HUD、航迹时间轴与回放、执行轨迹边、
  等待闸门、跟随执行、正式运行只读、一次性时刻(开场点亮、回扫、阻断级联、出具印章)
- 编辑:撤销重做、版本历史、问题面板可定位、三栏可收起、模板与表达式字段区分、变量血缘、快捷键、带搜索的选择器
- 回答流 / 问数据:贴底才跟随滚动、工件下钻、出具横幅列出缺口、答案可复制导出、按会话的忙闲、
  答案不再在 2000 字处截断、重启后这一轮会收尾
- 记录页:待审批 / 运行中 / 失败页签、筛选翻页、实时接流、排错路径指到节点、封存凭证常驻
- 管理页:新的「数据」页、写操作失败都有反馈、弹窗里先测连接、统一持久的连接状态
- 外壳:导航改版、⌘K 命令面板、? 快捷键帮助、404、页面标题与 favicon 状态点、后台提醒

验证(沙箱):tsc 干净,pytest 579 passed,14 个检查脚本中 13 个全绿;check-ui 剩 1 项旧断言(按旧日期格式找运行行),第三波更新。
2026-09-26 23:11:31 +08:00
ThearchyHelios 0997603ccf 画完先自查:条件写错在校验时就拦下,Copilot 交付前按同一套规则查一遍、查出问题先改
开发库里四次运行死在同一句"循环条件写错了:表达式里不允许出现 Set"。条件原文是
`{{ vars.gate }} != "ok"`——把模板写法带进了表达式:`{{ x }}` 在 Python 里是"装着
一个集合的集合"。Copilot 的提示词写着"模板语法在任意字符串里用",模型照做而已;
校验又不看表达式,写错了只有跑到那一步才知道,前面的步骤白跑。

1. 表达式:解析和静态检查收成一个 parse_expression,运行时、校验、Copilot 自查共用,
   不会出现"校验说能跑、跑起来报错"。多套的 {{ }} 意思无歧义,在语法树上剥掉(字符串
   里的花括号不动),按 vars.x 理解并提示。真写错的给能照改的提示:| 过滤器 → len(x)、
   集合 → 列表、不认识的函数列出能用的、= 当 ==、引号里的 {{ }} 永远不成立、推导式、
   切片、私有属性。and / or 短路掉的那半边也查。

2. 校验:按节点类型和模式挑出真正求值的字段(分支条件、while 条件、整形表达式、口径卡
   指标、skip_if)。写错是 error、挡住运行;漏写 vars. 的名字提示会取到空值;while 没写
   条件、整形没填表达式也说出来。foreach 的 condition、模板模式的 expression 不求值,不查。

3. 代码节点:assign_to 拿的是 stdout。「测试 1」修好条件后跑通了、结果却是空的——代码
   最后一行是裸表达式,那是 notebook 的行为。设了 assign_to 却找不到任何输出的 Python
   代码,校验直接拦下,提示 print(json.dumps(结果))。宁可漏判不可误判:子进程、exec、
   附带文件里的 print 都算有输出;整行只有一个 {{ }} 的代码是运行时才拿到的(⑤号示例
   整段就是 {{ vars.code }}),看不见,不猜。库里 20 张工作流无一误拦。

4. Copilot 自查:搭完用同一套校验过一遍,error 级的问题连同图交回模型——只修这些、只
   输出改动,最多两轮。改好了才自动运行;改不好照实交付、列出剩下的问题,不自动开跑。
   没问题的图不多花一次调用;只是多套了 {{ }} 的不返工。提示词改成"模板只在普通文本
   字段里用",并列出表达式字段的对 / 错写法和代码节点必须 print。

5. 前端:自查这一段在 Copilot 的过程里看得见——发现几处问题、交回去改、改好了没有;
   改不好标红并说明没有自动运行。"搭好了,共几步"那一行不会被挤掉。

测试:新增 test_expressions(开发库里真实死过的四条条件、静态提示、按模式挑字段、代码
输出、带 {{ }} 的循环真跑一遍)与 test_copilot_selfcheck(脚本化模型:改好 → 自动运行;
改不好 → 不运行、最多两轮;干净的图一次调用;多套 {{ }} 不返工)。test_continue_run 里
用来制造运行时失败的表达式换成 `2 ** 100000 > 1`——原来那条现在在校验阶段就被拦下了。
check-decode 补了自查的解码断言。
2026-09-25 17:04:39 +08:00
ThearchyHelios c8393c3c6b 移除仓库中的商业数据:删掉 21 个相关用例,其余标识符脱敏
这个仓库是公开的,而历史里带着一份客户 BI 仓的逐字转储:ANALYTICS(原
schema 名)下 53 张视图名、实测行数、字段结构表,外加一句写明取数方是谁
的注脚。还有一处把真人姓名和工作单位写进了检查脚本的断言里。这些都不是
化名,是能直接拿去对库的东西。

按"含敏感词的测试直接删"处理,共 21 个用例:

  test_agent_tool_loop.py   整个文件(2 个,agent 工具循环的真图验收)
  test_conversations.py     9 个(会话 CRUD、历史文本、上下文进 prompt)
  test_datasource.py        5 个(探查失败的实情、候选 schema 提示)
  test_copilot_context.py   3 个(数据源摘要的限定名)
  test_review.py            1 个(干净运行无信号)
  test_tool_args.py         1 个(call_tool 端到端)

辅助函数和产品源码不在"测试"之列,改为脱敏——否则删一个 _seeded 会连带
带走 8 个没被点名的用例。标识符一律换成中性名:shop→shop、
ANALYTICS→ANALYTICS、prod_shop→prod_shop、真人姓名→张三。

markdown 样本里那两条不是改标识符能解决的:行数、字段结构、业务域描述
整段都是客户的。换成同构的合成内容——领域换了,markdown 构造逐项对齐
(标题层级、有序/无序列表、三列表、带 inline code 的单元格、引用块),
渲染器的覆盖面没有变窄。check-stream 里那句"用的是真实输出"跟着改成实话。

286 → 265 passed,差值正好是删掉的 21 个。tsc 干净,decode / stream / ui
三个 check 不回归。

历史里的同一批内容另行用 git filter-repo 处理。
2026-09-20 22:52:28 +08:00
ThearchyHelios 00f8dd78a9 时间线上认得出并行分支了:3 路并行 · 合计 6.6s,实际 3.0s
图上一个节点连出多条边就是 fan-out,LangGraph 在同一个 superstep 里并发执行
——这一直是真并发。但在时间线上它们只是穿插出现的几行,"这三路是同时跑的、
因此省下了 3.6 秒"一个字都没说。上一轮给协作团队做了泳道,这条同样的信息
在 fan-out 这边还是缺的。

现在按时间跨度把同时跑的节点收进一个分组,标题写明几路、meta 写明省了多少:

    ⑂ 3 路并行                    合计 6.6s,实际 3.0s
       ├ 查销售库  2.2s
       ├ 查库存库  3.0s
       └ 查客户库  1.5s

**判据是"同一批出发",不是"跨度重叠"。** 第一版按包含关系排除嵌套,结果漏掉了
先跑完的那一路:三路同时开始但先后结束时,长的那条在时间上确实"包含"短的。
实测 leg0 [0.05,2.21] / leg1 [0.05,3.01] / leg2 [0.05,1.51],被漏掉的正是
最快的 leg2——而漏掉最快那路会让"省下多少"算少。改成看开始时刻是否落在同一批
(250ms 窗口),fan-out 的几路实测相差不到 1ms,子工作流那种真嵌套则是父节点
明显更早开始,自然分得开。

组内要求两两都同批,而不是链式传递:否则 A|B 重叠、B|C 重叠但 A|C 不重叠的
三个节点也会被串成一组,而它们并不是同时在跑。瞬时节点(<30ms)不参与——
它们谁跟谁都"重叠",是噪音不是信息。

**误报比漏报更糟**:它会让人以为省了时间。所以断言里专门守了反向——db、human、
loop_approve 三份顺序执行的样本一个都不能被判成并行。

fixtures 里新增的 fanout 是真跑出来的,不是手写的:三路分别 sleep 1.8 / 2.6 /
1.1 秒,事件原样收进来,所以 6.6s / 3.0s 这两个数和后端量到的是同一份。

验证:check-decode 新增 6 条(含三份顺序样本的反向断言);check-stream 新增
10 条,宽窄两栏都验了不溢出;后端 276 passed 不受影响;tsc 干净。
2026-09-20 21:09:30 +08:00
ThearchyHelios a027261701 Copilot 会自己记该记的了;顺带让写记忆这件事看得见
以前只有用户明说「记住」才会写:17 轮有图的对话里只有 4 轮带记忆节点,全在
那个用户主动谈记忆的会话里。于是他说过一次「我叫张三」,换个话题再问就
谁也不知道了。

现在 answer 路径多一步判断:这句话里有没有值得长期记住的东西,有就在主链路
前面加一个 memory(write) 节点。边界比指令本身要紧,所以写死了四类不该记的:
这一轮的问题本身、查出来的数(会过期且能重查)、一次性指令、以及**用户没说过
而你推测出来的**。另外两条是从召回的实际情况倒推的——content 要脱离上下文也
成立(召回时没有这轮对话,「他说他叫张三」读起来不知道「他」是谁),以及
拿不准就不记:记错了污染以后每一次对话,漏记一条用户再说一遍就是了。

**动手前先修了一个前提**:写记忆在界面上是隐形的。memory 节点发的是 info 级
日志,而解码层明确丢弃所有 info(decode.ts),也就是说自动记忆 = 系统背着
用户往一个跨会话、长期有效的地方存关于他的信息,而他看不见。

新增 memory.end 事件,解码层按动作分开渲染:写入把**记了什么**原样显示
(只报「写入 1 条」等于没说,用户没法判断它记的是不是他想让它记的)、召回说
「想起 N 条」、清空标成警告。

真模型验过(deepseek-v4-pro):

「我平时叫小蒋,以后报表金额用万元」
  → content='用户平时叫小蒋,他要求以后报表里的金额统一用万元作单位'
    scope 未写、跑完落库、事件里带着内容
  → 换个说法召回得到:「该怎么称呼我」「金额用什么单位」都命中

三条对照确认不滥记:
  shop 里有几家店        → 没记
  重来                    → 走 reply,连图都没建
  把上一轮的结果按月份排一下  → 没记
2026-09-19 22:41:27 +08:00
ThearchyHelios df935599f2 知识库:换 embedding 模型不再 500,检索能回指了
三件事,都是"这三个模块一条测试都没有"的后果。

**换 embedder 会让检索整体 500。** 库里是 512 维的本地向量、查询是 1536 维的
远端向量时,cosine 直接 `shapes (1536,) and (512,) not aligned`。from_blob 有
dim 参数,但 kb.py 和 memory/store.py 调用时都没传;而且存量向量属于哪个模型
代码里没有任何地方记着,所以也无从降级。

Chunk / MemoryItem 各加 embed_model + embed_dim(走现成的 _COLUMN_MIGRATIONS,
存量回填空值,正好表示"不知道是谁建的")。检索遇到对不上的向量整体退回纯
关键词并说破,不是把它们当 0 分静默沉底——那样用户看到的是结果莫名其妙变差,
而不是"这里有一批索引该重建了"。配套 kb.reindex + POST /api/kb/reindex。

长期记忆那份一起修了。判重那条更危险:维度碰巧相同但模型不同时 cosine 算出来
是随机数,会把不相关的旧记忆认成"重复",然后把新内容丢掉。

**默认 embedder 没有语义能力,而开关藏在一个环境变量里。** LocalEmbedder 是
特征哈希 + TF,和 BM25 吃同一批词;alpha 默认 0.5 等于给了它一半权重,同义
改写一条都召不回。开启真模型的唯一开关是 AGENTLAB_USE_OPENAI_EMBEDDINGS——
没界面、没人会发现。改成设置项(照 COPILOT_SETTING_KEY 的路子),知识库页顶部
能选能看能重建。

配置失败必须响:第一版写成静默退回本地,但选了 OpenAI 却拿到哈希向量的人,
在搜不出同义表达时完全无从怀疑到这里。现在抛 EmbedderUnavailable,API 400
原样交回"检查 OPENAI_API_KEY 是否配了",且不会把配置改掉。

**检索结果不进工件库。** db_query、工具节点、agent 工具调用都落 put_json 快照,
run_retrieve 什么都不落——于是结论只要来自知识库,"这个数是哪来的"就断在这儿,
而数字回指正是这个项目的签名能力。现在落 kind="retrieval_snapshot",并发一条
retrieve.end 带 artifact id。解码层补了正式映射(原来会掉进"未知事件"兜底,
画成一坨原始 JSON)。

新增 test_kb.py 11 条,补上这三个模块此前的零覆盖,包括 embeddings.py 里
注释记着、但一直没有测试守的那个归一化边界(单条候选被 min_score 误杀)。

端到端在真实服务上验过:撞上存量向量返回 200 + 降级说明(以前 500)、
重建后 stale 归零、配不起来的远端模型 400 且配置不变。
2026-09-19 09:27:07 +08:00
ThearchyHelios f43f56abb7 把 Copilot 的思考显示出来:模型一直在想,只是被丢掉了
用户问能不能看到"正在理解需求、规划步骤"里面发生了什么。查下来是这样:
模型确实在想,reasoning 花了实打实的 token,但一路上被丢了三次。

1. langchain 的 ChatOpenAI **故意**不保留第三方的 reasoning_content——它的
   文档原话是 "Non-standard response fields added by third-party providers
   are not extracted or preserved",建议换 ChatDeepSeek 之类的专用包。可
   "用哪个供应商"在这个产品里是用户在设置页配的(DeepSeek / Qianfan /
   vLLM / OpenRouter 都可能),为每种装一个专用包不现实。在 factory 里
   子类化捞回 additional_kwargs,只加字段,不动 langchain 对正文的处理。

2. thinking_text 只认 Anthropic 的 thinking 块。补上兼容端点那一种。

3. 前端把一段思考的标题固定成开头 60 个字。思考是流式的,开头往往是
   "用户想要一个……"这类复述,之后几十秒里它一直在推进——固定显示开头
   等于把一段活的叙述冻在起点上,看着就像卡住了。改成:还在想的时候显示
   最新一句,想完了换回开头那句(那时它是一条历史记录)。

实测一次真实生成,60 秒里侧栏逐句变化:

    3s  可用数据源 bi
    9s  人工节点 approve 模式,审核
   18s  我们可以用 db_query__sales 执行 SELECT COUNT(*) FROM orders 等
   36s  更合理:rejected -> query_agent(重新
   45s  对于 bi 有 53 个对象,靠
   57s  (plan 到达,思考收尾,换回开头那句)

顺带运行期的 LLM 节点也跟着有了思考——走的是同一个 thinking_text,之前
同样只有 Anthropic 系才有。已验证流式合并后思考连成完整一段,不是只剩
最后一片。

tests/test_thinking.py 守两种形态、合并、以及"不产思考的模型不硬凑"。
check-decode 补 5 条守"还在想显示最新一句、想完了换开头"。
2026-09-18 09:55:42 +08:00
ThearchyHelios aa18904af0 运行和 Copilot 分成两层;顺带修了一个会丢事件的时序缺陷
用户:「点击右上角运行后,会跳到流程;然后下方就又有 Copilot 对话框,把
结果和 Copilot 又放在一块儿了」。说得对,而且我上一版的理由是错的。

我当时把建图和跑图排进同一条时间轴,说它们是"一件事的两半"。它们在时间上
先后发生,但不是同一种东西——最直接的证据在我自己的代码里:运行那一轮的
question 只能填 undefined。一个没有提问的"对话轮次",本来就不是对话。

更实际的问题是那个输入框:它贴在运行结果下面,暗示你可以回复这次运行。
但你不能——在那儿打字改的是画布上的图。这是个假的可供性。两者的生命周期
也对不上:Copilot 的轮次会累积,运行永远只有最新一次。

现在右栏是一个常驻的家(Copilot 对话)加两层临时盖上来的东西(节点属性、
运行过程)。运行那一层没有输入框,有自己的头部和停止按钮;对话那一层底部
留一条运行进度,点一下过去。

---

顺着这条线查下去,发现一个更严重的问题:**后端在丢事件**。

浏览器侧连跑 5 次,2 次丢了中间一条。抓 WebSocket 原始帧才看清:所有帧的
replay 都是 false——历史读回来是空的,全部走实时;丢的是 run.started,
它在客户端订阅之前就广播了,而那时还没落库,所以历史里也没有。

根因是 _emit 的顺序:先广播、后落库。订阅者的完整性靠"先订阅 → 读历史 →
接实时"拼起来,它需要这条保证——提交早于我这次 SELECT 的,在历史里;提交
晚于我这次 SELECT 的,广播也晚于我订阅,在实时流里。后半句只有在"提交先于
广播"时才成立。交换顺序后,6 次 WebSocket 探测 + 5 次浏览器运行全部无缺口。

(上一轮我修过这个文件的另一个窗口——先读历史后订阅。那个修复是对的,但
只堵住了两步之间的缝,堵不住订阅建立之前的那一段。)

tests/test_emit_order.py 守这条不变式:广播的那一刻,这条事件必须已经能从
另一个 session 查到。去掉修复后它确实失败。

---

还修了三处:

- 停在人工介入时,"在不在等人"原先取自审批列表,而那是 4 秒轮询一次的;
  中断后会有几秒钟界面说"这次运行已结束",而它正等着你点通过。改成从事件
  推导(isAwaitingHuman),即时且和显示的内容一致。
- 新增 tests/conftest.py 把整个测试会话关进临时目录。test_emit_order 第一版
  直接用 SessionLocal 往开发库里写,几行假运行排到了运行列表最前面。已清掉
  那 12 行。这该是全局保障,不是只修一个文件。
- 开发期把 store 挂到 window.__studio,好让 playwright 直接读状态——"事件
  收全了没有"在界面上看不出来,只能问 store。已确认不进生产包。
2026-09-18 09:39:24 +08:00
ThearchyHelios 3de7567377 右栏重构:助手常驻,属性改为盖上去的一层
两个问题都源于同一件事——一栏 360px 被要求同时干两件活。

**没地方引导写问题。** Copilot 输入框是个 38px 高的框,挤在一条长长的流
底下,比它上面任何一块都不起眼;例句被截断成 16 个字的 chip,示范"该说到
什么颗粒度"这个作用就没了,只剩装饰。改成一个组件两种体量:没说过话时它
就是这一栏的主体(竖直居中、三行高、自动聚焦、完整句子的例句),并且告诉
用户它能够到什么(N 个数据源 / M 个工具)——不然"查销售库"这种话有没有意义
只能靠猜。有了对话之后它退到底部两行。不做成两个组件:那就是两个实例,
切换时会卸载其中一个,草稿、光标、输入法候选词全没。

**Copilot 和属性挤在一起。** 它们原是 [助手|属性] 两个 tab,而 tab 是互斥的
——跑图跑到一半点开节点看配置,整条执行过程就从眼前消失,切回来滚动位置和
输入草稿也没了(切 tab 会卸载组件)。改成属性滑出来盖在助手上:助手一直在
下面活着,属性是临时造访者。头部给一个明确的"‹ 助手",Esc 关得掉;盖住
期间底部留一条执行进度,点一下回去——不能因为换了实现就把原来最难受的
那个场景原样留着。

运行控件搬去工具栏。它原先和 Copilot 输入框一起常驻助手栏,两个都是"主要
动作"互相压着;而它们是两种意图(跑这张图 / 改这张图)。运行是对整张图的
动作,和保存、发布同类。有输入字段就弹表单,没有就一键直接跑。

顺带修了两处状态说反的地方:
- 停在人工介入时,承载的节点还在转蓝圈。转圈说的是"它在忙,你等着",而
  实际正相反——它在等你。改成待办色 + 手势图标。
- "继续执行"的去重原先靠"界面上还有没有 waiting 步骤",上一条改动多了一处
  产生 waiting 的地方就失效了。改用事件相邻性判断——那是这两条事件的固有
  性质,不会因为别处改了展示而变。

动效:面板滑入、例句依次入场、正在出字的那一步有一道光扫过(比转圈多说
一件事:它在产出,不只是在等)、空态标记轻微呼吸。全部 240ms 以内,缓动
统一 cubic-bezier(.32,.72,0,1)。另外补上了此前完全没有的
prefers-reduced-motion——这套界面里动的东西不少(边在流动、节点在脉冲、
面板在滑),系统里关了还照播,对前庭敏感的人是实打实的难受。

验证:check-ui 重写了助手栏那一节,核心断言是"选中节点再回来,输入的字
还在"——那正是 tab 方案做不到的。新增一条用 emulateMedia 验证关掉动效后
动画确实停了。真跑了一次带人工审批的图,期间打开属性面板确认进度条还在。
四套检查 + 82 个后端测试全过,生产构建通过。
2026-09-18 00:05:22 +08:00
ThearchyHelios 3ff6840b99 多 agent 节点不再永远转圈,调度决策也说出来了
运行 #6038f5:supervisor 节点跑完 31.7s 之后,researcher 那一行还在转圈。

agent.step.start / agent.step.end 是这套事件里**唯一**一对没做配对的
(node、llm、tool、sandbox、human 都配了)。start 那条建出来就是 running,
end 另起一行,于是没人去关它。这正是 decode.ts 自己文档里的第 2 条原则
——成对事件合并成一行——我写了却没用在 agent 上。

配对键用 (node_id, agent, round):光用 agent 名不够,supervisor 会在多轮里
反复派给同一个 agent,第二轮的 end 会错配到第一轮的 start 上。

顺带把 supervisor 的调度决策捞出来。后端把它标成 info 日志,而 info 是整类
丢弃的,结果"为什么派给 researcher"、"为什么只跑一轮就结束"在界面上完全
没有交代——那个节点就是一个跑了 31 秒的黑盒。带 round 字段的 info 才是调度
决策,和普通排查日志区分得开。FINISH 译成"结束协作"而不是"交给 FINISH",
后者会让人以为还有个叫 FINISH 的成员。

真实运行已收进 fixtures,check-decode 加 9 条(含"普通 info 日志仍然不进
主流程"和"warn 日志照常显示"这两条反向约束)。
2026-09-17 19:14:06 +08:00
ThearchyHelios f0fd63f0b9 UI 重构:Copilot 进侧栏,三个页面共用一条助手流
用户的原话是「copilot(包括问答)可以是侧边栏的效果」「显示他的简要思考节点,
使得用户知道他大致在想什么,以及用了哪些工具」。

画布右栏从「运行 / 属性 → 时间线 / 成果 / 事件」(两层嵌套 5 个视图)变成
「助手 / 属性」一层。助手栏里是一条流:你让它做什么、它做了什么、结果是什么。
Copilot 建图和跑图在协议上完全无关(SSE 操作流 vs 事件 WebSocket,不共享
seq 和 node_id),但对用户是一件事的两半,decode.ts 把两种流译成同一种 Step
之后才能排进同一条时间轴。

Copilot 不再是弹窗。弹窗的问题不是样式:每改一次图都要重开一次,关掉之后
需求文本就没了,想微调只能重打;而画布左下角那条浮条(z-30)和 Toast
(z-100)、Modal(z-50)三层各自为政,本身也是重复的一份 Copilot 状态渲染。
两个都删了,输入框就在栏里,说完一句接着说下一句。右栏没删——它是发起运行
和处理审批的唯一入口,RunLauncher 原样保留,只是跑起来会自动收起给执行过程
让位。tab 用 hidden 切换而不是卸载,不然点一下「属性」看个节点再切回来,
输入框草稿和展开状态全丢。

问数据页和运行页换成同一个 AssistantStream + 同一个 decode.ts。RunPanel 里
那套平行的 EVENT_META + describe() 删了:留着两套翻译,同一次运行在不同页面
会读出不同的故事,而用户没法判断哪个是真的。同理合并了两个耗时格式化函数
(画布节点显示"0ms",右边同一步什么都不显示,两块还并排放着)。运行页列表
加了内容摘要——原本每行只有"临时图 · 1.2s · 340 tok",十条长得一模一样,
要分清哪条是哪条只能挨个点开。

跑真实运行时发现两个缺陷:

1. 后端丢事件(app/core/bus.py)。WebSocket 先读历史、再订阅,两步之间图
   正好跑出来的事件既不在历史里也不在实时流里,永远丢了。而 subscribe() 是
   异步生成器,真正注册还要等到第一次迭代,窗口比看上去更大。实测一次跑图
   开头的 node.finished/node.started 消失,界面上表现为某个节点永远在转圈、
   它的子步骤跑到了顶层。改成先 attach 占位、再读历史,重叠部分按 seq 去重。
   tests/test_bus_attach.py 里有一条刻画旧行为的测试,说明这个窗口确实存在。

2. 循环里的多轮审批被算错。我在 S1 按内容去重,但三轮审批的 title 一模一样,
   和 LangGraph 的重放无法区分——结果第 2、3 次真实审批被当成重放吞掉,而
   三次"你驳回了/你放行了"各自成行。改成开/闭配对:开着就是同一次,闭了才是
   新一次;决定折进那条审批行而不是另起一行。真实运行(驳回 → 改写 → 再驳回
   → 改写 → 放行)已收进 fixtures。

验证:三层各守一件事,共 76 条断言全过。check-decode 守翻译(含新的多轮审批
和截断结果集),check-stream 守渲染,新增 check-ui 守"接起来还对不对"——真
浏览器真后端,包括「切走再切回来草稿还在」「唯一的运行入口还在」「原始事件
切得到也切得回」。另外真跑了一次带三轮审批的图和两次 Copilot 生成(60s,
deepseek-v4-pro),截图逐帧看过。后端 78 个测试全过。
2026-09-17 18:29:16 +08:00
ThearchyHelios c1c13fad41 助手流组件:一份 Step,宽屏和 360px 窄栏共用
AssistantStream 是纯展示的,不连 store。这样画布右栏和问数据页能用同一个
组件,而不是"一个组件三种停靠模式"——后者要求同一个实例出现在三个 DOM
父节点里,React 里除非 portal 否则不成立,硬做就变成跨路由 remount,
输入草稿和展开状态全丢。差别只在 dense 一个开关。

渲染真实事件时发现的几个问题,顺手一起修了:

- 取数运行的最终成果是一个**被截断的双重转义 JSON**(后端按字符数硬切,
  切点落在 JSON 中间是常态),严格 JSON.parse 必然失败,于是这个产品最
  典型的一次运行,成果区呈现为满屏 \"attribute01\", \"attribute02\"。
  parseQueryResult 改为解析失败时截到最后一条完整记录再解析——78 列 1 行
  的表格,比一屏转义文本有用得多。并区分"查询撞了行数上限"和"预览被切断":
  前者该去调查询,后者该去看完整工件,混成一句"已截断"等于没说。

- 审批恢复过的运行有"开始执行"+"继续执行"两条生命周期,run.finished 只
  收了第一条(find 而非 forEach),"继续执行"永远转圈——明明已经跑完。

- 处理完的审批保留着 warn 级别,一直是橙色,看着像还有事没办。

- 一屏 0ms:那些纯赋值节点确实不花时间,把没信息量的数字摆出来反而把真正
  慢的那一步淹了。低于 10ms 不显示,同时空耗时给 undefined 而不是空串。

- output 里 {"result": ""} 很常见(图跑通但出口没接上),会渲染出一条什么
  都没有的分隔线。

验证:新增 scripts/check-stream.mjs,真浏览器跑真组件——5 类真实运行 ×
(无报错/无转义 JSON/无 0ms/不横向溢出)+ 展开看 SQL 和结果表 + 空态。
check-decode.mjs 补 11 条,两个脚本共 68 条断言全过。frontend/preview.html
是离线预览页,只有 dev server 加载(已确认不进 vite build 产物)。
2026-09-17 17:00:50 +08:00
ThearchyHelios ca6e672dfa 事件解码器:全站唯一的"事件 → 人话"翻译层
UI 重构的地基。现有的 describe()(RunPanel.tsx:392)是工程师视角——
db_query__warehouse({"sql":...}) 是函数调用语法、1234+567 tok · $0.0021 是账单、
exit=0 · microvm 是退出码,而且 25 个事件类型里一半直接 return '' 没翻译。

做成独立模块而不是散在各页面,是因为画布时间线、助手栏、运行页要读同一个
故事。两套翻译的话同一次运行在不同地方讲得不一样,用户没法判断哪个是真的。

## 三条原则

- **不回写事件**:run 终态时对全部已落库事件算 manifest_hash,事后增删改会
  对不上。翻译只在前端内存里发生。
- **成对事件合并成一行**:tool.start + tool.end 是同一件事的两个时刻,拆两行
  等于把"查询数据"和"查完了"当成两件事。按 call_id 配对(并发调同名工具时
  按名字会错位),独立 Tool 节点没有 call_id 则回退到名字。
- **节点是天然的分组单位**:一个图节点内部可能有好几轮模型和工具调用,
  它们属于同一个步骤——这正是"简要思考节点"该有的结构。

## 用真实事件调出来的五个坑

从库里导出 5 类真实运行(带查询/带审批/带出具/失败/带思考)当 fixture,
构造的样本永远不会长成那样:

- **审批重复三次**。human.requested 没有 interrupt_id、run.interrupted 才有,
  两个 key 算不到一起;而审批恢复后 LangGraph 重放节点又发一遍 human.requested。
  改用两者共有的 node_id|mode|title 做键,三条收敛成一条。
- **节点重放让同名节点出现两次**。node.started 第二次来时复用原来那条。
- **"继续执行"说两遍**。run.resumed 和 run.started(resumed=true) 是同一件事。
- **标题退化成裸 id**。没起过名字的节点 label 等于 id(后端兜底),"in"/"h"/"out"
  对用户毫无意义——退到节点类型的中文名。
- **空 preview 当详情显示**。{} 不是详情,是噪音。

## 刻意不做的

- 不显示 token 数和美元:那是账单视角,不是"它干了什么"
- 不依赖 canvas/nodeDefs:解码器是纯翻译层,画布只是消费者之一。
  16 行的类型名映射重复得起,反向依赖会让运行页被迫加载整套节点定义
- 未知事件不静默吞掉:宁可显示一条原始的,也比让人觉得"什么都没发生"好

## 验证

scripts/check-decode.mjs 用真实 fixture 跑 21 条断言(转译借 vite dev server,
测的就是应用实际运行的那份)。守的是"审批只出现一次""没有裸 id""没有事件名
泄漏到标题"这类会悄悄退化的性质。
2026-09-17 16:25:33 +08:00