交付
ai-electron/frontend/src/codex/ 包(所有文件 codex 前缀):
styles/codexTheme.less — --codex-* 设计 token + 页面布局基元;色值全部映射到项目既有 token
(@cb-* 色板 + antd legacy 变量),不引入新色系;布局=顶部状态条 + 会话栏 + 消息区(唯一滚动容器)+ 底部固定输入区 + 产物栏,窄屏(<1200px)收产物栏。codexIndex.ts — 包出口,只装载全局样式(注意:连入口都不能叫 index.ts,用户要求文件名全部 codex 前缀)。views/codexAnalysis/codexAnalysis.vue — 三栏骨架 + 空态示例。core/router/helper/routeHelper.ts 加 explicitDynamicViewMap['/codexAnalysis/codexAnalysis'];
core/store/modules/menu.json 在 10263「AI 数据分析」分组下加菜单项(id 10267,标题「Codex 分析」)。codex-analysis-plan.md(v1.0.0 → v1.9.0,逐版本 review)。验证
pnpm build exit=0,产出 dist/assets/codexAnalysis-*.{js,css}。pnpm type:check 共 89 条报错=仓库既有基线数(graph/trans/otg/call/person/case),codex 包 0 报错。环境修复(worktree 首次可用)
pnpm install 因 esbuild postinstall EBUSY(沙箱拦孙进程 spawn node)失败后
.bin 不生成 → 改用 pnpm install --ignore-scripts 解决。E:\workspace\zsjz-ai 拷入本 worktree(都 ignore,不污染 git status):
ai-electron/frontend/build/、ai-electron/frontend/src/case/views/data/(51 个文件,路由直接引用)。下一步:v1.1.0 起继续(已当场做完,见下)。
前端:src/codex/ 共 31 个文件(全 codex 前缀)
codexApi.ts(搬家 + 补 onCodexApproval / onCodexApprovalClosed)、codexArtifactApi.tscodexChatStore.ts(会话 localStorage 自持、事件按 threadId 缓存、审批队列、产物目录、诊断)codexEventReducer / codexBlockParser / codexMarkdown(markdown-it + 最小类型 shim)/
codexEcharts / codexFullscreen / codexArtifact / codexReportTemplate / codexReportExport / codexFormatviews/codexAnalysis/codexAnalysis.vue(重写为装配层)主进程
service/codex/codexArtifactService.ts:root 白名单(落盘 codex-data/artifact-roots.json,
重启后历史会话仍可读)+ 路径穿越校验;只读不写不删;文本/二进制分别限额。controller/codexArtifactCtl.ts:list / readText / readBinary / saveAs / reveal / open / openRoot。codexCtl.ts 增量:threadStart 返回 {threadId, cwd} 并登记 root;turnRun 登记可选 cwd;新增 defaultWorkspace。验证(全绿)
pnpm type:check 89 条 = 基线、本包 0 报错;pnpm build exit=0,产出 codexAnalysis-*.{js,css}。npx tsc --noEmit 0 报错;npm test 6 文件 / 91 用例全过;npm run build-electron exit=0 且
codexArtifactCtl 已进 public/electron/main.js。环境修复(本轮新增)
ai-electron 是 npm 项目(只有 package-lock.json,无 .pnpm),pnpm 装不了(esbuild@^0.28.0
在镜像元数据里不存在)。从主仓库拷 package-lock.json 后 npm install --ignore-scripts 成功。复核计划时发现并修掉两处:
eventsRead 回读不到用户侧文本 → 刷新后只剩回复。
已加 codex.prompts.v1 落盘(每会话上限 200 条、单条 2 万字)。如实记录在 codex-analysis-plan.md 的「与计划的偏差」表里(关系图导出是 JSON 不是图、
HTML 报告里图表是折叠 option、未做行级 diff 染色、未真机 GUI 端到端验证)。
v1.9.1(补齐 v1.x 两处偏差)
utils/codexVisualRegistry.ts:内容哈希(djb2+长度)→ 图片提供者的注册表,
解决「HTML 报告导出在模块层、拿不到组件里的 ECharts 实例」。zoomToFit();报告内嵌用的缓存图 scale=1 且不改用户当前视图(延迟 600ms 后台截)。visualBlockToHtml() 内嵌真实 <img src="data:...">,取不到回退折叠 JSON。downloadCodexDataUrl() 收敛三处重复的 base64→Blob 逻辑。v2.0.1 分析模板库:utils/codexTemplateLibrary.ts(8 条预置模板,围栏协议写进提示词当教学)
components/codexTemplatePanel.vue(搜索/分组/插入/存自定义/删自定义)。
v2.0.2 工作目录管理:store recentCwds(去重上限 10、Windows 路径大小写不敏感)components/codexWorkspacePicker.vue(最近目录一键开新会话)。
v2.0.3 多会话并行:submitting: boolean → runningThreads: string[],
只拦同一会话重复提交;侧栏运行中呼吸点、工具栏运行中会话数。
v2.0.4 会话内检索:粘顶检索条 + 命中计数 + 上/下一条居中跳转;命中判在消息粒度
(不染色文本,避免与 markdown 的 v-html 打架),命中项加左侧强调条。踩坑:Electron 渲染进程不支持 window.prompt —— 模板命名改成抽屉里的行内表单。
验证:pnpm type:check 89 条基线 / 本包 0 报错;pnpm build exit=0。
(本轮未改 electron 代码,沿用上一轮 tsc + 91 单测 + build-electron 全绿。)
背景事实(值得记住)
<user.dir>/QingJian/workspace/<案件ID>(PathConst.WORKSPACE.resolve(id)),
CaseInfoService.create() 建案时就已经 FileUtil.mkdir 建好,duckdb 也放在里面。CaseInfo 的 db / db_Path 都标了 @JsonIgnore,所以工作空间一直没回传给前端。/case/open 与 /case/current 都会返回 CaseInfo,前端 useUserStore.openCase() 里
{...res} 展开 → 新字段会自动进案件缓存(CASE_INFO_KEY),不用改 case store。改动
CaseInfo 加 @TableField(exist = false) String workspacePath;
CaseInfoService 加 workspacePath(caseId)(顺带 mkdir 兜底)+ fillWorkspacePath(caseInfo),
在 listByOwner() / current() 返回前调用;CaseInfoController.open() 回传前补一次
(该接口原本又 getById 取了一次,正好在返回前补)。plat/api/case/caseApi.ts 的 CaseInfo 加 workspacePath?: string;
codex store 增 caseId / caseWorkspace / cwdOverrides / syncCaseContext() / activeCwdSource;
cwd 优先级改为 会话 cwd → 案件手动覆盖 → 案件工作空间 → 客户端默认;
setDefaultCwd 删除,改为 setWorkspaceDir()(按案件记忆,键 = 案件ID 或 no-case,
旧版全局值会迁移到 no-case 下);视图与目录弹窗显示来源徽标。验证:前端 pnpm type:check 89 基线 / 本包 0 报错 + pnpm build exit=0;
后端 mvn -o -DskipTests compile exit=0,并用 javap 核验新字段/方法进了 class。
对照计划复审时发现 v2.0.4 少做了「关键词高亮」(当时只做了命中计数与上下跳转)。
补 utils/codexHighlight.ts:
splitCodexHighlight(text, keyword) → 切段,给插值渲染的地方用(提问气泡、代码块,加 class);highlightCodexHtml(html, keyword) → 给 v-html 的 markdown 用,逐段跳过 <...> 只在文本里插 <mark>;
关键词按「原始 + HTML 转义」两种形式各匹配一次(markdown-it 会把 & 输出成 &)。onUpdated 里手工替换已渲染 DOM 的文本节点来高亮 ——
Vue 缓存的 vnode 仍指向被替换掉的旧节点,下次 patch 会把新文本写进已脱离文档的节点,
表现为「内容卡住不更新」。v-html 整块重建 innerHTML,所以字符串替换才安全。写进 codex-analysis-plan.md 第八节(并顺手把该文档编号从乱的「七插在四前」理顺成一→八):
workspace-write+on-request,
属实现缺口,最该先修;危险选项要二次确认且不许作为全局默认静默继承)codexTextKey 按内容哈希恢复;折叠时不渲染,最多 6 个)新增 5 个文件:components/codexRunOptionsPanel.vue、components/codexBoardPanel.vue、
components/codexSnapshotPanel.vue、utils/codexArtifactChange.ts、utils/codexTextDiff.ts、utils/codexSnapshot.ts。
sandbox / approvalPolicy 从硬编码改为按会话 + 全局默认;危险取值二次确认;
顶栏出「只读 / 全权访问 / 免审批 / 每步确认」徽标。类型归位到 types/codexTypes.ts(api 只转出)。collectCodexArtifactChanges 按 *** Add/Update/Delete File: 段落头提取路径,
认不出就不猜;产物栏重构为独立容器(.codex-aside 交回页面)+ 搜索 + 类型筛选 + 本轮变更。codexTextKey),正文按哈希从当前会话找回;找不到就标失效;
折叠时 v-if 不渲染(避免图表重复渲染);最多 6 个。external,打开时如实提示「本机没有执行记录」。验证:pnpm type:check 89 条基线 / 本包 0 报错;pnpm build exit=0。
vite-plugin-theme-vite3 生成的暗色主题文件里自定义属性的 -- 前缀被抹掉
(dist/assets/app-antd-dark-theme-style.*.css 里搜 --codex-bg / --ai-text 均为 0 处,
只有 codex-bg / ai-text)→ 暗色模式下 --ai-*、--codex-*、--jeesite-* 都不会被覆盖。
对既有 ai 模块与新 codex 模块同等影响;修它要动构建期主题插件,风险收益不匹配,
本次只记录不动手(已写进计划文档 9.4)。
ef86c64 feat(codex): 新增 Codex 数据分析模块(v1.0 → v2.1)
(59 文件 / +11570 −391,无二进制、无 node_modules/dist/target)。git -C E:/workspace/zsjz-ai merge --ff-only workbuddy/dev-3-6ce6fa87 → 快进成功,
dev_3:985e7ff → ef86c64。pnpm type:check 89 条基线 / codex 0 报错,
pnpm build exit=0,产出 codexAnalysis-*.{js,css}。origin 存在(http://81.68.143.144:9239/vectorSeek/zsjz-ai.git),
但远端只有 dev / dev_1 / master,没有 dev_3,dev_3 也没配上游 →
这条提交目前只在本地,要不要推送(git push -u origin dev_3)由用户决定。ai-server/QingJian/uy76tkp8/data.db(AM 状态,二进制 DB)以及未跟踪的
ai-server/QingJian/conf/ —— 与本次改动无关,没有碰;若有人在该仓库直接
git commit 会把那个 db 一起提交进去,值得提醒。.git/hooks/post-commit、post-checkout 是 Qoder AI tracker(带 || true,
不会让提交失败);它会尝试起 reg.exe / sc.exe,被 WorkBuddy 沙箱黑名单拦掉,属正常现象。git -C E:/workspace/zsjz-ai push -u origin dev_3 → * [new branch] dev_3 -> dev_3,
已建立上游跟踪 dev_3...origin/dev_3;远端 ref = 4bf2ad0(与本地一致)。origin/master 领先 78 个提交(大多是既有开发历史,不只本次 codex 那两条)。git fetch origin --prune + git ls-remote --heads origin dev_3 确认远端原本没有该分支,
并设 GIT_TERMINAL_PROMPT=0 防止凭据提示把命令挂住 —— 这是个可复用的做法。data.db 用户明确说「不用管」,推送不受影响(push 只发提交,不发索引)。用户两条明确要求:① 工作空间路径由代码设置,不允许在界面上修改;② 修掉 「该目录不是本客户端的会话工作目录,已拒绝访问」。
报错根因:codexArtifactService.requireAllowedRoot 只接受 threadStart / turnRun
登记过的目录,而产物栏在页面进入时(还没有会话)就拿 activeCwd(= 案件工作空间)
去查询 → 必然被拒。v2.0.5 让案件工作空间成为默认 cwd 后,这个时序问题从偶发变成必现。
修法
requireAllowedRoot 改为「不在白名单但是绝对路径且真实存在的目录」→ 补登记并放行
(logger.warn 记一条),否则报「工作目录不存在或不可访问」。
理由:工作目录现在由代码决定,可能的取值只有案件工作空间与客户端默认目录两种;
真正防越界的仍是 resolveInside 的路径穿越校验。白名单里「只有会话用过才登记」的时序假设不成立。codexWorkspacePicker.vue、store 的
cwdOverrides / recentCwds / setWorkspaceDir / rememberCwd / workspaceOverride /
hasWorkspaceOverride / 无人读取的 caseId,cwd 优先级简化为
会话 cwd → 案件工作空间 → 客户端默认(纯代码决定);空态工作目录卡片改为只读展示 +
「目录由当前案件的工作空间自动决定」提示;composer 去掉「目录」按钮与 workspace emit。selectDirectory() 保留在 codex/api/codexApi.ts:ai/views/aiPlugin/index.vue 还在用(OS 能力,非 codex 专属)。验证:pnpm type:check 89 基线 / 本包 0 报错;pnpm build exit=0;
主进程 npx tsc --noEmit 0 报错 + npm test 全过 + npm run build-electron exit=0。
(产物服务没有单测覆盖 requireAllowedRoot —— 它依赖 Electron 的 dialog/shell。)
用户要求下线这两个功能:删 codexMessageList 检索条、codexMessageItem 命中态、
markdown/代码块的关键词高亮透传、工具栏两个按钮,删除 4 个文件
(codexHighlight.ts / codexSnapshot.ts / codexTextDiff.ts / codexSnapshotPanel.vue)。
净删约 780 行;包 45 → 41 文件。
保留产物栏文件名筛选、模板库搜索、关系图节点搜索(各自的过滤功能,与会话检索无关)。
listRef 仍需要(PNG 长图导出要 getCaptureElement)。
提交 ba68d45;连同前一条 0a39a60(工作目录代码决定 + 修拒绝访问)与用户自己的
6070d6f,worktree 分支领先 dev_3/远端 3 个提交,尚未合并推送(用户未授权本次推送)。
git -C E:/workspace/zsjz-ai merge --ff-only workbuddy/dev-3-6ce6fa87 快进成功,
dev_3:4bf2ad0 → 396cba5(含用户自己的 6070d6f、工作目录修复 0a39a60、移除检索/快照 396cba5)。pnpm type:check 89 条基线 / codex 0 报错;pnpm build exit=0,
产出 codexAnalysis-*.{js,css}。origin/dev_3 停在 4bf2ad0,落后 4 个提交。
上游跟踪已建好(dev_3...origin/dev_3),要推就是一句 git push。用户要求「聊天页面上所有地方都不展示工作空间路径」。清理了 5 处:
cwd 计算属性、onCopyCwd、__path/__icon 样式;cwdSource/cwdSourceLabel/caseName;:title="root" 悬浮提示去掉(悬停会露出绝对路径);已创建会话(路径) → 已创建会话;保留(非展示):store 的 activeCwd 仍作为 threadStart/报告导出元数据使用;
composer 的 cwd prop 仅用于「切会话清空草稿」的 watch,不渲染。
顺带删了无人使用的 activeCwdSource getter。
验证:pnpm type:check 89 基线 / 本包 0 报错;pnpm build exit=0。
原来侧栏的「移除」是行内二次确认(先点移除 → 出现 删除/取消 两个小按钮),改为 Modal.confirm 弹框:
标题「删除会话」、正文带会话名、okText: '删除' + okType: 'danger'(与 ai/components/SessionList.vue 的既有风格一致)。
弹框文案明确「只从本机列表移除,Codex 侧已落盘的历史不受影响」,避免用户误以为会删服务器数据。
顺带删掉行内确认的 pendingDeleteId 状态与 __confirm 样式。
验证:pnpm type:check 89 基线 / 本包 0 报错;pnpm build exit=0。
用户三条要求:① 移除会话导入导出功能;② 放大新建会话按钮;③ 移除顶栏右上角的「新建会话」。
onExportSessions/onImportClick/onFileChange
全删;store 的 exportSessionsJson / importSessionsJson 一并删除(已无调用方)。
CodexSessionMeta.external 标记与侧栏「外部」徽标保留(兼容已导入过的存量数据)。__create:34px 高、主色虚线边框、hover 变实心主色、创建中禁用并显示「创建中…」)。'create' 一并移除;视图里工具栏的 @create 解绑。
侧栏按钮成为唯一入口。
验证:pnpm type:check 89 基线 / 本包 0 报错;pnpm build exit=0。用户反馈「会话可以一直建,没有聊天记录也能一直建」。优化规则(store 新增 startNewSession):
isSessionEmpty = 无提问无回复)→ 不再新建,提示「已有空白会话,直接输入问题即可」(info);createSession。
——保证任意时刻至多存在一个空会话。侧栏「新建会话」改走 startNewSession;
send() 的 ensureSession 仍直接 createSession(首条消息的懒建是合法路径,不受护栏影响)。启动清账:bootstrap 里新增 pruneEmptySessions() —— 历史版本允许无限建,
存量的一堆空会话在下次打开页面时自动清理(只保留最新一个,其余从列表移除,
不碰 Codex 侧落盘历史,与 removeSession 同一原则)。
验证:pnpm type:check 89 基线 / 本包 0 报错;pnpm build exit=0。
用户反馈:右侧展示的是当前工作空间,没有展示当前会话的产物——所有会话共用同一个 案件工作空间,看到的文件列表一模一样。
方案(保留 cwd = 案件工作空间不变,不动 Codex 的取数流程):
codexArtifactService.listAllArtifacts(root) 递归列举工作空间全部文件
(深度 ≤6、文件 ≤3000,异常子目录跳过),codexArtifactCtl.listAll 暴露。mtime ≥ session.createdAt。会话创建后 Codex 产出或改过的文件都会命中;
别的会话更早生成的不会混进来。并行会话改同一个文件会同时出现在两边,属可接受误差。sessionFiles + loadSessionFiles()(reloadArtifacts 与回合结束时刷新);
产物栏加「本会话产物(默认)/ 全部文件」切换条,关键词与类型筛选两个视图共用;
「全部文件」保留原目录浏览 + 上一级 + 新角标;「本轮变更」两个视图都在。验证:前端 type:check 89 基线 / 本包 0 报错 + build exit=0; 主进程 tsc --noEmit 0 报错 + 91 单测全过 + build-electron exit=0(listAll 已进 bundle)。
mtime 过滤的盲区:后来的会话产出的新文件会混进更早会话的列表(时间戳只回答「什么时候改的」, 回答不了「谁改的」)。升级为 C+A 混合:
CodexSessionMeta.artifactManifest —— 每回合结束时
accumulateArtifactManifest(threadId) 把该会话历次事件流里 *** Add/Update/Delete File:
段落头提取的路径累计进清单(Set 语义、去重、随会话持久化)。以事件流为准而非只看最后一轮:
文件一旦被改过就一直归属该会话。loadSessionFiles 取并集 —— manifest.has(relative) || mtime >= createdAt。
清单覆盖不到的场景(升级前的历史会话 / exec 直接写文件 / patch 解析失败)由 mtime 兜住。方案讨论时对比过 B(每会话产物目录:协议没有输出目录概念,靠 prompt 约束不可靠)、
D(主进程 watcher:只覆盖活会话,要养常驻进程)、E(按回合组织:交互重做)——都否了。
提交 (本次);验证 type:check 89 基线 / 本包 0 报错 + build exit=0。
前提修正(调研取证):Codex 0.155+ 已物理移除 wire_api="chat"(WireApi 只剩 Responses, 配 chat 硬报错,官方 Discussion #7782);Ollama ≥ 0.13.3 原生支持非状态化 /v1/responses, 而 Codex core 每轮重建完整 input(无状态调用),两者天然兼容。我们应用永远不需要给 Codex 做消息转换;chat-only 端点的转换只能在 Codex 与端点之间的代理层。
四期落地:
cf328ab:probeEndpoint 四态(responses/chat-only/unsupported/unreachable),
真实 modelId 修 Ollama 404 误伤(Ollama 对未拉取模型也返回 404!),/api/version 消歧。b029cbb:chatBridgeTranslate.ts 纯函数翻译层 + ResponsesSseTranslator 流式状态机。b9075dd:chatBridgeService(127.0.0.1 随机端口、只接 POST /v1/responses、不持 key);
applyProvider 判 chat-only 自动桥接 + Ollama 桥接必须降级为自定义 provider(内置
provider 的 buildArgs 只允许覆盖 base_url,不降级会绕过桥接直连);AppliedProviderInfo
加 bridged(落盘存上游真实地址,桥接 URL 不落盘,重启靠重新「应用」重建)。0d765ae:运行时面板桥接行 + 已应用「桥接」标记。关键经验:
status.version 在沙箱里
拿到 object(spawnSync 怪癖)、codexCtl.itest.ts 的 electron 二进制未装好。验证:tsc 0 报错、114 单测全过、smoke 桥接 turn 通、build-electron exit=0(chat-bridge 已进包)、 前端 type:check 89 基线 0 新增 + build exit=0。