http://39.97.59.228:8013/hrx/mky-vent-base/compare/master...fix/audit-r4-20260929tools/verify/代码审核-第四轮-mky-vent-base-20260929.md(§1 R4-1/R4-2/R4-4/R4-5,§2 M1/M2/M3 重放落点)tools/verify/第四轮-施工任务书-20260929.md 第二章(工作令)AGENTS.md §七(§7.2 基座口径)/§九(改动说明随分支提交)fix/audit-r4-20260929(worktree /root/VentAnaly60/.wt/mky-r4)upstream/master = 46df977e(团队 AI 助手文件层面重构后的新结构)git log 为准(不写死自指 hash)一项一提交,提交信息均带编号(以 git log 46df977e..HEAD 为准):
| # | 提交 | 内容 |
|---|---|---|
| 1 | [Fix R4-M1] … |
点选解读 SSE 补 X-Access-Token(重放 fix/base-agent-token M1,保留令牌可选语义) |
| 2 | [Fix R4-M2] … |
throwStreamHttpError 加 export;点选链路复用它替换裸 throw new Error;formatClickError(重放 M2) |
| 3 | [Fix R4-M3] … |
/ventAI/api/me + getMe() + 按 is_admin 隐藏技能/子智能体/思考级别入口(重放 M3;该路径 2026-09-30 实测校正为 /ventAI/api/model/me,见 §⑩) |
| 4 | [Fix R4-R4-4] … |
删除 Modal/src/index.less 与 antCss.less 双份定义的关闭按钮宽度规则 |
| 5 | [Fix R4-R4-2] … |
extractMarkdownLinks 加 MD_LINK_CACHE_MAX=200 LRU |
| 6 | [Fix R4-R4-5] … |
ConversationNav 滚动跟随缓存 wrappers 列表 |
| 7 | [Fix R4-R4-7/8] … |
markdownSafe 注释旧路径更正;useChatScroll 卸载时 cancelAnimationFrame |
| 8 | [Docs] … |
本说明 + docs/ai-changes/README.md |
| 9 | [Fix R4-R4-5] …(复核 FR-1 收口) |
缓存失效源补 props.messages.length(删 AI 消息场景)+ 注释/文档同步 |
| 10 | [Merge] … |
合并上游 origin/master(5 个新提交,见 §⑨):3 处冲突解决 + 复验 |
| 文件 | 改了什么 |
|---|---|
src/views/ventAI/dataPicker/api.ts |
M1:import getToken;ssePost headers 增 'X-Access-Token': getToken()。M2:import throwStreamHttpError;!response.ok 分支改 await throwStreamHttpError(response) |
src/views/ventAI/dataPicker/useDataPicker.ts |
M2:import StreamError;新增 formatClickError(e)(429 带 retryAfter→分钟、413/404 兜底文案);catch 分支改 error.value = formatClickError(e) |
src/views/ventAI/manageAssistent/api/client.ts |
M2:throwStreamHttpError 加 export(唯一硬阻塞点,函数体零改动)。M3:Api 枚举加 me = '/ventAI/api/me';新增 MeInfo 接口与 getMe()(errorMessageMode: 'none') |
src/views/ventAI/manageAssistent/components/AiAssistantModal.vue |
M3:import getMe;新增 isAdmin ref(默认 false);新增 fetchMe()(失败置 false 不缓存);visible watch 的 fetch 组加 fetchMe();TaskListPanel/ChatInputArea 各透传 :isAdmin="isAdmin" |
src/views/ventAI/manageAssistent/components/panels/TaskListPanel.vue |
M3:props 加 isAdmin?: boolean;optionItems 由普通数组转 computed,按 !isAdmin 过滤 skill/sub-agents |
src/views/ventAI/manageAssistent/components/chat/ChatInputArea.vue |
M3:props 加 isAdmin?: boolean(默认 false);ThinkLevelPopup 连同前面的 .action-divider 包 v-if="isAdmin"。未改 ThinkLevelPopup 组件内部 |
src/components/Modal/src/index.less |
R4-4:删除与 antCss.less 重复的 .zxm-modal-close-x 96px 规则块;保留本块独有的 font-weight/outline |
src/views/ventAI/manageAssistent/lib/markdown.ts |
R4-2:extractMarkdownLinks 加 MD_LINK_CACHE_MAX=200 FIFO LRU(与 renderMarkdownCached 同型) |
src/views/ventAI/manageAssistent/components/chat/ConversationNav.vue |
R4-5:新增 getWrappers() 缓存(失效源=qaPairs.length + props.messages.length + O(1) container.contains 存活校验),updateActivePairByScroll/scrollToPair 改用它,rAF 合并不变 |
src/utils/markdownSafe.ts |
R4-7(顺手):头部注释的旧路径 chatModal/utils.ts 更正为 lib/markdown.ts(纯注释) |
src/views/ventAI/manageAssistent/hooks/useChatScroll.ts |
R4-8(顺手):onBeforeUnmount 时 cancelAnimationFrame(scrollRafId) |
冻结面零触碰:permissionGuard.ts / useAutoLogin.ts / mock-login 相关、.env、public/js/config.js 均未改(git diff 46df977e..HEAD --name-only 已核,无命中)。
X-Access-Token(重放自我方 fix/base-agent-token M1)dataPicker/api.ts 的 ssePost 只带 Content-Type,是基座唯一一条匿名调 TfAgents 的路径;消耗记到兜底账号 click-anonymous,无法按登录人记账。'X-Access-Token': getToken(),与对话链路(client.ts:217/266/317)同名同源。consumeSse(api/sse.ts),本项只补头,没有把旧分支的手写 SSE 循环带回来。throwStreamHttpError/StreamError,429 变可读文案(重放 M2)!response.ok 时裸 throw new Error('HTTP错误: 429');useDataPicker.ts 一行 e?.message || '请求失败',用户看不懂 429/413/404。client.ts:70 的 throwStreamHttpError 加 export(新结构导致的唯一硬阻塞点,函数体零改动;StreamError 本就导出);dataPicker/api.ts 改 await throwStreamHttpError(response)——解析响应体 detail(字符串或 {code,message,retry_after,limit_tokens,spent_tokens}),抛出带 status/retryAfter 的 StreamError;useDataPicker.ts 新增 formatClickError(e):429 带 retryAfter 换算分钟(不足 1 分钟显示「不到 1 分钟」);413 服务端无文案时兜底「附件体积超过上限…」;404 兜底「会话不存在或无权访问」;普通 Error 一律走原 fallback,行为不回归。HTTP错误: xxx 变为可读文案;其余链路(consumeSse 流处理、成功路径)不变。/ventAI/api/model/me + getMe() + 按 is_admin 隐藏全局变更入口(重放 M3;路径经 2026-09-30 实测校正,见 §⑩)client.ts:Api 枚举加 me;新增 MeInfo + getMe()(走 defHttp,axios 拦截器自动带令牌;errorMessageMode:'none' 静默失败,防接口未上线时每次开弹框都弹红错)。AiAssistantModal.vue:新增 isAdmin ref(默认 false)与 fetchMe();visible watch(immediate: true)的 fetch 组每次打开都调 fetchMe();向 TaskListPanel、ChatInputArea 透传 :isAdmin。TaskListPanel.vue:optionItems 转 computed,!isAdmin 时过滤 skill/sub-agents;「新建任务/定时任务」保持开放(属个人功能)。ChatInputArea.vue:ThinkLevelPopup 连同前面的 .action-divider 一起包 v-if="isAdmin"(不连分隔线会残留竖线);思考级别的读取(getModelMsg)不受影响,对所有人可用。fetchMe 失败置 isAdmin=false 且不缓存(每次打开重新拉取,管理员名单可能在服务端变更)——这是历史浏览器验收踩过的坑(失败停留 false 导致管理员看不到入口),已原样保留。未改 ThinkLevelPopup 组件内部(可能被别处复用)。旧分支的 adminConfigured ref 在模板中从未消费(死状态),本次未搬运。Modal/src/index.less:77-87 与 design/vent/antCss.less:201-209 双份定义同一条 .zxm-modal-close-x { 96px } 规则(f4a47a3c 为修生产错位写了两遍),加载顺序不同会互相覆盖。antCss.less 一份(其注释说明了用后代选择器提升优先级的意图);index.less 该块独有的 font-weight: normal; outline: none; 保留。extractMarkdownLinks 加 LRUmarked.lexer 全量解析;流式期间胶囊 computed 每个 token 全量重扫所有 AI 消息,CPU 随会话长度线性放大。MD_LINK_CACHE_MAX=200 的 FIFO Map(与 renderMarkdownCached 同型),以原文为 key;唯一消费方 lib/capsule.ts:61 只读遍历,返回数组被缓存共享安全。updateActivePairByScroll 在滚动 rAF 回调里每帧 querySelectorAll('.message-wrapper') 全量查询(>100 条时每帧 O(n))。watch([qaPairs.length, props.messages.length]));另加 O(1) 存活校验(container.contains 首个 wrapper),防 DOM 整批替换(如切换任务同问答对数)后用到游离节点。scrollToPair 的点击路径也复用。rAF 合并不变。qaPairs.length,而它只随 user 消息数变化——删除 AI 消息(ChatMessages.vue 的 delete 按钮)不会改它,缓存会陈旧(外观级、可自愈)。已把 props.messages.length 并入失效源并同步注释。utils/markdownSafe.ts 头部注释引用的 chatModal/utils.ts 已不存在(重构迁至 lib/markdown.ts),纯注释更正,无行为变化。useChatScroll 的 scrollRafId 原仅在重入时 cancel 自身,卸载后不 cancel(残留回调全 optional chaining,无崩溃);现 onBeforeUnmount 对称 cancel。该 composable 仅在 AiAssistantModal setup 内调用,钩子等价。Modal/src/index.less 用仓库 less@4.7.0 编译(缺失的全局变量按 generateModifyVars 语义打桩),输出 CSS 中 .zxm-modal-close-x 规则已消失、font-weight: normal 保留 → 删除的是与 antCss.less 完全相同的选择器+值,渲染等价。getToken(/@/utils/auth)、throwStreamHttpError/StreamError/getMe(/@/views/ventAI/manageAssistent/api/client)均为真实导出;AiAssistantModal → TaskListPanel/ChatInputArea 的 :isAdmin 透传与两侧 props 声明一一对应;formatClickError 在 handleClick 之前定义(函数声明提升亦成立)。vite build(NAS 16G 悬崖边,硬性禁止;由 Lead 统一走远程构建)。extractMarkdownLinks 缓存命中收益未跑 profiler 量化(结构推导可靠)。在 .wt/mky-r4 执行(node_modules 为指向主检出的只读软链):
# 1) .ts 解析(仓库自带 @babel/parser@7.29.7,经由 .pnpm 路径 createRequire)
node /tmp/parse-ts.mjs <changed .ts files>
# 结果:6 个 .ts 全部 OK(exit 0)
# 2) .vue 解析(仓库自带 @vue/compiler-sfc@3.5.40 的 parse + compileScript)
node /tmp/parse-vue.mjs AiAssistantModal.vue TaskListPanel.vue ChatInputArea.vue ConversationNav.vue
# 结果:4 个 .vue 全部 OK(exit 0)
# 3) ESLint(eslint@8.57.1,仓库 .eslintrc.js)
node_modules/.bin/eslint --ext .ts,.vue <全部 10 个改动文件>
# 结果:exit 0,0 error / 0 warning(-f json 复核:10 文件 messages=0)
# 4) less 编译抽查(R4-4 行为等价)
less.render(index.less, modifyVars 打桩) # 输出无 modal-close-x、保留 font-weight → OK
--ext 参数与直接文件列表混用时 eslint 仅处理匹配文件,已用 -f json 复核确认真处理了目标文件。vite build(任务书明令禁止,8092 构建属 Lead 收口步骤)。adminConfigured:旧分支该 ref 在模板中从未消费(死状态),按最小改动原则未重放。qaPairs.length 失效外,额外保留 O(1) 的 container.contains 校验,覆盖「切换任务后问答对数恰好相同」的边角——比审计建议略保守,无行为风险。extractMarkdownLinks LRU:胶囊 computed 每 token 全量重扫 messages 的外层循环仍在(收益主要在免除最重的 lexer);按消息对象缓存 links 的更大改动未做(改动面与回归面变大,收益递减)。:key="index")——审计定级 🟢 且明确"非本轮施工/可延后"。fix/audit-r4-20260929(worktree /root/VentAnaly60/.wt/mky-r4),基线 upstream/master = 46df977e,无分叉冲突项。[Docs] 提交(清单见 §②,hash 以 git log 为准)。upstream/master 新起(46df977e);2026-09-30 已 merge 更新后的 origin/master(+5 提交,含 API 按域拆分重构、dxf 支持、文件预览修复、导航条缓存),冲突与复验见 §⑨。http://39.97.59.228:8013/hrx/mky-vent-base/compare/master...fix/audit-r4-20260929(推送后由 Lead 核验 HTTP 200)。审计分支交付后,上游 origin/master 又前进 5 个提交(83eb0a1e 来源卡片默认折叠、e7e89214 导航条逻辑+警告修复、
6de78411 API 按域拆分重构、5f41a3be dxf 支持、64cfbb1a 文件预览修复)。本次将 origin/master 合入分支,
逐项核对「审计修改在新结构上是否仍然成立」。
取值原则:AI 助手相关代码冲突时以审计分支为主(M1/M2/M3/R4-* 的审计语义一律保留);
上游若已删除/重构掉载体文件,则把审计逻辑平移到新结构(见下表 api/client.ts 一行),不改审计行为。
核对方式:对每个审计改动文件比对 git grep -c "审计-第四轮" 在审计分支与合并结果上的计数,10 个文件全部一致。
| 文件 | 冲突 | 解决 |
|---|---|---|
api/client.ts |
上游删除(拆分成 api/*.ts)vs 本分支修改(M2 加 export + M3 getMe) |
接受删除。M2 的「加 export」在新结构中天然满足(throwStreamHttpError/StreamError 已是 api/errors.ts 的导出);M3 的 MeInfo/getMe() 迁到新建 api/user.ts(按 barrel 约定 export * from './user') |
components/AiAssistantModal.vue |
双方改同一 import 行 | 取上游的 barrel 路径并补 getMe:import { …, StreamError, getMe } from '.../api' |
hooks/useChatScroll.ts |
双方各加了 onBeforeUnmount 取消 rAF(R4-8 与上游重构撞车) |
两边等价(上游为 destroyOnClose 语义,本分支为 R4-8),按冲突以审计分支为主取审计版本(if (scrollRafId !== null) + R4-8 注释) |
dataPicker/api.ts、dataPicker/useDataPicker.ts:throwStreamHttpError/StreamError 的 import 由已删除的
api/client 改为 api/errors;注释中的旧路径一并更正(与 R4-7 同类)。ConversationNav.vue(R4-5 的 getWrappers 缓存与上游 toPlainTextCached 共存,
失效源仍为「qaPairs.length + props.messages.length」FR-1 收口版)、ChatInputArea.vue/TaskListPanel.vue
(M3 的 isAdmin 过滤/隐藏完整)、lib/markdown.ts(R4-2 LRU 完整)。prettier/prettier 后 0 error / 0 warning。
(prettier 报的 3422 处 Delete ␍ 是 Windows 检出 CRLF 环境噪声——对照组:本次未触碰的 api/chat.ts 同样报 161 处。).tmp-tsconfig.json(scoped:ventAI/** + dataPicker/** + utils/markdownSafe.ts)编译,
ventAI 相关 0 error;其余 61 处均为既有问题(store/、router/、utils/http/ 等,与本次无关)。vite build:exit 0(全量生产构建)。/ventAI/api/me(M3)、X-Access-Token(M1,useDataPicker chunk)、
formatClickError 的 429/413/404 分支文案(M2)、ConversationNav chunk(R4-5)均出现在 dist;
R4-4 在编译后 CSS 中 .zxm-modal-close-x{…width:96px…} 仅剩 1 处(antCss.less 一份,组件层重复已消失)。在 dev(:3100)+ 部署后端上做了浏览器实测,发现 M3 的 me 路径与部署不一致,已修正:
api/user.ts 用 /ventAI/api/me(审计文档 §④ 当时写的契约是 "GET /api/me")。/ventAI 前缀,实际请求 /api/me —— 部署后端返回 404;
该后端 OpenAPI 中实际提供的是 /api/model/me(描述与 MeInfo 契约一致:
{"username": str, "is_admin": bool, "admin_configured": bool}),带令牌请求 200。me = '/ventAI/api/model/me',getMe() 注释同步更新(补 admin_configured 语义)。实测结果(当前部署):
/api/model/me → {"username":"admin","is_admin":false,"admin_configured":false}(管理员账号也是 false);POST /api/subagents/{name}/toggle → 403 admin_not_configured:
"管理员未配置(TFAGENTS_ADMIN_USERNAMES 为空),全局变更端点对所有人拒绝;请运维在 .env 配置
TFAGENTS_ADMIN_USERNAMES 后重启"。即:当前部署下前端隐藏入口与后端"对所有人拒绝"是一致的,不是前端过严;要让管理员看到
「技能 / 子智能体 / 思考级别」入口,需 TfAgents 侧配置 TFAGENTS_ADMIN_USERNAMES 并重启。
其余实测(隐藏分支):左侧仅「新建任务 / 定时任务」、输入区无思考级别控件、打开弹框无红色报错
(errorMessageMode:'none' 生效);对话 SSE 流式、链接胶囊(R4-2)、导航条点击跳转与滚动跟随(R4-5)、
关闭弹框(R4-4/R4-8)均已在浏览器中跑通。M1/M2 的点选解读因该环境主风机页无设备数据、无行可点,
UI 触发未能实测(运行模块经 dev server 取源码确认已含 M1/M2 改动)。