fix(skill): split vault-browse from paper-search, add browsing flow to vault-knowledge

This commit is contained in:
Research Assistant 2026-05-11 19:03:38 +08:00
parent 80aa37a091
commit f031fd26ef
7 changed files with 50 additions and 12 deletions

View file

@ -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())

View file

@ -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:

View file

@ -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 "?"

View file

@ -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 <query>`, `pf-deep <query>`, "精读 XXX", "深度阅读 XXX", "带我读", "组会讲这篇", "读一下这篇" | [references/deep-reading.md](references/deep-reading.md) |
| 三阶段精读(查看队列) | `/pf-deep`(无参数), "精读队列", "有哪些该读了" | [references/deep-reading.md](references/deep-reading.md) |
| 论文问答 | `/pf-paper <query>`, `pf-paper <query>`, "做这篇的问答", "帮我看看 XXX", "这篇文章讲了什么", "查一下" | [references/paper-qa.md](references/paper-qa.md) |

View file

@ -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

View file

@ -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. 获取路径

View file

@ -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()