# 大龙虾后端（zm-ai-server）开发进展

> 文档维护规则：每完成一个交付阶段都要回来打勾，并在 **变更纪要** 里留一行日期 + 关键变更。不要在此文件里重复设计文档内容。

## 0. 背景

| 项 | 说明 |
|---|---|
| 代号 | 大龙虾 / Big Lobster |
| 后端包 | `zm-ai-server` |
| 部署环境 | 国产 OS + 政务外网（默认无互联网） |
| 语言/框架 | Java 8 + zmeg_new（cyan / arachne / thunwind / nest） |
| 数据库 | MySQL（后续迁移国产库） |
| LLM 底座 | LangChain4j 1.x（首期自建 HTTP adapter 兼容 OpenAI / Ollama / vLLM） |
| 核心设计文档 | [`/big-lobster-doc/prd`](../../big-lobster-doc/prd/) |

## 1. 阶段进度总览

| Phase | 目标 | 状态 | 说明 |
|---|---|---|---|
| Phase 0 — 工程骨架 | 项目结构、pom、框架接入 | ✅ 完成 | `pom.xml` 已包含 cyan 系 system 依赖占位 + LangChain4j + Jackson + JUnit |
| Phase 1 — 核心运行时 | Thread / Run / Workspace / Artifact / PendingRequest 模型 + 主循环 + Context Assembler + 流式输出 | ✅ 完成 | `AgentRuntime` + `ContextAssembler` + 7 层策略 + SSE StreamEmitter |
| Phase 2 — 工具层 | 统一工具协议 + ToolRegistry + 6 层内置工具 + MCP 桥接 | ✅ 完成 | `ToolExecutorDispatcher` + 7 组内置工具 + `McpToolBridge`（骨架） |
| Phase 3 — Context 治理 | send-view 分层、预算硬闸、宽度治理、轻量 transcript | ✅ 完成（含 v2 优化：动态预算 + 双端截断 + LLM 摘要） | `ContextBudgetPolicy` / `ContextCompactionPolicy` / `ToolResultWidthPolicy` / `SummarizerService` (Bullet+Llm) / `ReadToolResultTool` |
| Phase 4 — Artifact / Pending 查询 | 数据模型 + REST 查询 + 用户响应后 resume | ✅ 完成 | `ThreadApi` + `PendingRequestApi`，用户响应即触发续接 Run |
| Phase 5 — 文档工坊 | 打开 OA 文档 → 工作副本 → 回写 | ✅ 完成（最小闭环） | `write_file(WORKING_COPY)` + `save_to_oa` 走 pending 确认 |
| Phase 6 — Skill / Memory | 系统 / 组织 skill、个人记忆、去重限速 | ✅ 完成 | `SkillService` + `MemoryService`（二元组检索） |
| Phase 7 — LLM 管理层 | ModelProfile + Router + Adapter + Fallback + 审计 | ✅ 完成 | `ModelRouter` 决策链 + `LlmRuntime` fallback + `ModelCallLog` |
| Phase 8 — 配额/限流/熔断 | 并发守卫、工具限速、模型熔断器 | ✅ 完成 | `ConcurrencyGuard` + `ToolRateLimiter` + `CircuitBreakerRegistry` |
| Phase 9 — 审计 | AuditLog + ModelCallLog 全链路 | ✅ 完成 | 所有 LLM 调用 / 工具调用 / OA 操作均入审计 |
| Phase 10 — Agent 质量与护栏 | Loop / Claim / Content / Sanitizer / Plan | ✅ 完成 | `AgentLoopDetector` + `ClaimConsistencyChecker` + `InternalInfoSanitizer` + `update_plan` 工具 + `plan_update` 事件 |
| Phase 11 — 多模态 | 用户图片上传 + LLM vision content + 历史回看 | ✅ 完成 | 复用 USER_UPLOAD 通道（不另建 `media` 表）；前端按 mimeType 分流 `attachmentMediaIds` vs `attachedResourceIds`；后端 `ContextAssembler.buildTranscriptView` 按 `ThreadMessage.attachmentsJson` 还原 base64 dataUrl；`OpenAI`/`Ollama` adapter 双协议序列化；`ModelRouter` 按 `requiresMultimodal` 筛选 `ModelProfile.multimodal=true` |
| Phase 12 — Evaluation / Feedback | 反馈 + 指标聚合 + 回归测试 | ⚠️ 最小闭环 | `UserFeedback` + DAO + API；A/B 与回归 runner 占位，后续迭代 |
| Phase 13 — 代码沙箱 + Skill 资产 (P0+P1) | Docker 沙箱 / code_exec / skill bundle / 官方 Claude skill | ✅ 完成 | 设计/实现详见 [`SANDBOX-SKILL-IMPL.md`](SANDBOX-SKILL-IMPL.md)；支持 docx/xlsx/pptx/pdf/web-artifacts 5 个系统 skill，离线打包 React 单文件 artifact |

## 2. 已完成交付清单

### 2.1 数据层（zmeg_new @Entity + @OQL DAO）

- `AI_THREAD` — ThreadRoom
- `AI_THREAD_MESSAGE` — 含 contentRef / toolCallsJson
- `AI_RUN` — 仅观测记录
- `AI_RUN_STREAM_EVENT` — Run 可重连观察流事件投影
- `AI_WORKSPACE` / `AI_WORKSPACE_RESOURCE`
- `AI_ARTIFACT`（content_ref + 路径规范）
- `AI_PENDING_REQUEST`
- `AI_PERSONAL_MEMORY`
- `AI_PLAN` / `AI_PLAN_ITEM`
- `AI_AUDIT_LOG` / `AI_MODEL_CALL_LOG`
- `AI_MODEL_PROFILE` / `AI_AGENT_PROFILE` / `AI_SKILL_DEFINITION`
- `AI_COMPACTION_EVENT` — LLM 历史摘要的幂等缓存索引 + 审计
- `AI_PROMPT_TEMPLATE` / `AI_MCP_SERVER_CONFIG` / `AI_QUOTA_POLICY` / `AI_TOOL_DEFINITION`
- `AI_USER_FEEDBACK`

全部使用 `@Entity` + `@Generatable` + `@ColumnDescription` + `@Index` 统一注解；DAO 走 `GeneralDao` + `@OQL` / `@OQLUpdate`。

对于可维护集合表（`AI_TOOL_DEFINITION` / `AI_SKILL_DEFINITION` / `AI_MODEL_PROFILE` / `AI_MCP_SERVER_CONFIG`），DAO 额外提供 `listAll(offset,limit)` / `countAll()` / `deleteByXxx(id)`，供 `/ai/api/admin/*` CRUD 使用。

### 2.2 Agent Runtime 核心

`AgentRuntime.run(RunRequest, StreamEmitter)` 实现 ReAct 主循环，完全按设计文档：

1. 并发许可（`ConcurrencyGuard`）
2. 创建 Run 观测记录
3. 把用户输入写入 `transcript`
4. 路由模型（`ModelRouter`）并发出 `run_started`
5. 循环：`ContextAssembler.assemble` → `LlmRuntime.chat`（带 fallback）→ `sanitizer` → `ContentFilter` → 流式推送 → `AgentLoopDetector` → `ToolExecutorDispatcher` → `ClaimConsistencyChecker`
6. 自然结束 / `max_turns` / `loop_detected` / `no_progress` / `pending` / `error` 六种退出
7. 写 Run.endedAt + `run.end` 审计 + `run_ended` 事件

### 2.3 Context Assembler

7 层按稳定到动态顺序：`System Skeleton → Dynamic Rule → Skill Index/Detail → Memory → Workspace → Plan → Transcript Send-View → Current Turn Input`。

- `ContextBudgetPolicy` 按比例分配 token 软上限
- `ToolResultWidthPolicy` 单条工具返回超限截断并追加 stub
- `ContextCompactionPolicy` 折叠旧 user+assistant 回合为摘要 system 消息
- 硬闸失败再跑一次「只保留最近 1 轮」的激进压缩

### 2.4 LLM 管理层

- `LobsterMessage` / `ToolCall` / `ToolSpec` 协议中立抽象
- `OpenAiCompatibleAdapter`（兼容 OpenAI / vLLM，HTTP 直连）
- `OllamaAdapter`（`/api/chat`，NDJSON 解析；检测到 tools 时降级非流式）
- `ModelRouter` 决策链：组织 → Agent → Skill → Task → 负载 → fallback
- `LlmRuntime` 执行 fallback 链，逐层落 `ModelCallLog` 审计

### 2.5 内置工具（6 层）

| Layer | 工具 |
|---|---|
| Workspace | `list_files`, `search_files`, `read_file`, `write_file`, `save_to_oa` |
| Memory | `memory_search`, `memory_write` |
| Skill | `list_skills`, `use_skill` |
| Interaction | `confirm_action`, `ask_user`（返回 `pending`） |
| OA | `oa_list_files`, `oa_read_file`, `oa_write_file`, `oa_get_file_metadata`, `oa_search_knowledge`, `oa_get_knowledge_detail` |
| Plan | `update_plan`（额外触发 `plan_update` 事件） |
| MCP | 动态工具，启动时从 `McpServerConfig` 发现 |

### 2.6 护栏与治理

- `AgentLoopDetector`：3 次相同工具调用 / 8 轮无进展 熔断
- `ClaimConsistencyChecker`：assistant 写完成性声明但无 ok 工具结果 → WARN
- `KeywordContentFilter`：组织敏感词库兜底
- `InternalInfoSanitizer`：脱敏内部工具名 / 绝对路径 / 内部 ID
- `CircuitBreakerRegistry`：模型 / 工具级熔断
- `ConcurrencyGuard`：用户 / thread 维度并发许可
- `ToolRateLimiter`：按风险等级分层的滑动窗口限速

### 2.7 REST + SSE

> **URL 唯一规则**：Arachne 框架里 url 是全局唯一的（与 HttpMethod 无关）。同一资源的"列表 / 新建 / 更新 / 删除"必须各写不同 url，不允许用"同 url + 不同 method"区分。

| URL | 方法 | 说明 |
|---|---|---|
| `/ai/api/threads/create` | POST | 创建 Thread |
| `/ai/api/threads/list` | GET | 列出我的 Thread |
| `/ai/api/threads/{id}` | GET | Thread 详情（含用户可见状态） |
| `/ai/api/threads/{id}/messages` | GET | 轻量 transcript |
| `/ai/api/threads/{id}/artifacts` | GET | 工件列表 |
| `/ai/api/threads/{id}/resources` | GET | workspace 资源列表 |
| `/ai/api/threads/{id}/pending-requests` | GET | 待处理请求 |
| `/ai/api/artifacts/{id}` | GET | 工件内容（按 offset/limit 分页） |
| `/ai/api/artifacts/{id}/update` | POST | 直写工件（文档工坊保存） |
| `/ai/api/runs` | POST | 同步触发 Run |
| `/ai/api/runs/stream` | POST | SSE 流式 Run |
| `/ai/api/runs/status?runId=...` | ALL | 查询 Run 状态（含 active/detached/stale 与 event seq） |
| `/ai/api/threads/{id}/runs/active` | ALL | 查询 thread 当前 running run，用于恢复观察 |
| `/ai/api/runs/{id}/cancel` | POST | 取消 Run |
| `/ai/api/pending-requests/{id}/resolve` | POST | 响应 pending → 触发续接 Run |
| `/ai/api/me/pending-requests` | GET | 当前用户所有 open pending |
| `/ai/api/memories/list` | GET | 列个人记忆 |
| `/ai/api/memories/write` | POST | 写入个人记忆 |
| `/ai/api/feedback` | POST | 用户反馈 |
| `/ai/api/health` | ALL | 健康检查（无鉴权） |
| `/ai/api/admin/tools/list` | GET | 工具治理列表 |
| `/ai/api/admin/tools/create` | POST | 新建工具 |
| `/ai/api/admin/tools/{name}` | GET | 工具详情 |
| `/ai/api/admin/tools/{name}/update` | POST | 更新工具 |
| `/ai/api/admin/tools/{name}/delete` | POST | 删除工具 |
| `/ai/api/admin/tools/{name}/enabled` | POST | 工具启停 |
| `/ai/api/admin/skills/list` | GET | Skill 列表 |
| `/ai/api/admin/skills/create` | POST | 新建 Skill |
| `/ai/api/admin/skills/{id}` | GET | Skill 详情 |
| `/ai/api/admin/skills/{id}/update` | POST | 更新 Skill |
| `/ai/api/admin/skills/{id}/delete` | POST | 删除 Skill |
| `/ai/api/admin/skills/{id}/enabled` | POST | Skill 启停 |
| `/ai/api/admin/models/list` | GET | 模型列表（apiKey 回显脱敏） |
| `/ai/api/admin/models/create` | POST | 新建模型 |
| `/ai/api/admin/models?id={id}` | GET | 模型详情（id 走 query，避免 modelId 含 `.` 撞路由） |
| `/ai/api/admin/models/update` | POST | 更新模型（body 携带 `id`） |
| `/ai/api/admin/models/delete` | POST | 删除模型（body 携带 `id`） |
| `/ai/api/admin/models/enabled` | POST | 模型启停（body 携带 `id`） |
| `/ai/api/admin/mcp-servers/list` | GET | MCP Server 列表 |
| `/ai/api/admin/mcp-servers/create` | POST | 新建 MCP Server |
| `/ai/api/admin/mcp-servers/{id}` | GET | MCP Server 详情 |
| `/ai/api/admin/mcp-servers/{id}/update` | POST | 更新 MCP Server |
| `/ai/api/admin/mcp-servers/{id}/delete` | POST | 删除 MCP Server |
| `/ai/api/admin/mcp-servers/{id}/enabled` | POST | MCP Server 启停 |
| `/ai/api/admin/calls/list` | GET | 模型调用审计列表（可按 runId / threadId 筛） |
| `/ai/api/admin/calls/{callId}` | GET | 单条调用元数据（token / 耗时 / 是否降级 / 是否带 trace） |
| `/ai/api/admin/calls/{callId}/trace` | GET | **完整 LLM I/O trace**（messages / tools / response / 错误堆栈） |

> `/ai/api/admin/*` 全部要求 `UserContext.hasRole(LobsterConfig.getAdminRoleName())`（默认角色名 `ai_admin`，可经 `lobster.xml` 覆盖）。

SSE 事件类型：`run_started / assistant_text / plan_update / tool_call / tool_result / pending_request / run_ended / system_hint / error`。

### 2.8 测试

位于 `test/com/gzzm/lobster`，共 16 个测试类 / 40+ 测试方法，覆盖：

- 基础工具：Token/Json/Id
- 存储：ContentStore
- 工具：Registry / Dispatcher / Result / Schema
- 护栏：Loop / Claim / Sanitizer
- 配额：并发 / 熔断器
- LLM 抽象：ToolCall / LobsterMessage
- Context：Budget / Compaction / Workspace Index
- Runtime：三条 RunLoop 主路径 + 循环熔断（Mockito 集成）

## 3. 当前未完成 / 后续迭代

| 项 | 原因 | 预计补全 |
|---|---|---|
| 多模态上传服务 + `media` 表 | 与主闭环解耦；首期以 `LobsterMessage.imageUrls` + OpenAI vision content 预留 | 下一迭代按 design-big-lobster-multimodal 2026-04-05 补 |
| LangChain4j 正式 MCP 集成 | MCP Server 端未部署，无法联调；目前 `McpToolBridge` 注册 `{ns}.ping` 占位 | 待 MCP Server 可用后接入 LangChain4j `McpClient` |
| A/B 与回归测试运行器 | Phase 12 非首期阻塞项 | 按 design-big-lobster-evaluation 待排期 |
| Prompt Cache 优化细节 | 已落地稳定 skeleton + run-scoped 装配骨架；cache_control 注入待接入真实模型网关后做 | 视上线的模型网关而定 |

## 4. 变更纪要

| 日期 | 变更 |
|---|---|
| 2026-04-30 | **code_exec 脚本传递收敛**：取消“inline code 一律自动落盘”的默认路径，改为短脚本直接 `code` 执行；`code` schema 标注 `maxLength=8000`，服务端以 8000 字符 / 12KB 双阈值拒绝长 inline，并明确提示 `write_file(artifactType=CODE_SCRIPT)` + `code_exec(code_ref=artifactId)`。`write_file.artifactType` 枚举补 `CODE_SCRIPT`，`sys_code-exec-guide` 同步改成“几十行以内 inline，长脚本走 code_ref”。 |
| 2026-04-20 | 按 18 份设计文档 + zmeg_new 框架完成从 0 到 1 的后端实现（Phase 0-10 全部 ✅；Phase 11-12 最小闭环就绪） |
| 2026-04-20 | 新增 `GET /ai/api/health` 探活接口（无鉴权） |
| 2026-04-20 | 规范化 `@Service` 注解写法：API 类上仅 `@Service`（不带 url），方法上写完整 url + 实际 `HttpMethod`；沿用既有写法的 `AgentRuntime` 等保留不动 |
| 2026-04-20 | 新增 `com.gzzm.lobster.config.LobsterConfig` 全局 tunables + `web/WEB-INF/config/lobster.xml` 初始化；字段涵盖 agent runtime / memory / tool 限流 / 列表分页 / admin 角色名 / apiKey 脱敏长度 |
| 2026-04-20 | 新增后台管理 CRUD：`AdminToolApi` / `AdminSkillApi` / `AdminModelApi` / `AdminMcpServerApi`（对应 `ToolDefinitionConfig` / `SkillDefinition` / `ModelProfile` / `McpServerConfig`）；DAO 增加 `listAll(offset,limit)` / `countAll()` / `deleteByXxx()`；统一由 `AdminGuard.requireAdmin()` 鉴权；`ModelProfile.apiKey` 回显脱敏，更新时携带脱敏串或空值不覆盖原值 |
| 2026-04-20 | **URL 全局唯一规则**：Arachne 框架不按 HttpMethod 区分路由。原来复用同 url 的端点全部拆 url 后缀：`POST /threads` → `/threads/create`；`GET /threads` → `/threads/list`；`GET/POST /memories` → `/memories/list` + `/memories/write`；Admin CRUD 统一改为 `/list` `/create` `/{id}/update` `/{id}/delete` `/{id}/enabled` 六段式，不再使用 PUT/DELETE method。前端 `lobsterApi.js` 与 `pages/lobster/README.md` 已同步 |
| 2026-04-20 | 新增 zmeg_new 框架 CRUD 类：`ToolDefinitionCrud` / `SkillDefinitionCrud` / `ModelProfileCrud`（分别继承 `BaseNormalCrud`），挂载在 `/ai/admin/tool`、`/ai/admin/skill`、`/ai/admin/model`；框架自动渲染后台列表/表单 UI，与 REST 风格 `/ai/api/admin/*` 并存互不冲突；`ModelProfileCrud` 用占位符保护 apiKey 不被误清空 |
| 2026-04-21 | **LLM 全量 I/O trace**：`LlmRuntime` 每次调用都把完整 messages（含 system skeleton / 动态规则 / skill 索引 / memory / workspace / transcript）+ 工具列表 + 响应 + 错误堆栈序列化成 JSON，经 `ContentStore` 落盘，ref 存入 `ModelCallLog.traceRef`。后台查询入口 `AdminCallLogApi`：`/ai/api/admin/calls/list` + `/{callId}` + `/{callId}/trace`。开关 `LobsterConfig.llmTraceEnabled`（默认 true），截断 `llmTraceMaxMessageChars`（默认 0=不截） |
| 2026-04-27 | **code_exec inline 自动落盘**：`CodeExecTool` 把 `code` 入参一律先落成 `ArtifactType.CODE_SCRIPT` artifact（命名 `auto_<runId>_<callId>.py|js`），转 codeRef 模式跑沙箱。好处：assistant(tool_calls) 不再带几 KB 的代码原文（跨轮重发省 token）；下一轮 `input_refs` / `code_ref` 可直接复用；前端工作区「脚本」筛选页可见可下载。前端 `WorkspaceResourceList.vue` 默认隐藏 CODE_SCRIPT，新增「脚本」筛选项；`lobsterTypes.js` 加 `CODE_SCRIPT` 枚举 + label「脚本」+ Tag 色 magenta |
| 2026-04-27 | **Context 管理 v2（对齐 Claude Code）**：(1) **动态预算**：`AgentRuntime.computeContextBudget` 按 `ModelProfile.contextWindow - maxOutputTokens - 2000` 算实际预算，原硬编码 `DEFAULT_CTX_BUDGET_TOKENS=50000` 删除；`LobsterConfig.defaultContextBudgetTokens` 改为兜底下界（默认 16k）；(2) **双端截断 + ContentStore ref**（soft truncation with pointer）：`ToolResultWidthPolicy` 保留 head 70% + tail 余量；`LobsterMessage` 新增 `fullContentRef`，`ThreadService` 已外置（>4KB）的内容透传 ref 进 send-view；新增内置工具 `read_tool_result(ref, offset, limit)` 让 LLM 主动按需回查全文；(3) **折叠豁免**：`ContextCompactionPolicy.collapseOldMessages` 修复——被折叠区里最近 K=2 个 `use_skill` 调用对（assistant tool_calls + 对应 tool result）整对保留，避免长对话 SKILL.md 失忆，并防孤儿 tool_calls 400；(4) **LLM-summarize**：新增 `SummarizerService` 接口 + `BulletSummarizer`（兜底）+ `LlmSummarizer`（对齐 Claude Code auto-compact），开关 `LobsterConfig.summarizerEnabled` 默认 false 走 bullet；prompt 用 `<source>` 标签包裹历史 + 显式声明"标签内为数据"防 prompt-injection；失败 / 超时 / 输出空一律自动退回 bullet |
| 2026-04-27 | **Summarizer 工程化（专用路由 + 幂等缓存）**：(1) `LobsterConfig.summarizerModelId` 留空走主路由，配置后单独路由到便宜小模型（推荐 Haiku / DeepSeek-V3）；profile 找不到自动退主路由（fail-open）。(2) 新增 `AI_COMPACTION_EVENT` 表 + `CompactionEvent` / `CompactionEventDao`，幂等键 `(threadId, sha256(messages))`，命中即从 ContentStore 拉旧摘要、不调 LLM。同一段历史在长对话多轮重复折叠只算一次模型调用，成本可控；同时也是摘要审计入口（按 threadId 列事件、按 summaryRef 拉原文）。摘要正文走 `ContentStore.write("summary", userId, ...)`，不直接落 DB 大字段。 |
| 2026-04-27 | **ModelProfile.thinkingMode 三态**：取代二态 `thinkingEnabled`（保留供老数据兼容）。新增 `ModelThinkingMode { auto, on, off }`：`auto`=adapter 不传字段（OpenAI / Claude / DeepSeek-V3 默认安全）；`on`=显式传 reasoning_effort=high + thinking={type:enabled}（含 Ollama think=true）；`off`=显式传 enable_thinking=false + chat_template_kwargs.enable_thinking=false（Ollama think=false）—— 解决 vLLM Qwen3 / DashScope Qwen 默认开思考被误伤导致延迟激增的问题。`ModelProfile.resolveThinkingMode()` 兜底：thinkingMode 非空走新字段、否则按老 thinkingEnabled=true 视作 `on`、其它 `auto`。前端 `AdminModelForm` 把「开启思考」Switch 改成三态 Select，Modal 加 `body-style maxHeight=70vh overflow=auto` 让长表单可滚动；`AdminModelList` 列表里以 `think:on` / `think:off` Tag 显示当前状态。 |
| 2026-04-27 | **多模态打通（Phase 11）**：(1) **后端**：`UploadApi` 新增 `/uploads/{rid}/inline` 内联端点（不强制 attachment）让前端 `<img>` 直接渲染；`ThreadService.appendMessage` 加 `attachmentsJson` 持久化 user 消息关联的 imageMediaIds；`ContextAssembler.buildTranscriptView` 看到 attachmentsJson 即从 ContentStore 读图片二进制 → base64 dataUrl → `LobsterMessage.userWithImages`，保证切 thread / 重启 run 历史图片仍可回看；`AgentRuntime.filterImageMediaIds` 按 mimeType 过滤、`ModelSelectionContext.setRequiresMultimodal(true)` 触发 `ModelRouter` 筛选 `ModelProfile.multimodal=true` 的模型，没有就报 `llm.route.no_multimodal`；`OllamaAdapter.toOllamaMessages` 补充 `images` 字段（base64 数组，剥 `data:` 前缀）兼容 LLaVA / Qwen-VL；`OpenAiCompatibleAdapter` 原本已有 `image_url` 序列化，本轮接通才真正被触发。(2) **前端**：`ChatComposer` 加「图片」按钮（accept=image/*），上传后按 `file.type.startsWith('image/')` 标记 `isImage`，UI 显示缩略图（替代文档 chip）；发送时按 isImage 分流到 `attachments` (vision) vs `attachedResourceIds` (prelude)；`MessageItem` 用 antd `Image` 组件渲染 user 消息附图，自带 preview 点击放大；`lobsterStore` 在 `rebuildMessagesFromTranscript` 解析 `attachmentsJson.imageMediaIds` 暴露给 MessageItem，`sendMessage` 乐观 push 时也挂 imageMediaIds，发送瞬间即可看到自己刚发的图。(3) **多入口 + 高保真压缩**：新增 `imageCompress.js`，长边 > 1568px 时等比缩放（对齐 Claude/GPT-4V 推荐分辨率），透明 PNG/WebP 保留 PNG 无损、其它走 JPEG q=0.92；`ChatComposer` 拖拽（dataTransfer.files 进 doUpload）+ 粘贴（textarea @paste 拦截 clipboardData.items 截图场景）+ 50MB 硬上限 / 8MB 软警示。 |
| 2026-04-24 | **Skill 激活机制对齐 Claude Code progressive disclosure**（详见 [`SANDBOX-SKILL-IMPL.md` §5.2](SANDBOX-SKILL-IMPL.md)）。老机制（use_skill 激活 → system prompt 注入 guidance）被废弃，改为"system 只放 id/name/description，guidance 通过 `use_skill` 工具调用以 tool result 形式拉回"。同步上线：①`ContextCompactionPolicy` 对 `use_skill` 的 tool result 豁免宽度截断，防长对话失忆；② 幂等：同 thread 重复 `use_skill(sameId)` 不回灌全文，只返回短提示；③ `SkillDefinition.triggerCondition` 扩到 `varchar(2000)`（对齐 Claude Code 1536 spec；线上需 `ALTER TABLE AI_SKILL_DEFINITION MODIFY COLUMN TRIGGERCONDITION VARCHAR(2000)`）；④ `AdminSkillApi.create/update` 新增 description 软校验，返回 `warnings[]`（空/太短/缺触发提示词/接近上限）；⑤ bundle 清单兜底：`use_skill` 返回时若 SKILL.md 未列出 bundle 文件，自动附 `## 可用资源` 段（≤50 条、单文件<10MB、深度 3）；⑥ 新增 `AI_SKILL_INVOCATION` 表 + `GET /ai/api/admin/skills/{id}/invocations` 后台 stats，看 `duplicateRatio` 判断 SKILL.md 可读性 |
