@youweichen/pi-web-ui 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -0
  3. package/bin/pi-web-ui.mjs +1818 -0
  4. package/deploy/com.xingshuyin.pi-web-ui.plist +48 -0
  5. package/deploy/nginx-subpath.conf +88 -0
  6. package/deploy/pi-web-ui-task.xml +71 -0
  7. package/deploy/pi-web-ui.service +31 -0
  8. package/dist/server/agent-service.js +2975 -0
  9. package/dist/server/attachments.js +672 -0
  10. package/dist/server/bg-servers.js +150 -0
  11. package/dist/server/client-state.js +234 -0
  12. package/dist/server/control-socket.js +177 -0
  13. package/dist/server/ensure-bash.js +80 -0
  14. package/dist/server/files-service.js +709 -0
  15. package/dist/server/goal-service.js +869 -0
  16. package/dist/server/index.js +879 -0
  17. package/dist/server/mcp-bridge.js +268 -0
  18. package/dist/server/model-admin.js +821 -0
  19. package/dist/server/patch-node-pty.js +80 -0
  20. package/dist/server/plugin-facilities.js +299 -0
  21. package/dist/server/plugin-updater.js +226 -0
  22. package/dist/server/plugins.js +893 -0
  23. package/dist/server/process-utils.js +112 -0
  24. package/dist/server/protocol-version.js +11 -0
  25. package/dist/server/protocol.js +6 -0
  26. package/dist/server/scm.js +298 -0
  27. package/dist/server/serialize.js +172 -0
  28. package/dist/server/settings-service.js +395 -0
  29. package/dist/server/slash-commands.js +263 -0
  30. package/dist/server/terminals.js +1372 -0
  31. package/dist/server/text-sniff.js +268 -0
  32. package/dist/server/uploads.js +107 -0
  33. package/dist/server/vision-bridge.js +131 -0
  34. package/dist/server/webui-context.js +222 -0
  35. package/extensions/webui.ts +192 -0
  36. package/package.json +99 -0
  37. package/web/dist/assets/TerminalPanel-6GBZ9nXN.css +32 -0
  38. package/web/dist/assets/TerminalPanel-D4AktsEn.js +2 -0
  39. package/web/dist/assets/abnfDiagram-VCTEODGH-Ct5StJwt.js +1 -0
  40. package/web/dist/assets/arc-pkuxEN8A.js +1 -0
  41. package/web/dist/assets/architectureDiagram-5GKGNRK7-C6P6yn4y.js +36 -0
  42. package/web/dist/assets/blockDiagram-I7D4REHJ-BmgfH1jt.js +129 -0
  43. package/web/dist/assets/c4Diagram-7LVT6UL2-CgmwTFsK.js +38 -0
  44. package/web/dist/assets/channel-0Y__6sq2.js +1 -0
  45. package/web/dist/assets/chunk-2Q5K7J3B-DX_NqAiH.js +1 -0
  46. package/web/dist/assets/chunk-5VM5RSS4-gLeuQuYv.js +15 -0
  47. package/web/dist/assets/chunk-F27PBJKO-D4WX4mhI.js +1 -0
  48. package/web/dist/assets/chunk-IMKFNOWR-DBpDxGQD.js +231 -0
  49. package/web/dist/assets/chunk-JWPE2WC7-DIQ8iZN5.js +1 -0
  50. package/web/dist/assets/chunk-POPQ4Y6H-F_YTNoGz.js +1 -0
  51. package/web/dist/assets/chunk-SVP7TREG-CmDIvX-r.js +88 -0
  52. package/web/dist/assets/chunk-TICWLB2K-BQUzHQyS.js +206 -0
  53. package/web/dist/assets/chunk-XXDRQBXY-DqR9iffH.js +1 -0
  54. package/web/dist/assets/classDiagram-ZZMXUADV-BEfGIpzu.js +1 -0
  55. package/web/dist/assets/classDiagram-v2-VYDZK3BY-BEfGIpzu.js +1 -0
  56. package/web/dist/assets/cose-bilkent-JH36ORCC-Dl67W4Dx.js +1 -0
  57. package/web/dist/assets/cynefin-OW5HDTMX-fWdqaCpX.js +178 -0
  58. package/web/dist/assets/cynefinDiagram-5FMLGOSQ-CSUAXbaP.js +62 -0
  59. package/web/dist/assets/cytoscape.esm-Bch-eiPH.js +331 -0
  60. package/web/dist/assets/dagre-GXQ25YYZ-CqUVwu1X.js +4 -0
  61. package/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  62. package/web/dist/assets/diagram-S7CK7UJ4-bRce-WYV.js +30 -0
  63. package/web/dist/assets/diagram-UQ7AKVKN-CiqPtPBE.js +41 -0
  64. package/web/dist/assets/diagram-VSXAHHWV-CbEA05LX.js +3 -0
  65. package/web/dist/assets/diagram-VX7I27RA-CRcY9MD-.js +24 -0
  66. package/web/dist/assets/diagram-Z3DM3KII-DmTwvZdS.js +24 -0
  67. package/web/dist/assets/ebnfDiagram-PWID7BFC-DbwmiGVj.js +1 -0
  68. package/web/dist/assets/erDiagram-RLTQ6QDP-Cxiaf87y.js +99 -0
  69. package/web/dist/assets/flowDiagram-HODETNUW-BQiecP1I.js +168 -0
  70. package/web/dist/assets/ganttDiagram-EL5Y4UJY-CSr8RRNw.js +292 -0
  71. package/web/dist/assets/gitGraphDiagram-WWUBYQGX-C4Tdis3g.js +106 -0
  72. package/web/dist/assets/index-CniHcFxT.js +22 -0
  73. package/web/dist/assets/index-DYz2iP_B.css +10 -0
  74. package/web/dist/assets/infoDiagram-27XIBGKW-ij_5A0pY.js +2 -0
  75. package/web/dist/assets/init-Gi6I4Gst.js +1 -0
  76. package/web/dist/assets/ishikawaDiagram-5VMMS53U-C8-tm2LV.js +70 -0
  77. package/web/dist/assets/journeyDiagram-3NMN7TZE-ZJedkNel.js +139 -0
  78. package/web/dist/assets/kanban-definition-UXKFOSKX-BEuoM53n.js +89 -0
  79. package/web/dist/assets/katex-C5jXJg4s.js +257 -0
  80. package/web/dist/assets/layout-zq_9DigZ.js +1 -0
  81. package/web/dist/assets/linear-D-QXJ5Wc.js +1 -0
  82. package/web/dist/assets/markdown-CUPbftfV.js +51 -0
  83. package/web/dist/assets/mermaid.core-CCiG-bOm.js +312 -0
  84. package/web/dist/assets/mindmap-definition-YA3MSWOX-VaAisUIe.js +96 -0
  85. package/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  86. package/web/dist/assets/pegDiagram-XKGWAZYB-D5vmRtkn.js +1 -0
  87. package/web/dist/assets/pieDiagram-E7YTZNPT-loD6V8fn.js +39 -0
  88. package/web/dist/assets/quadrantDiagram-AXDQQJYC-B7E4mH1-.js +7 -0
  89. package/web/dist/assets/railroadDiagram-O6MQD6OU-DTALHvDa.js +1 -0
  90. package/web/dist/assets/react-ZkKJMDQQ.js +24 -0
  91. package/web/dist/assets/requirementDiagram-BXWQKSXE-ZjJKuEcR.js +84 -0
  92. package/web/dist/assets/sankeyDiagram-P5KCCOFB-DufTudDP.js +40 -0
  93. package/web/dist/assets/sequenceDiagram-WJ2MYXX4-B-Zk4alI.js +162 -0
  94. package/web/dist/assets/sizeCapture-INFHLROL-B1pjlxXN.js +1 -0
  95. package/web/dist/assets/stateDiagram-D77RDMKH-BeWg6PIR.js +1 -0
  96. package/web/dist/assets/stateDiagram-v2-MP3YSRHH-DagWhIFJ.js +1 -0
  97. package/web/dist/assets/swimlanes-42K2YHIH-4J4jmMmO.js +1 -0
  98. package/web/dist/assets/swimlanesDiagram-VR7AAH4N-Dsi9x540.js +8 -0
  99. package/web/dist/assets/timeline-definition-24CTP7MA-ByqLfxqe.js +120 -0
  100. package/web/dist/assets/vennDiagram-4TSXK5OY-B_EqVW4R.js +34 -0
  101. package/web/dist/assets/wardleyDiagram-VM6X3IG4-Bu7sA6F8.js +78 -0
  102. package/web/dist/assets/xterm-D1D2FVe3.js +38 -0
  103. package/web/dist/assets/xychartDiagram-S5SC5T6Z-w1KPVGKk.js +7 -0
  104. package/web/dist/favicon.svg +8 -0
  105. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  106. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  107. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  108. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  109. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  110. package/web/dist/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  111. package/web/dist/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  112. package/web/dist/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  113. package/web/dist/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  114. package/web/dist/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  115. package/web/dist/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  116. package/web/dist/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  117. package/web/dist/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  118. package/web/dist/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  119. package/web/dist/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  120. package/web/dist/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  121. package/web/dist/icon.ico +0 -0
  122. package/web/dist/index.html +17 -0
  123. package/web/public/favicon.svg +8 -0
  124. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  125. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  126. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  127. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  128. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  129. package/web/public/fonts/ibm-plex-mono/ibm-plex-mono-latin-ext-600-normal.woff2 +0 -0
  130. package/web/public/fonts/inter/inter-latin-400-normal.woff2 +0 -0
  131. package/web/public/fonts/inter/inter-latin-500-normal.woff2 +0 -0
  132. package/web/public/fonts/inter/inter-latin-600-normal.woff2 +0 -0
  133. package/web/public/fonts/inter/inter-latin-700-normal.woff2 +0 -0
  134. package/web/public/fonts/inter/inter-latin-800-normal.woff2 +0 -0
  135. package/web/public/fonts/inter/inter-latin-ext-400-normal.woff2 +0 -0
  136. package/web/public/fonts/inter/inter-latin-ext-500-normal.woff2 +0 -0
  137. package/web/public/fonts/inter/inter-latin-ext-600-normal.woff2 +0 -0
  138. package/web/public/fonts/inter/inter-latin-ext-700-normal.woff2 +0 -0
  139. package/web/public/fonts/inter/inter-latin-ext-800-normal.woff2 +0 -0
  140. package/web/public/icon.ico +0 -0
@@ -0,0 +1,268 @@
1
+ /**
2
+ * MCP 工具桥 —— 把外部 Model Context Protocol(stdio)服务器暴露的工具接入
3
+ * pi 会话,让 AI 能调用真实的第三方工具(文件、数据库、GitHub…)。
4
+ *
5
+ * 约定(MCP 规范流式子集):
6
+ * - stdio 传输 = stdin/stdout 上换行分隔的 JSON-RPC 2.0(NDJSON),不依赖任何
7
+ * 第三方包;stderr 是自由日志通道。
8
+ * - 握手:initialize(带 protocolVersion)→ notifications/initialized →
9
+ * tools/list → tools/call。
10
+ * - 工具工具入会:本模块把每个远端工具适配成 PluginAgentTool,经
11
+ * pluginToolsProvider 走与插件工具完全相同的 customTools 管线。
12
+ *
13
+ * 配置:<PI_WEB_DATA_DIR>/mcp.json,形如
14
+ * { "servers": { "gitserv": { "command": "node", "args": ["mcp.js"], "cwd": "/x" } } }
15
+ */
16
+ import { spawn } from "node:child_process";
17
+ import { readFileSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ const PROTOCOL_VERSION = "2025-03-26"; // 广泛支持的工具版本
20
+ let rpcSeq = 0;
21
+ /**
22
+ * 单个 MCP 服务器的客户端:管理子进程、请求/响应按 id 关联、握手与工具调用。
23
+ * 线程模型:无需并发控制(MCP 允许乱序 + 我们按请求 id 匹配响应)。
24
+ */
25
+ export class McpClient {
26
+ spec;
27
+ child = null;
28
+ buffer = "";
29
+ nextId = 1;
30
+ pending = new Map();
31
+ log;
32
+ name;
33
+ /** 已握手的工具列表(tools/list 结果缓存)。 */
34
+ tools = [];
35
+ shuttingDown = false;
36
+ constructor(name, spec, log) {
37
+ this.spec = spec;
38
+ this.name = name;
39
+ this.log = log ?? (() => { });
40
+ }
41
+ /** 启动子进程 + 握手 + 拉取工具列表。 */
42
+ async start(timeoutMs = 8000) {
43
+ if (this.child)
44
+ return;
45
+ const { command, args = [], cwd, env } = this.spec;
46
+ this.log(`[mcp:${this.name}] starting: ${command} ${args.join(" ")}`);
47
+ const child = spawn(command, args, {
48
+ cwd: cwd ?? undefined,
49
+ env: { ...process.env, ...env },
50
+ stdio: ["pipe", "pipe", "pipe"],
51
+ windowsHide: true,
52
+ });
53
+ this.child = child;
54
+ child.stderr.on("data", (d) => this.log(`[mcp:${this.name}] stderr:`, d.toString().trimEnd()));
55
+ child.on("error", (err) => this.rejectAll(new Error(`[mcp:${this.name}] spawn error: ${err.message}`)));
56
+ child.on("exit", (code, sig) => {
57
+ this.child = null;
58
+ if (!this.shuttingDown)
59
+ this.rejectAll(new Error(`[mcp:${this.name}] 进程退出 (${sig ?? code})`));
60
+ });
61
+ child.stdout.setEncoding("utf8");
62
+ child.stdout.on("data", (chunk) => this.onData(chunk));
63
+ // 握手
64
+ const handshake = await this.request("initialize", {
65
+ protocolVersion: this.spec.protocolVersion ?? PROTOCOL_VERSION,
66
+ capabilities: {},
67
+ clientInfo: { name: "pi-web-ui", version: "0.41.0" },
68
+ });
69
+ const version = handshake?.protocolVersion ?? this.spec.protocolVersion ?? PROTOCOL_VERSION;
70
+ // 通知 initialized(无 id 的 notification)
71
+ this.send({ jsonrpc: "2.0", method: "notifications/initialized" });
72
+ // 仍以协商协议版本调用 tools(多数服务器对新版本容忍,这里用协商结果)
73
+ void version;
74
+ const listed = (await this.request("tools/list", {}) ?? {});
75
+ this.tools = Array.isArray(listed.tools) ? listed.tools : [];
76
+ this.log(`[mcp:${this.name}] ready, ${this.tools.length} tools`);
77
+ }
78
+ /** 已发现工具。 */
79
+ getTools() {
80
+ return this.tools.map((t) => ({ ...t }));
81
+ }
82
+ /** 调用一个工具,返回结果文本(多 content 拼接为 JSON 字符串保真)。 */
83
+ async call(name, args, timeoutMs = 60000) {
84
+ const res = (await this.request("tools/call", { name, arguments: args }, timeoutMs));
85
+ if (res?.isError) {
86
+ const msg = (res.content ?? []).map((c) => c.text ?? "").join("\n").trim() || "MCP 工具错误";
87
+ throw new Error(msg);
88
+ }
89
+ // 结构化结果优先,其次文本内容。
90
+ if (res?.structuredContent !== undefined)
91
+ return res.structuredContent;
92
+ const text = (res.content ?? []).map((c) => c.text ?? "").filter((x) => x).join("\n");
93
+ return { content: text, isError: !!res.isError };
94
+ }
95
+ /** 关闭:kill 子进程,拒绝所有在途请求。 */
96
+ close() {
97
+ this.shuttingDown = true;
98
+ this.rejectAll(new Error("[mcp] client closed"));
99
+ if (this.child) {
100
+ try {
101
+ this.child.kill();
102
+ }
103
+ catch {
104
+ /* 已退出 */
105
+ }
106
+ this.child = null;
107
+ }
108
+ }
109
+ // -- 内部 -------------------------------------------------------------
110
+ send(msg) {
111
+ const stdin = this.child?.stdin;
112
+ if (!stdin || !stdin.writable)
113
+ return;
114
+ stdin.write(JSON.stringify(msg) + "\n");
115
+ }
116
+ request(method, params, timeoutMs = 8000) {
117
+ const id = (rpcSeq++);
118
+ const outId = String(id);
119
+ return new Promise((resolve, reject) => {
120
+ const timer = setTimeout(() => {
121
+ this.pending.delete(outId);
122
+ reject(new Error(`[mcp:${this.name}] ${method} 超时 (${timeoutMs}ms)`));
123
+ }, timeoutMs);
124
+ this.pending.set(outId, { resolve, reject, timer });
125
+ this.send({ jsonrpc: "2.0", id: id, method, params });
126
+ });
127
+ }
128
+ onData(chunk) {
129
+ this.buffer += chunk;
130
+ let nl;
131
+ while ((nl = this.buffer.indexOf("\n")) >= 0) {
132
+ const line = this.buffer.slice(0, nl).trim();
133
+ this.buffer = this.buffer.slice(nl + 1);
134
+ if (!line)
135
+ continue;
136
+ let msg;
137
+ try {
138
+ msg = JSON.parse(line);
139
+ }
140
+ catch {
141
+ this.log(`[mcp:${this.name}] 非 JSON 行(忽略):`, line.slice(0, 120));
142
+ continue;
143
+ }
144
+ this.handleMessage(msg);
145
+ }
146
+ }
147
+ handleMessage(msg) {
148
+ if (msg.id !== undefined) {
149
+ const pending = this.pending.get(String(msg.id));
150
+ if (!pending) {
151
+ this.log(`[mcp:${this.name}] 未知响应 id=${msg.id}`);
152
+ return;
153
+ }
154
+ this.pending.delete(String(msg.id));
155
+ clearTimeout(pending.timer);
156
+ if (msg.error)
157
+ pending.reject(new Error(`[mcp:${this.name}] ${msg.error.message ?? "MCP 错误"}`));
158
+ else
159
+ pending.resolve(msg.result);
160
+ return;
161
+ }
162
+ // 服务端主动通知(log / cancelled 等)——仅记录。
163
+ if (msg.method === "notifications/message") {
164
+ const p = msg.params;
165
+ if (p?.message)
166
+ this.log(`[mcp:${this.name}] ${p.level ?? "message"}:`, p.message);
167
+ }
168
+ }
169
+ rejectAll(err) {
170
+ for (const [, p] of this.pending) {
171
+ clearTimeout(p.timer);
172
+ p.reject(err);
173
+ }
174
+ this.pending.clear();
175
+ }
176
+ }
177
+ /** 读取 <dataDir>/mcp.json 里的服务器清单(尽力而为)。 */
178
+ export function readMcpConfig(dataDir) {
179
+ try {
180
+ const raw = JSON.parse(readFileSync(join(dataDir, "mcp.json"), "utf8"));
181
+ const servers = {};
182
+ for (const [name, s] of Object.entries(raw.servers ?? {})) {
183
+ if (!s || typeof s.command !== "string" || !s.command.trim())
184
+ continue;
185
+ servers[name] = {
186
+ command: s.command,
187
+ args: Array.isArray(s.args) ? s.args.map(String) : [],
188
+ cwd: typeof s.cwd === "string" ? s.cwd : undefined,
189
+ env: s.env && typeof s.env === "object" ? s.env : undefined,
190
+ };
191
+ }
192
+ return { servers };
193
+ }
194
+ catch {
195
+ return { servers: {} };
196
+ }
197
+ }
198
+ /**
199
+ * 整个 MCP 管理器的工具适配:把每个 MCP 工具变成 PluginAgentTool。
200
+ * getAllToolsTool(name, callFn) 生成 execute → 转发到对应 McpClient.call。
201
+ */
202
+ function adaptMcpTool(serverName, mcpTool, client) {
203
+ const name = sanitizeToolName(mcpTool.name);
204
+ return {
205
+ name,
206
+ label: `${serverName} · ${mcpTool.name}`,
207
+ description: mcpTool.description ?? `从 MCP 服务器「${serverName}」提供的工具 ${mcpTool.name}`,
208
+ parameters: mcpTool.inputSchema ?? {},
209
+ execute: async (_toolCallId, params, _signal) => {
210
+ return client.call(mcpTool.name, params ?? {});
211
+ },
212
+ };
213
+ }
214
+ /** 工具名必须是 [A-Za-z0-9_-]+(与插件工具同规则),MCP 可能含冒号/斜杠 — 归一化。 */
215
+ function sanitizeToolName(name) {
216
+ const cleaned = (name || "").replace(/[^A-Za-z0-9_-]/g, "_");
217
+ return cleaned || "mcp_tool";
218
+ }
219
+ /** MCP 服务器管理器:自管多服务器生命周期 + 聚合工具。 */
220
+ export class McpBridge {
221
+ dataDir;
222
+ log;
223
+ opts;
224
+ clients = [];
225
+ tools = [];
226
+ constructor(dataDir, log = () => { }, opts = {}) {
227
+ this.dataDir = dataDir;
228
+ this.log = log;
229
+ this.opts = opts;
230
+ }
231
+ /** 读取配置并启动全部服务器(顺序 fail-fast:单个失败记日志不拖垮其它)。 */
232
+ async load() {
233
+ const cfg = optsOverrideOrRead(this.opts.specOverride, this.dataDir);
234
+ await Promise.all(Object.entries(cfg.servers).map(async ([name, spec]) => {
235
+ try {
236
+ const client = new McpClient(name, spec, this.log);
237
+ await client.start();
238
+ this.clients.push(client);
239
+ for (const t of client.getTools())
240
+ this.tools.push(adaptMcpTool(name, t, client));
241
+ }
242
+ catch (err) {
243
+ this.log(`[mcp] 服务器「${name}」启动失败:`, err instanceof Error ? err.message : err);
244
+ }
245
+ }));
246
+ }
247
+ getTools() {
248
+ return this.tools;
249
+ }
250
+ hasServers() {
251
+ return this.clients.length > 0;
252
+ }
253
+ dispose() {
254
+ for (const c of this.clients)
255
+ c.close();
256
+ this.clients = [];
257
+ this.tools = [];
258
+ }
259
+ }
260
+ function optsOverrideOrRead(specOverride, dataDir) {
261
+ if (specOverride && specOverride.length > 0) {
262
+ const servers = {};
263
+ for (const o of specOverride)
264
+ servers[o.name] = o.spec;
265
+ return { servers };
266
+ }
267
+ return readMcpConfig(dataDir);
268
+ }