版本(Version)
1.12.2
环境(Environment)
dsh: @deepseek-ai/dsh ^0.1.1-rc.2 (dsh web) OS: Windows
复现步骤(Steps to reproduce)
复现步骤
-
以 dsh web 启动 DeepSeek Harness。
-
打开任意会话,点击头部工具栏的「知识节点」按钮。
-
右侧面板打开,底部出现「加载中…」后立即报:
加载失败: transport failure for /gf/nodes: HTTP 405
手动复现(对正在运行的 server 直接发请求):
POST http://127.0.0.1:3080/gf/nodes
Content-Type: application/json
{"type":"client-request","rpcId":"probe-1","method":"nodes","payload":{"workspaceRoot":"D:\\WorkSpace\\serendipity-engine"}}
# → HTTP 405
期望行为(Expected behavior)
打开「知识节点」面板,应从 graphflow 图谱存储返回 workbench / dialogue 的 JSON(正常显示主题与对话记录),而不是 HTTP 405。
实际行为(Actual behavior)
在 DeepSeek Harness(dsh web)界面点开右侧「知识节点」面板,取数请求报错:
加载失败: transport failure for /gf/nodes: HTTP 405
根因指向 graphflow 自身的 dsh glue 插件(@roarpeng/graphflow/dsh)。它负责注册 /gf RPC 通道供面板取数,但该注册逻辑全程静默吞错,导致 /gf 通道从未被宿主应答,请求落到 SPA 静态兜底路由,返回 405 Method Not Allowed。
这不是使用者的配置问题。 面板能渲染、MCP 工具能工作、glue 插件已加载且 active,唯独 /gf 通道没有生效——这是插件代码层面的缺陷。
补充信息(可选)
诊断证据(实测)
1. MCP 工具正常,面板取数异常
同一个插件的两条通道,一条能用一条不能用:
| 通道 |
实现路径 |
结果 |
MCP 工具(mcp__graphflow__graph_*,含 graphflow_diagnose) |
独立 stdio 进程 graphflow-mcp,经 @deepseek-ai/dsh-mcp-client 挂载到工具目录 |
✅ 正常 |
知识节点面板取数(/gf/nodes) |
dsh/plugin.mjs 的 registerNodesRpcChannel 注册 /gf RPC 通道 |
❌ 405 |
2. glue 插件已加载且 active
通过 DSH 的 pluginInventory/list API 直接读取当前生效插件树:
entryId=include:graphflow-dsh moduleName=@roarpeng/graphflow/dsh enabled=true fiberPhase=active
entryId=include:mcp-graphflow moduleName=@deepseek-ai/dsh-mcp-client enabled=true fiberPhase=active
graphflow-dsh 已经加载并运行。 所以问题不是「没挂载」,而是「挂载了但 /gf 通道没注册成功 / 没有被应答」。
3. 路由对照探测
POST /gf/nodes → HTTP 405 (复现用户报错)
POST /api/session.list → HTTP 200 (内置 RPC 正常,宿主 /api 前缀工作正常)
/gf/nodes 这条 POST 没被任何已注册路由接住,落到 Web 前端的静态文件兜底(SPA dist),对「不存在的虚拟路径」返回 405。
根因分析
关键源码:dsh/plugin.mjs → registerNodesRpcChannel(ctx, config)
function registerNodesRpcChannel(ctx, config) {
const connection = typeof ctx.get === "function" ? ctx.get("connection") : ctx.connection;
if (!connection || typeof connection !== "object") return undefined; // (1) 静默
const rpc = connection.rpc;
if (!rpc || typeof rpc.handle !== "function") return undefined; // (2) 静默
if (wiredRpcConnections.has(connection)) return undefined; // (3) 静默
wiredRpcConnections.add(connection);
let disposer;
try {
disposer = rpc.handle("/gf", (endpoint, payload, signal) => rpcNodesHandler(...), {
authority: "trusted-host",
});
} catch {
wiredRpcConnections.delete(connection);
return undefined; // (4) 静默
}
...
return dispose;
}
三处((1)(2)(4))失败全部静默 return undefined,不抛错、不写日志。 一旦 /gf 通道注册失败,面板取数就毫无提示地 405。
宿主侧路由:@deepseek-ai/dsh-host-webserver register()
register(route) {
const table = route.kind === "exact" ? this.exact : this.prefixes;
if (table.has(route.path)) throw new Error(`webserver: duplicate ${route.kind} route "${route.path}"`);
table.set(route.path, route);
return () => { table.delete(route.path); };
}
路由匹配是最长前缀优先(match())。若 /gf 前缀不在 prefix 表里(注册抛错或未发生),/gf/nodes 就命中 fallback(SPA 静态服务器)→ 405。
可疑根因:同步 ctx.get("connection")
glue 在 apply() 里同步调用 ctx.get("connection") 拿 connection 服务。而当前 dsh 的宿主 connection 服务是带依赖注入声明的:
id: connection
name: '@deepseek-ai/dsh-client-connection'
inject: [webRuntime] # 依赖 webRuntime
若 graphflow-dsh apply 时 connection 尚未就绪,ctx.get("connection") 返回 undefined → 直接 return undefined(失败点 (1)),/gf 通道就此静默缺失。
对比 DSH 内置的 api-gateway(@deepseek-ai/dsh-api-gateway)——它等待 connection 的方式是异步注入:
ctx.inject(["connection"], (connectionCtx) => {
connectionCtx.connection.rpc.intercept("/api", ...);
});
而不是 ctx.get("connection")。graphflow 的写法假设 connection 在 apply 时同步可用,这在当前 dsh 版本下很可能不成立。
补充:为什么 patch 加 inject 救不了
初步猜测「在 patch 里给 graphflow-dsh 行加 inject: [connection]」即可修复。但核实 Cordis 源码(@deepseek-ai/cordis/lib/index.js)后确认:
plugin(plugin, config, ...) {
const fiber = new Fiber(this.ctx, config, Inject.resolve(plugin.inject), ...);
}
Cordis 只读取插件模块的静态 inject(plugin.inject)做依赖等待,不读取 patch 行上的 inject 字段。 因此该问题无法通过配置层(patch)修复,只能在插件代码层(dsh/plugin.mjs)修改。
建议修复方案(供作者参考)
-
异步等待 connection 服务:把
const connection = ctx.get("connection");
改为
ctx.inject(["connection"], (connectionCtx) => { ... registerNodesRpcChannel ... });
或等效地让插件声明静态 inject 依赖(若 Cordis 支持从插件对象声明)。
-
不要静默吞错:registerNodesRpcChannel 的四处失败至少应写入日志/回调,让 /gf 通道注册失败可见,而不是无声的 405。
-
冲突检查:注册前确认没有其它路由已抢占 /gf(webserver.register 抛的 duplicate ... route 会被 catch 吞掉),必要时优先处理。
版本(Version)
1.12.2
环境(Environment)
dsh: @deepseek-ai/dsh ^0.1.1-rc.2 (dsh web) OS: Windows
复现步骤(Steps to reproduce)
复现步骤
以
dsh web启动 DeepSeek Harness。打开任意会话,点击头部工具栏的「知识节点」按钮。
右侧面板打开,底部出现「加载中…」后立即报:
手动复现(对正在运行的 server 直接发请求):
POST http://127.0.0.1:3080/gf/nodes Content-Type: application/json {"type":"client-request","rpcId":"probe-1","method":"nodes","payload":{"workspaceRoot":"D:\\WorkSpace\\serendipity-engine"}} # → HTTP 405期望行为(Expected behavior)
打开「知识节点」面板,应从 graphflow 图谱存储返回 workbench / dialogue 的 JSON(正常显示主题与对话记录),而不是
HTTP 405。实际行为(Actual behavior)
在 DeepSeek Harness(
dsh web)界面点开右侧「知识节点」面板,取数请求报错:根因指向 graphflow 自身的 dsh glue 插件(
@roarpeng/graphflow/dsh)。它负责注册/gfRPC 通道供面板取数,但该注册逻辑全程静默吞错,导致/gf通道从未被宿主应答,请求落到 SPA 静态兜底路由,返回405 Method Not Allowed。这不是使用者的配置问题。 面板能渲染、MCP 工具能工作、glue 插件已加载且 active,唯独
/gf通道没有生效——这是插件代码层面的缺陷。补充信息(可选)
诊断证据(实测)
1. MCP 工具正常,面板取数异常
同一个插件的两条通道,一条能用一条不能用:
mcp__graphflow__graph_*,含graphflow_diagnose)graphflow-mcp,经@deepseek-ai/dsh-mcp-client挂载到工具目录/gf/nodes)dsh/plugin.mjs的registerNodesRpcChannel注册/gfRPC 通道2. glue 插件已加载且 active
通过 DSH 的
pluginInventory/listAPI 直接读取当前生效插件树:graphflow-dsh已经加载并运行。 所以问题不是「没挂载」,而是「挂载了但/gf通道没注册成功 / 没有被应答」。3. 路由对照探测
/gf/nodes这条 POST 没被任何已注册路由接住,落到 Web 前端的静态文件兜底(SPA dist),对「不存在的虚拟路径」返回 405。根因分析
关键源码:
dsh/plugin.mjs→registerNodesRpcChannel(ctx, config)三处((1)(2)(4))失败全部静默
return undefined,不抛错、不写日志。 一旦/gf通道注册失败,面板取数就毫无提示地 405。宿主侧路由:
@deepseek-ai/dsh-host-webserverregister()路由匹配是最长前缀优先(
match())。若/gf前缀不在 prefix 表里(注册抛错或未发生),/gf/nodes就命中fallback(SPA 静态服务器)→ 405。可疑根因:同步
ctx.get("connection")glue 在
apply()里同步调用ctx.get("connection")拿 connection 服务。而当前 dsh 的宿主connection服务是带依赖注入声明的:若
graphflow-dshapply 时connection尚未就绪,ctx.get("connection")返回undefined→ 直接return undefined(失败点 (1)),/gf通道就此静默缺失。对比 DSH 内置的
api-gateway(@deepseek-ai/dsh-api-gateway)——它等待 connection 的方式是异步注入:而不是
ctx.get("connection")。graphflow 的写法假设 connection 在 apply 时同步可用,这在当前 dsh 版本下很可能不成立。补充:为什么 patch 加
inject救不了初步猜测「在 patch 里给
graphflow-dsh行加inject: [connection]」即可修复。但核实 Cordis 源码(@deepseek-ai/cordis/lib/index.js)后确认:Cordis 只读取插件模块的静态
inject(plugin.inject)做依赖等待,不读取 patch 行上的inject字段。 因此该问题无法通过配置层(patch)修复,只能在插件代码层(dsh/plugin.mjs)修改。建议修复方案(供作者参考)
异步等待 connection 服务:把
改为
或等效地让插件声明静态
inject依赖(若 Cordis 支持从插件对象声明)。不要静默吞错:
registerNodesRpcChannel的四处失败至少应写入日志/回调,让/gf通道注册失败可见,而不是无声的 405。冲突检查:注册前确认没有其它路由已抢占
/gf(webserver.register抛的duplicate ... route会被catch吞掉),必要时优先处理。