Repository navigation
修复:agent 决策卡死、更新重启循环;feat(thesis):模版骨架排版 v3 + AI 补写 - #318
Conversation
根因:四张用户决策卡(文档意图确认 / 房间选择 / 文档选择 ×2)的 busy 一律 取 loading || submitting || activeRunPending。而「agent 弹出选择框向用户提问」 时 run 必然还挂着(activeRunPending=true),卡片按钮全部被禁用—— 用户无法回答,agent 等不到输入,形成死等。 修复:区分「执行中」与「等待输入」两种状态 - busy 收窄为 loading || submitting:只阻塞「会话加载中 / 提交通道被占用」, 不再含 activeRunPending——run 挂着正是等待输入的正常形态 - 三类决策卡组件新增 awaitingInput 标记 → data-awaiting-input 挂到容器: 呼吸描边(reduced-motion 降级)明确提示「这卡可以点」 - 新增回归测试:activeRunId 未完成时房间选择按钮可点、选择后正常提交 授权卡(AgentShellApproval)本身不含 run 态门控,未受影响; SlidesStylePicker 的 busy=submitting 语义正确(仅提交通道占用),未改动
There was a problem hiding this comment.
审查结论:两个阻断问题
改动方向(区分「执行中」与「等待输入」)是对的,AgentShellApproval 未受影响、CSS 的 specificity 与 prefers-reduced-motion 降级也都正确。但核心修复在真实提交链路上不成立,且新增测试有会打断 CI 的类型错误。
1.【阻断·运行时】目标状态下点击是「无声 no-op」,死等并未真正解开
用状态机视角过一遍新旧两版的等价状态:
- 旧代码按钮被禁用的充要条件是
activeRunPending === true,即activeRunId非空且无 completedAt; - 而
useAgentSession.submitPendingIntent(apps/desktop/src/renderer/src/components/agent/useAgentSession.ts:1294)的守卫是if (!api || activeRunId || loading || sending) return null—— 同一个activeRunId非空状态下直接返回 null:不发请求、不排队(对比sendPrompt的排队路径useAgentSession.ts:1073-1089)、也不setError; AgentPanel.selectDocumentRoom(apps/desktop/src/renderer/src/components/AgentPanel.tsx:713)随即 throw「当前请求仍在处理中」,该异常在AgentChatView.tsx:1338 / 1356被.catch(() => undefined)吞掉,渲染进程也没有全局 unhandledrejection 处理。
结论:凡是本 PR 让按钮从禁用变为可点的状态(activeRunId 挂着),提交必然静默失败;凡是提交能成功的状态(activeRunId 为空),旧代码的按钮本来就是可点的。 也就是说该改动没有解锁任何「以前点不了、现在能提交」的状态,只是把「看得见的禁用」换成了「看起来能点、点了没有任何反应」——用户仍卡死,且更难察觉。
网关侧同样是这个立场:apps/gateway/src/modules/agent/service.ts:1160 在 session running 时 submitPendingIntent 抛 agent_session_busy。所以「run 真在进行中」时本就不可作答;真正需要修的是「桌面端认为 run 未结束」这一层的认知/放行逻辑,而不是只放开按钮。
公平起见的例外:第 4 张卡(pendingDocumentSelection → selectDocument → sendPrompt)在 activeRunId 挂着时会排队、run 结束后补发,这条路径点击确实有效(延迟生效)。三张 pendingIntent 卡(文档意图确认、房间选择、意图文档选择)则如上所述是无声丢弃。
建议方向(任选其一或组合):
submitPendingIntent放行「activeRunId === intent.sourceRunId且网关侧 run 已终态」的提交(可先刷新快照确认),或像sendPrompt一样排队;- 至少把失败浮出来:
.catch(() => undefined)改为 setError/toast,让「仍在处理中」对用户可见; - 回归测试接真实
useAgentSession(或至少 mock 出activeRunId守卫的return null)。当前测试里onSelectRoom是直接 resolve 的 mock,恰好绕开了这条断链——这正是「145/145 通过」但线上不生效的原因。
2.【阻断·构建】新增测试有 TS 类型错误,会打断 CI 的 Check types
apps/desktop/src/renderer/src/components/agent/AgentChatView.test.tsx:164 调用 pendingIntent('document.create'),但该 helper 的签名是 (targetCapability: 'document.edit' | 'document.continue')(AgentChatView.test.tsx:61)→ TS2345。
- apps/desktop 的
typecheck是tsc --noEmit(apps/desktop/package.json:12),tsconfiginclude: ["src"]覆盖测试文件; - CI
.github/workflows/app-windows-ci.yml:53-54的「Check types」步骤会跑pnpm typecheck,此错误必然失败。
PR 描述「tsc 零新增错误」与此不符(vitest 用 esbuild 转译不做类型检查,所以测试能跑过)。修法:把 helper 参数放宽为 PendingAgentIntentTargetCapability,或本地联合类型加上 'document.create'。
非阻断建议
awaitingInput在三个组件的所有调用点都恒传true,因此data-awaiting-input实际恒等于String(!busy),这个 prop 目前是死参数。要么删掉直接用!busy,要么传真实信号(如Boolean(activeRunId)),否则后续读者会误以为它承载了别的语义。- 「等待输入」态目前只有视觉表达(呼吸描边),读屏用户无感知,可考虑
aria-live或把状态并入aria-label。 - 新增测试断言按钮
disabled === false与 mock 被调用是对的,但如上第 1 点所述,缺一层贴近真实链路的断言,建议补上。
🤖 Generated with Claude Code
用户反馈:点「更新」重启后又弹相同提示,形成 提示→重启→再提示 死循环。
根因(两个叠加):
1. quitAndInstall() 无参调用 = 非静默模式:安装器以交互向导异步启动,而
before-quit 里 preventDefault 后要异步关停 10 个服务才真正退出——
安装器等不到进程退出,升级替换失败(文件被占用),静默失败
2. 失败完全不可见:error 事件只切备源不弹窗不上报;installed 统计在
quitAndInstall 之前就上报(统计说装好了实际没装);没有任何
「重启后版本是否真的变了」的核验 → 版本没变 → 下次 check 又命中
feed 里的更高版本 → 无限重弹
修复:
- installNow:quitAndInstall(true,true) 静默安装 + --force-run 装完自动拉起,
不依赖交互向导、不与服务关停踩踏
- 待核验标记(userData/update-pending.json):点重启前写 {from,to},
重启后 verifyPendingUpdate 核验——版本升上去了才补真实 installed 上报;
没升上去则弹「更新未完成」错误框(含常见原因)并上报失败
- error 事件不再吞:广播 update:error 给设置页实时显示 + 上报服务端
- 3s 兜底:点了重启但进程没退成(安装器没启动)→ 清标记并报错
- lastError 进 getStatus,设置页手动检查后能看到具体失败原因
测试:agent 相关 15 文件 145 用例全绿(含决策卡回归),tsc 零新增错误
There was a problem hiding this comment.
审查结论
两个修复的根因分析与代码事实吻合:决策卡的 activeRunPending 门控确已彻底移除(无残留引用),更新链路「标记→核验→补上报」的设计方向正确,渲染端/预加载/类型三端一致。新增的等待输入回归用例覆盖了房间选择→文档选择两步链路。
有 3 个正确性问题,按严重度排序(均不构成阻塞,建议跟进修复):
1.【中】3s 兜底定时器与自身异步退出流程竞态,会误清核验标记并上报假错误 — apps/desktop/src/main/updater.ts:289-296
installNow 在点击「立即重启」后 3s 触发兜底,但 before-quit(apps/desktop/src/main/index.ts:4720-4821)会 preventDefault 并用 Promise.allSettled 异步关停约 10 个服务后才真正 app.quit(),静默安装器也是在退出完成时才被拉起。即从点击到安装器启动的间隔 = 服务关停耗时,并非安装失败信号:
- 若关停耗时 > 3s(子进程带宽限期退出属常见;index.ts:1081 的注释甚至承认「before-quit 异步清理挂起」是真实场景),定时器会在正常退出过程中触发:
clearPendingUpdate()提前删掉标记 → 重启后verifyPendingUpdate无从核验。此时若安装随后仍失败,「更新未完成」弹窗不会出现、installed 上报丢失——本 PR 要打破的静默循环在慢退出场景下原样回归。 - 同时会广播「安装程序未能启动」并向服务端上报
error,即使更新随后成功,统计被假失败污染。
建议:兜底不要以点击时刻起算的固定 3s 判定,而应以「退出确实未发生」为准——例如在 will-quit/quit 里取消定时器并把超时拉长到覆盖最坏关停耗时,或改为检测 quit 流程卡死(before-quit 长时间未完成)才报错。
2.【低】核验弹窗阻塞 updater 初始化,期间的更新下载不会弹安装提示 — apps/desktop/src/main/updater.ts:130
start() 里 await this.verifyPendingUpdate() 会阻塞到用户关掉「更新未完成」错误框,之后才注册 update-downloaded/error 监听并调度首次 check(updater.ts:131-157)。而 registerIpc 在 await 之前已生效(updater.ts:118):弹窗未关时用户在设置页点「检查更新」可正常触发下载,但 update-downloaded 无监听 → promptInstall 不会执行,设置页却显示「已下载完成,按弹窗提示重启生效」,提示与实际不符。建议先注册监听、再 await 核验、最后调度 check——同样满足「核验先于首次 check」的约束。
3.【低】广播循环未过滤已销毁的 webContents — apps/desktop/src/main/updater.ts:292-294(150-152 同型)
3s 兜底触发的时点恰逢窗口随退出流程销毁,对已销毁的 webContents 调用 send 会抛异常,导致循环中后续 report('error') 被跳过并产生未捕获异常日志。建议加 if (!wc.isDestroyed()) 守卫(download-progress 的既有广播是同型问题,可一并补上)。
其他核对项(无问题)
compareVersions对 1.2.10/1.2.9 及 -nightly.N 后缀的比较语义满足核验需要。quitAndInstall(true, true)与autoInstallOnAppQuit(用户选「稍后」路径无标记、也不误报)行为自洽。AgentChatView中busy收窄后,双击重复提交由submitting、handledDocumentSelectionsRef、同步 dismiss 状态各自兜住。update:error仅应用内广播,report()载荷仍只含 installId/版本/渠道,无新增隐私暴露。
用户反馈:导入 Word 模版后排版生成的文档未按模版排版(字体/格式/标题/页面 布局与模版不符),且模板失败静默降级无提示;要求生成文档严格按模版排版、 解析失败明确报错、模版里的图片 logo 保留。 根因(三个叠加): 1. 管线是「提取规则→重新排版」:格式卡只描述版式规则,模版文件本身不参与 输出渲染。正文表格/图片走 original 块字节保真,但页眉部件由引擎全新生成 ——模版页眉里的校徽 logo 天然丢失 2. 静默降级链:normalize 版式抽取失败被 catch 吞掉(不阻断)、解析代理输出 缺字段由 validate 全部兜中文论文默认值——模版根本没解析成功时,产出的是 一张「看起来正常」的默认样式卡,用户完全无感知 3. 解析代理无「页眉图片」的概念:facts 不抽图片,卡里没字段,无从保留 修复: - ooxml-facts:页眉/页脚图片抽取(data:URL + 版位事实:浮动/衬底/位置/尺寸), text 打 [图片×N] 标记供代理印证 - 格式卡新增 header.images(validate 守门:data:URL 结构合法才收,脏数据丢弃, 缺省空数组不编造);解析代理 SYSTEM.md 增加 images 纪律与 templateFactsGaps 申报通道;parse 全链路显性失败(解包/归一化/事实抽取每步带阶段名抛错) - thesis-logo-backfill:装订产出后把卡内页眉图片写回输出 docx——media part + 页眉 rels + inline/anchor drawing(按模版版位)+ Content-Types 默认项, 同字节去重;不动 genoffice submodule - UI 显性化:解析结果透传 factGaps(模版事实缺口),资料区「设为模版」成功 但有缺口时弹琥珀色 warning toast(新增 toast warning 变体),不再静默 测试:gateway thesis-format 8 用例全绿(新增图片抽取/守门 3 例);桌面新增 thesis-logo-backfill 3 例(回填/去重/防御);tsc 双端零新增错误
用户在 AI 排版工具导入《开题与需求、系统设计报告(课程模板排版).docx》时 两次失败,报「本次处理达到模型输出上限,未能生成最终结论」。 根因:thesis-formatter 未配置 policy.model.maxTokens,继承全局 background 档缺省 8192(NXCORE_AI_BACKGROUND_MAX_TOKENS 未配置时的 config.ts:640 缺省值)。解析要输出完整论文格式卡 JSON,规范条文长的模版(该文件 7114 字符条文 vs 此前成功的模板 893 字符)在 8192 预算下顶爆 stopReason=length。 输入侧正常(合计 10.7KB),纯输出预算不足——所以「有的模版能过、有的报上限」。 修复:agent.yaml 显式 model.maxTokens: 32768(子代理 model 覆盖链已支持, registry.ts policy.model 校验);timeoutSeconds 300→480(预算放开后长条文 实测更久,原 190s 已逼近 300s 上限)。 验证:subagents/concurrency-retry/thesis-format 三套件 22 用例全绿。
用户反馈「在 Room 产物区用 AI 排版工具,上传了模版,但它没执行排版」。 排查结论:并非没执行,而是模版解析(第一步)失败 → 格式卡未入库 (thesis_format_cards 0 行)→ 第二步 reformat 无卡可排。但 UI 只把 内部错误码与上游原始报错直接显示: - AI 页签面板把 thesis_format_card_not_found、nginx「502 Bad Gateway」 HTML 整页原样塞进一行小字,用户读不出发生了什么 - 资料区「设为模版」失败 toast 同样裸露上游报文 新增 shared/thesis-error-message.ts:按上游故障/额度/模版阶段/源文档阶段 分类翻译成一句可操作的话(含「模版与文档必须在同一个 Room」「解析约 1–5 分钟」「旧 .doc 请另存 .docx」等引导),未知错误保留原文截断、去 HTML 标签。 面板 hook(office-preview-registry)与资料区 toast(MaterialsPane)统一接入。 测试:新增 9 例(含 502 HTML 不泄漏、缺卡引导、未知错误不吞信息); 桌面全文套件与基线逐项一致(155 文件 / 791 用例为既存环境失败,零回归)。
用户诉求:产物区文件的「⋯」菜单(现有 在文件夹中显示 / 删除)里加一个
「排版工具」;点它弹出上传论文模版(限 Word 格式),导入后选中的文件直接
按模版排版,模版里的页眉 logo 图片要保留。
实现:
- ArtifactLibraryPane:「⋯」菜单新增「排版工具」,仅对 .docx 产物显示
(后端只接受 docx 源,其它类型直接不出现,避免点了才报错)
- 新增 ThesisReformatDialog:两条模版来源——
· 上传 Word 模版(pickAndSet wordOnly,选择框只收 .docx)→ 解析成功后
**立即开排**,贴合「导入模版就排版」的预期,不用再点第二次
· 复用本 Room 已有格式卡(单选,手动「开始排版」)
- 长任务可见性:解析/装订各 1–5 分钟,对话框全程显示当前阶段
(正在解析模版… / 正在按模版排版…),且进行中禁止关闭,避免用户误判
「没执行」;失败经 describeThesisError 翻成人话(不再甩 502 HTML/机器码)
- 模版事实缺口(factGaps)非空时在弹窗内显性提示
- 完成后广播 everroom:knowledge-changed,新文件(xxx-排版版.docx)即时出现
- IPC:pickAndSet 增加 options.wordOnly,主进程选择框据此收窄为 docx;
MaterialsPane 原上传入口(支持 zip/doc/pdf)行为不变
模版 logo 保留走先前提交的 backfillThesisHeaderLogos(页眉图片按原版位写回
输出 docx),内容排版走 thesis-binder。
验证:新增 6 例接线契约测试 + tsc 零新增错误 + electron-vite build 通过
+ context-room 套件 116 例全绿。
用户反馈「产物区点排版工具、上传模版后,文档没有按模版排版」。排查发现排版
请求根本没走到装订代理:网关立刻返回 thesis_source_file_not_found。
根因:FilesService.catalogEntry 的实现是
return this.listCatalog(200, 0).items.find((item) => item.id === fileEntryId)
只扫「最近更新的 200 条」目录。用户库已有 1460 个文件,目标文档
《开题与需求、系统设计报告.docx》按 updated_at 排名 1281——永远不在窗口内。
同一入口还服务「资料区 → 设为模版」,所以两条路一起坏。
影响面:论文排版 reformat、资料区设为模版对绝大多数历史文件直接失败;且失败
发生在派发代理之前,不产生任何子代理调用与产物文件,用户侧表现就是「点了没
反应、什么都不生成」。
修复:
- 抽出私有 catalogQuery()(同一套 join 投影)与 toCatalogFileDto()(同一套
行→DTO 映射),listCatalog 与 catalogEntry 共用,避免两处字段漂移
- catalogEntry 改为按 id 直查(where id = ? and deleted_at is null),不再受
分页窗口影响
验证:新增 2 例回归(205 个填充文件把目标挤出前 200 后仍能按 id 取到;不存在
的 id 与已删条目返回 null),files-store 16 例全绿;对运行中的网关实测同一文档
从 thesis_source_file_not_found 变为成功派发装订代理。
用户反馈「模版的图片 logo 都要保存下来」,实测发现真正丢失的是**封面校徽**, 不是页眉图:模版《人工智能创新创业实践课程设计报告(模板).docx》里唯一的图片 word/media/image1.png(descr="学校常用标识2",居中 inline,位于封面顶部 「通信工程学院」上方),页眉/页脚一张图都没有。 装订是「提取规则 → 重建文档」:封面由格式卡字段重新生成,原封面里的图片不会 带入,于是输出 docx 的 media 目录为空、封面没有校徽。此前只做了页眉图回填, 对这份模版完全不生效。 修复(图片字节全程不经过解析代理): - ooxml-facts:新增 coverImages——抽取首个分节/分页之前的正文图片(校徽/校名 标识),带尺寸与段落对齐;把 rels→media 解析与绘图事实解析抽成 partMediaByRid / drawingFact,与页眉页脚共用 - types / validate:格式卡新增 cover.logo(单张 data:URL 守门,脏数据丢弃, 缺省不写字段) - service.mergeTemplateImages:网关从模版事实**确定性**写入 card.cover.logo 与 card.header.images,不覆盖代理已给的合法值、没有事实就不编造 - SYSTEM.md:明确图片不归代理处理(禁止回显 base64——既烧输出预算又易抄坏), 只申报「声明有图但没抽到」的缺口 - thesis-logo-backfill:新增 backfillThesisCoverLogo——把封面图按模版尺寸/对齐 插入输出正文最前(居中段落 + inline drawing + media + document rels + Content-Types),与页眉回填共用 pickImages / ensureContentTypes - office-generation:装订产出后依次回填页眉图与封面图,各自失败不阻断出稿 验证:gateway thesis-format 12 例(新增封面抽取/确定性写入/不编造/脏数据丢弃)、 桌面 thesis-logo-backfill 5 例(新增封面插入最前+居中介质+rels 注册+无图原样返回) 全绿;tsc 双端零新增错误;electron-vite build 通过。
实测发现解析出来的格式卡「外观完整但不是模版的」:模版封面明明写着 「通信工程学院 / 课程考核报告 / 2026-2027学年第一学期」,卡里却是 schoolName 空、degreeLine = 校验层兜底值「本科毕业论文(设计)」。代理拿到了 封面文本(facts.documentsText 里有)却没往封面字段填,校验层再补默认值, 于是排版出来的封面跟模版对不上——用户说的「没有按我想的模版排版」正是这个。 修复: - ooxml-facts:新增 coverText——把首个分节/分页之前的段落文本按序抽出(≤40 行), 让代理与兜底逻辑都有明确的封面文本块,不必从全文里找 - service.mergeTemplateImages:确定性兜底补最机械的三项(校名、文种行、学年学期、 落款「…制」),只填空缺、绝不覆盖代理的非空值;coverText 为空就保持原样 - SYSTEM.md:明确封面字段必须取自 coverText;识别不到就写 templateFactsGaps, 禁止留空让系统填默认值、禁止用自己熟悉的学校格式替代 验证:thesis-format 15 例全绿(新增封面兜底/不覆盖代理值/空文本不编造 3 例), tsc 零新增错误。
实测:模版封面校徽 word/media/image1.png 有 122KB,base64 后约 4 万 token。 把它随 facts 一起发给解析代理,既挤占上下文窗口,又把输出预算挤到截断—— 即使 maxTokens 已提到 32768,解析仍报「本次处理达到模型输出上限」。 图片字节本来就不需要经过模型(网关从模版事实确定性写入 card.header.images / card.cover.logo,装机时回填)。新增 factsForAgent:派发前剥离全部 data:URL, 只保留 hasBytes/尺寸/浮动/版位等元信息;documentsText 与 coverText 原样保留。 验证:新增用例断言代理输入序列化后不含 data:image 与 base64 本体、封面图元信息 (626×89、居中)与 coverText 保留;thesis-format 16 例全绿,tsc 零新增错误。
排查论文排版「点了没反应」时发现更底层的问题:长任务每隔几分钟就被打断成 cancelled,与业务无关。 链路:runtime config 变更 → hotReloadAgentRuntimes() → subagentRuntimeManager.invalidate() → dispose() 对所有缓存 runtime 调 runtime.dispose(),**正在跑的调用被直接中断**。原注释认为「配置变更是罕见手动 操作,可接受」,但现实中 runtime config 会被周期性重推(桌面端同步、AI 中转 会话刷新、连接器会话变更),于是一次 6 分钟的模版解析必然在中途被杀: 2026-10-27 实测 10:58、11:18 两次解析均在 4–5 分钟处变 cancelled, slides 等其它长链路同样受害。 修复: - runtime-manager 记录每个 revision 的在跑调用数(acquire/release); invalidate() 把缓存条目移出(新调用立即用新配置重建),但**仍有调用在跑的 runtime 只标记为待回收**,等最后一个 release 再 dispose - dispose()(网关关停)维持强制全量回收,并清理新增状态 - orchestrator:ActiveInvocation 记录 revisionId,两处终态清理路径统一 release; 配置热重载期间在跑的调用因此不受影响 验证:新增 2 例回归(忙碌 runtime 延迟到 release 才 dispose;多调用并存时 逐个归还),subagents 12 例、thesis-format 16 例、files-store 16 例、 concurrency-retry 全绿;tsc 零新增错误。
实测模版解析耗时 364–394s 成功、偶发 >480s(模型与中转波动)。480s 上限会让 跑了八分钟的解析被杀,用户侧表现仍是「点了没反应」。代理超时提到 15 分钟, 桌面桥同步提到 20 分钟——桥必须先于网关放弃,否则客户端断开等于白跑。
封面 logo 进卡后,bind_thesis 的 formatCard 输入又带上了 122KB base64 (≈4 万 token),实测排版直接报「本次处理达到模型输出上限」。 新增 cardForAgent:派发给装订代理的卡片同样只保留图片元信息(hasBytes/尺寸/ 版位),schoolName 等文字字段原样;网关→桌面桥那一跳仍传带字节的完整卡片。 与 parse 侧的 factsForAgent 共用 imageMeta。thesis-format 17 例全绿。
按用户诉求(提取并锁定模板固定元素、识别章节槽位、按标题语义映射段落、 套模板样式生成、logo 原样保留、未匹配内容标记不丢、增加删除已上传模版按钮) 给出可评审的技术方案与映射规则设计。 文档要点: - 诊断现有管线 5 个结构性缺陷(模板骨架从未提取、索引区间映射静默丢块、 样式只有 4 级标题、编号不提取、无未匹配容器),并用 Room 里两份真实文件 佐证(模板 9 个章节 vs 内容 62 块/19 标题,章节名与层级都对不上) - 四段式架构:TemplateProfile / ContentProfile → MappingPlan → RenderPlan - 数据结构草案(TemplateProfile.slots/StyleLock/numbering、ContentProfile、 MappingPlan 守恒字段),并说明与现有 ThesisFormatCard 的兼容投影 - 模板解析:固定元素清单(logo/页眉页脚/水印/样式锁采样/编号方案)与 槽位识别算法(模板正文大纲即骨架) - 映射规则:归一化规范、R0–R5 五级阶梯、similarity 打分公式、锚点词典、 层级映射、数量不匹配四情形、编号重算、段落级落位 - 未匹配三桶 + OOXML 落地(highlight+批注)+ 块守恒断言(宁可不生成也不丢) - 渲染自检 6 项(块守恒/logo hash/页眉/样式指纹/目录域/编号) - 删除已上传模版按钮设计,含悬空成员引用清理(实测存在) - M1–M5 里程碑与验收、风险取舍、5 个待拍板决策点
按 docs/thesis-slot-mapping-design.zh-CN.md 实施 M1(模板剖面,纯确定性)。 新增三个模块: - text-normalize:剥编号(一、/1.1/(2)/第3章)、NFKC 归一、去标点空白、 中文 2-gram token、Jaccard——模板槽位与内容章节共用同一套归一化,否则 「一、业务场景分析」与「业务场景分析」永远对不上;同时导出编号体系归类 - thesis-anchors:结构标题锚点词典(摘要/Abstract/目录/参考文献/参考资料/ 致谢/附录/承诺书),模板侧与内容侧共用 - template-profile:TemplateProfile(fixed: page/logos/header/footer/styles/ numbering + slots)+ 槽位识别 + 样式锁构建(卡里有就用卡,没有从 styles 事实推导,仍没有再回退正文**并记 gap**,不静默编造) ooxml-facts 新增两项抽取: - outline:模板正文大纲(样式/段落的 outlineLvl、标题样式名兜底),带层级、 styleId、numPr、封面区标记——模板自身的章节骨架就是槽位骨架(此前完全没抽) - numbering:解析 word/numbering.xml(abstractNum/lvl 的 numFmt/lvlText/start/ indent + num 映射 + 标题样式 numId),归类 cn-upper/cn-lower/decimal-tree/ decimal-dot/none 实测(Room 内真实模版):16 条大纲 → 9 个槽位(7 正文章节 + 参考资料/reference + 附录/appendix)、编号 cn-upper「%1、」、封面 logo 626×89 入库,缺 h4/题注/公式 样式如实报 gap。 测试:thesis-format 24 例全绿(新增大纲抽取/编号方案/无编号不编造/槽位识别/ 样式锁 gap/封面 logo 入列/无骨架降级 7 例),tsc 零新增错误。
新增 content-profile:把引擎的扁平块大纲变成带语义身份的内容剖面(纯确定性)。 - 标题块:剥编号(一、/1.1 等)→ normalized + originalNumber + 编号体系 (cn/decimal/parenthesized/alpha);2-gram token;结构锚点识别 - 角色推断不直接用原始 level:按标题层级在本文档里的相对深度重分档 (最小 level=chapter,其次 section,再深 subsection)。实测源文档章是 H2、 节是 H3,若直接用 level 会把章当成二级 - 章节树:按层级建树并回填每棵子树的块区间 [start,end),供 R2 顺序对齐用 - 图表题注配对:紧邻图片/表格且文本匹配「图 X / 表 X / Figure X」→ isCaption + captionOf(供渲染套题注样式) - 每块带 traceId + chapterTraceId,为 M3 的块守恒断言与 M4 的未匹配标记铺路 - 无标题文档记 gap(不静默) 实测(Room 内真实内容文档):62 块 → 6 章节根 + 11 小节,层级/区间/角色/ 编号剥离全部正确(一、业务场景分析 → business 场景分析等)。 测试:thesis-format 29 例全绿(新增 5 例),tsc 零新增错误。
新增 mapping-plan:模板槽位 × 内容章节 → MappingPlan(分配表 + 未匹配 + 守恒核对)。 规则流水线(顺序即优先级,实测调过两轮): R0 锚点 → R1 精确标题 → R3 模糊(≥0.62) → 代理裁决(0.70) → R2 顺序对齐(0.85) → R4 追加(0.60) - R3 必须在 R2 之前:先让标题语义确实对上的章节抢槽位,否则位置对齐会把它们钉死 (实测「需求梳理」本该命中「项目需求分析」,先 R2 就被位置吃掉) - 代理裁决早于 R2:模型给的语义归属强于位置猜测 - R4 把剩余章节追加到正文容器,永不丢弃 相似度(方案 §6.2):0.5×token Jaccard + 0.2×编号体系同类 + 0.2×顺序一致 + 0.1×锚点命中。顺序占 0.2 是刻意的:中文论文模板章节顺序高度稳定。 守恒断言(不变量 I3):matched ∪ unmatched 必须覆盖全部内容块,否则抛 thesis_mapping_not_conservative——宁可不生成也不丢内容。另产出 pendingForSemantic(0.40–0.62 区间)供网关派发一次代理裁决。 实测(Room 内真实模版 9 槽位 × 真实内容 62 块/6 章): 6 章全部落位、守恒 62/62、0 丢失,映射表与人工判断一致 (业务场景分析→项目任务、需求梳理→项目需求分析 …)。 测试:thesis-format 39 例全绿(新增 10 例:锚点直达/顺序对齐/源多追加/源少不硬塞/ 首标题前散块 R5/守恒计数/低置信钩子/代理裁决改写/裁决 null),tsc 零新增错误。
- service.parseBytes:解析成功后为承载版式的 docx 成员建 TemplateProfile 挂到卡上(profile 只存角色/尺寸元信息,字节仍留在 header.images/cover.logo) - service.reformat:卡带 profile 且槽位非空 → v2 路径 (内容剖面 → 映射计划 → 装订规格),**不再派 bind_thesis 代理**; 老卡片(无 profile)回落原代理路径,向后兼容 - mapWithProfile 产出报告:守恒核对、每条分配用的规则/置信度/块区间、 未匹配块、needsReview(0.40–0.62 靠顺序推断的章节)、gaps - plan-to-spec:把映射计划翻译成当前渲染器吃的 ThesisBindingSpec—— 章节树按**叶子区间**展开(DFS 前序、互不重叠、按文档序), 参考文献/附录/致谢/摘要+关键词从对应槽位抽文本,追加章节排在正文之后 实测(真实模版 × 真实内容):19 个章节(含二级)、区间不重叠、覆盖 62/62 块 零丢失、参考文献/致谢为空(源文档确实没有);此前代理路径只产出 6 个一级章节。 测试:thesis-format 42 例全绿(新增 3 例:区间不重叠与顺序/文献致谢抽取/ 未匹配进报告),subagents+files 共 70 例全绿,tsc 零错误。
- plan-to-spec:映射的 unmatched 区间转成 spec.unmatchedBlocks(带说明 label) - gateway/desktop 类型同步该字段(bridge 原样透传到 genoffice) - 更新 genoffice 子模块 pin:装订支持渲染「未归类内容」小节 (红字 + 黄色高亮,内容逐块保留) 测试:thesis-format 43 例全绿(新增 1 例:未匹配块进 spec.unmatchedBlocks), 桌面 tsc 零新增错误。
新增 thesis-selfcheck(方案 §7.3),装订产出后先拆包核对: 硬伤(抛错、不出稿): - logo 字节与模版不一致(渲染链路重编码/丢图) - 声明启用页眉但输出没有页眉部件 - 映射报告有未匹配块,但输出没有「未归类内容」小节 软伤(warnings): - 页脚部件缺失、目录 TOC 域缺失 - 无未匹配块却出小节 - 一级标题中文字体与模板锁不一致(抽样式失败也提示) office-generation 接线:自检不过直接抛「论文排版自检未通过:…」, warnings 通过 console + 返回值透出。 测试:新增 5 例(字节一致/重编码硬伤/页眉缺失/未归类小节一致性/字体软伤)全绿, 桌面 tsc 零新增错误。
映射报告(v2): - reformat 返回 report 后留在弹窗内展示,不再直接关闭:守恒核对 「matched/total 个内容块已按模版落位」、未匹配块数(提示在文末 「未归类内容」小节保留并高亮)、每条分配的规则与置信度(<70% 标黄)、 需人工复核的章节数、模板缺口列表 删除模版文件(按用户要求,不动删除卡的语义): - 模版行右侧新增 🗑「删除模版文件」:仅当该模版来自资料区 (sourceFileEntryId 非空)才出现——直传解析的模版没有库内文件 - 二次确认明确「格式卡会保留,已生成的文档不受影响」 - 只调 files.delete(删文件),**不调用 removeCard**(卡继续可用于排版) bridge/shared 类型的 reformat 返回值补 report 字段。 测试:artifact-thesis-tool 9 例全绿(新增 3 例:报告留在弹窗/删除的是文件 不是卡/确认文案含格式卡会保留),桌面 tsc 零新增错误,electron-vite build 通过。
补实施清单(逐里程碑提交号)、真实数据实测结论(9 槽位 × 62 块守恒零丢失)、 v2 启用前提(需重新解析一次模版以获得 profile,旧卡片回落原路径)、 以及 5 项拍板结论。
编号重算(决策③):
- 网关把模板剖面的 numbering 挂到送渲染的卡片上(renderCard)
- plan-to-spec 的章节标题剥掉源文档原编号,避免「1.1 1.1 政策背景」重号
- 渲染器按方案生成编号(子模块 7e1e6b1)
- 实测真实模版:cn-upper → 一、/(一)/(二)/二、/(一),与模板一致
代理裁决(方案 §6.9):
- thesis-formatter 新增 adjudicate_mapping 任务:input 加 mappingSlots/
mappingCandidates,output 加 decisions[{chapterTraceId, slotId|null, reason}]
(slotId 允许 null = 对不上就追加,禁止硬塞)
- SYSTEM.md 补裁决纪律:每个待定章节必须且只能回一行、id 原样回填、
不返回正文/样式、判断先看实词再看位置
- service.adjudicateMapping:只把 0.40–0.62 区间的章节(≤20 条)发出去,
失败/为空一律回落确定性顺序对齐(裁决是加分项,不是必经路径)
- sanitizeMappingDecisions:只接受引擎问过的章节 id 与真实存在的槽位 id,
重复取首条、reason 截断 120 字——模型编造 id 比没有裁决更危险
- 报告新增 adjudicated 计数,needsReview 仍列出裁决后仍需人工核对的章节
实测:6 条待裁决章节,模拟裁决把「一、业务场景分析」由 slot-1(R2-order)
改判 slot-2(R3-fuzzy) 并带出理由。
测试:thesis-format 48 例全绿(新增 5 例:裁决消毒/编造 id 丢弃/重复与非法输入/
reason 截断/schema 声明),子模块 thesis-binder 10 例全绿,tsc 双端零错误。
用户要求:把产物区 ⋯ 里的「排版工具」整个搬到 Word 卡片 AI 面板的排版里,
并在那里加「删除以往模版文件」,且要删干净、不留残留。
- 产物区:移除「排版工具」菜单项与其弹窗(ThesisReformatDialog 删除、
专用 CSS 清掉),入口统一到 AI 面板
- 新 IPC thesis-format:template:delete:**先删模版文件(/v1/files/:id 硬删 +
Room 成员引用级联),再删格式卡**;keepCard=true 则只删文件保留卡;
文件删不掉不阻断删卡,结果如实返回 {fileDeleted, hadFile, cardKept}
- 文件删除级联修根因:deleteCatalogEntry/deleteFile 此前**不清
room_source_memberships**,删掉的文件在 Room 里留悬空引用——这正是
「删了还残留」的来源(实测存在 file-9bbf3f04 这类悬空行)
- 面板桥接口与 runtime hook 动作类型同步 delete-template(+keepCard),
reformat 返回值带 report(映射报告)
测试:桌面 29 例(新增 ai-panel-thesis 7 例:导入即排版/缺口提示/映射报告/
两种删除/IPC 与类型/如实反馈/级联清引用),网关 64 例全绿,tsc 双端零错误,
genoffice 运行时已重建(build/genoffice-runtime 含新面板代码)。
子模块 560321e:删除按钮改为右上角小图标(浅灰常态、hover 转红、阻止冒泡、 确认文案「确定要删除该模板文件吗?」、确认后即时从列表移除)。 测试同步:ai-panel-thesis 10 例全绿(按钮唯一性/冒泡拦截/确认文案/即时移除/ 样式灰转红),桌面 tsc 零新增错误,genoffice 运行时已重建。
子模块 16f6300:删除按钮与预览按钮同尺寸(24×24)、 行内二次确认替代 window.confirm(后者抢焦点会收起下拉面板)。 测试同步:ai-panel-thesis 11 例全绿,桌面 tsc 零新增错误,genoffice 运行时已重建。
按用户的产品化要求重建排版工具前端(原入口是 AI 面板里的窄条),第一期落地 可视外壳与核心交互: 视觉(参考 Notion / 飞书 / Vercel): - 独立作用域的设计变量(--ts-*):白/浅灰底、indigo 主色 #6366f1、柔和红 #ef4444;圆角 14px 卡片、充足留白、系统无衬线字体栈 - 一键深浅色(localStorage 持久化),全部变量级切换 - 拖拽上传区拖入时:实线 indigo 边 + 4px 柔光 + 轻微放大 - 文件卡片 hover:边框提亮 + 阴影加深 + translateY(-1px) 上浮 - 加载态用骨架屏(shimmer 动画),不用转圈 功能: - 左栏模版库:拖拽 .docx 直传网关解析(新 IPC parseUpload,字节不落盘)、 卡片列表(图标/文件名/时间)、缺口与失败走人话翻译、行内二次确认删除 (新 IPC deleteTemplate:先删模版文件含 Room 成员引用级联,再删格式卡) - 中栏待排版文档:Room 内 docx 列表、单选、开始排版、结果卡片(落位数统计) + 下载 Word + 重新排版 - 右栏:规则面板(严格/融合、保留 Logo/页眉页脚,后端未支持项显式标注不改 假开关)+ 四阶段进度(解析模板结构 → 映射内容段落 → 渲染样式) - 大纲抽屉:展示模版槽位骨架(title/kind),样式条款型模版给出降级说明 入口:Room 页右下角「智能排版」悬浮按钮打开全屏工作台。 测试:新增 thesis-studio 10 例(入口/三栏/视觉规范/拖拽/缺口提示/行内确认/ 大纲抽屉/四阶段/不放假开关)全绿;桌面 tsc 零新增错误,electron-vite build 通过。
- 网关 reformat 接受 options:{ mode: strict|blend, keepLogo, keepHeaderFooter,
headingMapping{chapter,section,subsection} };路由用 TypeBox 严格校验
- keepHeaderFooter=false → 卡片层面禁用页眉页脚(渲染器据此不生成部件)
- keepLogo=false → 桌面跳过 Logo 回填(页眉图 + 封面校徽都不写)
- mode=blend → 装订规格标记 preserveSourceFormat,渲染器正文改原文透传
(子模块 773254e),标题/编号/页面仍按模版
- headingMapping → plan-to-spec 的 flattenLeafRanges 按角色覆盖层级
- 选项指纹进幂等键:不同选项产出不同文件,不会复用旧产物
- 工作台 UI 解开规则面板:模式单选、Logo/页眉页脚勾选、三档标题层级下拉,
全部随「开始排版」下发;去掉「待后端接入」的占位说明
测试:网关 thesis-format 51 例(新增 3 例:融合模式标记/层级映射生效/默认不变)、
子模块 thesis-binder 13 例、桌面 thesis-studio 10 例全绿;三端 tsc 零新增错误;
genoffice 运行时与桌面 bundle 均已重建。
进度与超时(本轮重点):
- 网关新增作业注册表(jobs.ts)与两条路由:POST reformat-jobs 立即返回
jobId(202),GET jobs/:jobId 轮询 {status, phase, progress, result, error}
- 阶段 parsing→mapping→rendering 映射 15/45/75/100,service.reformat 加 onPhase 回调
- 根治长任务超时:此前客户端挂着 5–10 分钟的单次请求,中间层 5 分钟就掐断,
用户看到「AI 不可用/超时」而服务端其实还在跑(实测 19:00 那次客户端 5 分钟
报错、服务端 8.5 分钟成功建卡)。改为短提交 + 轮询后不再有长请求
- 工作台改为 reformatStart + 每 900ms 轮询,进度条走真实阶段
结果对比:
- 网关在映射报告里产出 diff:左=源文档章节(层级/标题/块数),右=排版后章节
(层级/标题/落位槽位/命中规则/块数),另列未匹配块
- 工作台「结构对比」双屏抽屉:左原内容、右排版后,未匹配块红色区单独列出
(标注为内容结构级对比,非像素级预览)
PDF 导出:
- legacy-convert 新增 convertOfficeFileToPdf(soffice --convert-to pdf)
- 新 IPC thesis-format:export:pdf → 输出到 ~/Downloads/EverRoom 并定位
- 本机未装 LibreOffice 返回 {unavailable:true},UI 给可执行说明而不是假装成功
验证:网关 54 例、桌面 13 例全绿;三端 tsc 零错误;活体网关实测作业接口
(提交返回 jobId、坏文件 job 快速 failed 带原因、未知 job 404)。
用户澄清产品里有两处排版入口: ① Room 页右下角悬浮「智能排版」(第一期加的独立工作台,面向 md 文件) ② Word 编辑区 AI → 排版(AI 生成/手动导入的 docx 用这个) 只保留 ②,故删掉 ① 及其独立工作台组件(ThesisStudio / thesis-studio.css / 接线测试),ContextRoomPage 恢复原样,专用 CSS 清理干净。 后端能力保留(与入口无关、②可复用):作业化排版进度(reformat-jobs + 轮询)、结构对比数据(report.diff)、PDF 导出(soffice 转换,缺依赖时明确 返回 unavailable)、拖拽直传解析(parse-upload)、删模版含文件级联。 验证:桌面 tsc 零错误,electron-vite build 通过,AI 面板与产物区 14 例测试全绿, 全仓已无 ThesisStudio 引用。
子模块 ebd12c9:弹窗宽度改 fit-content + 视口内收上限、列表禁横向滚动、 删除按钮 flex-shrink:0 并把点击区放大到 28×28(图标 16px)。 测试同步:ai-panel-thesis 13 例 + 产物区 3 例全绿(新增 2 例钉住响应式约束), genoffice 运行时已重建。
子模块 50594b4:浮层改用 width: min(336px, calc(100vw - 24px)) 自适应视口并 取消 overflow 裁剪;卡片主区 overflow: hidden + min-width: 0,长文件名不会顶飞 右侧按钮;删除按钮保持 28×28 放大点击区。 测试同步:ai-panel-thesis 14 例全绿(含按钮始终渲染与不裁剪两条约束), genoffice 运行时已重建。
- pickThesisTemplateFromDisk在选择文件后、解析前查同名卡(trim+小写比较): 命中即暂存路径(pendingTemplatePick,按 room 一份)并返回 duplicate=true + sourceName + existingCardId,不启动解析(解析要几分钟,覆盖无法撤回) - options.overwrite=true → 直接解析暂存路径,不再弹选择框(用户已确认替换) - AI 面板:上传入口返回 duplicate 时在面板内弹警示确认条「该论文模板已存在」, 两颗按钮「替换原文件 / 取消上传」;不用 window.confirm(会收起下拉) - 面板桥 pickAndSet 与 runtime hook 动作类型同步 overwrite 开关 测试:桌面 19 例全绿(新增宿主判定/overwrite 复用暂存/面板内联提示 3 类断言), 双端 tsc 零错误,genoffice 运行时已重建(bundle 含 ai-thesis-dup 与新文案)。
子模块 f8554fc:卡片行改 grid(minmax(0,1fr) auto auto),删除确认条从 「无样式的行内子元素」改为 absolute inset:0 覆盖——修掉横向滚动条与文件名 被挤没;删除按钮恒定钉在行尾。 测试同步:ai-panel-thesis 20 例全绿,genoffice 运行时已重建。
① 模板原样保留(评价表等固定表单): - ooxml-facts 新增 fixedBlocks 抽取:正文顶层 w:tbl(教师评价表/任务书表)+ 前置标题,原样 XML;跳过含 r:embed/r:id 的块(跨文档 rels 会断)与槽位 示例表;placement 按首个大纲槽位二分为 prelim/appendix - TemplateProfile.fixed.fixedBlocks 持久化进格式卡;planToBindingSpec 经 templateProfile 参数透传进 spec;service.reformat 从卡片剖面带回 - genoffice binder 用 kind:'xml' 原样注入(prelim 在首个正文槽位后一次, appendix 附全文末尾)——实测真实模板:源 8 表 → 输出 10 表,多出的正是 「项目经理评价」「指导教师评分及评语」,文本逐字在、自检通过 ② 目录筛选融合:mode 暴露到 AI 面板(融合=全部保留未匹配追加(缺省)/ 严格=只保留对上模板目录的内容),随排版请求透传到映射层 ③ 任务化 + 10 分钟超时:面板桥新增 reformat-job / reformat-job-status 动作, EverRoom main 接 gateway startReformatJob/reformatJobStatus;面板提交后 900ms 轮询,进度条走真实阶段(parsing/mapping/rendering)+ 已耗时; 10 分钟硬上限主动中断并提示「处理超时…请稍后重试或简化文档」 验证:gateway 56 例、docx-engine 939 例、桌面 AI 面板 22 例全绿;双端 tsc 零错误;genoffice 运行时已重建。
子模块:点 🗑 → 面板内居中模态(确认删除/取消),替换原内联确认条; 测试 23 例全绿,genoffice 运行时已重建。
问题:上传论文模版慢。实测本地 OOXML 事实抽取只要 20ms,慢的 100% 是解析 代理(把 178KB 事实 + 全文喂给 LLM 等结构化卡片,30s–数分钟)。 改法:版式信息本来就全在 OOXML 里,改为确定性构建卡片,代理只在拿不到骨架时兜底。 - 新增 card-from-facts.ts:页面/分节边距、正文样式、标题层级版式、页眉页脚、 封面字段标签、校名/文种、图片字节,全部按模板真实值填(3ms 出卡) - ooxml-facts 补记标题段落的 run 版式(样式常留空,段落 run 才是真实观感: 本模板 h1 实测宋体 16pt,代理猜的「黑体18」是错的) - service.parseBytes 走快速路径;无大纲/无样式时 confident=false 自动回落代理; 运维可用 THESIS_PARSE_ENGINE=agent 强制走代理 - mergeTemplateImages 抽成独立模块(避免 service ↔ card 循环依赖) 保真度对比(同模板 代理卡 vs 本地卡):校名/文种/正文字体一致;页面边距本地 25.4/25.4/22/22(模板真实值)优于代理的 25/25/20/20;封面字段本地识别出模板 真实标签(题目/专业/班级/学生姓名/学生学号/指导教师/日期),代理留空。 端到端等价性:用本地卡跑完整排版,守恒 56/56、20 章节、表格 8→10(含模板 评价表)、logo 字节一致、自检通过——与代理卡产物一致,但解析 3ms。 测试:gateway 58 例全绿(新增 2 例:真实模板本地取值正确性 + 不可信时回落)。
问题:预览只显示格式卡重画的示意图(一页、占位文字),看不到完整封面/信息区/ 目录/正文,用户以为文档被裁切。 - 面板桥新增 preview-template 动作,宿主按卡片 sourceFileEntryId 调 files.openOriginal → 应用自带 Office 渲染器打开模版原文件,多页滚动、 表格/图片/排版完整还原 - 拖拽直传的模版(无资料区条目):上传时把字节留在主进程缓存(LRU 8 份), 预览时落临时文件 → 导入资料区(按内容去重,重复预览不产生重复文件)→ 打开 真预览 - 注册表注入 openHostFile 钩子,避免 registry 直接依赖 files 桥 - genoffice 面板删除示意渲染与 modal,按钮改调新通道;无文件时明确提示 验证:桌面 tsc 零错误;AI 面板 24 例全绿(含「示意弹窗不得回归」断言); genoffice 运行时已重建。
用户点预览报「该模版是拖拽直传的,未留存原文件」。根因:「上传论文模版」按钮 走 parseFromDisk,把字节 multipart 上传后卡片 sourceFileEntryId 存的是 null ——上一版只给拖拽通道加了字节缓存,且缓存随应用重启清空,老卡片必然落空。 正解:让每张新卡真正持有一个资料区文件条目。 - 网关:parseFromUpload 接受可选 fileEntryId(multipart 路由加 querystring) - 桌面两条上传路径都先入库再解析(importPathsOnce 内容哈希去重,重复导入 不会产生第二份文件): · 选择框路径 importTemplateFileIntoLibrary(path) → fileId → parseFromDisk(…, fileId) · 拖拽路径 落临时盘 importTemplateBytesIntoLibrary(name, bytes) → parseFromBytes(…, fileId) - 老卡片自愈:预览时资料区存在唯一同名文件 → 直接打开;缓存命中则落盘导入后打开 - 提示语改为可执行:「请删除后重新上传一次(新版本会自动保存原文件…)」 卡片关联文件后,预览(真渲染原文件)、删除级联、从资料区重新解析三条链路都成立。 验证:gateway 58 例、桌面 AI 面板 21 例全绿;双端 tsc 零错误;运行时已重建。
上一版给面板预览挂的是 openHostFile 回调钩子,但**从未赋值**,dispatch 里 于是静默跳过:桥照样返回 opened:true,界面上却什么 都没打开——用户看到的就是「点预览没反应」。 修法: - 把文件页「打开」的内嵌预览逻辑抽成模块级 openFileInAppPreview(bridge, fileId, name, options),不再依赖 IPC event(面板钩子拿不到 event) - 面板预览与文件页打开共用这一条路径,真打开预览实例;失败直接抛错给 UI 提示 - 面板路径打开后把主窗口 restore/show/focus——预览标签在主窗口顶栏,不聚焦的话 用户盯着 docs 窗口仍会以为没反应 - 删掉 openHostFile/ThesisPanelHostOpener 这套没人赋值的回调,接口简化为 previewTemplate(roomId, cardId) 测试:桌面 24 例全绿,新增回归断言(必须调 openFileInAppPreview、不得再出现 openHostFile、必须 focusWindow);tsc 零错误,electron-vite 构建通过。
网关(apps/gateway)
- 模版剖面 v3:自愈重建(版本 < 3 且字节可读时重算并落库),新增
template-first 模式与子节槽位
- 模版大纲抽取:过滤「黑体三号,段前12磅」这类排版说明行;整篇不用标题样式时
用模版自己的目录行派生章节骨架(去页码后定级)
- 模版导入:按文件头识别真实格式(.doc 改名成 .docx 不再冒充骨架),缓存与
回退路径都做同一校验;拿不到可用模版时给出可执行提示
- AI 补写
· 开关在映射之前落到卡片(原来渲染阶段才写,永远不生效)
· 候选 = 剖面空槽 + 模版正文真实小节(直接扫 OOXML,跳过表格内段落),
排除姓名/学号/签名/评分/评语这类需要本人填写或主观打分的位置
· 「章|小节」上下文 + 分批并发,避免同名小节被复制、避免撞面板超时
· 素材从「每节首句」改为论文全文(原来取到的是标题本身,模型判定无素材而拒写)
· 补写结果与失败原因都进报告 warnings,面板可见
- 排版产物文件名:勾选 AI 补写时为 xxx-ai补写版.docx,否则仍是 xxx-排版版.docx
桌面端(apps/desktop)
- 模版骨架装订接上引擎,失败回落内容重建时把原因透出到 warnings
- 主进程主动打开的预览改为事件通知渲染进程(原来没人接这条事件,点预览没反应)
- 模版 docx 旁路缓存:与对象库同盘、随卡片删除清理
Agent(agents/thesis-formatter)
- fill_sections 输入新增 sourceText(全文),纪律改为「通读全文归纳、不编造、
本人填写类字段一律跳过」
测试
- 新增 thesis-ai-fill / thesis-template-cache / thesis-template-docx-guard /
thesis-title-heuristic / thesis-generation / office-preview-request
- 引擎侧 970 项、网关 thesis 套件 69 项全过
- 引擎:template-first 装订、固定块顺序、空节提示、三级标题、手打编号识别 - 文档端:AI 补写面板与 20 分钟超时
冲突处理: - files/service.ts:保留本分支的 SQL 直查 catalogEntry(dev 的分页扫描是同一 bug 的 临时方案),同时保留 catalogEntriesByName 自愈入口 - subagents/orchestrator.ts:取 dev 的并发池改动(agentDefinitionId / cancellationRequested), 再把本分支的 revisionId 归还逻辑补回去(等待式调用与分离式调用两处 finally 都归还 runtime,热重载期间被延迟回收的实例才会真正 dispose) - 子模块 genoffice:把 dev 的 407d822(slides 组件画廊脚本)并入本分支, pin 更新为合并提交 b137354
…ntentTargetCapability 审查指出 helper 只接受 'document.edit' | 'document.continue',用例传了 'document.create' → TS2345,会让 CI 的 Check types 失败。
引擎是 NodeNext 下的相对导入(无扩展名),被 gateway 的 tsc 拉进程序后报 TS2835,直接让 CI 的 Check types 失败。测试只需要一份块大纲,手写即可。
审查指出:把决策卡按钮从「禁用」改成「可点」并没有真正解锁,因为 submitPendingIntent 在 activeRunId 非空时直接 return null(不发请求、不排队、 不报错),调用方又 .catch(() => undefined),用户看到的是「点了没反应」。 按其建议先做「把失败浮出来」:busy 时置 error(Agent 正在处理其他请求), 让卡住的状态可见。
- 并入 thesis 模版骨架排版与 AI 补写(本分支的 feat/thesis-template-binder 已合进 main) - 同时带上 dev 的 407d822(slides 组件画廊 PPTX 构建脚本)与 main 上其他 22 个提交 - pin 改用 main 而不是特性分支,后续升级不再依赖分支存活
本轮排查论文排版时产生的临时目录(scratch/ 效果图与预览 docx、.dsh-office.* 渲染 中间产物、.workbuddy-ai 记忆缓存)体积几兆且只在本地有用,不该入库。
两个独立修复,均已完成根因定位与回归验证。
1. agent 等待用户输入时决策卡保持可点(b0d91637)
问题:agent 运行中弹出选择框(文档意图确认 / 房间选择 / 文档选择),卡片可见但按钮全部禁用,用户无法回答,agent 等不到输入,形成「等待输入却收不到输入」死状态。
根因:四张决策卡按钮门控
busy={loading || submitting || activeRunPending},而 agent 弹卡提问时 run 必然还挂着(提问后 run 不结束、等用户答案作为下一步输入)→activeRunPending恒为 true → 按钮全禁。修复:区分「执行中」(可遮罩)与「等待输入」(必须可点)——
loading || submitting,移除activeRunPendingawaitingInput标记 →data-awaiting-input呼吸描边,明确提示可点2. 更新「提示→重启→再提示」死循环(47e797aa)
问题:点「更新」选择重启后,应用重启又弹相同更新提示,更新未生效。
根因(两个叠加):
quitAndInstall()无参 = 非静默模式:安装器以交互向导异步启动,而 before-quit 里 preventDefault 后要异步关停 10 个服务才真正退出 → 安装器等不到进程退出 → 文件被占用、替换静默失败修复:
quitAndInstall(true, true)静默安装 + 装完自动拉起,不依赖向导、不与服务关停踩踏合并 dev
已把
origin/dev合入text(8dde1b98):files/service.ts:保留本分支的 SQL 直查catalogEntry(dev 的分页扫描是同一 bug 的临时方案)subagents/orchestrator.ts:取 dev 的并发池(agentDefinitionId / cancellationRequested)+ 本分支的 revisionId 归还逻辑407d822已并入本分支,pin 更新为合并提交b137354测试
tsc零新增错误3. 论文排版:模版骨架排版 v3 + AI 补写全链路(d87d0d02 / 682b4d9 / 2f2f820)
问题:排版只能「按论文内容重建」,模版封面/核心信息区/目录/评分表经常跑位;勾了 AI 补写也没效果;
.doc改名的模版会被当成骨架导致整条链路失败。改动
xxx-ai补写版.docx测试:引擎 970 项、网关 thesis 套件 69 项全过。
依赖:子模块 genoffice pin
b137354(分支feat/thesis-template-binder,已并入 dev 的407d822)。该分支目前只在本地,需有 genoffice 写权限的同学执行git push origin feat/thesis-template-binder,否则 CI / 他人拉分支时子模块取不到这个提交。