fix(agent): 保留持久化Run完整内容

This commit is contained in:
2026-09-01 10:05:58 +08:00
parent a5b709a46f
commit 0e8d4b7b9f
13 changed files with 123 additions and 44 deletions
+1 -1
View File
@@ -118,7 +118,7 @@ cd frontend
pnpm test pnpm test
``` ```
当前回归基线为后端 80 项测试、前端 27 项测试,且生产构建通过。测试数量会随功能增长,以本地实际输出和 CI 为准。 当前回归基线为后端 81 项测试、前端 27 项测试,且生产构建通过。测试数量会随功能增长,以本地实际输出和 CI 为准。
构建产物位于 `frontend/dist`,该目录不提交到 Git。 构建产物位于 `frontend/dist`,该目录不提交到 Git。
+1 -1
View File
@@ -23,7 +23,7 @@ uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 8000
uv run pytest uv run pytest
``` ```
当前基线为 80 项测试通过。Provider API Key 可通过前端设置页写入,也可用 `OPENAI_API_KEY``DEEPSEEK_API_KEY``AINOTE_CREDENTIAL_<ID>` 注入;不要把真实密钥写入仓库。 当前基线为 81 项测试通过。Provider API Key 可通过前端设置页写入,也可用 `OPENAI_API_KEY``DEEPSEEK_API_KEY``AINOTE_CREDENTIAL_<ID>` 注入;不要把真实密钥写入仓库。
团队接口清单见 `../docs/contracts/后端接口契约-开发版.md`,机器可读契约以运行时的 `/openapi.json` 为准。 团队接口清单见 `../docs/contracts/后端接口契约-开发版.md`,机器可读契约以运行时的 `/openapi.json` 为准。
+24 -11
View File
@@ -46,15 +46,17 @@ _BEARER_PATTERN = re.compile(r"(?i)\bBearer\s+[^\s,;]+")
_API_KEY_PATTERN = re.compile(r"\bsk-[A-Za-z0-9_-]{8,}\b") _API_KEY_PATTERN = re.compile(r"\bsk-[A-Za-z0-9_-]{8,}\b")
def sanitize_trace_value(value: Any, *, depth: int = 0) -> Any: def sanitize_trace_value(
"""递归净化 Trace 数据;键名疑似 Secret 时不保留原值。""" value: Any, *, depth: int = 0, apply_limits: bool = True
) -> Any:
"""递归净化持久化数据;可按审计用途限制体积,Secret 始终脱敏。"""
if depth >= MAX_TRACE_DEPTH: if apply_limits and depth >= MAX_TRACE_DEPTH:
return "[MAX_DEPTH]" return "[MAX_DEPTH]"
if isinstance(value, dict): if isinstance(value, dict):
sanitized: dict[str, Any] = {} sanitized: dict[str, Any] = {}
for index, (key, item) in enumerate(value.items()): for index, (key, item) in enumerate(value.items()):
if index >= MAX_TRACE_COLLECTION: if apply_limits and index >= MAX_TRACE_COLLECTION:
sanitized["__truncated__"] = True sanitized["__truncated__"] = True
break break
normalized = str(key).casefold().replace("-", "_") normalized = str(key).casefold().replace("-", "_")
@@ -62,26 +64,33 @@ def sanitize_trace_value(value: Any, *, depth: int = 0) -> Any:
"[REDACTED]" "[REDACTED]"
if normalized in _SECRET_KEYS if normalized in _SECRET_KEYS
or normalized.endswith(_SECRET_KEY_SUFFIXES) or normalized.endswith(_SECRET_KEY_SUFFIXES)
else sanitize_trace_value(item, depth=depth + 1) else sanitize_trace_value(
item, depth=depth + 1, apply_limits=apply_limits
)
) )
return sanitized return sanitized
if isinstance(value, (list, tuple)): if isinstance(value, (list, tuple)):
source_items = value[:MAX_TRACE_COLLECTION] if apply_limits else value
items = [ items = [
sanitize_trace_value(item, depth=depth + 1) sanitize_trace_value(
for item in value[:MAX_TRACE_COLLECTION] item, depth=depth + 1, apply_limits=apply_limits
)
for item in source_items
] ]
if len(value) > MAX_TRACE_COLLECTION: if apply_limits and len(value) > MAX_TRACE_COLLECTION:
items.append("[TRUNCATED]") items.append("[TRUNCATED]")
return items return items
if isinstance(value, str): if isinstance(value, str):
value = _BEARER_PATTERN.sub("Bearer [REDACTED]", value) value = _BEARER_PATTERN.sub("Bearer [REDACTED]", value)
value = _API_KEY_PATTERN.sub("[REDACTED]", value) value = _API_KEY_PATTERN.sub("[REDACTED]", value)
if len(value) > MAX_TRACE_STRING: if apply_limits and len(value) > MAX_TRACE_STRING:
return f"{value[:MAX_TRACE_STRING]}...[TRUNCATED]" return f"{value[:MAX_TRACE_STRING]}...[TRUNCATED]"
return value return value
if value is None or isinstance(value, (str, int, float, bool)): if value is None or isinstance(value, (str, int, float, bool)):
return value return value
return sanitize_trace_value(str(value), depth=depth + 1) return sanitize_trace_value(
str(value), depth=depth + 1, apply_limits=apply_limits
)
class AgentTraceRepository: class AgentTraceRepository:
@@ -354,6 +363,10 @@ class AgentTraceRepository:
@staticmethod @staticmethod
def _serialize_run(run: AgentRun) -> str: def _serialize_run(run: AgentRun) -> str:
# Run 是重启后 GET/list 的完整事实;只做 Secret 脱敏,不套用 Trace 摘要限长。
return json.dumps( return json.dumps(
sanitize_trace_value(run.model_dump(mode="json")), ensure_ascii=False sanitize_trace_value(
run.model_dump(mode="json"), apply_limits=False
),
ensure_ascii=False,
) )
+32
View File
@@ -242,6 +242,38 @@ def test_trace_redacts_secrets_and_truncates_large_values() -> None:
run(scenario()) run(scenario())
def test_persisted_agent_run_preserves_long_input_and_output() -> None:
"""审计事件可以限长,但重启后读取的 AgentRun 不能丢失正文。"""
now = datetime.now(timezone.utc)
long_input = "输入" * 2_500
long_output = "输出" * 2_500
request = AgentRunCreateRequest(
input=long_input,
provider_id="mock",
model="mock-1",
)
persisted = AgentRun(
run_id="run_long_content",
status=AgentRunStatus.completed,
input=long_input,
output=long_output,
provider_id=request.provider_id,
model=request.model,
max_steps=request.max_steps,
created_at=now,
updated_at=now,
)
repository = AgentTraceRepository()
repository.create_run(persisted, request, {"model": request.model})
restored = repository.get_run(persisted.run_id)
assert restored is not None
assert restored.input == long_input
assert restored.output == long_output
async def _collect_events(iterator): async def _collect_events(iterator):
return [event async for event in iterator] return [event async for event in iterator]
@@ -2321,7 +2321,7 @@ Markdown Workspace
第一阶段 Plugin Runtime 已完成安装、启用、停用、权限和声明式 Tool 注册,建立 Skill 调用 Plugin Tool 的基础链路。Command、Settings 和 MCP 执行不计入第一阶段完成项。 第一阶段 Plugin Runtime 已完成安装、启用、停用、权限和声明式 Tool 注册,建立 Skill 调用 Plugin Tool 的基础链路。Command、Settings 和 MCP 执行不计入第一阶段完成项。
截至 2026-09-01,上述第一阶段后端链路和 Web 联调前端均已完成,第二阶段前置的 Workspace 去 Mock 联调及 Agent Trace 持久化/恢复接口也已完成。当前验证基线为后端 80 项测试、前端 27 项测试及生产构建通过。向量链路当前使用 `HashEmbeddingProvider` 验证工程正确性,真实 Embedding 召回质量不属于该测试结论。 截至 2026-09-01,上述第一阶段后端链路和 Web 联调前端均已完成,第二阶段前置的 Workspace 去 Mock 联调及 Agent Trace 持久化/恢复接口也已完成。当前验证基线为后端 81 项测试、前端 27 项测试及生产构建通过。向量链路当前使用 `HashEmbeddingProvider` 验证工程正确性,真实 Embedding 召回质量不属于该测试结论。
第二阶段在既有 Contract 上接入: 第二阶段在既有 Contract 上接入:
@@ -174,7 +174,7 @@ RunCancelled
## 当前实现状态 ## 当前实现状态
更新至 2026-09-01:后端 80 项回归测试通过。 更新至 2026-09-01:后端 81 项回归测试通过。
- Chat、Agent Run、Agent Events、Tool 列表、Provider 配置生命周期、模型列表和连接测试已经接入 AI Core。 - Chat、Agent Run、Agent Events、Tool 列表、Provider 配置生命周期、模型列表和连接测试已经接入 AI Core。
- Agent Run/Event 已持久化到 SQLiteSSE 帧携带 sequence `id`,断线后可以回放缺失事件。Trace API 与 Benchmark 共用同一事件事实,并在入库前执行 Secret 脱敏和结果限长。 - Agent Run/Event 已持久化到 SQLiteSSE 帧携带 sequence `id`,断线后可以回放缺失事件。Trace API 与 Benchmark 共用同一事件事实,并在入库前执行 Secret 脱敏和结果限长。
@@ -2,7 +2,7 @@
> 本文档用于团队开发和模块联调,记录当前已经落地的核心边界与使用方式。 > 本文档用于团队开发和模块联调,记录当前已经落地的核心边界与使用方式。
> 更新日期:2026-09-01。第一阶段 AI Core、Agent Core、Extension Core 和 Model Core 主链路已经完成;第二阶段 Agent Trace 持久化和可恢复 SSE 已落地,后端当前回归基线为 80 项测试通过。 > 更新日期:2026-09-01。第一阶段 AI Core、Agent Core、Extension Core 和 Model Core 主链路已经完成;第二阶段 Agent Trace 持久化和可恢复 SSE 已落地,后端当前回归基线为 81 项测试通过。
## 当前实现 ## 当前实现
@@ -178,7 +178,7 @@ POST /api/agent/runs/{run_id}/cancel
Run 与 AgentEvent 已写入 SQLite`run_id + sequence` 是幂等键。SSE 每帧包含 `id: sequence`;客户端可以通过 `Last-Event-ID` 请求头或 `after_sequence` 查询参数恢复缺失事件。Trace API 返回平铺事件、下一游标、分页状态、模型/工具调用统计、耗时、Token Usage 和创建 Run 时的配置快照,不负责生成前端树形布局。 Run 与 AgentEvent 已写入 SQLite`run_id + sequence` 是幂等键。SSE 每帧包含 `id: sequence`;客户端可以通过 `Last-Event-ID` 请求头或 `after_sequence` 查询参数恢复缺失事件。Trace API 返回平铺事件、下一游标、分页状态、模型/工具调用统计、耗时、Token Usage 和创建 Run 时的配置快照,不负责生成前端树形布局。
运行时内存仍只保留最近 2000 个事件用于实时订阅,完整 Trace 以 SQLite 为准。AI Core 重启后,已经终止的 Run 可以继续查询和回放;重启前未终止的 Run 会收束为 `AGENT_PROCESS_RESTARTED`,避免永久停在 `running`。API Key、Authorization、Password、Secret、常见 `sk-`/Bearer 值在入库前脱敏超长字符串集合会截断。 运行时内存仍只保留最近 2000 个事件用于实时订阅,完整 Trace 以 SQLite 为准。AI Core 重启后,已经终止的 Run 可以继续查询和回放;重启前未终止的 Run 会收束为 `AGENT_PROCESS_RESTARTED`,避免永久停在 `running`。API Key、Authorization、Password、Secret、常见 `sk-`/Bearer 值在入库前脱敏。Event、Request 和配置快照中的超长字符串集合会截断;作为查询事实来源的 `AgentRun` 只脱敏、不限长,保证重启前后 input/output 内容一致
## Tool Calling ## Tool Calling
@@ -3,7 +3,7 @@
> 本文档用于团队开发和模块联调,记录 Knowledge Core / Retrieval Core 已经落地的 > 本文档用于团队开发和模块联调,记录 Knowledge Core / Retrieval Core 已经落地的
> 模块边界、数据模型、接口与使用方式,对应分工表中的杨星萱。 > 模块边界、数据模型、接口与使用方式,对应分工表中的杨星萱。
> 更新日期:2026-09-01。第一阶段 Knowledge/Retrieval 主链路已经完成,并已接入 Agent Tool Registry;完整后端回归基线为 80 项测试通过。 > 更新日期:2026-09-01。第一阶段 Knowledge/Retrieval 主链路已经完成,并已接入 Agent Tool Registry;完整后端回归基线为 81 项测试通过。
## 当前实现 ## 当前实现
@@ -187,12 +187,12 @@ pnpm build
```text ```text
pnpm build passed pnpm build passed
pnpm test 27 passed pnpm test 27 passed
uv run pytest 80 passed uv run pytest 81 passed
preview smoke HTTP 200 preview smoke HTTP 200
git diff --check passed git diff --check passed
``` ```
当前前端使用 Vitest 执行 Store、Workspace API Adapter、SSE 恢复游标、文件树、编辑器组件、智能体标签、轻量动效约束、Markdown 对比度 Token、scoped CSS 选择器约束和 Shiki GitHub 双主题测试;`pnpm build` 同时执行 `vue-tsc -b` 与 Vite 生产构建。后端测试出现过 `.pytest_cache` 无法写入的 Windows 权限警告,不影响 80 项测试结果,也不涉及产品代码。 当前前端使用 Vitest 执行 Store、Workspace API Adapter、SSE 恢复游标、文件树、编辑器组件、智能体标签、轻量动效约束、Markdown 对比度 Token、scoped CSS 选择器约束和 Shiki GitHub 双主题测试;`pnpm build` 同时执行 `vue-tsc -b` 与 Vite 生产构建。后端测试出现过 `.pytest_cache` 无法写入的 Windows 权限警告,不影响 81 项测试结果,也不涉及产品代码。
Vite 当前会提示 Chat 与 Workspace 的部分异步 Chunk 超过 500 kB,这是 Milkdown、CodeMirror、KaTeX 和 Shiki 等编辑/渲染依赖带来的性能优化项,不影响构建成功或功能正确性;进入桌面打包前应通过手动分包或更细粒度动态加载继续优化。 Vite 当前会提示 Chat 与 Workspace 的部分异步 Chunk 超过 500 kB,这是 Milkdown、CodeMirror、KaTeX 和 Shiki 等编辑/渲染依赖带来的性能优化项,不影响构建成功或功能正确性;进入桌面打包前应通过手动分包或更细粒度动态加载继续优化。
@@ -104,4 +104,4 @@ pnpm build
自动化验证覆盖 Provider 预设、OpenAI-Compatible `/models` 请求与鉴权头、模型映射、前端自动刷新、排序去重及按 Provider 隔离错误。生产构建同时执行 Vue 和 TypeScript 类型检查。 自动化验证覆盖 Provider 预设、OpenAI-Compatible `/models` 请求与鉴权头、模型映射、前端自动刷新、排序去重及按 Provider 隔离错误。生产构建同时执行 Vue 和 TypeScript 类型检查。
当前完整回归基线:后端 80 项测试、前端 27 项测试通过,前端类型检查和生产构建通过。Provider 配置目前仍保存在内存 Registry,AI Core 重启后需要重新创建;凭据密文会保留。OpenAI Responses 与 Anthropic Messages Adapter 尚未实现,设置页正式预设不会使用这两种协议。 当前完整回归基线:后端 81 项测试、前端 27 项测试通过,前端类型检查和生产构建通过。Provider 配置目前仍保存在内存 Registry,AI Core 重启后需要重新创建;凭据密文会保留。OpenAI Responses 与 Anthropic Messages Adapter 尚未实现,设置页正式预设不会使用这两种协议。
@@ -68,7 +68,7 @@ uv run pytest -q -p no:cacheprovider
当前基线: 当前基线:
```text ```text
80 passed 81 passed
``` ```
通过标准:退出码为 0、失败数为 0。用例数可以随功能增加,但不得低于当前基线。 通过标准:退出码为 0、失败数为 0。用例数可以随功能增加,但不得低于当前基线。
@@ -9,7 +9,7 @@
第一阶段 Agent Runtime 已经能够完成模型调用、Tool Calling、权限确认、取消、Usage 和 Citation,但 Run 与 Event 仍以进程内字典和列表为事实来源。第一阶段审阅加入的 Run/Event 数量上限解决了内存无界增长,却没有解决重启丢失、断线续传、Benchmark 复用和敏感数据审计等第二阶段问题。 第一阶段 Agent Runtime 已经能够完成模型调用、Tool Calling、权限确认、取消、Usage 和 Citation,但 Run 与 Event 仍以进程内字典和列表为事实来源。第一阶段审阅加入的 Run/Event 数量上限解决了内存无界增长,却没有解决重启丢失、断线续传、Benchmark 复用和敏感数据审计等第二阶段问题。
本轮处理了 8 类问题: 本轮处理了 9 类问题:
| 编号 | 问题 | 级别 | 处理结果 | | 编号 | 问题 | 级别 | 处理结果 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
@@ -18,11 +18,12 @@
| A-03 | SSE 断线后无法从指定事件恢复 | P1 | 支持 `Last-Event-ID``after_sequence` 和 SSE `id` | | A-03 | SSE 断线后无法从指定事件恢复 | P1 | 支持 `Last-Event-ID``after_sequence` 和 SSE `id` |
| A-04 | 缺少可分页 Trace 与 Benchmark 配置快照 | P1 | 增加 Trace API、统计摘要与配置快照 | | A-04 | 缺少可分页 Trace 与 Benchmark 配置快照 | P1 | 增加 Trace API、统计摘要与配置快照 |
| A-05 | Trace 缺少模型调用、父子关系和权限结果 | P1 | 增加第二阶段事件及耗时/parent 字段 | | A-05 | Trace 缺少模型调用、父子关系和权限结果 | P1 | 增加第二阶段事件及耗时/parent 字段 |
| A-06 | Trace 可能保存 Secret 和超大 Tool Result | P0 | Run、Request、Config、Event 统一脱敏与截断 | | A-06 | Trace 可能保存 Secret 和超大 Tool Result | P0 | 全部持久化副本统一脱敏,审计摘要限长 |
| A-07 | 进程重启后未终止 Run 永久显示运行中 | P1 | 自动收束为 `AGENT_PROCESS_RESTARTED` | | A-07 | 进程重启后未终止 Run 永久显示运行中 | P1 | 自动收束为 `AGENT_PROCESS_RESTARTED` |
| A-08 | 前端 DTO 与 SSE Client 无法消费恢复协议 | P1 | 同步 TypeScript Contract、Service、标签和 SSE id | | A-08 | 前端 DTO 与 SSE Client 无法消费恢复协议 | P1 | 同步 TypeScript Contract、Service、标签和 SSE id |
| A-09 | AgentRun 与审计摘要共用限长规则 | P1 | Run 只脱敏不限长,保证重启前后内容一致 |
修复后的验证基线为:后端 80 项测试、前端 27 项测试、TypeScript 类型检查和生产构建通过。 修复后的验证基线为:后端 81 项测试、前端 27 项测试、TypeScript 类型检查和生产构建通过。
## 2. A-01Agent Run 与 Event 只存在于进程内存 ## 2. A-01Agent Run 与 Event 只存在于进程内存
@@ -267,7 +268,7 @@ Tool 参数和输出来自模型、插件或外部服务,属于不可信数据
### 解决思路 ### 解决思路
所有进入持久化边界的数据统一经过同一个净化函数,不能分别在 Router、Runtime 和 Repository 中维护不同规则。净化必须同时覆盖键名、常见密钥值模式、递归深度、字符串长度和集合大小 所有进入持久化边界的数据统一经过同一个净化函数,不能分别在 Router、Runtime 和 Repository 中维护不同脱敏规则。Secret 脱敏适用于全部副本;体积限制只适用于 Trace Event、Request 和 Config 等审计数据,不能改变对外查询所依赖的 AgentRun 事实
### 解决方案 ### 解决方案
@@ -280,17 +281,20 @@ Config Snapshot
AgentEvent Data AgentEvent Data
``` ```
净化规则: 通用脱敏规则:
- `api_key`、Authorization、Access/Refresh Token、Password、Secret 等键替换为 `[REDACTED]` - `api_key`、Authorization、Access/Refresh Token、Password、Secret 等键替换为 `[REDACTED]`
- 常见 `sk-...``Bearer ...` 字符串模式直接替换; - 常见 `sk-...``Bearer ...` 字符串模式直接替换;
- `credential_id` 等非明文引用保留,不误判为 Secret。
Event、Request 和 Config 审计副本额外执行限长:
- 单字符串最多保留 4096 个字符; - 单字符串最多保留 4096 个字符;
- 单集合最多保留 100 项; - 单集合最多保留 100 项;
- 递归深度最多 8 层; - 递归深度最多 8 层;
- 超限位置使用明确的 `[TRUNCATED]``[MAX_DEPTH]` 标记 - 超限位置使用明确的 `[TRUNCATED]``[MAX_DEPTH]` 标记
- `credential_id` 等非明文引用保留,不误判为 Secret。
回归测试直接读取 `agent_runs` 原始 SQLite 字段,确认测试密钥没有落盘,避免只验证 API 响应造成假安全。 `AgentRun` Snapshot 仍经过同一套 Secret 脱敏,但不执行长度、集合和深度截断。回归测试直接读取 `agent_runs` 原始 SQLite 字段,确认测试密钥没有落盘,避免只验证 API 响应造成假安全。
## 8. A-07:重启后未终止 Run 永久显示运行中 ## 8. A-07:重启后未终止 Run 永久显示运行中
@@ -352,7 +356,35 @@ Agent 的异步 Task 和 Permission Future 不能跨进程恢复。持久化 Run
Trace 时间线、树形布局、筛选、节点展开和 Citation 跳转仍由前端负责人实现。 Trace 时间线、树形布局、筛选、节点展开和 Citation 跳转仍由前端负责人实现。
## 10. 事务、顺序与恢复不变量 ## 10. A-09AgentRun 与审计摘要共用限长规则
### 原因
初版使用 `sanitize_trace_value()` 同时处理 `run_json`、Request、Config 和 Event。该函数不仅脱敏,还会截断超过 4096 字符的字符串、超过 100 项的集合和超过 8 层的结构。`run_json` 随后又被 `GET /agent/runs/{run_id}` 和 Run 列表当作重启后的事实来源,因此审计数据的防膨胀规则意外改变了业务响应。
### 后果
- 常见的长模型回答在 AI Core 重启后只剩前 4096 个字符和截断标记;
- 长输入、Tool Result 和嵌套结果也可能丢失;
- 同一个 Run 在进程内与重启后的接口响应不一致;
- 前端刷新、Benchmark 复核和问题追踪无法取得原始运行结果;
- 原有测试只验证了 Trace 参数截断,没有比较重启前后的长正文。
审阅时使用 5000 字符 output 复现:写入前长度为 5000,重新读取后长度变为 4110,并以 `...[TRUNCATED]` 结尾。
### 解决思路
持久化边界包含两类数据:AgentRun 是业务事实,Trace Event、Request 和 Config 是可视化与审计摘要。两类数据必须共享 Secret 脱敏规则,但不能共享有损的体积限制。
### 解决方案
- `sanitize_trace_value()` 增加明确的 `apply_limits` 策略参数;
- 默认继续限长,保持 Event、Request 和 Config 的安全边界;
- `_serialize_run()` 使用 `apply_limits=False`,完整保留 input、output、Tool Result 和 Citation
- Secret 键名及 `sk-`、Bearer 模式在两种策略下始终脱敏;
- 增加超过 4096 字符的 input/output 持久化回归测试,直接从新 Repository 读取并逐字比较。
## 11. 事务、顺序与恢复不变量
本轮修复明确了以下不变量: 本轮修复明确了以下不变量:
@@ -364,8 +396,9 @@ Trace 时间线、树形布局、筛选、节点展开和 Citation 跳转仍由
6. 重启后不能安全继续执行的 Run 必须明确失败,不能永久悬挂。 6. 重启后不能安全继续执行的 Run 必须明确失败,不能永久悬挂。
7. 所有持久化 Trace 数据先脱敏、再写入。 7. 所有持久化 Trace 数据先脱敏、再写入。
8. 前端按 `run_id + sequence` 去重,不能依赖一次网络读取对应一条 SSE Event。 8. 前端按 `run_id + sequence` 去重,不能依赖一次网络读取对应一条 SSE Event。
9. AgentRun 的持久化副本不执行审计摘要限长,重启前后业务字段必须一致。
## 11. 验证方法 ## 12. 验证方法
后端: 后端:
@@ -393,7 +426,7 @@ git diff --check
验证结果: 验证结果:
```text ```text
backend pytest 80 passed backend pytest 81 passed
backend compileall passed backend compileall passed
frontend vitest 11 files / 27 tests passed frontend vitest 11 files / 27 tests passed
frontend type-check passed frontend type-check passed
@@ -409,22 +442,23 @@ git diff --check passed
- Model Call、Permission Resolved 和 Tool parent ID - Model Call、Permission Resolved 和 Tool parent ID
- 进程中断 Run 自动生成唯一终止事件; - 进程中断 Run 自动生成唯一终止事件;
- API Key、Authorization 和超长 Tool 参数净化; - API Key、Authorization 和超长 Tool 参数净化;
- 超长 AgentRun input/output 在持久化和重启读取后保持完整;
- 直接检查 SQLite,确认 Secret 未进入 Run/Request/Config Snapshot - 直接检查 SQLite,确认 Secret 未进入 Run/Request/Config Snapshot
- OpenAPI 发布 Trace 路径; - OpenAPI 发布 Trace 路径;
- 前端 SSE Client 发送和解析恢复游标。 - 前端 SSE Client 发送和解析恢复游标。
测试仍会出现本机 `.pytest_cache` 无写入权限警告,不影响 80 项用例结果,也不涉及产品代码。 测试仍会出现本机 `.pytest_cache` 无写入权限警告,不影响 81 项用例结果,也不涉及产品代码。
## 12. 当前边界与后续工作 ## 13. 当前边界与后续工作
### 12.1 本阶段明确不做 ### 13.1 本阶段明确不做
- 不在后端生成前端 Trace 树形布局; - 不在后端生成前端 Trace 树形布局;
- 不在进程重启后自动重放未完成 Tool 副作用; - 不在进程重启后自动重放未完成 Tool 副作用;
- 不把 Secret 明文放入 Trace、日志或 Benchmark - 不把 Secret 明文放入 Trace、日志或 Benchmark
- 不为 Benchmark 建立绕过 Agent Runtime 的专用执行协议。 - 不为 Benchmark 建立绕过 Agent Runtime 的专用执行协议。
### 12.2 后续需要继续处理 ### 13.2 后续需要继续处理
- 增加 Trace 保留、归档和被 Benchmark 引用时的保护策略; - 增加 Trace 保留、归档和被 Benchmark 引用时的保护策略;
- 引入保留窗口后实现 `TRACE_CURSOR_EXPIRED` - 引入保留窗口后实现 `TRACE_CURSOR_EXPIRED`
@@ -434,24 +468,24 @@ git diff --check passed
- 前端完成 Trace Timeline/Tree、筛选、节点详情和 Citation 跳转; - 前端完成 Trace Timeline/Tree、筛选、节点详情和 Citation 跳转;
- 多进程或远程执行出现需求后,再设计带租约和幂等副作用的执行恢复。 - 多进程或远程执行出现需求后,再设计带租约和幂等副作用的执行恢复。
## 13. 可复用经验 ## 14. 可复用经验
### 13.1 资源上限不等于持久化 ### 14.1 资源上限不等于持久化
限制内存 Run 和 Event 数量只能防止进程膨胀,不能解决重启、审计和报告复现。临时保护措施应在文档中明确标注,不能被误认为最终架构已经完成。 限制内存 Run 和 Event 数量只能防止进程膨胀,不能解决重启、审计和报告复现。临时保护措施应在文档中明确标注,不能被误认为最终架构已经完成。
### 13.2 游标必须独立于缓存结构 ### 14.2 游标必须独立于缓存结构
只要 sequence 来源于 `len(list)`、数组下标或当前页位置,裁剪和分页就可能破坏唯一性。可恢复事件流必须使用独立、单调且可持久化的逻辑序号。 只要 sequence 来源于 `len(list)`、数组下标或当前页位置,裁剪和分页就可能破坏唯一性。可恢复事件流必须使用独立、单调且可持久化的逻辑序号。
### 13.3 恢复读取不等于恢复执行 ### 14.3 恢复读取不等于恢复执行
恢复 Run/Trace 查询相对安全;恢复一个包含 Tool 副作用的执行任务需要额外的幂等、租约和补偿机制。在没有这些机制时,明确失败比重复执行更可靠。 恢复 Run/Trace 查询相对安全;恢复一个包含 Tool 副作用的执行任务需要额外的幂等、租约和补偿机制。在没有这些机制时,明确失败比重复执行更可靠。
### 13.4 脱敏要覆盖全部持久化副本 ### 14.4 脱敏要覆盖全部持久化副本
同一敏感值可能同时出现在 Event、Run Snapshot、Request、Config、日志和报告中。只检查最终 API 响应无法证明数据没有落盘,安全测试应直接验证持久化介质。 同一敏感值可能同时出现在 Event、Run Snapshot、Request、Config、日志和报告中。只检查最终 API 响应无法证明数据没有落盘,安全测试应直接验证持久化介质。
### 13.5 生产者和消费者 Contract 必须同时更新 ### 14.5 生产者和消费者 Contract 必须同时更新
后端新增事件类型、字段或 SSE 规则时,至少同步 Pydantic、OpenAPI、TypeScript DTO、Service 和协议测试。可视化页面可以由另一成员开发,但不能让对方从后端实现反推 Contract。 后端新增事件类型、字段或 SSE 规则时,至少同步 Pydantic、OpenAPI、TypeScript DTO、Service 和协议测试。可视化页面可以由另一成员开发,但不能让对方从后端实现反推 Contract。
@@ -4,7 +4,7 @@
> 审阅范围:FastAPI、Knowledge / Retrieval Core、Agent Core、Extension Core、Provider Adapter、公共接口和后端开发文档。 > 审阅范围:FastAPI、Knowledge / Retrieval Core、Agent Core、Extension Core、Provider Adapter、公共接口和后端开发文档。
> 文档用途:记录问题形成原因、实际影响、修复判断和落地方案,供后续开发文档、比赛材料与技术博客使用。 > 文档用途:记录问题形成原因、实际影响、修复判断和落地方案,供后续开发文档、比赛材料与技术博客使用。
> 2026-09-01 状态补充:本文记录的缺陷均保持修复。此后又加入 Provider 预设、模型发现、DeepSeek/OpenAI 凭据解析、Fernet 加密存储和 Agent Trace 持久化,当前完整后端回归基线为 80 项测试通过。 > 2026-09-01 状态补充:本文记录的缺陷均保持修复。此后又加入 Provider 预设、模型发现、DeepSeek/OpenAI 凭据解析、Fernet 加密存储和 Agent Trace 持久化,当前完整后端回归基线为 81 项测试通过。
## 1. 审阅结论 ## 1. 审阅结论