From f031fd26ef135d02bf2fa7b48fe6a954d33b548f Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Mon, 11 May 2026 19:03:38 +0800 Subject: [PATCH] fix(skill): split vault-browse from paper-search, add browsing flow to vault-knowledge --- paperforge/setup/runtime.py | 4 ++++ paperforge/setup/vault.py | 2 ++ paperforge/setup_wizard.py | 16 ++++++++++++---- paperforge/skills/literature-qa/SKILL.md | 3 ++- .../literature-qa/references/paper-search.md | 12 ++++++++++-- .../literature-qa/references/vault-knowledge.md | 16 ++++++++++++++++ paperforge/worker/status.py | 9 ++++----- 7 files changed, 50 insertions(+), 12 deletions(-) diff --git a/paperforge/setup/runtime.py b/paperforge/setup/runtime.py index b0b56474..0fffab9d 100644 --- a/paperforge/setup/runtime.py +++ b/paperforge/setup/runtime.py @@ -38,6 +38,8 @@ class RuntimeInstaller: [sys.executable, "-m", "pip", "install", package_spec], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=120, ) return (result.returncode == 0, result.stdout, result.stderr) @@ -96,6 +98,8 @@ class RuntimeInstaller: ], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=10, ) return result.returncode == 0 and bool(result.stdout.strip()) diff --git a/paperforge/setup/vault.py b/paperforge/setup/vault.py index 8c720367..15ee7b5b 100644 --- a/paperforge/setup/vault.py +++ b/paperforge/setup/vault.py @@ -80,6 +80,8 @@ class VaultInitializer: ["cmd", "/c", "mklink", "/J", str(zotero_link), zotero_path], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=30, ) if result.returncode != 0: diff --git a/paperforge/setup_wizard.py b/paperforge/setup_wizard.py index a880e9f7..33f854bf 100644 --- a/paperforge/setup_wizard.py +++ b/paperforge/setup_wizard.py @@ -118,7 +118,7 @@ class EnvChecker: def install_dependencies(self) -> bool: deps = ["requests", "pymupdf", "pillow"] try: - subprocess.run([sys.executable, "-m", "pip", "install"] + deps, check=True, capture_output=True) + subprocess.run([sys.executable, "-m", "pip", "install"] + deps, check=True, capture_output=True, encoding="utf-8", errors="replace") return True except subprocess.CalledProcessError: return False @@ -178,7 +178,7 @@ class EnvChecker: return p # 4. 通过 where 命令检测 try: - result = subprocess.run(["where", "zotero"], capture_output=True, text=True, timeout=5) + result = subprocess.run(["where", "zotero"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5) if result.returncode == 0: for line in result.stdout.strip().split("\n"): p = Path(line.strip()) @@ -196,7 +196,7 @@ class EnvChecker: return p # 通过 which 检测 try: - result = subprocess.run(["which", "zotero"], capture_output=True, text=True, timeout=5) + result = subprocess.run(["which", "zotero"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5) if result.returncode == 0: return Path(result.stdout.strip()) except Exception: @@ -213,7 +213,7 @@ class EnvChecker: if p.exists(): return p try: - result = subprocess.run(["which", "zotero"], capture_output=True, text=True, timeout=5) + result = subprocess.run(["which", "zotero"], capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=5) if result.returncode == 0: return Path(result.stdout.strip()) except Exception: @@ -527,6 +527,8 @@ def headless_setup( ["cmd", "/c", "mklink", "/J", str(zotero_link_path), str(zotero_data)], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=30, ) if result.returncode != 0: @@ -767,6 +769,8 @@ def headless_setup( [sys.executable, "-m", "pip", "install", "--upgrade"] + install_target, capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=120, ) if result.returncode != 0: @@ -775,6 +779,8 @@ def headless_setup( [sys.executable, "-m", "pip", "install", "--upgrade"] + git_target, capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=120, ) if result.returncode == 0: @@ -782,6 +788,8 @@ def headless_setup( [sys.executable, "-c", "import paperforge; print(getattr(paperforge, '__version__', '?'))"], capture_output=True, text=True, + encoding="utf-8", + errors="replace", timeout=15, ) new_ver = _new.stdout.strip() or "?" diff --git a/paperforge/skills/literature-qa/SKILL.md b/paperforge/skills/literature-qa/SKILL.md index a11d5bd1..fbcb63d9 100644 --- a/paperforge/skills/literature-qa/SKILL.md +++ b/paperforge/skills/literature-qa/SKILL.md @@ -21,7 +21,8 @@ Agent 读到本文件后,首先根据用户意图路由到对应的 reference | 用户意图 | 典型输入 | 加载文件 | |---------|---------|---------| -| 文献检索 | "找一下骨科...文献", "查一下 TGF-beta", "库里有没有支架材料的", "搜一下 Smith 的文章", "collection 里有没有", "看一下库里内容", "看一下文献库", "库里有什么", "浏览文献" | [references/paper-search.md](references/paper-search.md) | +| 查看 Vault 概况 | "看一下库里内容", "看一下文献库", "库里有什么", "浏览文献" | [references/vault-knowledge.md](references/vault-knowledge.md) | +| 文献检索 | "找一下骨科...文献", "查一下 TGF-beta", "库里有没有支架材料的", "搜一下 Smith 的文章", "collection 里有没有" | [references/paper-search.md](references/paper-search.md) | | 三阶段精读(指定论文) | `/pf-deep `, `pf-deep `, "精读 XXX", "深度阅读 XXX", "带我读", "组会讲这篇", "读一下这篇" | [references/deep-reading.md](references/deep-reading.md) | | 三阶段精读(查看队列) | `/pf-deep`(无参数), "精读队列", "有哪些该读了" | [references/deep-reading.md](references/deep-reading.md) | | 论文问答 | `/pf-paper `, `pf-paper `, "做这篇的问答", "帮我看看 XXX", "这篇文章讲了什么", "查一下" | [references/paper-qa.md](references/paper-qa.md) | diff --git a/paperforge/skills/literature-qa/references/paper-search.md b/paperforge/skills/literature-qa/references/paper-search.md index e7a30759..63b3c53c 100644 --- a/paperforge/skills/literature-qa/references/paper-search.md +++ b/paperforge/skills/literature-qa/references/paper-search.md @@ -11,8 +11,6 @@ - "库里有没有讲支架材料的" - "这个 collection 有哪些文献" - "搜一下 Smith 2024 的文章" -- "看一下库里内容" / "看一下文献库" -- "库里有什么" / "浏览文献" ## 流程 @@ -67,6 +65,16 @@ Agent 直接读 `index_path`,在 JSON 中筛选: - `这篇讲了什么` → 进入 [paper-qa.md](paper-qa.md) 流程 - 不需要继续 → 结束 +### Step 6: 写作辅助场景 + +如果用户原始意图包含**写作/优化/参考文献/综述/引用**等,搜索结果不是终点: + +1. 提示用户圈选最相关的 3-5 篇 +2. 对每篇进入 [deep-reading.md](deep-reading.md) 或至少通读 formal note + fulltext 关键段落 +3. 读完所有选定论文后,Agent 整合知识辅助写作 + +> 示例:"我从库里 X 篇文献中提取了以下关键发现……要不要基于这些帮你写 XX 部分?" + ## 注意事项 - 如果是大型 library(>500 篇),优先用 paper_resolver 而不是全量读 JSON diff --git a/paperforge/skills/literature-qa/references/vault-knowledge.md b/paperforge/skills/literature-qa/references/vault-knowledge.md index a9969332..c651cc94 100644 --- a/paperforge/skills/literature-qa/references/vault-knowledge.md +++ b/paperforge/skills/literature-qa/references/vault-knowledge.md @@ -2,6 +2,22 @@ 本文件是 Agent 理解 PaperForge Vault 的共享参考。精读、问答、检索等所有工作流都先从这里获取基础概念。 +## 浏览场景 + +用户说"看一下库里内容"、"看一下文献库"、"库里有什么"、"浏览文献"时,Agent 展示 Vault 概况: + +1. 跑 `paths` 获取 `literature_dir` 和 `index_path` +2. 列出 `literature_dir` 下所有 domain(领域文件夹名) +3. 读 formal-library.json,统计每个 domain:论文总数、OCR 完成数、无 PDF 数 +4. 输出概览: + +``` +Vault: D:\...\Vault +Domains: 骨科 (550 papers, 520 OCR done), 运动医学 (263 papers, 129 OCR done) +``` + +如果用户继续问具体内容,Agent 加载 [paper-search.md](paper-search.md) 搜索。 + --- ## 1. 获取路径 diff --git a/paperforge/worker/status.py b/paperforge/worker/status.py index bb640557..0ee73220 100644 --- a/paperforge/worker/status.py +++ b/paperforge/worker/status.py @@ -278,8 +278,7 @@ def _resolve_plugin_interpreter(vault: Path, plugin_data: dict) -> tuple[str, st for path, extra in system_candidates: try: cmd = [path] + extra + ["--version"] - result = subprocess.run(cmd, capture_output=True, timeout=5, text=True) - if result.returncode == 0 and "Python" in (result.stdout or ""): + result = subprocess.run(cmd, capture_output=True, timeout=5, text=True, encoding="utf-8", errors="replace") return (path, "auto-detected", extra) except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError, OSError): continue @@ -295,7 +294,7 @@ def _query_resolved_version(interp: str, extra_args: list[str]) -> tuple[str | N """ try: cmd = [interp] + extra_args + ["--version"] - result = subprocess.run(cmd, capture_output=True, timeout=10, text=True) + result = subprocess.run(cmd, capture_output=True, timeout=10, text=True, encoding="utf-8", errors="replace") if result.returncode != 0: return (None, None) output = (result.stdout or "").strip() or (result.stderr or "").strip() @@ -317,7 +316,7 @@ def _query_resolved_package(interp: str, extra_args: list[str], package_name: st """ try: cmd = [interp] + extra_args + ["-m", "pip", "show", package_name] - result = subprocess.run(cmd, capture_output=True, timeout=15, text=True) + result = subprocess.run(cmd, capture_output=True, timeout=15, text=True, encoding="utf-8", errors="replace") if result.returncode != 0: return None output = (result.stdout or "").strip() @@ -345,7 +344,7 @@ def _query_resolved_module(interp: str, extra_args: list[str], module_name: str) ) try: cmd = [interp] + extra_args + ["-c", script] - result = subprocess.run(cmd, capture_output=True, timeout=15, text=True) + result = subprocess.run(cmd, capture_output=True, timeout=15, text=True, encoding="utf-8", errors="replace") if result.returncode != 0: return None output = (result.stdout or "").strip()