Skip to content

v0.1.18

Choose a tag to compare

@github-actions github-actions released this 30 Jul 15:18
· 21 commits to main since this release
aa9582a

一次底座迁移 + 反向评审补强的发布:① MCP 宿主迁到官方 SDK v2 / 协议 2026-07-28(P4.18,
等价迁移——命令契约与工具集逐字保留,代价是 [mcp] extra 的安装面破坏性变更);② 三条源自兄弟项目
反向评审的确定性补强/修复——ingest 撞名守卫(llm_wiki)、wiki/·.trash/ 写走原子覆盖(OpenKB)、
交给 agentao 前剔除毒空 *_API_KEY(gbrain);③ 检索收敛提示与一批文档校正。不新增退出码、不动门禁、
raw/ 只读不破。

新增

  • ingest 摄入前挡「同一 source 页 slug 撞名」(源自 nashsu/llm_wiki v0.6 反向评审 §2.2,见 docs/backlog/notes/llm_wiki-反向评审-v0.6.md
    —— source 摘要页由 rawio.find_source_pageraw_slug(stem)(=页身份归口)定位,故两篇 raw
    .md 只要 raw_slug(stem) 相同(a/report.mdb/report.mdannual report.md
    annual-report.md.report.mdreport.md)就会误关联到同一张 wiki/sources/<slug>.md——
    一张压另一张、raw_digest 只认得一个版本(此前源命名仅约定 kebab、无子目录消歧防线)。ingest.py
    新增确定性 _reject_source_slug_collision 预检:摄入前扫 raw/其余 .md,凡 raw_slug(stem)
    与目标相同即 EXIT_USAGE 拒绝、列出冲突路径、要求改名。零-LLM、只堵不重构——复用既有 raw_slug
    (不新增 slug 方案/哈希/迁移),只比 .md(唯一会被 ingest 建 source 页者)故不误伤 convert 的
    report.pdf+report.md 同源对。经 xhigh code-review 三轮收敛:① 从初版「按 basename 判」收正为「按
    raw_slug 判」(basename 太窄,漏 annual reportannual-report 等异名同 slug 的真撞页);② 合法重摄
    豁免
    ——目标页已存在且其 raw_digest 确证归属本文件时放行(_target_page_owned_by,复用
    provenance.parse_digest_value 归口),使「属主页长期维护、同 slug 旁支只是未摄草稿」时重摄不再假阳被挡;
    真撞(拿非属主旁支覆盖属主页)改在摄入那个旁支时当场拒,安全性不减;③ 每次 ingest 全量 rglob
    一遍 raw/已接受代价run_guarded_writegate.snapshot_raw 本就同量级遍历 raw/、还带哈希,
    故此遍历同阶更轻)。残留:find_source_page.- 回退(1.报告1-报告)键不同、不在此拦(窄边角,
    不复刻 rawio 折叠逻辑以免漂移)。测试见 tests/test_ingest.py(撞页拒绝 / slug-fold 折叠拒绝 / pdf+md 同源
    对放行 / 属主重摄放行 / 非属主覆盖拒绝)。

优化

  • query / Web 续跑提示补「检索收敛红线」(源自 v0.6 反向评审 §2.1) —— CLI query.QUERY_PROMPT
    Web 目标续跑 web/conversation._continuation_prompt(P4.16 循环,正是易空转处)各补一句「不要重复等价
    检索;证据足够即回答
    」,压重复召回 / 空转。纯提示词、零代码逻辑改动——刻意不写进 AGENTAO.md
    (会波及 ingest 等所有工作流,太宽)、不引 agentao 侧预算参数(保薄壳 + 循环真相源在 agentao)。测试见
    tests/test_query.py(CLI 提示含红线)/ tests/test_web.py(Web 续跑提示含红线)。

变更

  • ⚠️ 破坏性(可选 extra):MCP 宿主底座迁到官方 SDK v2 + 协议 2026-07-28(P4.18,见
    docs/P4.18-MCP2.0迁移.md
    —— guanlan-wiki[mcp] 的 SDK 依赖从
    mcp>=1.27,<2 硬切到 mcp>=2,<3:官方 SDK 2.0.0(2026-07-28,与协议修订同日)转 stable、v1.x 转入
    只收安全修复的维护态
    ,且 v2 删掉了整个 mcp.server.fastmcp 包(无 alias),故不做双大版本兼容层而是
    硬切(决策P4.18-2;前置依据:guanlan 的核心依赖 agentao 已自带探针式 1.x/2.x 兼容,Tool 注入反方向不受影响,
    决策P4.18-10)。装了 mcp 1.x 的环境须一并升级——guanlan mcp 会以 EXIT_USAGE 明示需要 mcp>=2
    (决策P4.18-9)。核心依赖 agentao[cli] 下限一并从 >=0.4.13 抬到 >=0.4.17:agentao 的 1.x/2.x 探针兼容层
    是 0.4.17 才有的,不抬下限就留下一个"依赖解析完全允许、但一定坏"的组合——已装 0.4.13–0.4.16 的用户装本 extra 时
    pip 只把 mcp 抬到 2.x,于是 guanlan mcp 照常工作,而 ingest/query/Web 问答每次都死在 agentao 侧的 Tool schema
    上,故障点看起来与真凶毫不相干(决策P4.18-10)。
    • 定性:等价迁移(决策P4.18-1)——命令契约(--transport/--host/--port/--auth-token-env/
      --allowed-host/--allow-ask 与全部默认值)、七工具集与信封形状、page/path 口径、只读 + KB 零写契约、
      退出码逐字保留;已注册在 ~/.claude.json / mcp.json 里的 stdio & http 条目零改——v2 仍服务握手
      时代旧修订,stdio / http 双传输均实测 initialize2025-06-18 时正常协商并可继续 tools/list
      tools/call(客户端首帧决定本连接 era,同一连接不得混 era,决策P4.18-7)。
    • 唯一主动引入的 wire 变化initialize.serverInfo.version 现报 guanlan 自身版本(单一来源
      guanlan/__init__.py)。此前 SDK v1 在此回的是所装 mcp SDK 的版本(实测 1.29.0)、v2 默认回空串
      ——两者都不是 guanlan 的版本,故一次钉正(决策P4.18-12)。
    • 代码面mcp.server.fastmcp.FastMCPmcp.server.mcpserver.MCPServer(含三处类型注解)、
      ToolErrormcp.server.mcpserver.exceptions;http 姿态从 mcp.settings 后置赋值改走
      streamable_http_app(stateless_http=…, transport_security=…, host=…) kwargs——v2 的 Settings 已无这些
      字段(后置赋值直接 ValueError,决策P4.18-4)。tools.py 七个工具逻辑、_http_security /
      _BearerTokenMiddleware / _serve_httpto_thread 卸载姿态(决策P4.18-5)全部逐字节不动。
    • 协议红利与刻意不做:无状态核心 / server/discover / 标准错误码白拿(我们本就 stateless_http +
      零服务端会话);Mcp-Method/Mcp-Name 路由头服务端不强制、仅在 P4.17 §5 补注为反代限流手段;
      cache hints 与 tasks 扩展显式不做——前者的 CacheableMethod 只覆盖 list/read/discover 类方法
      tools/call 不在其列,救不了 list_pages/graph 的大 payload),后者在 SDK 2.0.0 里无现成实现且与无状态
      姿态冲突(决策P4.18-8);OAuth 面(RFC 9207 iss、DCR→CIMD)仍属 E2。
    • 测试:in-memory 会话改 Client(mcp, mode="legacy")——v1 的
      create_connected_server_and_client_session 已删除,而 v2 默认 mode="auto"DirectDispatcher 直调
      无流、无 JSON-RPC 帧、无握手),无脑替换会静默丢掉全套用例的序列化覆盖(决策P4.18-11);另加
      test_in_memory_modern_mode_parity(默认 mode 覆盖 2026 现代路径、与 legacy 结果一致)、
      test_stdio_subprocess_emits_only_jsonrpc_frames真 stdio 子进程逐帧解析,决策P4.18-6)、
      test_http_serves_legacy_protocol_client(http 上钉 2025-06-18 的现役客户端不掉线)。
    • 回归网按 xhigh code-review 补齐(这些位置此前"改对了但没上网",变异实测能被静默丢掉):
      test_http_rejects_forged_host_and_origin(伪造 Host → 421 / Origin → 403,含非环回 host 一档
      ——丢掉 transport_security= kwarg 时,绑环回仍被 SDK 兜底白名单救回 421、只有非环回档会漏成 200)、
      test_http_is_stateless(响应头无 Mcp-Session-Id;丢掉 stateless_http=True 时 SDK 客户端往返用例照旧
      全绿,因为它会透明回传 session id)、test_http_serves_modern_protocol_client现代 era 的真帧覆盖
      对 in-process server,Clientmode="legacy" 外任何 mode 都走 DirectDispatcher、不产生帧,故手写
      2026 per-request 信封直接 POST)、越界用例加断消息文案(证守卫仍在我们手里、未被 v2 的 ResourceSecurity
      接管)、era 断言改按 SDK 的 {MODERN,HANDSHAKE}_PROTOCOL_VERSIONS 集合(不钉 2026- 日期前缀,避免上游
      改版把未动的代码搞红)、importorskip 钉到 mcp.server.mcpserver(只探顶层 mcp 时 1.x 环境会 collection
      ImportError 中断整场 pytest 而非整体 skip)、stdio helper 加 -u + encoding="utf-8" + stderr 独立抽干
      不再过滤空白行(三者分别修:不 flush 的泄漏被 fd-1 改指吞掉、非 UTF-8 locale 下伪装成"一帧没吐"、
      stderr 写满约 64 KB 管道后卡满 timeout、空行污染恒真)、降级用例补"装着 1.x"与"内部 ImportError 不得被
      冒充成缺 extra"两档。
    • stdout 洁净的机制说明校正(实测):v2 的 stdio_server() 在服务期把 fd 1 改指 stderr,故服务期的
      print/os.write(1,…)(含 OTel 若真吐字节)都进不了帧——这层是 SDK 的结构性保证;子进程用例真正守的是
      SDK 接管 fd 1 之前那段窗口(require_kb_root/P5.4 预热/build_mcp/argparse),也正是我们自己的代码
      可能泄漏处。原先"在真传输上实证 OTel 不污染 stdout"的说法说过头了,已按实测改写(决策P4.18-6)。
    • tests/test_mcp.py 63 例、全套 1141 通过 / 1 skip。

文档

  • CLAUDE.md 优化:status 段瘦身、过时事实校正、命令清单补全。
  • 发版前收口:docs/发布到-PyPI.md 改按「版本单一源在 guanlan/__init__.py」重写实操步骤(并补两-PR
    仪式、CHANGELOG 段头格式、tag 落点、发布后验证的缓存坑);归档 gbrain / llm_wiki / swarmvault 三份
    反向评审笔记;校正 README.en.mdDESIGN.md §7 中与 P4.18 不一致的口径。

修复

  • 在 Claude Code 会话里跑 guanlan 时「.env 有真 key 却报无 key」(源自 gbrain v0.42.58 反向评审 §2,
    探针 gbrain #1249,见 docs/backlog/notes/gbrain-v0.42.58-反向评审.md

    —— Claude Code 会给子进程注入 ANTHROPIC_API_KEY='' 以掐断子进程的 LLM 调用;而 agentao 的
    safe_load_dotenvos.environ.setdefault(no-override),空串也算「已设置」,于是 .env 里的真 key
    永远 setdefault 不进来,os.getenv 恒返空串。触发面:provider = Anthropic 且从 Claude Code 会话里跑
    guanlan ingest/query/web
    (OpenAI provider 不受影响——Claude Code 不注入 OPENAI_API_KEY='')。
    现在两条 LLM 路径在交给 agentao 之前都剔除毒空值:CLI 子进程路径 runtime._subprocess_runner 显式传
    env=scrubbed_environ()(不再裸继承父环境);Web 进程内嵌入路径在 chat.build_from_environment 前调
    drop_poisoned_api_keys() 就地摘除(时机关键——build_from_environment 在调用期才 safe_load_dotenv)。
    只删空/纯空白的 *_API_KEY,绝不注入或读取任何真 key——守「脚本零 LLM、wrapper 不持 API key」不变量,
    与本接缝已有的 stdin=DEVNULL 同类(喂给子进程前的环境清洗)。已实测:摘掉毒值后 agentao 自己的
    dotenv 加载即恢复正常(safe_load_dotenv → 真 key,discover_llm_kwargs → 真 key)。测试见
    tests/test_runtime.py(只删空值不删真值 / 子进程 env 实际内容 / 就地摘除幂等 / API 只回变量名不泄值)与
    tests/test_web.py(断言摘除早于 build_from_environment)。

    注:根因在 agentao,已另提 agentao#157safe_load_dotenv
    但观澜这层清洗不依赖那个修复——它对任何 agentao 版本都生效,故不锁 agentao 下限。
    另更正 backlog note §2 的处方:只改 agentao discover_llm_kwargs 跳过空值不足以修复(那只是把
    api_key='' 变成缺省,真 key 仍因掩蔽而永不加载),必须在加载器或调用方摘掉毒值。

  • wiki/.trash/ 的确定性写全部走原子覆盖,消除半写坏页(源自 OpenKB 反向评审 §2,见
    docs/backlog/notes/openkb-2026-07-反向评审.md
    ——
    此前 wiki/ 的零 LLM 写用裸 Path.write_text/write_bytes:进程中断 / 磁盘满卡在写一半,会把
    权威 markdown(内容页、index.md、撤回恢复配方、frontmatter 修复页)截成半截;而 rawio 早有的
    atomic_write_raw(tmp + os.replace)此前只用在 raw/。现抽两支公共原语——atomic_write_bytes
    逐字节底座 + atomic_write_text UTF-8 文本外壳(atomic_write_raw 重构为复用文本壳,字节级行为不变),
    单一实现杜绝多处落盘规则漂移;7 处确定性写改用原语:remove_drop_slug_from_page(内容页)/
    _prune_index_lineindex.md)/ .trash manifest、reindexindex.md 回填、
    fmrepair.repair_page_frontmatter(CRLF 保真故走字节底座)、gate 门禁回滚原字节、
    provenance.stamp_raw_digest 的 stamp 与回滚。覆盖既有文件时 _preserve_metadata 保留原权限位 +
    属主 uid/gid
    (best-effort)——否则 os.replace 换新 inode 会把 0644 页窄化成 0600 并改掉属主
    (经两轮 codex 评审的 P2 项)。有意保留的固有取舍(同既有 atomic_write_raw,均记在原语 docstring):
    符号链接不写穿——对本模块反而更安全(fmrepair/provenance 本就先拒链接,remove/reindex 不跟随
    即杜绝写逃逸出 KB,旧的就地写反而会写穿);不保 ACL/xattr——os.*xattr 在 macOS 不可用、POSIX ACL
    无标准库,对纯 markdown 无实义。测试见 tests/test_atomic_write.py(14 例:三类失败模式 × 文本/字节两路

    • CRLF 逐字 + 权限/属主保留 + 新建跳过 + 经 _drop_slug_from_page/fmrepair/provenance 的集成证明)。

Full Changelog: v0.1.17...v0.1.18