chore(仓库对齐): 文档库结构治理 + IM/插件线落地
build / build-and-scan (push) Canceled after 0s

文档库:目录改为编号制(01-规范/02-架构设计/03-数据库/04-调整方案/
05-交接单/06-ops/07-scripts/08-skills/09-archive),顶层散文件归入 01-规范/;
INDEX.md 与 docs-manifest.json 重刷(档案 146 篇);旧目录名引用全量对齐。

IM 线:src/im/**(SDK / hub / store / presence / ws / gateway-token)、
src/web/routes/im.ts、src/db/plugin-data/**、src/supervisor/plugin-assembly.ts
及对应 test/**。

插件线:poc/{im-agent-bridge,im-connection-gateway,im-conversation-tabs,
business-plugins-im,carbon-mcp-probe}、src/web/routes/{sessions,overlay-device}.ts、
src/net/relay/{device-grant,instance-credential}.ts。

仓库卫生:清出 40 个历史误入库 / 已改名文件(34 个交接单归档 + 6 个旧结构,
本地均有副本);dsh-server-docs/.gitignore 补 tmp/;交接单不入库(政策)。
This commit is contained in:
admin committed 2026-09-24 07:25:16 +08:00
1 parent 3d8f50e366
commit e6207aa691
239 files changed
+34477 -14633

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
# @dsh-local/carbon-mcp-probe — bundle patch(host 半区)
#
# 本 POC 的**全部挂载动作**都在这两行里:
# ① 载入本包自己的 host 半区(lib/index.js,探针)
# ② insert 一条**内核** mcp-client entry,用 streamable-http 连远程 MCP server
#
# name 解析:profile 文档规定 bundle 名**先从 dsh 安装目录解析**,再从 profile 目录
# ⇒ 业务包无需自带 @deepseek-ai/dsh-mcp-client 依赖即可引用内核插件。
#
# ⛔ 生产形态里 url 不该硬编码在这里(应来自实例 env / 平台注入);
# 本文件只服务 POC,值是本机 loopback 上的 stub。
- insert:
- id: carbon-mcp-probe
name: '@dsh-local/carbon-mcp-probe'
- id: carbon-mcp-probe-server
name: '@deepseek-ai/dsh-mcp-client'
config:
transport: streamable-http
serverName: carbonprobe
url: 'http://127.0.0.1:39877/mcp'
toolCallTimeoutMs: 15000
failOnStartupError: true
+7
View File
@@ -0,0 +1,7 @@
// @dsh-local/carbon-mcp-probe — client 半区(POC)
// 本轮问题**只涉及 host 半区**(MCP 工具能否暴露给 agent),
// 故 client 侧刻意留空 apply:不做任何界面、不注入任何槽位。
export function apply() {
// 空 apply —— 见文件头说明
}
+132
View File
@@ -0,0 +1,132 @@
// @dsh-local/carbon-mcp-probe — host 半区(POC 探针)
//
// 本包在 host 侧只做一件事:**取证**。
// 真正的 MCP 挂载不在这里写代码,而是由同目录的 cordis.patch.yml
// insert 一条内核 `@deepseek-ai/dsh-mcp-client` entry 完成 —— 这正是本轮要验证的契约。
//
// 探针把「boot 后 ctx.tools 注册表里到底有没有 MCP 工具」落成 JSON 证据文件,
// 使验收不依赖 agent / 模型 / 界面。
import { appendFileSync, writeFileSync } from "node:fs";
const DUMP = process.env.CARBON_PROBE_DUMP || "/tmp/carbon-probe-dump.json";
const LOG = process.env.CARBON_PROBE_LOG || "/tmp/carbon-probe-host.log";
const DELAY_MS = Number(process.env.CARBON_PROBE_DELAY_MS || 8000);
function log(line) {
const row = `${new Date().toISOString()} ${line}\n`;
try { appendFileSync(LOG, row); } catch { /* 日志不可写不致命 */ }
process.stdout.write(`[carbon-mcp-probe] ${row}`);
}
/** 在有界预算内 BFS 一个对象图,收集所有含 needle 的键/字符串路径。 */
function bfsFind(root, needle, maxDepth = 5, maxNodes = 600) {
const seen = new Set();
const hits = [];
const queue = [[root, "$", 0]];
let visited = 0;
while (queue.length && visited < maxNodes) {
const [value, path, depth] = queue.shift();
if (value === null || value === undefined) continue;
const t = typeof value;
if (t !== "object" && t !== "function") {
if (t === "string" && value.includes(needle)) hits.push({ path, kind: "string", value: value.slice(0, 160) });
continue;
}
if (seen.has(value)) continue;
seen.add(value);
visited += 1;
let keys = [];
try { keys = Object.getOwnPropertyNames(value); } catch { continue; }
for (const k of keys) {
if (k.includes(needle)) hits.push({ path: `${path}.${k}`, kind: "KEY" });
if (depth + 1 > maxDepth) continue;
let child;
try { child = value[k]; } catch { continue; } // getter 抛错就跳过,绝不冒泡
const childPath = `${path}.${k}`;
if (child instanceof Map) {
let i = 0;
for (const [mk, mv] of child) {
const mp = `${childPath}{Map}["${String(mk)}"]`;
if (String(mk).includes(needle)) hits.push({ path: mp, kind: "MAPKEY" });
if (i++ > 250) break;
queue.push([mv, mp, depth + 1]);
}
} else if (Array.isArray(child)) {
child.slice(0, 60).forEach((v, i) => queue.push([v, `${childPath}[${i}]`, depth + 1]));
} else if (typeof child === "string") {
if (child.includes(needle)) hits.push({ path: childPath, kind: "string", value: child.slice(0, 160) });
} else if (child !== null && typeof child === "object") {
queue.push([child, childPath, depth + 1]);
}
}
}
return { hits, visited, truncated: queue.length > 0 };
}
function probeTools(ctx) {
const rec = { ts: new Date().toISOString(), pid: process.pid };
// 通道 A:直接属性(未声明 inject 时应报错 —— 这本身是契约事实)
try {
const direct = ctx.tools;
rec.channelA = { ok: true, type: typeof direct };
} catch (e) {
rec.channelA = { ok: false, error: String(e) };
}
// 通道 B:ctx.get(name)
let tools = null;
try {
tools = typeof ctx.get === "function" ? ctx.get("tools") : null;
rec.channelB = { ok: !!tools, name: tools && tools.name };
} catch (e) {
rec.channelB = { ok: false, error: String(e) };
}
if (!tools) return rec;
// 通道 C:已知候选方法(不预设签名,逐个试)
rec.channelC = {};
for (const m of ["list", "view", "schemas", "sdkSchemas", "get", "names"]) {
try {
if (typeof tools[m] !== "function") { rec.channelC[m] = { skipped: "not a function" }; continue; }
const v = typeof tools[m] === "function" ? tools[m]() : undefined;
if (v && typeof v.then === "function") { rec.channelC[m] = { async: true }; continue; }
rec.channelC[m] = { type: typeof v, preview: JSON.stringify(v).slice(0, 400) };
} catch (e) {
rec.channelC[m] = { threw: String(e).slice(0, 200) };
}
}
// 通道 D:有界 BFS —— 不依赖任何 API 形状,直接找 mcp__ 命名
try {
rec.channelD = bfsFind(tools, "mcp__");
rec.hasMcpQualified = rec.channelD.hits.length > 0;
} catch (e) {
rec.channelD = { error: String(e) };
rec.hasMcpQualified = false;
}
// 通道 E:同上,只要 carbonprobe 片段(防命名前缀差异)
try {
rec.channelE = bfsFind(tools, "carbonprobe", 5, 600);
} catch (e) {
rec.channelE = { error: String(e) };
}
return rec;
}
export function apply(ctx) {
log(`apply pid=${process.pid} node=${process.version} dump=${DUMP}`);
const timer = setTimeout(() => {
let rec;
try { rec = probeTools(ctx); }
catch (e) { rec = { ts: new Date().toISOString(), fatal: String(e) }; }
try { writeFileSync(DUMP, JSON.stringify(rec, null, 2)); log(`dump written -> ${DUMP}`); }
catch (e) { log(`dump write failed: ${e}`); }
log(`SUMMARY hasMcpQualified=${rec.hasMcpQualified} D.hits=${JSON.stringify(rec.channelD?.hits || []).slice(0, 800)}`);
}, DELAY_MS);
timer.unref?.();
}
+29
View File
@@ -0,0 +1,29 @@
{
"name": "@dsh-local/carbon-mcp-probe",
"version": "0.0.1",
"private": true,
"description": "POC(非正式包):验证 dsh 0.1.5-rc.1 业务插件的 host 半区能否把远程 HTTP MCP server 的工具暴露给 agent。仅用于回答契约问题,不承载任何业务功能。",
"type": "module",
"main": "lib/index.js",
"exports": {
".": "./lib/index.js",
"./client": "./lib/client.js"
},
"dsh": {
"bundle": {
"patch": "./cordis.patch.yml"
},
"client": {
"platform": "web",
"inject": []
}
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@deepseek-ai/dsh": "^0.1.5-rc.1"
},
"dependencies": {},
"license": "MIT"
}
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env node
/**
* carbon MCP probe — stub MCP server(零依赖单文件)
*
* 用途:只回答「dsh 的 mcp-client 能否连上并同步工具」这一个问题。
* 不实现任何业务语义,不做任何外部访问。
*
* 用法:
* node stub-mcp-server.mjs # stdio(默认)
* node stub-mcp-server.mjs --http 39877 # Streamable HTTP
*
* 环境变量:
* PROBE_LOG 请求日志落点(默认 ./stub-mcp.log)
*/
import http from "node:http";
import { appendFileSync, writeFileSync } from "node:fs";
const LOG = process.env.PROBE_LOG || "./stub-mcp.log";
const TOOL_NAME = "ping";
const PONG_PREFIX = "pong-carbon-probe";
try { writeFileSync(LOG, ""); } catch { /* 日志不可写不致命 */ }
function log(line) {
const row = `${new Date().toISOString()} ${line}\n`;
try { appendFileSync(LOG, row); } catch { /* ignore */ }
process.stderr.write(row);
}
const PING_TOOL = {
name: TOOL_NAME,
description: "carbon MCP probe stub — 返回固定串与服务端时间",
inputSchema: { type: "object", properties: {}, additionalProperties: false },
};
function callTool(params) {
if (!params || params.name !== TOOL_NAME) {
return { content: [{ type: "text", text: `unknown tool: ${params && params.name}` }], isError: true };
}
const ts = new Date().toISOString();
return { content: [{ type: "text", text: `${PONG_PREFIX} ${ts}` }] };
}
/** 处理一条 JSON-RPC message,返回 response 或 null(notification)。 */
function handle(msg) {
const method = msg && msg.method;
const id = msg && msg.id;
log(`rpc method=${method} id=${id === undefined ? "-" : id}`);
if (method === "initialize") {
return {
jsonrpc: "2.0",
id,
result: {
protocolVersion: msg.params && msg.params.protocolVersion ? msg.params.protocolVersion : "2025-06-18",
capabilities: { tools: { listChanged: false } },
serverInfo: { name: "carbon-probe-stub", version: "0.0.1" },
},
};
}
if (method === "notifications/initialized" || String(method || "").startsWith("notifications/")) return null;
if (method === "ping") return { jsonrpc: "2.0", id, result: {} };
if (method === "tools/list") return { jsonrpc: "2.0", id, result: { tools: [PING_TOOL] } };
if (method === "tools/call") return { jsonrpc: "2.0", id, result: callTool(msg.params) };
if (id === undefined) return null;
return { jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } };
}
function dispatch(payload) {
const msgs = Array.isArray(payload) ? payload : [payload];
const out = [];
for (const m of msgs) {
const r = handle(m);
if (r !== null) out.push(r);
}
return out;
}
/* ---------------- Streamable HTTP ---------------- */
function startHttp(port) {
const server = http.createServer((req, res) => {
if (req.method === "GET" && req.url.startsWith("/health")) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, server: "carbon-probe-stub" }));
return;
}
if (req.method !== "POST") {
res.writeHead(405).end();
return;
}
let body = "";
req.on("data", (c) => { body += c.toString("utf8"); });
req.on("end", () => {
let payload;
try {
payload = JSON.parse(body);
} catch (e) {
log(`http parse-error ${e.message} raw=${body.slice(0, 200)}`);
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } }));
return;
}
const out = dispatch(payload);
if (out.length === 0) {
log("http -> 202 (notification only)");
res.writeHead(202, { "Mcp-Session-Id": "probe-session-1" });
res.end();
return;
}
const payloadOut = out.length === 1 ? out[0] : out;
log(`http -> 200 ${JSON.stringify(payloadOut).slice(0, 300)}`);
res.writeHead(200, {
"Content-Type": "application/json",
"Mcp-Session-Id": "probe-session-1",
});
res.end(JSON.stringify(payloadOut));
});
});
server.listen(port, "127.0.0.1", () => {
log(`stub MCP server listening on http://127.0.0.1:${port}/mcp (transport=streamable-http)`);
});
server.on("error", (e) => { log(`listen error ${e.message}`); process.exit(1); });
}
/* ---------------- stdio ---------------- */
function startStdio() {
log("stub MCP server on stdio (transport=stdio)");
let buf = "";
process.stdin.setEncoding("utf8");
process.stdin.on("data", (chunk) => {
buf += chunk;
let idx;
while ((idx = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, idx).trim();
buf = buf.slice(idx + 1);
if (!line) continue;
let msg;
try { msg = JSON.parse(line); } catch (e) { log(`stdio parse-error ${e.message}`); continue; }
const out = dispatch(msg);
for (const r of out) process.stdout.write(JSON.stringify(r) + "\n");
}
});
process.stdin.on("end", () => process.exit(0));
}
const argv = process.argv.slice(2);
const httpFlag = argv.indexOf("--http");
if (httpFlag >= 0) {
const port = Number(argv[httpFlag + 1] || 39877);
startHttp(port);
} else {
startStdio();
}