v0.1.17
新增
-
P4.17 MCP 远程传输
guanlan mcp --transport http(Streamable HTTP,见docs/P4.17-MCP远程传输.md)
—— 给 P4.10 stdio-only MCP 宿主补第二种传输:官方 Streamable HTTP(spec 2025-03-26 起、取代 legacy
HTTP+SSE)。同一套只读工具、只换传输:tools.py零改、build_mcp仅加一个allow_ask注册门(把
ask的注册包进if allow_ask:,六个零 LLM 工具无条件注册照旧),host/port/无状态/DNS-rebinding/token
校验全部接线收在serve_mcp的 http 分支(_http_security/_BearerTokenMiddleware/_build_http_app/
_serve_http)。安全默认:默认仍绑127.0.0.1(沿用决策P4-4)、非环回强制 bearer token(从
--auth-token-env指定的环境变量读、绝不命令行明文/落盘;最小 ASGI 中间件hmac.compare_digest常量
时间比对、失败 401、不引 OAuth)否则拒启EXIT_USAGE、askover http 默认关
(allow_ask=(transport=='stdio' or --allow-ask):stdio 永远七工具、http 默认六、--allow-ask才七,避
任意网络客户端触发付费 LLM 子进程的成本/DoS 放大 + 子进程写探问放大)、无状态stateless_http(无
Mcp-Session-Id、无事件重放)、DNS-rebinding 白名单派生自--host/--allowed-host(可重复,反代对外
域名须显式补入否则被拒)、TLS 外置。完整 OAuth(MCP 2025-06-18)/ 多租户 source 级 scoping 显式留给
E2。零依赖变更——已实测装的 mcp SDK(mcp>=1.27,<2)原生支持transport="streamable-http"且自带
starlette/uvicorn硬依赖,[mcp]extra 与 SDK 下界均不改。新增 CLI 旗标
--transport {stdio,http}/--host/--port/--auth-token-env/--allowed-host/--allow-ask(默认全部
向后兼容 P4.10:不带--transport即 stdio 字节等价)。测试:tests/test_mcp.pyP4.17 套件(ask 门控、
绑定红线拒启、token 闸 401/放行、rebinding 白名单派生、真 uvicorn + streamable-http 客户端端到端往返)+
tests/test_cli.py旗标解析。编号辨析:属 P4 宿主家族兄弟半阶段(非 P4.10.1 补漏、非新里程碑、非直接
E2)。连带更新 DESIGN §7(P4 行 + E2 前哨行改「已实现」)、CLAUDE.md status/命令注释、docs/P4.17-MCP远程 传输.md(标题去「· 草案」、状态改「已实现」)。 -
指令层:告知 Agent 可直接写
```mermaid围栏图 ——AGENTAO.md新增「图表(流程图等直接写
mermaid)」节、conventions.md新增「§图表(mermaid 直绘)」(带最小示例)、SKILL.mdingest 工作流补
指针:表达流程/时序/状态/类/架构/关系等结构时,Agent 直接在正文写 mermaid 围栏块即可,无需调用任何
外部画图/生图工具——围栏块本身就是产物,Web 宿主(P4.13)浏览器内strict渲染成图、CLI 回退字面源码
(markdown 唯一事实不破)。明确边界:新综合的关系图用 mermaid 直绘,raw/源里已有的插图仍按图片
引用、不重画;图里实体/概念正文仍须[[wikilink]]互链(mermaid 节点文本不进 wikilink/召回解析);
与guanlan graph的graph.json/html(确定性 wikilink 关系图)无关。纯指令/文档,零代码改动,解锁的
是 P4.13 已落地的既有渲染能力。同步补examples/AGENTAO.md(init 模板单源)与本地库kbs/{jindu,jingzhen}
的AGENTAO.md(init不覆盖既有文件,存量库需就地补)。 -
Web 正文/对话里的
raw/<slug>.md引用联成只读 raw 源链 —— wiki 单页(/api/page)与对话答案
渲染时,把指向现存raw/<slug>.md的引用联成可点的a.rawlink[data-raw]:点击在右栏内联
调既有/api/raw/file只读渲染那篇原始素材(复用单页历史栈,一键「←」回引用它的原页;左栏对话气泡
里点亦切右栏,与既有[[wikilink]]行为一致)。识别四种写法(口径由用户敲定):① 裸路径串
raw/x.md(plain text 行内);② 整段恰好是raw/x.md的行内 code;③[[raw/x]](wikilink,带/不带
.md、支持|别名);④ markdown 链接[文字](raw/x.md)(改写<a href>、保留链接文字/内联格式、容
./前缀)。与[[wikilink]]同纪律——仅真实存在的 raw 文件联链,缺失标灰span.rawlink.broken
(dashed 下划线区别于 wikilink 的 dotted)。- 纯前端复用既有只读端点:
render.py三处处理器共用raw/*.md真实文件名集(wiki.parent/raw
推出、每渲染重扫,同_stem_to_path纪律)——_RawPathTreeprocessor(裸路径串 + markdown 链接 href,
树上处理故按祖先跳过a/code/pre)、_CodePathLinkTreeprocessor内 raw 分支、_resolve_wikilink
内raw/前缀拦截;右栏新增{kind:"raw"}视图(banner 标明「原始素材·只读」+ raw/ 路径 +
「在新标签打开」逃生口经/?raw=弹出 SPA,供并排对照源)。后端/api/raw/file·/api/raw零改动
(reader 下仍注册)。 - 关键正确性:
raw/x.md即便存在同名 wiki 页也联到 raw 源、绝不误链 wiki(raw 分支先于link_stem
页解析);裸路径串改用 treeprocessor(而非 inline)按祖先跳过<a>——根除[raw/x.md](url)在链接
文字里被回灌再联、套出非法嵌套<a>的隐患;[**强调**](raw/x.md)内联格式不丢;外链/绝对路径
(https://…/raw/y.md、/raw/x.md)不动;cat raw/x.md命令 code 与围栏块字面保留;不给wiki时
不联链(保 P4 既有姿态)。新增 4 条 i18n 键raw.{banner,openInTab,loading,openFail}(中英平价);
测试见tests/test_web.py。 - 多智能体评审修复(同一变更内):
span纳入_RAW_SKIP_SUBTREE——杜绝[raw/exists.md](raw/missing.md)
在断链灰 span 内再联出可点且指向另一源的 rawlink;basename 解析改为「按存在性试<名>与<名>.md」
(_lookup_raw)——含内部点的 stem(raw/1.示例报告)正确命中,且 markdown 链接指向非.md资产
(raw/report.pdf、raw/images/x.png)时原样保留 href、不再毁成断链 span;去掉 wikilink display 的
AtomicString包裹——恢复[[页|**别名**]]别名内联格式渲染(旧实现的既有 wikilink 回归);裸路径尾界
(?![\w\-])放行句末.(raw/x.md.仍联链);_raw_name_index精确文件名键 + 无冲突小写兜底(镜像
link_resolution_indexfold 纪律)——大小写敏感盘上raw/Foo.md/raw/foo.md各按真实名解析、零串台;
markdown 链接改写保留作者title;_linkify尾段插入改 O(n) 偏移跟踪(去list.index的 O(n²))。
- 纯前端复用既有只读端点:
变更
- Web 翻 auto 后的确认 UI 收进单框 —— 用户点「本会话起自动放行」(confirm_mode 翻 auto)后,原先
「恢复逐次确认」按钮与「本会话已自动放行 ASK 操作(仍只写 wiki/workspace,raw/ 与 AGENTAO.md 仍只读)」
安全提示是确认框下方另起的一条独立黄条;现把两者都并进「Agent 请求执行操作(请审阅)」确认框内:
恢复钮就近放在置灰的「拒绝」钮前,提示文案作为框内一行(.interaction-automode-inline)。点「恢复逐次
确认」后框内如实回滚——note.remove()撤掉已失真的提示行、去掉「本会话起自动放行」前的✓(移除
.chosen)、钮后内联「已恢复逐次确认。」。冷启刷新时页面已是 auto(无对应确认框可挂钮)仍走原来的独立
黄条带钮路径不变。实现上抽出共享的makeRestoreButton()工厂(确认框内一处、独立黄条一处共用),
scripts/smoke_p415.py同步把定位从独立.interaction-automode改为框内.interaction-automode-inline。
纯前端、零服务端改动;写边界与/confirm-mode {ask}可逆语义不变。
修复
-
毒会话状态文件(goal sidecar / 会话快照)不再把会话或整个侧栏打成永久 500(反向评审 gbrain v0.42.53
探针 + 后续 code-review 加固) —— 一份手改/半写成非对象 JSON(null/[]/标量)或坏 UTF-8 字节
的状态文件会让上层data.items()/data.get(...)抛AttributeError/UnicodeDecodeError,逃逸出只接
(OSError, JSONDecodeError)的容错网,把读它的端点打成 500。统一补齐三条读路径的毒值容错:read_goal(web/goal_io.py)加isinstance(data, dict)守卫 → 非对象 goal sidecar 退化为「无目标」,
不再经ConversationStore.restore()/cold_info/GET /api/info持续 500(违其 docstring 承诺)。_prune_old_snapshots(web/chat_support.py)加isinstance守卫 + 把UnicodeDecodeError并入 catch →
非对象/坏字节的兄弟快照在每轮 save 前的卫生步被跳过,不再崩 save(形同 gbrain「整循环崩在 checkpoint
写」)、不再令_save静默不落盘丢会话。ConversationStore(web/conversation_store.py)新增_safe_list_sessions包装 agentaolist_sessions
的 per-file.get毒值抛错(其except (IOError, JSONDecodeError)不接AttributeError/UnicodeDecodeError),
list/_disk_session(喂restore/messages_for)改走它 → 一份坏快照不再 500 整个会话侧栏
GET /api/conversations与冷会话 restore/messages(降级为「该坏文件不计入盘 catalog」),并给两处
load_sessioncatch 补 race 兜底。- 回归测试:
test_read_goal_tolerates_non_object_json/test_poison_goal_sidecar_degrades_cold_info_not_500
/test_prune_old_snapshots_tolerates_non_dict_session/test_prune_old_snapshots_tolerates_bad_utf8_bytes
/test_poison_session_snapshot_degrades_list_not_500。
(本轮另一处「convert 子进程强制 UTF-8 解码」的改动经 code-review 判定方向错误并已回退:skill 子进程裸
print发 locale 编码字节,父端强解 UTF-8 反而打断 matched-locale 下的中文路径往返、且 stderr surrogateescape
会漏孤 surrogate 把 Web 解析端点 500 ——_run_converter维持text=Truelocale-faithful 行为。convert 编码
的真正修法(子进程两端协同强制 UTF-8 + stderrerrors=replace+ pypdf/LANG parity 测试验证)连同续跑循环无
进展探测 / convert 无超时等结论,见docs/backlog/notes/gbrain-v0.42.53-反向审计-guanlan缺陷.md。)
Full Changelog: v0.1.16...v0.1.17