From b6b8167cf5f7f9c6a4d2be524e69614921808b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=85=83=E5=9E=9A?= <15135142681@163.com> Date: Mon, 3 Aug 2026 01:01:57 +0800 Subject: [PATCH 1/2] Clarify partial research recovery --- docs/PROJECT_STATUS.md | 15 +++++++++-- .../web-lookup/ChatResearchRecovery.test.tsx | 24 ++++++++++++++++++ .../web-lookup/ChatResearchRecovery.tsx | 18 +++++++++++-- .../web-lookup/webLookupController.test.tsx | 25 +++++++++++++++++++ .../web-lookup/webLookupControllerCore.ts | 2 +- .../features/wechat-workspace/WechatPanel.tsx | 5 +++- tools/real_stack_test_server.py | 4 +++ 7 files changed, 87 insertions(+), 6 deletions(-) diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index 0d8f154f..b797446f 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -1,10 +1,11 @@ # Study Agent 当前状态 > **唯一进度入口** -> 更新:2026-07-31 +> 更新:2026-08-03 > 产品定义:**Study Agent 是长期保持“正在学什么、已经确认什么、还不会什么、下一步是什么”的个人学习工作台。** > 当前主线:**仅检查和改进 Study Agent 本体的代码功能、核心学习流程与桌面/移动端体验。** > 冻结边界:**Provider replay 扩展、生产 claim producer / claim UI、生产 ChatTurn 接入、群聊能力扩张、新闻产品化与可执行 agent 均不是当前开发主线。** +> 当前切片:**P1-R1 联网研究阶段解释已实现;全量 pytest 与 detect-secrets 仍待在不受本地执行时限影响的终端完成,尚未合并。** 本文件只维护当前事实、可复核证据、缺口和执行顺序。不得新增并列长期 STATUS / ROADMAP / NEXT_PHASE / AUDIT 文档。 @@ -82,6 +83,16 @@ PR #93 修复 ResearchRun 两阶段取消在前端没有收口的问题: 1. 静态 owner 测试只读取旧控制器入口,迁移为稳定 re-export 后需要同时审查入口与实现文件; 2. 新增 real-stack 文件最初被普通 fixture Playwright 项目误收录,最终加入统一 `REAL_STACK_TESTS` 排除清单,由专用真实全栈配置运行。 +### 4.3 P1-R1 联网研究阶段解释(待最终全量门禁) + +当前活动分支 `codex/p1-r1-research-stage-explanations` 收口“工程状态不可理解”的一处核心语义: + +- `partial` ResearchRun 保留查询、来源与 checkpoint,但不再自动作为下一轮聊天资料;学习者可显式选用,或重试补全; +- 聊天恢复卡与群聊研究面板明确说明部分结果、已保留的查询/来源和重试语义; +- Windows 真实全栈重置在释放缓存连接后再删除 SQLite 文件,desktop 与 390×844 可连续执行; +- 已通过前端 222 项单元测试、TypeScript/Vite production build,以及 ResearchRun 取消—刷新—同 run 重试的 desktop 与 390×844 真实全栈旅程; +- 尚未完成本地全量 pytest 与 detect-secrets 的最终运行,因此不得将本切片记录为 CI 全绿、Ready 或已合并。 + ## 5. 当前质量门禁 CI #1731 完整通过: @@ -133,7 +144,7 @@ CI #1731 完整通过: **P1:核心功能与体验** 1. 继续逐批检查首次学习、返回学习、上传资料、理解验证和学习结束的真实交互细节,优先修阻断和高频困惑; -2. 扩展联网研究在多查询、读取失败、部分结果和重试过程中的阶段解释,避免只显示工程状态; +2. 完成 P1-R1 的全量 pytest、detect-secrets 与远程 CI 后,再将联网研究的阶段解释标记为已合并; 3. 对实体安卓/iOS 输入法、软键盘安全区、触摸惯性和返回键做实机抽样; 4. 加强源码学习的 symbol mapping、CI association precision 与 partial-result 用户解释。 diff --git a/frontend/src/features/web-lookup/ChatResearchRecovery.test.tsx b/frontend/src/features/web-lookup/ChatResearchRecovery.test.tsx index e355eea1..cb09ca19 100644 --- a/frontend/src/features/web-lookup/ChatResearchRecovery.test.tsx +++ b/frontend/src/features/web-lookup/ChatResearchRecovery.test.tsx @@ -79,6 +79,30 @@ describe("ChatResearchRecovery", () => { expect(onRetry).toHaveBeenCalledOnce(); }); + it("keeps partial findings out of chat until the learner explicitly uses them", () => { + const { container } = render( + , + ); + + expect(container).toHaveTextContent("不会自动用于下一轮聊天"); + expect(container).toHaveTextContent("1 个来源"); + expect(container).toHaveTextContent("研究得到部分可用结果"); + }); + it("does not expose standalone research as chat recovery", () => { const { container } = render( = { cancelled: "研究已停止", }; +function runDetail(run: ResearchLookupResponse): string { + if (run.status === "partial") { + return "部分结果不会自动用于下一轮聊天;你可以重试以补全研究"; + } + if (run.status === "failed") { + return `${run.error || "本次研究未完成"};重试会从已保存的进度继续`; + } + if (run.status === "cancelled") { + return "已停止本次研究;需要时可从已保存的进度重试"; + } + return run.stop_reason || stageLabels[run.stage] || run.stage; +} + export function ChatResearchRecovery({ run, progress = null, @@ -48,12 +61,13 @@ export function ChatResearchRecovery({ if (run?.research_context.run_kind !== "chat_tool_loop") return null; const recovered = run.status === "completed" && run.provider_status === "found"; if (!canRetry && !canResume && !isBusy && !(recovered && useInChat)) return null; - const detail = run.error || run.stop_reason || stageLabels[run.stage] || run.stage; + const detail = runDetail(run); + const heading = run.status === "partial" ? "研究得到部分可用结果" : stageLabels[run.stage] ?? "联网研究可恢复"; return ( - {recovered ? "联网研究已恢复" : stageLabels[run.stage] ?? "联网研究可恢复"} + {recovered ? "联网研究已恢复" : heading} {recovered && useInChat ? "恢复结果已设为下一轮聊天资料。" diff --git a/frontend/src/features/web-lookup/webLookupController.test.tsx b/frontend/src/features/web-lookup/webLookupController.test.tsx index d5b14f54..1e7bbce6 100644 --- a/frontend/src/features/web-lookup/webLookupController.test.tsx +++ b/frontend/src/features/web-lookup/webLookupController.test.tsx @@ -150,6 +150,31 @@ describe("useWebLookupController", () => { expect(apiMocks.createResearchRun).not.toHaveBeenCalled(); }); + it("does not automatically use partial research in chat", async () => { + apiMocks.createResearchRun.mockResolvedValue( + runPayload({ status: "pending", stage: "planned", news_items: [], source_block: "" }), + ); + apiMocks.executeResearchRun.mockResolvedValue( + runPayload({ status: "partial", stage: "completed", provider_status: "partial" }), + ); + + const { result } = renderHook(() => + useWebLookupController({ + query: "Python docs", + setOperationError: vi.fn(), + setActiveRunId: vi.fn(), + }), + ); + + await act(async () => { + await result.current.lookup(); + }); + + expect(result.current.result?.status).toBe("partial"); + expect(result.current.useInChat).toBe(false); + expect(result.current.canRetry).toBe(true); + }); + it("sends server cancellation before invalidating the browser request", async () => { apiMocks.loadResearchRun.mockResolvedValue( runPayload({ status: "running", stage: "reading", active_operation_id: "op_1" }), diff --git a/frontend/src/features/web-lookup/webLookupControllerCore.ts b/frontend/src/features/web-lookup/webLookupControllerCore.ts index 8df92a0c..90f3c541 100644 --- a/frontend/src/features/web-lookup/webLookupControllerCore.ts +++ b/frontend/src/features/web-lookup/webLookupControllerCore.ts @@ -19,7 +19,7 @@ type WebLookupControllerOptions = { }; function isUsable(response: ResearchLookupResponse): boolean { - return ["completed", "partial"].includes(response.status) && response.news_items.length > 0; + return response.status === "completed" && response.news_items.length > 0; } function isRetryable(response: ResearchLookupResponse | null): boolean { diff --git a/frontend/src/features/wechat-workspace/WechatPanel.tsx b/frontend/src/features/wechat-workspace/WechatPanel.tsx index 035b5d00..58ffb2ec 100644 --- a/frontend/src/features/wechat-workspace/WechatPanel.tsx +++ b/frontend/src/features/wechat-workspace/WechatPanel.tsx @@ -107,6 +107,9 @@ function researchStateText(value: ResearchLookupResponse): string { if (value.status === "failed") { return value.error || "研究服务暂时不可用,可重试。"; } + if (value.status === "partial") { + return `已保留 ${selected} 个来源和 ${attempts} 次查询。部分结果不会自动用于下一轮聊天;可手动确认或重试补全。`; + } if (value.provider_status === "empty") { return `当前来源未返回结果;这不代表目标不存在。已记录 ${attempts} 次查询。`; } @@ -328,7 +331,7 @@ export function WechatPanel({ {webLookup ? ( - {stageLabels[webLookup.stage] ?? webLookup.stage} + {webLookup.status === "partial" ? "研究得到部分可用结果" : stageLabels[webLookup.stage] ?? webLookup.stage} {researchStateText(webLookup)} diff --git a/tools/real_stack_test_server.py b/tools/real_stack_test_server.py index 0edd196b..283b21e9 100644 --- a/tools/real_stack_test_server.py +++ b/tools/real_stack_test_server.py @@ -8,6 +8,7 @@ from __future__ import annotations import asyncio +import gc import os import shutil from dataclasses import asdict @@ -329,6 +330,9 @@ def _real_stack_closure_service() -> LearningClosureService: def _remove_runtime_database() -> None: + # Cached repositories hold SQLite connections. Releasing them before + # unlinking is required on Windows, where an open connection locks the file. + gc.collect() database_path = runtime_database_path() for suffix in ("", "-wal", "-shm"): Path(f"{database_path}{suffix}").unlink(missing_ok=True) From f4ba4b5b0ca416f8dc71aad09b78a9d6a6344ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=85=83=E5=9E=9A?= <15135142681@163.com> Date: Mon, 3 Aug 2026 01:16:44 +0800 Subject: [PATCH 2/2] Record P1-R1 CI completion --- docs/PROJECT_STATUS.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/PROJECT_STATUS.md b/docs/PROJECT_STATUS.md index b797446f..44281d83 100644 --- a/docs/PROJECT_STATUS.md +++ b/docs/PROJECT_STATUS.md @@ -5,7 +5,7 @@ > 产品定义:**Study Agent 是长期保持“正在学什么、已经确认什么、还不会什么、下一步是什么”的个人学习工作台。** > 当前主线:**仅检查和改进 Study Agent 本体的代码功能、核心学习流程与桌面/移动端体验。** > 冻结边界:**Provider replay 扩展、生产 claim producer / claim UI、生产 ChatTurn 接入、群聊能力扩张、新闻产品化与可执行 agent 均不是当前开发主线。** -> 当前切片:**P1-R1 联网研究阶段解释已实现;全量 pytest 与 detect-secrets 仍待在不受本地执行时限影响的终端完成,尚未合并。** +> 当前切片:**P1-R1 联网研究阶段解释已实现,PR #96 的远程完整 CI 已通过;PR 仍为 Draft,等待用户决定是否合并。** 本文件只维护当前事实、可复核证据、缺口和执行顺序。不得新增并列长期 STATUS / ROADMAP / NEXT_PHASE / AUDIT 文档。 @@ -83,7 +83,7 @@ PR #93 修复 ResearchRun 两阶段取消在前端没有收口的问题: 1. 静态 owner 测试只读取旧控制器入口,迁移为稳定 re-export 后需要同时审查入口与实现文件; 2. 新增 real-stack 文件最初被普通 fixture Playwright 项目误收录,最终加入统一 `REAL_STACK_TESTS` 排除清单,由专用真实全栈配置运行。 -### 4.3 P1-R1 联网研究阶段解释(待最终全量门禁) +### 4.3 P1-R1 联网研究阶段解释(远程 CI 已通过) 当前活动分支 `codex/p1-r1-research-stage-explanations` 收口“工程状态不可理解”的一处核心语义: @@ -91,7 +91,7 @@ PR #93 修复 ResearchRun 两阶段取消在前端没有收口的问题: - 聊天恢复卡与群聊研究面板明确说明部分结果、已保留的查询/来源和重试语义; - Windows 真实全栈重置在释放缓存连接后再删除 SQLite 文件,desktop 与 390×844 可连续执行; - 已通过前端 222 项单元测试、TypeScript/Vite production build,以及 ResearchRun 取消—刷新—同 run 重试的 desktop 与 390×844 真实全栈旅程; -- 尚未完成本地全量 pytest 与 detect-secrets 的最终运行,因此不得将本切片记录为 CI 全绿、Ready 或已合并。 +- PR #96 的两项 GitHub Actions CI 已通过,覆盖全量 pytest、RAG K1、Ruff、打包、detect-secrets、mypy、前端构建与两类浏览器门禁;该 PR 仍为 Draft,未经用户确认不得合并。 ## 5. 当前质量门禁 @@ -144,7 +144,7 @@ CI #1731 完整通过: **P1:核心功能与体验** 1. 继续逐批检查首次学习、返回学习、上传资料、理解验证和学习结束的真实交互细节,优先修阻断和高频困惑; -2. 完成 P1-R1 的全量 pytest、detect-secrets 与远程 CI 后,再将联网研究的阶段解释标记为已合并; +2. 根据用户决定将 PR #96 继续保持 Draft、转 Ready 或合并;合并前不得扩大 P1-R1 范围; 3. 对实体安卓/iOS 输入法、软键盘安全区、触摸惯性和返回键做实机抽样; 4. 加强源码学习的 symbol mapping、CI association precision 与 partial-result 用户解释。