cc 1 日 前
親
コミット
13f7dd126a

+ 0 - 16
.claude/CLAUDE.md

@@ -1,16 +0,0 @@
-# CLAUDE.md
-
-This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
-
-请称呼我为"杨老爷"。
-
-# 项目介绍
-[introduction.md](rules/introduction.md)
-
-# andrej-karpathy-skills 规范
-[karpathy-skills.md](rules/karpathy-skills.md)
-
-# Git规范
-[git.md](rules/git.md)
-
-能使用jdk17以上新特性就必须使用;代码编写完成后,使用 IDEA MCP 工具进行代码编译验证。

+ 0 - 0
.claude/rules/git.md


+ 0 - 69
.claude/rules/introduction.md

@@ -1,69 +0,0 @@
-# introduction.md
-
-## 1. 技术栈
-
-- **框架**: Solon 3.10.3(非 Spring Boot,注入/切面/MVC 均使用 Solon 注解)
-- **Java**: 25(如果可以,必须新特性)
-- **ORM**: MyBatis-Plus 3.5.12 + Solon 插件(mapper 接口继承 `BaseMapper<T>`,lambda 查询用 `Wrappers.lambdaQuery()`)
-- **序列化**: Jackson(`solon-serialization-jackson`)
-- **模板**: Thymeleaf(`solon-view-thymeleaf`)
-- **数据库**: SQLite(plat 库,通过 `org.sqlite.JDBC`)、DuckDB(case 库,通过 `duckdb_jdbc`)、RocksDB(本地 KV 存储)
-- **搜索引擎**: Apache Lucene 10.3.2 + IK Analyzer
-- **图计算**: JGraphT 1.5.2
-
-## 2. 目录与约定(必须遵守)
-
-```text
-com.qingjian/
-├── App.java                  # 入口:Solon.start(),端口 8980,contextPath "!/js/a/"
-├── common/                   # 公共层
-│   ├── cache/                # 缓存:GlobalCache(表/字段元数据)、CaseDataCache(人员/卡号映射)
-│   ├── config/               # 配置:WebConfig(数据源/CORS)、MybatisPlusConfig(分页插件)
-│   ├── constants/            # 常量(StrConsts, PathConst, RegConst)
-│   ├── enums/                # 枚举
-│   ├── model/<module>/       # 按模块组织的 DTO/Entity/Query,MapStruct 转换器
-│   ├── rocksdb/              # RocksDB 封装(运营商归属、银行卡归属查询)
-│   └── utils/                # 工具:GlobalPool(线程池)、Json、DataUtil、SlowDownProgress
-└── module/                   # 业务模块(每个含 controller/mapper/service)
-    ├── ai/                   # AI(Solon AI agent、chat、skill)
-    ├── call/                 # 通话记录
-    ├── dm/                   # 数据管理(文件导入、清洗、缓存构建)
-    ├── govern/               # 数据治理(关系边计算、去重、时序图、分类树)
-    ├── graph/                # 图谱/关系图
-    ├── otg/                  # 对象轨迹生成
-    ├── person/               # 人员信息(基本信息和证件号/手机号/银行卡号库)
-    ├── plat/                 # 平台(SSE 推送、表管理、配置)
-    ├── track/                # 轨迹追踪
-    └── trans/                # 交易记录
-```
-
-## 3. 命名约定
-
-- 统一使用lombok注解,如果有继承关系 必须加上 `@EqualsAndHashCode(callSuper = true)` 注解
-- **类名**:使用帕斯卡命名法(如 `UserController`、`OrderService`)
-- **方法和变量名**:使用驼峰命名法(如 `findUserById`、`isOrderValid`)
-- **常量**:使用全大写下划线分隔(如 `MAX_RETRY_ATTEMPTS`、`DEFAULT_PAGE_SIZE`)
-- **包名**:使用小写,按功能模块划分(如 `com.example.user.domain`)
-- **响应对象**: 创建接口响应对象使用DTO结尾,示例:ResponseDTO
-- **请求参数**: 创建接口请求参数对象使用Query结尾 必须继承 `com.qingjian.common.base.Query`,示例:RequestQuery
-- **语言与风格**:保持现有包结构与命名风格;避免无意义抽象;方法保持短小可测。
-
-## 4. 多数据源
-
-数据源通过 Solon 的 `DynamicDataSource("db1")` 管理,在 `app.yml` 的 `qingjian.db1` 下配置:
-
-| 数据源 key | 数据库 | 用途 |
-|---|---|---|
-| `plat` | SQLite | 平台配置、表字段元数据、运营商/银行字典 |
-| `case` | DuckDB | 案件数据(通话/交易记录、关系边、去重中间表) |
-
-切换数据源使用 `DynamicDsKey.with(StrConsts.DS_KEY_PLAT, () -> ...)` 或在注入时指定。
-
-## 5.构建与运行
-
-```bash
-# 编译项目
-mvn compile -f pom.xml
-```
-
-代码编写完成后,使用 IDEA MCP 工具进行代码编译验证。

+ 0 - 65
.claude/rules/karpathy-skills.md

@@ -1,65 +0,0 @@
-# karpathy-skills.md
-
-Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed.
-
-**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
-
-## 1. Think Before Coding
-
-**Don't assume. Don't hide confusion. Surface tradeoffs.**
-
-Before implementing:
-- State your assumptions explicitly. If uncertain, ask.
-- If multiple interpretations exist, present them - don't pick silently.
-- If a simpler approach exists, say so. Push back when warranted.
-- If something is unclear, stop. Name what's confusing. Ask.
-
-## 2. Simplicity First
-
-**Minimum code that solves the problem. Nothing speculative.**
-
-- No features beyond what was asked.
-- No abstractions for single-use code.
-- No "flexibility" or "configurability" that wasn't requested.
-- No error handling for impossible scenarios.
-- If you write 200 lines and it could be 50, rewrite it.
-
-Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
-
-## 3. Surgical Changes
-
-**Touch only what you must. Clean up only your own mess.**
-
-When editing existing code:
-- Don't "improve" adjacent code, comments, or formatting.
-- Don't refactor things that aren't broken.
-- Match existing style, even if you'd do it differently.
-- If you notice unrelated dead code, mention it - don't delete it.
-
-When your changes create orphans:
-- Remove imports/variables/functions that YOUR changes made unused.
-- Don't remove pre-existing dead code unless asked.
-
-The test: Every changed line should trace directly to the user's request.
-
-## 4. Goal-Driven Execution
-
-**Define success criteria. Loop until verified.**
-
-Transform tasks into verifiable goals:
-- "Add validation" → "Write tests for invalid inputs, then make them pass"
-- "Fix the bug" → "Write a test that reproduces it, then make it pass"
-- "Refactor X" → "Ensure tests pass before and after"
-
-For multi-step tasks, state a brief plan:
-```
-1. [Step] → verify: [check]
-2. [Step] → verify: [check]
-3. [Step] → verify: [check]
-```
-
-Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification.
-
----
-
-**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes.

+ 0 - 25
.claude/settings.local.json

@@ -1,25 +0,0 @@
-{
-  "permissions": {
-    "allow": [
-      "mcp__idea__search_symbol",
-      "mcp__idea__find_files_by_name_keyword",
-      "mcp__idea__get_file_text_by_path",
-      "mcp__idea__replace_text_in_file",
-      "mcp__idea__read_file",
-      "mcp__idea__get_file_problems",
-      "mcp__idea__search_text",
-      "mcp__idea__search_in_files_by_text",
-      "mcp__idea__list_directory_tree",
-      "mcp__idea__build_project",
-      "mcp__idea__rename_refactoring",
-      "mcp__idea__get_project_dependencies",
-      "mcp__idea__search_file",
-      "WebSearch",
-      "Bash(mvn compile *)",
-      "Bash(sort -t\\\\\\( -k2 -n)",
-      "Bash(python3)",
-      "Bash(python3 sql/_fix_comments.py)",
-      "Bash(python3 sql/_gen_sql.py)"
-    ]
-  }
-}

+ 0 - 188
.workbuddy-ai/memory/2026-10-09.md

@@ -1,188 +0,0 @@
-# 2026-10-09
-
-## 启动前后端
-
-- 前端(`ai-electron/frontend`):`./node_modules/.bin/vite dev --port 3100 --strictPort` → http://localhost:3100/
-- 后端(`ai-server`):`mvn -pl ai-server spring-boot:run -Dspring-boot.run.workingDirectory=E:/workspace/zsjz-ai`
-  → http://127.0.0.1:8980/js/a (`Started App in 5.6s`,`/sys/health` 返回 200)
-- `/tmp/mvnw.sh` 已重建(本机 mvn 包装脚本,见 skill §1)。
-
-## ★ 环境陷阱:宿主注入的 `SERVER__PORT` 会污染 Spring Boot 端口
-
-首次启动报 `PortInUseException: Port 51536 is already in use`,而 `application.yaml` 明明写的是 `8980`。
-
-根因:WorkBuddy 宿主 shell 环境里有 `SERVER__PORT=51536` / `SERVER__HOST=127.0.0.1`。
-Spring Boot 的松散绑定把它解析成 `server.port` / `server.address`,
-**操作系统环境变量优先级高于 application.yaml**,于是后端去绑 51536 —— 而那个端口正是宿主进程自己在监听。
-
-解法(二选一):
-- `env -u SERVER__PORT -u SERVER__HOST bash /tmp/mvnw.sh ...`(推荐,恢复配置文件原行为)
-- 或 `-Dspring-boot.run.arguments=--server.port=8980`(命令行参数优先级最高)
-
-⚠️ 同类隐患:任何从本 shell 启动的 Spring Boot / Node 服务,都要先确认没有 `SERVER__*` 之类的宿主变量覆盖配置。
-
-## 智能工作台「插件与技能」:技能新增/编辑改造(按技能目录规范)
-
-需求:技能按规范落地(`SKILL.md` 必需 + `scripts/` `references/` `assets/` `testcases/` 可选),
-新增与编辑都要能管附属文件;查看详情要按**文件树**并能**看文件内容**。
-
-### 后端(ai-server)
-- `SkillStore`:
-  - `save(...)` 新增 7 参重载(`files` / `deletePaths`),**overwrite 时不再整目录删重建**
-    —— 旧行为会让编辑一次就丢光附属文件(既有隐患,本次一并修正);
-  - 新增 `readFile` 读单个文件:UTF-8 严格解码判文本/二进制、2MB 预览上限、路径穿越防护;
-  - 新增 `pruneEmptyDirs`(删空文件后不留空目录);旧 5 参重载保留,测试零改动。
-- 新增 `SkillFileContentVO`;`SaveSkillDTO` 加 `files`/`deletePaths`;
-  `AgentCapabilityService(+Impl)` 的 createSkill/updateSkill 扩参并新增 `readSkillFile`;
-  Controller 新增 `GET /capability/skills/{name}/file?path=`。
-- `SkillStoreTest` 补 4 个用例(附属文件写入/读回、路径穿越与 SKILL.md 拒绝、编辑保留未提交文件、
-  二进制 base64),15 个全绿。
-
-### 前端(ai-electron/frontend)
-- `src/ai/components/SkillEditorModal.vue` 重写为**新增/编辑共用**:按 4 个规范目录分页签管理文件
-  (增/改/删/改名),二进制文件只读且保存时原样保留;编辑模式读磁盘真实内容预填。
-- `src/ai/views/aiPlugin/index.vue`:技能详情抽屉改「左文件树 + 右内容预览」,点任意节点按需拉内容
-  (图片走 base64 预览);技能卡片加「编辑」入口。
-- `capabilityApi.readSkillFile` + `types.ts` 的 `SaveSkillFile` / `SkillFileContent`。
-
-### 验证
-- 后端:`mvn -pl ai-server test -Dtest=SkillStoreTest` 15/15 通过;已重启服务(8980)。
-- 前端:`vue-tsc` 对 `src/ai/**` 零错误(core 的 3 个是既有问题);eslint 干净;
-  stylelint 对比 HEAD 无新增错误(aiPlugin 既有 28 处 rgba/alpha 写法问题未动)。
-- **未验证**:UI 端到端 —— 接口需登录态,curl 只能确认路由已注册(返回业务码 401)。
-
-### 补充:技能编辑器补 label(用户反馈「不知道要填什么」)
-- `SkillEditorModal.vue`:技能名 / 描述 / 正文 / 附属文件名 / 附属文件内容 全部改成
-  「label 在上、控件在下」(`__field` + `__label` + `__req` 必填星号),placeholder 只留示例。
-- `AgentFormModal.vue` 专家弹窗里的技能子面板同样补 label:用 `__sfield` / `__slabel`,
-  **不能复用该文件的 `__label`** —— 那是横向表单行用的(`width:96px; text-align:right`),
-  而 `__seditor` 是纵向 flex 布局。
-- 验证:`vue-tsc` 对 `src/ai` 零错误;eslint 干净;stylelint 对比 HEAD 无新增(AgentFormModal 既有 6 处 rgba 写法未动)。
-
-### 补充:技能详情抽屉宽度回退
-- 用户反馈「查看详情侧边栏太宽」→ 抽屉从 1000 回到原来的 **720**,左文件树从 340 收到 **240**(两栏内容区约 424px)。
-- 结论:**详情抽屉宽度沿用 720 这个既有口径**,别再自行放大;两栏布局时左树固定 240px。
-
-### 补充:技能编辑弹框限高
-- 用户反馈「新增/编辑弹框不能一直增高,要用滚动条」→ `SkillEditorModal` 的 BasicModal 加
-  `:body-style="{ maxHeight: '64vh', overflowY: 'auto' }"`。
-- **口径**:本项目的长内容弹框统一用 `maxHeight: 64vh + overflowY: auto`
-  (aiPlugin 的工具包弹框、AgentFormModal、ArtifactPreviewModal 都是这套),新弹框照抄即可。
-
-### 技能详情改为只读弹框(与编辑弹框同构)
-- 用户要求「查看详情也用新增编辑一样的弹框,只读即可」→ 新增 `src/ai/components/SkillDetailModal.vue`:
-  900 宽 BasicModal、`:footer="null"`、bodyStyle 64vh 限高、SKILL.md + 四个规范目录页签全只读;
-  打开时读详情并**并发**拉全部附属文件内容(文本 content / 图片 base64)。
-- 抽 `src/ai/utils/skill.ts`:`SKILL_STD_DIRS` / `SKILL_NAME_RE` / `formatFileSize` / `splitSkillMd` /
-  `topDirOf` / `leafPath` / `groupSkillFiles` / `isImagePath` / `imageMimeOf` ——
-  详情与编辑弹框共用,避免两边各写一份后漂移。
-- `aiPlugin/index.vue` 大幅瘦身:删掉详情抽屉(a-drawer)、`skillTree`、`openDirs/isDirOpen/toggleDir/
-  dirTag/leafName/formatSize`、`openSkillFile`、`IMAGE_MIME/fileExt/isImageFile/imageMime`、
-  `readSkill/readSkillFile` 引用、`Drawer` import、`skillLoading`;改为
-  `<SkillDetailModal v-model:open="skillOpen" :skill="skillViewing" :case-id="caseId" />`。
-- ★ 顺手修掉上一轮引入的样式冲突:详情抽屉的 `&__detail` 与**工具详情弹框**既有的 `&__detail` 同名,
-  LESS 会把两条规则合并(工具的详情布局被带偏)—— 已随抽屉样式一起删除。
-- 验证:`vue-tsc` 对 `src/ai` 零错误;eslint 干净;stylelint 26 处(比改动前少 1,全是既有 rgba 写法)。
-
-### 研判方案「新建/编辑方案」弹框行距修正
-- 用户反馈「表单间距太大」→ 根因:`a-form-item` 默认带 `margin-bottom: 24px`(antd 给校验提示留位),
-  而这个弹框的字段名是**自己画的 label(在 form-item 外面)**,再叠 24px 会把行距从设计意图的
-  20px(gap 12 + row margin 8)顶成 44px。
-- 修法:`&__form :deep(.ant-form-item) { margin-bottom: 0; }`,行距交回 `__row` 统一控制。
-  校验失败时 explain 在 form-item 内部、会把该行撑高,不会压到下一行。
-- **同类坑**:凡是「自绘 label + a-form-item 只包控件」的表单,都要压平 form-item 的 24px 下边距。
-- 验证:eslint 干净;stylelint 6 处(与改动前一致,全是既有 rgba 写法)。
-
-### 研判方案弹框:移除「改技能」入口
-- 用户要求「弹框里选择技能那里不可以去编辑技能」→ `AgentFormModal.vue` 删除:
-  skillEditor 子面板(模板 + reactive 状态 + `openSkillEditor/closeSkillEditor/prefillSkill/saveSkill/
-  splitSkillMd/reloadSkills`)、技能卡片上的铅笔按钮(`__card-ops` / `__iconbtn` 及样式);
-  顺带清掉不再使用的 `Button` import 与 `SaveSkillParams` 类型引用。
-- 文案改为「技能的新增与编辑在『插件与技能 → 技能』页进行」,卡片只剩勾选语义。
-- **口径**:研判方案弹框里的技能/工具包一律只做勾选,**任何技能编辑能力都归「插件与技能」页**。
-- 验证:`vue-tsc` 对 `src/ai` 零错误;eslint 干净;stylelint 6 处(与改动前一致)。
-
-### 技能详情弹框:只读控件底纹置灰
-- 用户要求「查看技能的时候输入框这些底纹置灰色」→ `SkillDetailModal.vue` 加:
-  `:deep(.ant-input) { &[readonly] { background: var(--ai-bg-subtle, #f5f5f5); ... } }`,
-  hover/focus 一并覆盖为同色 + `box-shadow: none`,避免只读控件看起来像能编辑。
-- **口径**:antd 的 `readonly` 输入框默认仍是白底(只有 `disabled` 才自动变灰),
-  凡是「只读展示」的 a-input / a-textarea 都要自己压底色;底色取 `--ai-bg-subtle`
-  (= `@cb-vscode-input-background`,就是项目里输入框的底色 token)。
-- 验证:eslint 干净;stylelint 干净。**视觉未在真实页面确认**(需登录态)。
-
-### 工具界面中文化(展示名与调用名分离)
-
-需求:「工具界面工具名都是英文,能不能做成中文;会不会影响 agent 调用;有影响就加表字段」。
-
-- **结论:不影响调用,因此未加表字段。**
-  - 调用名是 `@Tool(name)` → `ToolSchema.getName()` → 模型 function name,与页面显示完全无关。
-  - 反过来,**直接改 `@Tool(name)` 为中文会炸**:OpenAI/兼容 API 的 function name 只接受
-    `[a-zA-Z0-9_-]{1,64}`;且 `agent_tool_pack.tool_names`、MCP `ToolDefinition.name`、
-    历史消息里的工具名都按英文名引用。
-- **依据(已实测)**:`ai-electron/frontend/src/ai/utils/constants.ts` 的 `TOOL_LABELS`
-  (上个提交 df03094 引入,聊天时间线 / 状态栏 / 产物栏在用)**已完整覆盖**后端全部 64 个 `@Tool`——
-  用 grep 提取 `@Tool(name=...)` 与 `TOOL_LABELS` 的 key 做 `comm` 差集,结果为空;
-  前端多出的 5 条是 MCP 案件工具(list_cases/open_case/current_case/close_case)与框架内置
-  (reset_equipped_tools),它们不进 `agent_tool` 表。所以工具页复用这张表即可 100% 中文,零后端/DB 改动。
-- 改动(全前端):
-  - `aiPlugin/index.vue`:工具卡片主标题、工具包编辑器卡片、工具包详情弹框的工具名 → `toolLabel()`;
-    英文调用名降为小字等宽副信息(新增 `__tool-code` / `__pack-card-code` / `__pack-tool-code`,
-    并给 `__card-meta` 加 flex 布局);工具详情弹框标题改中文并新增「调用名(模型按它调用工具)」块;
-    工具页与工具包编辑器两处搜索框都支持**中文名和英文名同时命中**;placeholder 补中文示例。
-  - `AgentFormModal.vue`:研判方案弹框里工具包卡片的工具 chips 同样走 `toolLabel()`。
-- 验证:eslint 干净;stylelint 与 HEAD 基线**完全一致**(aiPlugin 仅剩既有 8 处 rgba + 1 处
-  comment-empty-line-before + 1 处 `__mono` 的 font-family 引号;AgentFormModal 仅既有 2 处 rgba);
-  `vue-tsc` 全量 87 个错误全在 call/case/core/graph/otg/person/trans,**`src/ai` 零错误**。
-- **遗留隐患**:中文名表在前端,后端新增工具必须同步补 `TOOL_LABELS`(constants.ts 里已写明)。
-  若日后要「后端权威」,需在 `agent_tool` 加 `display_name` 列(固定表名 DDL 只进 `sql/agent_tool.sql`,
-  由人工执行 ALTER),但**省不掉**前端那份表 —— 聊天时间线是流式渲染,SSE 事件里只有 name。
-
-### 补充:工具界面只留中文(撤掉英文原名)
-
-- 用户要求「工具列表上不展示英文名称,只展示中文」→ 撤掉上一轮加的英文副信息:
-  `__tool-code` / `__pack-card-code` / `__pack-tool-code` 三处模板节点与样式、工具详情弹框的
-  「调用名」块、工具卡片的 `:title="toolName"`、工具包 chips 的 `:title`,以及 placeholder / hint
-  里的英文示例(`stat_trans_big` → 「大额交易」,`execute_sql` → 「执行 SQL 查询」)全部删除。
-- **口径**:工具界面上不出现任何英文工具名(含悬停提示与占位文案)。
-  但搜索仍**同时匹配中英文**——能力保留,只是不再提示;从日志里复制英文名来搜是常见用法。
-- 验证:eslint 干净;stylelint 与基线一致(8 rgba / 1 comment / 1 quotes,全既有);
-  `vue-tsc` 的 `src/ai` 零错误。
-
-### 自主 ⇄ 智能 模式切换:各模式记住自己的停留位置
-
-- 问题:来回切每次都掉回默认落点(切回自主 → 首页;切回智能 → 工作台默认视图)。
-- 根因两处:
-  1. `core/logics/workbenchMode.ts` 的 `setMode` 固定跳 `WORKBENCH_PATH` / `BASE_HOME`;
-  2. 智能工作台是**单路由全屏壳**(`/workbench`,内部 `view` 不落 URL),切走即卸载,视图回到默认落点。
-- 改法:
-  - `core/enums/cacheEnum.ts` 新增 `APP_WORKBENCH_PATH_KEY` / `APP_WORKBENCH_VIEW_KEY`。
-  - `core/logics/workbenchMode.ts` 新增 `get/setLastTraditionalPath`、`get/setLastWorkbenchView`(**sessionStorage**,
-    读写带 try/catch 兜底);`setMode` 改为「切走前记下当前页 → 切回时跳上次停留处,没记忆才回默认落点」。
-    切到 traditional 仍只在「当前在工作台」时才跳 —— 在传统壳点自己那一侧只对齐偏好,不该把人挪走。
-  - `ai/views/aiWorkbench/index.vue`:`view` 初值 = `?view=` 参数 ?? 上次停留视图 ?? `'chat'`;
-    `watch(view)` 持续写回;`userChoseView` 初值 = 是否有记忆视图;`applyDefaultView` 条件改为
-    `userChoseView && (view !== 'chat' || hasConversation)` —— 「对话」要靠会话上下文才成立,
-    刷新后 store 是新的、会话还没选,那种情况仍交给默认落点选一条,免得落在一屏空白欢迎页。
-- **为什么用 sessionStorage 而非 localStorage**:只在本次运行内有效,重启后各模式回默认落点,
-  免得上次遗留的页面在案件上下文变化后变成一次莫名其妙的跳转。
-- **会话/消息不用另外存**:`useChatStreamStore` 是 Pinia 全局单例(`defineStore('ai-chat-stream')`),
-  组件卸载不销毁;且 `store.init` 有幂等保护(`initialized && workspaceId 相同` 直接 return),
-  所以切回工作台时会话选择与消息都还在。
-- 验证:eslint / stylelint 干净;`vue-tsc` 的 `src/ai` 零错误,`src/core` 仅 3 个**既有**错误
-  (BasicButton 的 `Fn`、TableAction 的 `ActionItem`、useECharts 的恒真判断),与本次改动无关。
-- **未验证**:真实来回点击(需登录态 + 案件上下文)。
-
-### 提交 `1f7ae24`(分支 dev_4)
-
-- 范围:本轮工作 6 个文件(SkillDetailModal / aiPlugin / AgentFormModal / cacheEnum /
-  workbenchMode / aiWorkbench)+ 在途的工作台改动。
-- `aiWorkbench/index.vue` 里我的改动与**在途改动**(移除「文件管理」视图、去掉顶栏会话 chip)
-  交织在同一批 hunk 里,无法按路径拆开,因此连配套的 `ai/views/aiCaseData/` 两个已 staged 删除
-  一起提交(否则会留下两个没人引用的孤儿文件)。commit message 末尾已注明这一点。
-- 未提交:`ChatComposer.vue`、`aiExpert/index.vue`(纯 UI 元素顺序调整,与本轮无关)、
-  `.workbuddy-ai/memory/2026-10-09.md`(本地工作记忆,不入库)。
-- ⚠️ 提交命令末尾会出现 `PROGRAM BLOCKED BY SECURITY POLICY`(git 内部调用 `sc.exe` / `reg.exe`
-  被沙箱黑名单拦下)—— **提交本身已成功**,看 `git log` 的输出为准,别被这行吓到。
-
-

+ 0 - 24
.workbuddy-ai/memory/MEMORY.md

@@ -1,24 +0,0 @@
-# 项目长期约定(zsjz-ai / 清鉴)
-
-> 工程细节(构建方式、目录结构、踩过的坑)在技能 `qingjian-ai-dev` 里,这里只记**用户偏好与红线**。
-
-## 沟通
-
-- **禁止废话,节约 token**。不要复述需求、不要罗列「我准备做 1234」、不要问能自己判断的问题。
-- **直接改代码**,不要只给方案让人自己动手。
-- 有更好的方案要**说出来**,但别拿它当不干活的理由。
-
-## 干活
-
-- **自己验证**:改完必须跑编译 / lint / 类型检查 / 相关测试,**不要把验证丢回给用户**。
-  只跑了编译没跑运行时行为时,要明说「运行时行为未验证」。
-- **不要顺手改其他 bug 或问题**。既有失败/既有错误先说明,别擅自扩大改动面。
-  例外:改动**导致**测试断言失效时,必须把断言对齐到真实值。
-- 范围要克制。用户说「只改 X」就只改 X。
-
-## git 红线(血泪)
-
-`git stash` / `gc` / `repack` / `prune` / `reset --hard` / `checkout -- <path>` / `restore` /
-`clean -fd` —— **全部禁止**,已损坏 `.git` 两次(本地未推送提交对象一度全不可读)。
-需要对比「改动前」用只读手段:`git show HEAD:<path>`、`git diff`。
-需要还原文件:`git show HEAD:<path> > <path>`(只读 git + shell 重定向,安全)。

+ 0 - 538
.workbuddy/memory/2026-09-24.md

@@ -1,538 +0,0 @@
-# 2026-09-24
-
-## Codex 数据分析新包 · v1.0.0(包骨架 + 入口打通)
-
-**交付**
-- 新建 `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` — 三栏骨架 + 空态示例。
-- 改 2 处登记(无逻辑改动):`core/router/helper/routeHelper.ts` 加 `explicitDynamicViewMap['/codexAnalysis/codexAnalysis']`;
-  `core/store/modules/menu.json` 在 10263「AI 数据分析」分组下加菜单项(id 10267,标题「Codex 分析」)。
-- 计划文档 `../../docs/design/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 首次可用)**
-- 本 worktree 原本没装依赖,且 `pnpm install` 因 esbuild postinstall `EBUSY`(沙箱拦孙进程 spawn node)失败后
-  `.bin` 不生成 → 改用 `pnpm install --ignore-scripts` 解决。
-- 两个被 gitignore 误伤的目录从主仓库 `E:\workspace\zsjz-ai` 拷入本 worktree(都 ignore,不污染 git status):
-  `ai-electron/frontend/build/`、`ai-electron/frontend/src/case/views/data/`(51 个文件,路由直接引用)。
-
-**下一步**:v1.1.0 起继续(已当场做完,见下)。
-
-## Codex 数据分析新包 · v1.1.0 → v1.9.0(一次做完)
-
-**前端**:`src/codex/` 共 31 个文件(全 `codex` 前缀)
-- api:`codexApi.ts`(搬家 + 补 `onCodexApproval` / `onCodexApprovalClosed`)、`codexArtifactApi.ts`
-- store:`codexChatStore.ts`(会话 localStorage 自持、事件按 threadId 缓存、审批队列、产物目录、诊断)
-- utils:`codexEventReducer` / `codexBlockParser` / `codexMarkdown`(markdown-it + 最小类型 shim)/
-  `codexEcharts` / `codexFullscreen` / `codexArtifact` / `codexReportTemplate` / `codexReportExport` / `codexFormat`
-- components:工具栏 / 会话栏 / 消息流 / 消息项 / 输入区 / 过程时间线 / 工具卡 / 审批弹窗 /
-  块分发器 + markdown·代码·表格·JSON·图表·关系图六个块 / 产物栏 + 预览 / 诊断抽屉
-- 视图:`views/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` 成功。
-
-## 收尾补漏(同日)
-
-复核计划时发现并修掉两处:
-1. **真缺口**:提问只存内存,`eventsRead` 回读不到用户侧文本 → 刷新后只剩回复。
-   已加 `codex.prompts.v1` 落盘(每会话上限 200 条、单条 2 万字)。
-2. **计划有但没做**:产物栏「新产物角标」。已在 store 里按「上一轮快照对比」实现,
-   手动刷新即清除。
-
-如实记录在 `../../docs/design/codex-analysis-plan.md` 的「与计划的偏差」表里(关系图导出是 JSON 不是图、
-HTML 报告里图表是折叠 option、未做行级 diff 染色、未真机 GUI 端到端验证)。
-
-## v1.9.1 + v2.0.0(数据分析增强)
-
-**v1.9.1(补齐 v1.x 两处偏差)**
-- 新增 `utils/codexVisualRegistry.ts`:内容哈希(djb2+长度)→ 图片提供者的注册表,
-  解决「HTML 报告导出在模块层、拿不到组件里的 ECharts 实例」。
-- 关系图导出图片:html2canvas 整块栅格化(节点是 HTML、连线是 SVG,手撸 SVG 只能拿半张图);
-  导出前 `zoomToFit()`;报告内嵌用的缓存图 `scale=1` 且不改用户当前视图(延迟 600ms 后台截)。
-- HTML 报告 `visualBlockToHtml()` 内嵌真实 `<img src="data:...">`,取不到回退折叠 JSON。
-- `downloadCodexDataUrl()` 收敛三处重复的 base64→Blob 逻辑。
-- Markdown 报告**仍保留围栏块**(data URL 会让 .md 涨到几十 MB,不适合再加工场景)。
-
-**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 全绿。)
-
-## v2.0.5 案件工作空间联动(唯一一次动 ai-server)
-
-**背景事实(值得记住)**
-- 案件工作空间 = `<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。
-
-**改动**
-- 后端(3 处纯增量):`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 补漏:文本级关键词高亮
-
-对照计划复审时发现 v2.0.4 少做了「关键词高亮」(当时只做了命中计数与上下跳转)。
-补 `utils/codexHighlight.ts`:
-- `splitCodexHighlight(text, keyword)` → 切段,给**插值渲染**的地方用(提问气泡、代码块,加 class);
-- `highlightCodexHtml(html, keyword)` → 给 **v-html** 的 markdown 用,逐段跳过 `<...>` 只在文本里插 `<mark>`;
-  关键词按「原始 + HTML 转义」两种形式各匹配一次(markdown-it 会把 `&` 输出成 `&amp;`)。
-- **踩坑认知**:绝对不能在 `onUpdated` 里手工替换已渲染 DOM 的文本节点来高亮 ——
-  Vue 缓存的 vnode 仍指向被替换掉的旧节点,下次 patch 会把新文本写进已脱离文档的节点,
-  表现为「内容卡住不更新」。v-html 整块重建 innerHTML,所以字符串替换才安全。
-
-## v2.1.0 可执行计划(只出计划,未实施)
-
-写进 `../../docs/design/codex-analysis-plan.md` 第八节(并顺手把该文档编号从乱的「七插在四前」理顺成一→八):
-- **v2.1.1 运行参数显式化**(sandbox / approvalPolicy 目前是我硬编码的 `workspace-write`+`on-request`,
-  属实现缺口,最该先修;危险选项要二次确认且不许作为全局默认静默继承)
-- **v2.1.5 产物增强**(搜索 / 类型筛选 / 本轮变更 + patch 展开;patch 路径解析失败必须静默降级)
-- **v2.1.2 块级钉到看板**(复用 `codexTextKey` 按内容哈希恢复;折叠时不渲染,最多 6 个)
-- **v2.1.3 结论快照与对比**(只存文本与表格、不存图;行级 LCS diff)
-- **v2.1.4 模板 / 会话列表导入导出**(导入的会话本机没有记录,要明确提示,不装作能恢复)
-- 8.7 记了**评估后不做**的三项(图表联动筛选做不出可靠交互、SQL 直连预览、消息级删除),附理由
-- 8.8 汇总 D1–D5 决策点待确认;8.9 登记 v2.2.0 候选
-
-## v2.1.0 实施完成(v2.1.1 → v2.1.5,五个子版本一次做完)
-
-新增 5 个文件:`components/codexRunOptionsPanel.vue`、`components/codexBoardPanel.vue`、
-`components/codexSnapshotPanel.vue`、`utils/codexArtifactChange.ts`、`utils/codexTextDiff.ts`、`utils/codexSnapshot.ts`。
-- **v2.1.1**:`sandbox` / `approvalPolicy` 从硬编码改为按会话 + 全局默认;危险取值二次确认;
-  顶栏出「只读 / 全权访问 / 免审批 / 每步确认」徽标。类型归位到 `types/codexTypes.ts`(api 只转出)。
-- **v2.1.5**:`collectCodexArtifactChanges` 按 `*** Add/Update/Delete File:` 段落头提取路径,
-  **认不出就不猜**;产物栏重构为独立容器(`.codex-aside` 交回页面)+ 搜索 + 类型筛选 + 本轮变更。
-- **v2.1.2**:看板只存内容哈希(`codexTextKey`),正文按哈希从当前会话找回;找不到就标失效;
-  折叠时 `v-if` 不渲染(避免图表重复渲染);最多 6 个。
-- **v2.1.3**:快照只存文本与表格(不存图,localStorage 配额撑不住);diff 用**统一视图**而非并排
-  (并排要两列行号对齐,长报告一折行就错位);LCS 限长 1200 行。
-- **v2.1.4**:模板与会话列表都能导出 / 导入;导入的会话标 `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)。
-
-## 合并到 dev_3
-
-- 在 worktree 提交:`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 一起提交进去,值得提醒。
-- Hook 说明:`.git/hooks/post-commit`、`post-checkout` 是 **Qoder AI tracker**(带 `|| true`,
-  不会让提交失败);它会尝试起 `reg.exe` / `sc.exe`,被 WorkBuddy 沙箱黑名单拦掉,属正常现象。
-
-## 推送 dev_3 到远端
-
-- `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 只发提交,不发索引)。
-- 本条笔记**刻意没有提交、没有推送**:用户授权的是「推送 dev_3」这一次对外动作,
-  为一条笔记再打提交会给他们共享分支添噪音。需要时再补。
-
-## 工作目录改为代码决定 + 修「已拒绝访问」
-
-**用户两条明确要求**:① 工作空间路径由代码设置,**不允许在界面上修改**;② 修掉
-「该目录不是本客户端的会话工作目录,已拒绝访问」。
-
-**报错根因**:`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 个提交,**尚未合并推送**(用户未授权本次推送)。
-
-## 再次合并到 dev_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。
-
-## 移除会话导入导出 + 新建会话入口调整
-
-用户三条要求:① 移除会话导入导出功能;② 放大新建会话按钮;③ 移除顶栏右上角的「新建会话」。
-- **导入导出**:侧栏「导出 / 导入」按钮 + 隐藏 file input + `onExportSessions/onImportClick/onFileChange`
-  全删;store 的 `exportSessionsJson / importSessionsJson` 一并删除(已无调用方)。
-  `CodexSessionMeta.external` 标记与侧栏「外部」徽标保留(兼容已导入过的存量数据)。
-- **新建会话**:侧栏 head 里的「新建」小按钮移出,改成 head 下方**整行主按钮**
-  (`__create`:34px 高、主色虚线边框、hover 变实心主色、创建中禁用并显示「创建中…」)。
-- **顶栏**:右上角「新建会话」按钮删除,emit `'create'` 一并移除;视图里工具栏的 `@create` 解绑。
-  侧栏按钮成为唯一入口。
-验证:`pnpm type:check` 89 基线 / 本包 0 报错;`pnpm build` exit=0。
-
-## 会话防重:不允许堆空白会话
-
-用户反馈「会话可以一直建,没有聊天记录也能一直建」。优化规则(store 新增 `startNewSession`):
-1. 当前会话是空的(`isSessionEmpty` = 无提问无回复)→ 不再新建,提示「已有空白会话,直接输入问题即可」(info);
-2. 列表里已有空会话 → 直接切过去,不新建;
-3. 都没有才真正 `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 产出或改过的文件都会命中;
-  别的会话更早生成的不会混进来。并行会话改同一个文件会同时出现在两边,属可接受误差。
-- **前端**:store 增 `sessionFiles` + `loadSessionFiles()`(reloadArtifacts 与回合结束时刷新);
-  产物栏加「本会话产物(默认)/ 全部文件」切换条,关键词与类型筛选两个视图共用;
-  「全部文件」保留原目录浏览 + 上一级 + 新角标;「本轮变更」两个视图都在。
-- 面板默认视图 = 本会话产物;空态文案引导「想看工作空间里的全部文件请切到全部文件」。
-
-验证:前端 type:check 89 基线 / 本包 0 报错 + build exit=0;
-主进程 tsc --noEmit 0 报错 + 91 单测全过 + build-electron exit=0(listAll 已进 bundle)。
-
-## 本会话产物升级为混合口径(C+A)
-
-mtime 过滤的盲区:**后来的会话产出的新文件会混进更早会话的列表**(时间戳只回答「什么时候改的」,
-回答不了「谁改的」)。升级为 C+A 混合:
-
-- **C(精确)**:`CodexSessionMeta.artifactManifest` —— 每回合结束时
-  `accumulateArtifactManifest(threadId)` 把该会话**历次**事件流里 `*** Add/Update/Delete File:`
-  段落头提取的路径累计进清单(Set 语义、去重、随会话持久化)。以事件流为准而非只看最后一轮:
-  文件一旦被改过就一直归属该会话。
-- **A(兜底)**:`loadSessionFiles` 取**并集** —— `manifest.has(relative) || mtime >= createdAt`。
-  清单覆盖不到的场景(升级前的历史会话 / exec 直接写文件 / patch 解析失败)由 mtime 兜住。
-
-方案讨论时对比过 B(每会话产物目录:协议没有输出目录概念,靠 prompt 约束不可靠)、
-D(主进程 watcher:只覆盖活会话,要养常驻进程)、E(按回合组织:交互重做)——都否了。
-提交 `(本次)`;验证 type:check 89 基线 / 本包 0 报错 + build exit=0。
-
-## Codex 支持本地 Ollama / chat-only 端点(P1-P4 全部落地)
-
-**前提修正(调研取证)**:Codex 0.155+ 已物理移除 wire_api="chat"(WireApi 只剩 Responses,
-配 chat 硬报错,官方 Discussion #7782);Ollama ≥ 0.13.3 原生支持非状态化 /v1/responses,
-而 Codex core 每轮重建完整 input(无状态调用),两者天然兼容。我们应用永远不需要给 Codex
-做消息转换;chat-only 端点的转换只能在 Codex 与端点之间的代理层。
-
-**四期落地**:
-- P1 `cf328ab`:probeEndpoint 四态(responses/chat-only/unsupported/unreachable),
-  真实 modelId 修 Ollama 404 误伤(Ollama 对未拉取模型也返回 404!),/api/version 消歧。
-- P2 `b029cbb`:chatBridgeTranslate.ts 纯函数翻译层 + ResponsesSseTranslator 流式状态机。
-- P3 `b9075dd`:chatBridgeService(127.0.0.1 随机端口、只接 POST /v1/responses、不持 key);
-  applyProvider 判 chat-only 自动桥接 + **Ollama 桥接必须降级为自定义 provider**(内置
-  provider 的 buildArgs 只允许覆盖 base_url,不降级会绕过桥接直连);AppliedProviderInfo
-  加 bridged(落盘存上游真实地址,桥接 URL 不落盘,重启靠重新「应用」重建)。
-- P4 `0d765ae`:运行时面板桥接行 + 已应用「桥接」标记。
-
-**关键经验**:
-- 冒烟(npm run smoke:codex)**真实验证了 Codex 无状态调用**:第二轮 turn 上游收到的
-  messages 含第一轮全部内容(user+assistant 回放)——这是 Ollama 非状态化兼容的根基。
-- 冒烟有两个**基线既有失败**(git stash 对照证实,与本改动无关):`status.version` 在沙箱里
-  拿到 object(spawnSync 怪癖)、codexCtl.itest.ts 的 electron 二进制未装好。
-- SSE 事件序列按 OpenAI Responses 规范合成(created→item.added→part.added→delta→done
-  系列→completed),真实 Codex 一次跑通,说明规范实现即可被接受。
-- provider.json 的 AppliedProviderInfo.bridged 只在桥接时写入;重启后 bridge 不自动重建,
-  页面重新「应用」时自然恢复(与 apiKey 注入同一流程)。
-
-验证:tsc 0 报错、114 单测全过、smoke 桥接 turn 通、build-electron exit=0(chat-bridge 已进包)、
-前端 type:check 89 基线 0 新增 + build exit=0。
-
-## 聊天页模型选择器:数据源换后端模型管理 + 会话中锁定
-
-需求:选择器用「模型管理」里配置的模型;会话过程中不能切换模型。已拍板:选中即应用、有消息即锁。
-
-- store:`models` 由 codexListModels(内置目录)改为 `modelApi.listModels()`(过滤 llm)+
-  `listProviders()`(查 providerType);新 action `applySessionModel(recordId)` —— 组装
-  ApplyProviderInput 调 codexApplyProvider(重启运行时),成功后回写 `this.runtime = result.status`
-  + `setSessionModel(activeThreadId, record.modelId)`;失败抛错由页面提示。
-- 选择器 value 用**后端记录 id**(防同 modelId 不同端点重名);会话 model 仍存 modelId
-  (turnRun/导出直接用,语义不变)。页面弃用 v-model 改 `:model-value + @update`——
-  应用是 async 可失败,显示值由 selectedRecordId 反查驱动,失败天然回退。
-- composer:props.models 改轻量 `{value,label}[]`(组件不感知 AiModel);新增
-  `modelLocked`(有消息即锁,title 提示)/`modelLoading`;删「跟随 Codex 默认模型」空值项。
-- 清理:codexApi.codexListModels / CodexModelOption 已删(前端零引用);
-  主进程 codexCtl.listModels handler 保留未动。
-- 边界:未登录后端 → models=[] → 「未配置可用模型」(同现状静默降级)。
-
-验证:pnpm type:check 89 基线 / codex 包 0 报错;pnpm build exit=0。主进程未动。
-
-## Codex「协议流已关闭」:内置 provider 覆盖是保留字,兜底整体移除
-
-现象:聊天页选模型(Ollama 记录)后 codex 起不来,报 `Codex 协议流已关闭`,之后点多少次启动都是同一句。
-根因(用 vendor 里的 codex.exe 0.155.1 实跑复现):`buildArgs` 对内置 provider 发
-`-c model_providers.ollama.base_url=…`,而 0.155.1 把内置 id 列为保留字——
-`model_providers contains reserved built-in provider IDs: ollama` → **app-server exit 1**,
-stdout 随之关闭,`jsonRpcPeer` 把挂着的 `initialize` reject 成那句没信息量的话。
-(同法验证:`model=""` 报 provider name must not be empty;不带地址的 `model_provider="ollama"` 正常。)
-
-- 二次伤害:`CodexRuntime.applyProvider` 先写 `#provider` 再 start,失败不回滚 →
-  坏 spec 常驻内存,之后每次 start 重放同一组参数,日志里连着一排 `start failed`,
-  只有重启应用或「清除模型」才能解锁。
-- 按拍板**删掉内置 provider 这条路**(不做任何 localhost 兜底):
-  `resolveBuiltinProvider` / `normalizeOssBaseUrl`(含自动补 `/v1`)/ `toProviderSpec` 内置分支 /
-  `buildArgs` 内置分支 / `CodexProviderSpec.builtinProvider` / `ApplyProviderInput.builtinProvider` /
-  `AppliedProviderInfo.builtinProvider` / codexCtl 里两处「Ollama 未配地址就探 localhost」全删;
-  一律翻译成自定义 provider `zsjz`,`id`/`baseUrl` 收紧为必填。
-  `probeEndpoint` 不再收 builtinProvider,Ollama 消歧只看 `providerType`。
-- 新增校验:缺 base_url → 「模型「x」没有配置 base_url,请到「模型管理」补全(形如 https://host:port/v1)」;
-  聊天页未选模型点发送 → 拦下发并提示(有候选提示去选,无候选提示去模型管理加)。
-  理由:未应用 provider 时 Codex 打它自己的默认端点,失败信息完全不可读。
-- 可诊断性:`createExitWatcher` 统一 stdout 关闭 / exit / spawn error 三个出口,等 close(≤300ms)
-  收完 stderr 再拼 `Codex App Server 退出(1):<Error 原文>`,`#start` 的 catch 用它改写
-  `error.message`(保留错误对象与 stack);`pushDiagnostic` 同步 `logger.warn` 落 ee.log
-  (原来只进内存环形缓冲,进程一没就查不到)。
-- **行为变化**:库里把 Ollama 存成 `http://host:11434`(缺 `/v1`)的记录不再被自动补齐,
-  探测会判 unsupported,需去模型管理补版本段(`baseUrlHint` 本来就一直在提示)。
-
-验证:tsc 0 报错;vitest 111 passed(内置 provider 相关 6 条用例随功能删除,新增保留 id 断言);
-前端 vue-tsc 在 src/codex 与 src/ai/views/aiPlugin 上 0 报错(93 条存量在别处);
-另用真实 codex.exe + 临时 codexHome 跑过三条端到端(错误信息含 exit 1 与原文、
-applyProvider 失败后 provider 回滚且下次 start 到 ready、Ollama 记录生成的参数不含保留 id 且进程起得来),
-临时用例已删。未跑 build / itest。
-
-## 端点探测收敛为 Chat 单协议 + 模型默认选中与延迟应用
-
-拍板:**后端「模型管理」里的记录一律是 `/chat/completions` 端点**,不会有 `/responses`;
-Responses 只属于 Codex 自己的模型。据此把四态判定砍成 `chat | unsupported | unreachable`。
-
-实测数据(`10.66.66.66:8080`,llama.cpp 挂 35B GGUF,推翻了我先前两次判断):
-
-| 请求 | 结果 |
-| --- | --- |
-| `GET /v1/models`、`/health` | 14–29ms |
-| `POST /v1/responses` 非流式 1 token | 11.0 秒 |
-| 同上 16 token(原探测参数) | >30 秒 |
-| 同上 `stream:true` | 20 秒不吐首字节 |
-| `POST /v1/chat/completions` `max_tokens:1` | 13.99 秒 |
-| `POST /v1/chat/completions` 只带 `{model}` | **400,15–27ms** |
-| `GET /v1/models`(被一次进行中的生成堵住时) | 21 秒无响应 |
-
-结论:**单槽推理服务会把整个 HTTP 服务占住**,所以探测既不能要求生成完成、也不能用短超时,
-`GET /models` 探活同样会被堵(先加的 2 秒探活因此撤掉)。
-
-- `probeEndpoint`:只 `POST {base}/chat/completions`,body 故意只带 `model`、不带 `messages`
-  → 端点在校验阶段回 400,实测 15–27ms;非 404/405 即判 `chat`。超时留 15 秒作排队余量。
-  代价说清楚:**模型名写错不再被探测拦住**,要等真正提问时才暴露。
-  报错文案带具体 URL 与耗时(`端点不可达:http://… 在 15.0 秒内没有响应…`)。
-- 删除:`/responses` 探测、`/models` 探活、Ollama 版本消歧整条链
-  (`probeOllamaVersion`/`parseVersion`/`versionLt`/最低版本常量)、
-  `responsesStatus`/`ollamaVersion`/`ollamaNeedsUpgrade` 字段、恒真的「桥接」chip 与其 CSS、
-  `providerType` 全链路(含 store 里为它而加的 `providers` 与 `listProviders()` 请求)。
-- `applyProvider` 成功即桥接,去掉 `bridged` 分支与「直通时 stop 桥接」的分支。
-- 模型选择:**默认选中后端 `defaultModel` 那条,但只在首次发送时应用**
-  (应用=重启子进程+探测,进页面就做会白等)。store 新增 `sessionModelRecordId`
-  (会话记过就反查、新会话退默认模型、记录被删返回空)、`modelApplied`、`modelGuardMessage`;
-  `send()` 开头 `ensureModelApplied()`;选择器改动只写进会话并提示「下次提问时生效」;
-  `modelLocked` 加「记录被删则解锁」,否则既发不出也换不了;未选模型点发送给提示不发。
-
-验证:tsc 0 报错;vitest 109 passed(新断言:探测请求体不得带 `messages`、不可达提示必须含 URL);
-vue-tsc 93 条存量、src/codex 与 aiPlugin 命中 0;用应用自身 `probeEndpoint` 打真实端点 27ms 判 `chat`。
-itest / build / 客户端 UI 未跑(渲染进程要 Electron 的 codex IPC 桥,浏览器里进不去)。
-
-## `turn/start: thread not found` 与「正在处理中」不退
-
-日志时间线(pid 18984):`21:11:20` codex 进程 #1 起来(`createSession → thread/start` 用的它)
-→ `21:11:26` `chat-bridge 已启动`(= 我在 `send()` 里的 `ensureModelApplied` 重启了子进程)
-→ `21:11:27` `turnRun failed: thread not found`。
-
-**根因是自己引入的顺序错误**:`send()` 原先是「建线程 → 应用模型 → turnRun」,
-而应用模型会 stop+start app-server,刚建的线程当场作废。线程只存在于当前进程;
-Codex 只在**成功跑完一轮**后才落盘(`state_5.sqlite` 的 `threads` 表),所以从没成功过的会话
-连 `thread/resume` 都报 `no rollout found`。另外 `resumeThread` 此前全仓零调用。
-
-「正在处理中」不退是第二处:`resolveTurnStatus` 只认 `turn/completed`,而 `send()` 失败时
-自己造的 `local-error:<ts>` 分组没有该事件 → 错误气泡本身被判 running;
-`stop()` 又专挑 running 的助手消息去 interrupt → 连打 5 次 `thread not found`。
-
-修复(A+B+C+D1,已拍板):
-
-- **A** `send()`:`ensureModelApplied()` 提前到 `ensureSession()` 之前,并加「当前会话在跑就返回」守卫。
-- **B** `resolveTurnStatus`:无 `turn/completed` 但有 error 事件 ⇒ `error`;
-  新增 store action `noteTurnFailure`,把失败挂回卡在 running 的那一轮 turnId(找不到才自造分组),
-  `send()`/`stop()` 共用;`streaming` getter 读同一状态,因此能退出转圈。
-- **C** `stop()`:interrupt 失败也先本地收尾再抛给页面提示。
-- **D1** `codexCtl.turnRun`:`thread not found` → `resumeThread` → 重发本轮;
-  resume 也失败 → 「该会话的 Codex 线程已失效(中途换过模型或重启过应用),请新建会话继续」。
-  不自动换线程:事件缓存、产物归属、重新生成都按 threadId 索引,会话↔线程 1:1 是现有前提。
-
-实测(真 codex.exe + 桥接 + mock 上游):跑完一轮 → 重启 → `thread not found` 原文复现 →
-`resumeThread` 成功且第二轮完成、历史带回。顺带验掉一个疑点:`thread/resume` 的
-`excludeTurns` true/false **对上游收到的历史没有差别**(都是 8 条、含前两轮 user/assistant),
-所以没加这个开关,只在 `resumeThread` 里留一行注释记录结论。
-
-验证:tsc 0 报错;vitest 108 passed;vue-tsc 93 存量、src/codex 与 aiPlugin 命中 0
-(中途一次「0 报错」是我拆掉前端 junction 造成的假数,已重跑取实数)。
-未跑 itest(会写 data/codex-home 与 provider.json)、build、真实界面点击。
-
-## 「request timed out」排查:四个假设全被实测否掉 + 桥接补请求级诊断
-
-现场:codex stderr 每 ~15.0 秒一条 `stream disconnected - retrying sampling request (n/5)`、
-`sampling_error: "request timed out"`,5 次之后 `falling back to HTTP`;那一轮 21:34 完成、
-`ee.log` 里没有 `turnRun failed` —— 不是连不上,是被重试拖死。
-
-逐个否掉(全部真 codex.exe + 真桥接 + 受控 mock 上游):
-
-| 假设 | 实测 |
-| --- | --- |
-| 桥接有超时 | `chatBridgeService`/`chatBridgeTranslate` 无任何 timeout 常量 |
-| `stream_idle_timeout_ms` 默认 15s | 首事件前静默 26s、首事件后静默 30s,默认与 `=120000` 均 1 次请求、零重试 |
-| Codex 走 WebSocket、桥接无 `upgrade` 监听导致挂起 | 旁路嗅探 TCP 首包:零 upgrade,只有 `POST /v1/responses` |
-| 单槽排队堵死 | 复刻 100 秒排队:1 请求、同时在飞 1、100.5s 成功、零重试 |
-
-端点侧事实(`GET /props`):`total_slots: 1`、`n_ctx: 262144`、build b1005;
-流式 600 token 实测首字节 98.8 秒(生成只 10.5 秒、prefill 268ms)→ 那 98.8 秒是排队。
-
-结论:**15 秒只能来自那一轮真实请求**,而桥接此前只记过三行「已启动」,无法判断谁先放手。
-本轮只加日志(不改行为):`chat-bridge: #N 起 round model/stream/tools/messages/输入字数 → url`、
-`转发完成(耗时,上游字节→事件数)`、`Codex 提前断开(耗时,已发 N 个事件)`、
-`上游 HTTP xxx(耗时)`。下次真实提问即可定死:出现「Codex 提前断开 @15s」= Codex 侧阈值;
-出现「上游 HTTP/不可达」= 端点侧。
-
-顺带记下与 cc-switch「路由」的对照:那类开关做的是「声明上游接口格式 + 本地代理转协议」,
-我们的对应物就是内置桥接(应用模型时自动开,Codex 侧 `wire_api="responses"`)。
-差距在可配面:0.155.1 的 provider 认 `request_max_retries / stream_max_retries /
-stream_idle_timeout_ms / websocket_connect_timeout_ms / supports_websockets / query_params /
-http_headers …`,我们白名单只放行 4 个,所以「关 websocket」这类操作现在配不出来 —— 
-等 15 秒归属确认后再决定要不要放开,别提前改。
-
-另记 F1(未做):Codex 挂断后桥接不取消上游生成,会一路读到 `[DONE]`,
-被放弃的请求继续占唯一的槽 —— 这是重试互相排队的放大器,加 AbortController 绑 `res` 关闭即可。
-
-验证:tsc 0 报错;vitest 109 passed;诊断行样例由受控 mock 实跑取得。未跑 build / itest。
-
-## 超时根因收口:先建流 + 不重发 + 挂断即取消上游(对照 cc-switch)
-
-被实测否掉的四个假设(都是真 codex.exe + 真桥接 + 受控 mock 跑出来的):桥接层无任何超时常量;
-「首事件前静默 26s」「首事件后静默 30s」在默认与 `stream_idle_timeout_ms=120s` 下都是
-1 请求零重试;Codex 对本地 provider 零 WebSocket upgrade;复刻 100s 单槽排队也正常完成。
-
-`/slots` 才给出真相:一轮 prompt **46,122 token**,20 秒只 prefill 了 589 token(≈29 tok/s),
-`is_processing` 恒真 —— 一次要二十几分钟;而 Codex 每 15 秒掐一次并重发 5 次,
-等于把同样的几分钟重复五遍,还会挤掉前缀缓存(同一请求三次实测 53.0s / 3.9s / 23.1s,证明缓存确实生效)。
-
-读 cc-switch(`proxy/forwarder.rs`、`proxy/providers/codex.rs`、`codex_responses_sse.rs`)得到的经验:
-它把超时拆成 `non_streaming_timeout` + `streaming_first_byte_timeout`,而 `_streaming_idle_timeout`
-显式不用(流建起来就不靠 idle 杀);`max_retries` 是跨厂商 failover 而非对同一上游重发;
-连接生命周期靠 Drop 释放;路由判定是 `meta.api_format` 优先、URL 特征兜底。
-
-据此三处改动(**每条都先写测试跑红再实现**):
-
-1. 流式请求一到就先回 `response.created`/`response.in_progress`(新增 `ResponsesSseTranslator.begin()`,
-   复用其 `ensureStarted()` 幂等保护),不再等上游首字节 —— 测试首帧 6017ms → 立即。
-2. Codex 挂断即 `AbortController` 取消上游,单槽立刻归还(测试:上游 <1s 观察到中止)。
-3. provider 默认 `stream_max_retries=0`、`request_max_retries=0`、`stream_idle_timeout_ms=300s`、
-   `supports_websockets=false`(白名单补 `supports_websockets`;模型管理 config 显式值可覆盖)。
-
-真机 smoke `chatBridge.live.itest.ts` 保留(`npm test` 不跑,`npm run smoke:codex` 跑;端点不可达自动跳过)。
-注意 `describe.skipIf` 在收集阶段求值,可达性探测必须放模块顶层 await,放 beforeAll 会永远跳过 —— 我自己踩过。
-连跑两次真机:71.2s → **38.8s**,均 1 请求、0 重试、回复正确。
-
-下一步(未做,先记):学 cc-switch 在模型管理加显式 `api_format` 字段,
-「一律按 Chat」迟早要能被「这条记录就是原生 Responses」覆盖,而不是靠探测猜。
-
-验证:vitest 113 passed;tsc 0 报错;vue-tsc 93 存量、codex/aiPlugin 0;真机 smoke 连过两次。未跑 build。

+ 0 - 147
.workbuddy/memory/MEMORY.md

@@ -1,147 +0,0 @@
-# zsjz-ai 项目长期记忆
-
-## 本机环境怪癖(每次会话都会遇到)
-
-- **bash 工具可用**(2026-09-24 起实测 `ls/cat/grep/sed/find/rm/cp/comm/diff` 都正常);
-  早期会话的「shim 报 dirname not found」已不复现。仍优先用 Glob/Grep/Read 读文件。
-- PowerShell 工具的 stdout **不回显**:要拿输出必须落盘再读。落盘务必用
-  `... | Out-File -FilePath <f> -Encoding utf8`;**`>` 会写成 UTF-16**,Read 工具会当二进制拒绝。
-- `Remove-Item` 被 safe-delete 钩子拦截 → 删文件用 `[System.IO.File]::Delete('绝对路径')`;bash `rm -f` 可以。
-- 仓库根 = 主仓库 `E:\workspace\zsjz-ai`(git common dir);当前 worktree 是
-  `C:\Users\cc\WorkBuddy\Worktrees\zsjz-ai\dev-3-*`。**worktree 只有被跟踪的文件**,
-  被 gitignore 的本地目录不会带过来(见下),缺东西时去主仓库拷。
-
-### worktree 首次可用的初始化(2026-09-24 踩实)
-
-1. **`frontend/` 是 pnpm 项目**:`pnpm install` 会因 esbuild postinstall 报
-   `spawnSync node.exe EBUSY`(沙箱里孙进程 spawn node 被拦),**而且失败后 `node_modules/.bin`
-   不会生成**,于是 `pnpm type:check / build` 全报「不是内部或外部命令」。
-   → 解法:`pnpm install --ignore-scripts`。esbuild 真二进制在 `@esbuild/win32-x64`,
-   postinstall 只是 `--version` 校验,跳过无害(实测 `require('esbuild').version` 正常)。
-2. **`ai-electron/` 是 npm 项目**(只有 `package-lock.json`,node_modules 是扁平结构、无 `.pnpm`):
-   **不要用 pnpm 装**(会因 `esbuild@^0.28.0` 解析失败 —— 镜像元数据里 latest 还是 0.27.4)。
-   正确姿势:从主仓库拷 `ai-electron/package-lock.json`(被 gitignore 了),再
-   `npm install --ignore-scripts --no-audit --no-fund`(约 23s / 710 包)。
-   校验命令:`npx tsc --noEmit`(= `npm run typecheck`)、`npm test`(vitest,91 用例)、
-   `npm run build-electron`(产出 `public/electron/main.js`)。
-3. 被 gitignore 误伤、worktree 里没有、缺了直接构建失败的目录(从主仓库 `cp -r` 过来,不会污染 git status):
-   - `ai-electron/frontend/build/`(根 `.gitignore:22` 的 `build/` 命中;vite.config.ts 要 `./build`)
-   - `ai-electron/frontend/src/case/views/data/`(`ai-electron/.gitignore:7` 的 `data/` 命中,
-     把源码目录也误伤了;路由 `routes/index.ts` 直接引用,51 个文件)
-   - `ai-electron/package-lock.json`
-4. 其余 5600+ 条「主仓库有、worktree 没有」都是运行时垃圾(`data/`、`out/`、`logs/`),不用管。
-
-## 前端(`ai-electron/frontend/`)
-
-- 技术栈:Vue 3 + `<script setup lang="ts">` + ant-design-vue + vite + less + pnpm。
-- 常用命令(cwd 一律在 `ai-frontend/`):
-  - 构建:`pnpm build`(= `vite build --mode production`,**不做类型检查**,约 2 分钟)
-  - 类型:`pnpm type:check`(= `vue-tsc --noEmit --skipLibCheck`,约 25 秒)
-- **`pnpm type:check` 本来就过不了**:仓库有约 89 条既有报错,集中在
-  `src/call/`、`src/trans/`、`src/graph/`、`src/otg/`、`src/person/` 等无关文件。
-  判断自己有没有写坏,看的是「报错文件里有没有自己动的文件」,不是退出码。
-- `tsconfig.json`:`isolatedModules: true`,无 `verbatimModuleSyntax`;`noUnusedLocals: false`(所以多余的 import 不报错)。
-- **暗色主题的既有缺陷**:`vite-plugin-theme-vite3` 生成的
-  `dist/assets/app-antd-dark-theme-style.*.css` 里,**自定义属性的 `--` 前缀被抹掉**
-  (搜 `--codex-bg` / `--ai-text` 都是 0 处,只有 `codex-bg` / `ai-text`)。
-  即暗色模式下 `--ai-*` / `--codex-*` / `--jeesite-*` 都不会被覆盖,页面沿用浅色 `:root` 值
-  (antd 类名那部分正常)。全项目同等影响,v2.1 之前就存在;修它要动构建期主题插件,未动。
-- 构建时那条 `WARN warnings when minifying css: Invalid character(s) '@charset UTF-8";` 是同一个
-  主题插件的产物拼接问题,同样与业务代码无关。
-- 路由 / 别名:`@/*` → `src/*`;路由表在 `src/core/router/routes/index.ts`。
-- less 全局变量靠 `build/theme/modifyVars.ts` 里的
-  `hack: 'true; @import (reference) "src/core/design/var/index.less";'` 注入,
-  所以任意 `.vue` style 块和任意 `.less` 文件都能直接用 `@modal-header-bg-color` 这类变量,
-  SFC 的 style 抽成独立 `.less` 再 `@import` 回来是安全的。
-- ant-design-vue 的 Modal / Popover / Select 会 teleport 到 body,**样式必须全局(非 scoped)**,
-  靠 `wrapClassName` + 全局类名命中。
-
-## 新增页面 / 菜单入口机制(纯前端,不走后端)
-
-- **菜单不在后端**:来自本地 `src/core/store/modules/menu.json`(`permission.ts` 直接 import 它当 RootRoute)。
-  加页面 = 往对应父分组 `children` 里塞一条(`path`/`component`/`url` 三者同值,`name` 形如 `ViewsXxxYyy`,`id` 用递增号)。
-- **视图映射在 `src/core/router/helper/routeHelper.ts` 的 `explicitDynamicViewMap`**(显式登记优先于 glob 兜底),
-  格式:`'/xxx/yyy': () => import('@/xxx/views/yyy/index.vue')`。
-- 页面外壳沿用 `.ai-page` 那套反冲手法:`width: calc(100% + 20px); margin: 0 -10px;`,留 2px 灰底 gutter。
-
-## Codex 数据分析新包(`src/codex/`,2026-09-24 起)
-
-- 需求:**全新包、不复用 `src/ai/` 聊天代码、所有文件 `codex` 前缀**(含视图入口 `views/codexAnalysis/codexAnalysis.vue`)。
-- 计划文档:仓库根 `../../docs/design/codex-analysis-plan.md`(v1.0.0 → v1.9.0,逐版本停下 review)。
-- **v1.0.0 ~ v1.9.0 已全部落地**(骨架 / 会话 / 流式对话 / 富内容块 / 图表关系图 / 过程与审批 /
-  产物 / 报告导出 / 恢复与诊断 / 打磨)。前端 31 个文件全 `codex` 前缀;主进程新增
-  `codexArtifactService.ts` + `codexArtifactCtl.ts`(root 白名单落盘 + 路径穿越校验 + 只读不删),
-  `codexCtl.ts` 增量:`threadStart` 返回 `cwd`、`turnRun` 登记可选 cwd、新增 `defaultWorkspace`。
-- **关键实现约定**:事件按 threadId 缓存(同 id 覆盖、isDelta 追加);turn/completed 决定回合结局;
-  围栏协议 `echarts/graph/table/json/report`,解析失败回退成代码块;导出 md/html/xlsx/长图(png);
-  HTML 报告里的图表靠 `utils/codexVisualRegistry.ts`(内容哈希 → 图片提供者)内嵌真实图片。
-- **工作目录优先级**(v2.0.5 起):会话 cwd → 该案件的手动覆盖(`cwdOverrides`) → **案件工作空间** → 客户端默认。
-  案件工作空间 = `<user.dir>/QingJian/workspace/<案件ID>`,由后端 `CaseInfo.workspacePath` 回传
-  (`@TableField(exist = false)`,`CaseInfoService.fillWorkspacePath` 在 open/current/list 返回前置值)。
-- **验证口径**:前端 `pnpm type:check`(89 条基线、本包 0 报错)+ `pnpm build`(exit 0);
-  主进程 `npx tsc --noEmit` + `npm test`(91 用例)+ `npm run build-electron`;
-  后端 `mvn -o -DskipTests compile`(仓库根,多模块,`ai-server` 是子模块)。
-- **基座事实**:主进程 `electron/controller/codexCtl.ts` + `service/codex/*` 已全量可用;IPC 封装现在
-  唯一实现在 `src/codex/api/codexApi.ts`,`src/ai/api/codexApi.ts` 只剩一行 `export *`。渲染唯一输入是
-  `AgentEvent`(映射见 `service/codex/eventMapper.ts`)。
-- **协议参考**:官方 `https://developers.openai.com/codex/app-server`;本地 schema 全集在
-  `E:\workspace\ai\Noobi.ai\schemas\codex\v2\*.json`。**布局可参考 Noobi.ai(React),但样式必须用本项目主题**。
-
-## cleaning 页拆分工程(`src/case/views/data/cleaning.vue`)
-
-**当前进度(2026-09-18)**:`12029 → 944 行`(−92%),30+ 新文件,type:check 0 报错 + build 通过。
-依赖顺序(单向、无环):`useCaseDataCleaningShared`(底层状态)→ `useCleaningRules / useFieldMapping / useRecognitionRules / useTemplateLibrary`
-→ `useCaseDataCleaningActions`(叶子动作域,放在最后所以能直接用上游产物)。
-剩余(压到 ≤400):① 把顶层 `watch([...])` 移进 composable(watcher 在 composable 内注册仍绑定页面实例)→ 21 个退回声明可搬走;
-② 清未使用 import(匹配标识符要允许 `...` 展开);③ `useCaseDataCleaningActions`(1310) 可按域再拆成 useFileTree / usePreviewData / useColumnOps。
-
-- **消环的唯一正确姿势**:底层共享状态收进**唯一一个 shared composable**,其余域全部单向依赖它(+ 已有 handle);
-  叶子域一定要放在所有上游之后。不要用「多个平级 composable 互相注入」——底层状态同时被多方需要,会形成真实的环,
-  拓扑排序无解;破环退化成空壳 composable 就是垃圾。
-- 生成器可复用件(写这类脚本直接照搬):
-  - 声明解析用「**找下一个顶层起点**」,顶层起点要包含 `watch(` / `provide(` / `onMounted(` 等**非声明的顶层语句**,否则上一个声明的范围会吞掉它们
-  - 模板取词:`{{ }}` + `: / v- / @ / ref` 绑定值 + 标签名;表达式剥掉 `'…'`/`"…"` 但**保留 `${...}` 插值**
-  - 抽出的 `.ts` 补 `export`;跨文件相对路径按新层级重算;deps 类型**显式写 interface**(否则类型循环 → `Object.entries(any)` → `[string, unknown][]`)
-  - 接线模式:**页面在 setup 中实例化 composable + 同名解构**(页面代码/模板零改动),`provide` 放在 setup 末尾;
-    wiring 插在「最后一个依赖声明」**之后**(锚点是依赖声明的最后一行)
-
-- 方案文档:仓库根 `../../docs/design/cleaning-refactor-plan.md`。已拍板: **路由不动**(cleaning.vue 留作入口)、
-  通信走 **provide/inject + 领域 composable**、样式保持 **全局 less 非 scoped**、 **业务逻辑一律不许动**、
-  **每阶段停下来 review**。
-- 目录:`cleaning/{types,constants,context}.ts` + `cleaning/{utils,composables,components,styles}/`
-- 沟通模式(Phase 3 起确定的**主力模式**,后续阶段照这个来):
-  - 领域逻辑放 `composables/useXxx.ts`,**由页面在 setup 里实例化**
-  - 页面用**同名解构**接住返回值(`const { a, b } = useXxx(...)`)→ 页面原有代码与模板**零改动**
-  - 页面把 `useXxx` 实例放进 `provide(CleaningContextKey, { ..., xxx })`,子组件从 context 取
-  - `provide()` 放在 **setup 末尾**(避开 const 声明顺序的 TDZ);composable 的调用位置 = 「最后一个依赖声明之后」
-  - **composable 的 deps 必须显式声明类型**(写 interface),不要让 TS 从实参反推 —— 页面里有 decl 的签名依赖返回值时
-    会形成类型循环,导致 deps 退化成 `Record<string, any>`,而 `Object.entries(<any>)` 会产出 `[string, unknown][]` 引发一片 unknown 报错
-- **验证必须 type:check + build 双跑**:`vue-tsc` **抓不到 Vue 模板标签不平衡**,
-  抽块边界写错(多删一个 `</div>`)时 type:check 是 0 报错、`pnpm build` 才报 `Element is missing end tag`。
-  抽模板块时边界要取「配对的 `</div>` 行」,不要凭缩进猜(这个文件缩进本身不严格)。
-- 已踩实的坑:
-  - 抽出的 `.ts` 必须补 `export`(无 import/export 的 .ts 会被 TS 当全局脚本 → `TS2306`)
-  - 抽出文件引用原页面的相对路径 import 时,要按新层级重算(`@/` 别名例外)
-  - **页面级 `let`/`var` 标量不能注入**:跨文件是按值快照,两边各改一份(请求序号守卫之类会失效)
-  - **声明解析要用「找下一个顶层起点」**,不要用括号深度(多行泛型 `ref<\n Record<...>` 会让深度中途归零、把声明切一半)
-  - **模板取词要保留 `${...}` 插值**,只剥 `'…'` / `"…"`(整段 strip 会把 `` `is-${x}` `` 里的 x 抹掉)
-  - 判定「setup 顶层立即求值」时**只扫 wiring 位置之前的行**(末尾的 `provide(...)` 也是顶层语句,会假阳性);
-    `watch([...refs])` 整条都算立即求值;「可搬集合 ↔ wiring 位置」要迭代到收敛
-  - 算「页面还要解构哪些名字」时,扫描范围 = **整份模板 minus 被抽走的块**(别漏掉保留在页面里的部分,比如面板里的匹配条/外层 wrapper)
-  - 依赖若是页面里 `const { x } = someComposable;` 解构出来的,类型要写成 `ReturnType<typeof someComposable>['x']`(否则退化成 any → `Object.entries(any)` → unknown);
-    依赖类型文本里出现的类型名(`Ref<TemplateTab>` 里的 `TemplateTab`)也要补 import
-
-- **验证「重构没改业务逻辑」的可靠做法**:拿 `git show HEAD:<原文件>` 当基准,和「当前入口 + 抽出目录整棵树」做
-  **逐行多集合比对**(归一化:去 `export ` 前缀 / 折叠空白 / 去行尾 `,;`,跳过注释与 import),
-  判定条件 = 原始每行在当前树里的出现次数 ≥ 原始次数。本项目已验证 **script 丢失/被改动 = 0**。
-  注意:扫描范围只圈自己那一棵(别把同目录其它页面算进来)、SFC 的 template 结束点是 `<script` 不是 `<style`、
-  取原始文件用 node `execSync(...,{encoding:'buffer'})`(PowerShell `>` 会写成 UTF-16,且 `cmd /c` 被拦)。
-
-## 用户偏好
-
-- **直接干活,不要反复审计**。
-- **业务逻辑不许动**:重构只做搬家/抽取,逐行原样搬运。
-- **每个阶段停下来给 review**,确认后才进下一阶段。
-- 大文件重构:先给方案文档(会被认真审),方案里要列清决策点。
-- **别生成一堆垃圾文档**,编译/构建通过就是完成标准。
-- 不要留临时脚本、临时日志在仓库里,做完自己清理。