@ppdocs/mcp 3.2.20 → 3.2.22
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.
- package/dist/storage/httpClient.d.ts +30 -24
- package/dist/storage/httpClient.js +134 -79
- package/dist/storage/types.d.ts +0 -21
- package/dist/tools/analyzer.d.ts +4 -3
- package/dist/tools/analyzer.js +13 -241
- package/dist/tools/discussion.js +49 -12
- package/dist/tools/files.d.ts +1 -1
- package/dist/tools/files.js +61 -8
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +3 -3
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* API URL 格式: http://localhost:20001/api/:projectId/:password/...
|
|
6
6
|
*/
|
|
7
|
-
import type { DocData, DocNode,
|
|
7
|
+
import type { DocData, DocNode, Task, TaskSummary, TaskLogType, TaskExperience, FileInfo, RuleMeta } from './types.js';
|
|
8
8
|
interface TreeNode {
|
|
9
9
|
path: string;
|
|
10
10
|
name: string;
|
|
@@ -22,7 +22,6 @@ export declare class PpdocsApiClient {
|
|
|
22
22
|
createDoc(docPath: string, doc: Partial<DocData>): Promise<DocData>;
|
|
23
23
|
updateDoc(docPath: string, updates: Partial<DocData>): Promise<DocData | null>;
|
|
24
24
|
deleteDoc(docPath: string): Promise<boolean>;
|
|
25
|
-
searchDocs(keywords: string[], limit?: number): Promise<DocSearchResult[]>;
|
|
26
25
|
/** 按状态筛选文档 */
|
|
27
26
|
getDocsByStatus(statusList: string[]): Promise<DocNode[]>;
|
|
28
27
|
/** 获取目录树结构 */
|
|
@@ -43,14 +42,6 @@ export declare class PpdocsApiClient {
|
|
|
43
42
|
}, creator: string): Promise<Task>;
|
|
44
43
|
addTaskLog(taskId: string, logType: TaskLogType, content: string): Promise<Task | null>;
|
|
45
44
|
completeTask(taskId: string, experience: TaskExperience): Promise<Task | null>;
|
|
46
|
-
/** 创建项目 (公开路由,无需认证) */
|
|
47
|
-
createProject(id: string, name: string, description?: string, projectPath?: string): Promise<{
|
|
48
|
-
project: {
|
|
49
|
-
id: string;
|
|
50
|
-
name: string;
|
|
51
|
-
};
|
|
52
|
-
password: string;
|
|
53
|
-
}>;
|
|
54
45
|
/** 列出所有可访问的项目 */
|
|
55
46
|
crossListProjects(): Promise<{
|
|
56
47
|
id: string;
|
|
@@ -77,12 +68,10 @@ export declare class PpdocsApiClient {
|
|
|
77
68
|
localPath: string;
|
|
78
69
|
fileCount: number;
|
|
79
70
|
}>;
|
|
80
|
-
/**
|
|
81
|
-
uploadFiles(
|
|
71
|
+
/** 上传本地目录或单文件到中心服务器 (打包 zip → POST) */
|
|
72
|
+
uploadFiles(localPath: string, remoteDir?: string): Promise<{
|
|
82
73
|
fileCount: number;
|
|
83
74
|
}>;
|
|
84
|
-
/** 清空项目文件存储区 */
|
|
85
|
-
clearFiles(): Promise<void>;
|
|
86
75
|
/** 上传已打包好的 zip 数据到本项目 files/ (供 Code Beacon 使用) */
|
|
87
76
|
uploadRawZip(zipData: Buffer | Uint8Array | any, remoteDir?: string): Promise<{
|
|
88
77
|
fileCount: number;
|
|
@@ -98,12 +87,6 @@ export declare class PpdocsApiClient {
|
|
|
98
87
|
}>;
|
|
99
88
|
/** 扫描项目代码, 构建/更新索引 */
|
|
100
89
|
analyzerScan(projectPath: string, force?: boolean): Promise<unknown>;
|
|
101
|
-
/** 查询代码符号 (模糊匹配) */
|
|
102
|
-
analyzerQuery(projectPath: string, query: string): Promise<unknown>;
|
|
103
|
-
/** 分层影响分析 (BFS 递归, 可指定深度) */
|
|
104
|
-
analyzerImpactTree(projectPath: string, symbolName: string, depth?: number): Promise<unknown>;
|
|
105
|
-
/** 获取文件 360° 上下文 */
|
|
106
|
-
analyzerContext(projectPath: string, filePath: string): Promise<unknown>;
|
|
107
90
|
/** 智能上下文: 代码+文档+规则+任务全关联 */
|
|
108
91
|
analyzerSmartContext(projectPath: string, symbolName: string): Promise<unknown>;
|
|
109
92
|
/** 全关联路径: 两个符号之间的代码+文档路径 */
|
|
@@ -112,19 +95,42 @@ export declare class PpdocsApiClient {
|
|
|
112
95
|
/** 列出活跃讨论 (公开路由) */
|
|
113
96
|
discussionList(): Promise<unknown>;
|
|
114
97
|
/** 创建讨论 (公开路由) */
|
|
115
|
-
discussionCreate(title: string, initiator: string, participants: string[], content: string): Promise<{
|
|
98
|
+
discussionCreate(title: string, initiator: string, participants: string[], content: string, msgSummary?: string): Promise<{
|
|
116
99
|
id: string;
|
|
117
100
|
}>;
|
|
118
|
-
/** 批量读取讨论 (
|
|
119
|
-
discussionReadByIds(ids: string[]): Promise<unknown>;
|
|
101
|
+
/** 批量读取讨论 (公开路由, 支持已读追踪) */
|
|
102
|
+
discussionReadByIds(ids: string[], reader?: string, mode?: string): Promise<unknown>;
|
|
103
|
+
/** 列出所有讨论 (含历史, 按参与方筛选) */
|
|
104
|
+
discussionListAll(participant?: string): Promise<unknown>;
|
|
120
105
|
/** 回复讨论 (公开路由) */
|
|
121
|
-
discussionReply(id: string, sender: string, content: string, newSummary?: string): Promise<boolean>;
|
|
106
|
+
discussionReply(id: string, sender: string, content: string, msgSummary?: string, newSummary?: string): Promise<boolean>;
|
|
107
|
+
/** 完成讨论 (公开路由, 标记为 completed) */
|
|
108
|
+
discussionComplete(id: string): Promise<boolean>;
|
|
122
109
|
/** 删除讨论 (公开路由) */
|
|
123
110
|
discussionDelete(id: string): Promise<boolean>;
|
|
124
111
|
/** 结案归档讨论 (认证路由) */
|
|
125
112
|
discussionClose(id: string, conclusion: string): Promise<{
|
|
126
113
|
archived_path: string;
|
|
127
114
|
}>;
|
|
115
|
+
/** 列出公共文件 */
|
|
116
|
+
publicFilesList(dir?: string): Promise<FileInfo[]>;
|
|
117
|
+
/** 读取公共文件 (文本) */
|
|
118
|
+
publicFilesRead(filePath: string): Promise<string>;
|
|
119
|
+
/** 创建公共文件池子目录 */
|
|
120
|
+
publicFilesMkdir(dirPath: string): Promise<boolean>;
|
|
121
|
+
/** 重命名公共文件或目录 */
|
|
122
|
+
publicFilesRename(filePath: string, newName: string): Promise<boolean>;
|
|
123
|
+
/** 删除公共文件 */
|
|
124
|
+
publicFilesDelete(filePath: string): Promise<boolean>;
|
|
125
|
+
/** 下载公共文件或目录 */
|
|
126
|
+
publicFilesDownload(remotePath: string, localPath?: string): Promise<{
|
|
127
|
+
localPath: string;
|
|
128
|
+
fileCount: number;
|
|
129
|
+
}>;
|
|
130
|
+
/** 上传本地目录或单文件到公共文件池 */
|
|
131
|
+
publicFilesUpload(localPath: string, remoteDir?: string): Promise<{
|
|
132
|
+
fileCount: number;
|
|
133
|
+
}>;
|
|
128
134
|
meetingJoin(agentId: string, agentType: string): Promise<unknown>;
|
|
129
135
|
meetingLeave(agentId: string): Promise<unknown>;
|
|
130
136
|
meetingPost(agentId: string, content: string, msgType?: string): Promise<unknown>;
|
|
@@ -191,12 +191,6 @@ export class PpdocsApiClient {
|
|
|
191
191
|
return false;
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
|
-
async searchDocs(keywords, limit = 20) {
|
|
195
|
-
return this.request('/docs/search', {
|
|
196
|
-
method: 'POST',
|
|
197
|
-
body: JSON.stringify({ keywords, limit })
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
194
|
/** 按状态筛选文档 */
|
|
201
195
|
async getDocsByStatus(statusList) {
|
|
202
196
|
return this.request('/docs/by-status', {
|
|
@@ -328,22 +322,6 @@ export class PpdocsApiClient {
|
|
|
328
322
|
return null;
|
|
329
323
|
}
|
|
330
324
|
}
|
|
331
|
-
// ============ 项目管理 ============
|
|
332
|
-
/** 创建项目 (公开路由,无需认证) */
|
|
333
|
-
async createProject(id, name, description, projectPath) {
|
|
334
|
-
const url = `${this.serverUrl}/api/projects`;
|
|
335
|
-
const response = await fetch(url, {
|
|
336
|
-
method: 'POST',
|
|
337
|
-
headers: { 'Content-Type': 'application/json' },
|
|
338
|
-
body: JSON.stringify({ id, name, description: description || '', project_path: projectPath || '' }),
|
|
339
|
-
});
|
|
340
|
-
if (!response.ok) {
|
|
341
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
342
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
343
|
-
}
|
|
344
|
-
const json = await response.json();
|
|
345
|
-
return json.data;
|
|
346
|
-
}
|
|
347
325
|
// ============ 跨项目只读访问 ============
|
|
348
326
|
/** 列出所有可访问的项目 */
|
|
349
327
|
async crossListProjects() {
|
|
@@ -397,33 +375,41 @@ export class PpdocsApiClient {
|
|
|
397
375
|
async download(remotePath, localPath) {
|
|
398
376
|
return fetchAndExtractZip(`${this.baseUrl}/files-download/${cleanPath(remotePath)}`, localPath);
|
|
399
377
|
}
|
|
400
|
-
/**
|
|
401
|
-
async uploadFiles(
|
|
378
|
+
/** 上传本地目录或单文件到中心服务器 (打包 zip → POST) */
|
|
379
|
+
async uploadFiles(localPath, remoteDir) {
|
|
402
380
|
const fs = await import('fs');
|
|
403
381
|
const path = await import('path');
|
|
404
382
|
const AdmZip = (await import('adm-zip')).default;
|
|
405
|
-
if (!fs.existsSync(
|
|
406
|
-
throw new Error(
|
|
383
|
+
if (!fs.existsSync(localPath)) {
|
|
384
|
+
throw new Error(`本地路径不存在: ${localPath}`);
|
|
407
385
|
}
|
|
408
386
|
const zip = new AdmZip();
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (entry.isDirectory()) {
|
|
415
|
-
if (EXCLUDED_DIRS.has(entry.name))
|
|
416
|
-
continue;
|
|
417
|
-
addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
if (fs.statSync(fullPath).size > 10_485_760)
|
|
387
|
+
const stat = fs.statSync(localPath);
|
|
388
|
+
if (stat.isDirectory()) {
|
|
389
|
+
const addDir = (dirPath, zipPath) => {
|
|
390
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
391
|
+
if (entry.name.startsWith('.'))
|
|
421
392
|
continue;
|
|
422
|
-
|
|
393
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
394
|
+
if (entry.isDirectory()) {
|
|
395
|
+
if (EXCLUDED_DIRS.has(entry.name))
|
|
396
|
+
continue;
|
|
397
|
+
addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
if (fs.statSync(fullPath).size > 10_485_760)
|
|
401
|
+
continue;
|
|
402
|
+
zip.addLocalFile(fullPath, zipPath || undefined);
|
|
403
|
+
}
|
|
423
404
|
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
405
|
+
};
|
|
406
|
+
addDir(localPath, '');
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
if (stat.size > 10_485_760)
|
|
410
|
+
throw new Error('文件超过 10MB 限制');
|
|
411
|
+
zip.addLocalFile(localPath);
|
|
412
|
+
}
|
|
427
413
|
const buffer = zip.toBuffer();
|
|
428
414
|
if (buffer.length > 104_857_600) {
|
|
429
415
|
throw new Error('打包后超过 100MB 限制,请缩小目录范围');
|
|
@@ -441,15 +427,6 @@ export class PpdocsApiClient {
|
|
|
441
427
|
const result = await response.json();
|
|
442
428
|
return { fileCount: result.data?.fileCount || 0 };
|
|
443
429
|
}
|
|
444
|
-
/** 清空项目文件存储区 */
|
|
445
|
-
async clearFiles() {
|
|
446
|
-
const url = `${this.baseUrl}/files`;
|
|
447
|
-
const response = await fetch(url, { method: 'DELETE' });
|
|
448
|
-
if (!response.ok) {
|
|
449
|
-
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
450
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
430
|
// ============ 直接上传已打包的 ZIP ============
|
|
454
431
|
/** 上传已打包好的 zip 数据到本项目 files/ (供 Code Beacon 使用) */
|
|
455
432
|
async uploadRawZip(zipData, remoteDir) {
|
|
@@ -488,27 +465,6 @@ export class PpdocsApiClient {
|
|
|
488
465
|
body: JSON.stringify({ project_path: projectPath, force }),
|
|
489
466
|
});
|
|
490
467
|
}
|
|
491
|
-
/** 查询代码符号 (模糊匹配) */
|
|
492
|
-
async analyzerQuery(projectPath, query) {
|
|
493
|
-
return this.request('/analyzer/query', {
|
|
494
|
-
method: 'POST',
|
|
495
|
-
body: JSON.stringify({ project_path: projectPath, query }),
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
/** 分层影响分析 (BFS 递归, 可指定深度) */
|
|
499
|
-
async analyzerImpactTree(projectPath, symbolName, depth = 2) {
|
|
500
|
-
return this.request('/analyzer/impact-tree', {
|
|
501
|
-
method: 'POST',
|
|
502
|
-
body: JSON.stringify({ project_path: projectPath, symbol_name: symbolName, depth }),
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
/** 获取文件 360° 上下文 */
|
|
506
|
-
async analyzerContext(projectPath, filePath) {
|
|
507
|
-
return this.request('/analyzer/context', {
|
|
508
|
-
method: 'POST',
|
|
509
|
-
body: JSON.stringify({ project_path: projectPath, file_path: filePath }),
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
468
|
/** 智能上下文: 代码+文档+规则+任务全关联 */
|
|
513
469
|
async analyzerSmartContext(projectPath, symbolName) {
|
|
514
470
|
return this.request('/analyzer/smart-context', {
|
|
@@ -544,24 +500,37 @@ export class PpdocsApiClient {
|
|
|
544
500
|
return this.publicRequest('/api/discussions');
|
|
545
501
|
}
|
|
546
502
|
/** 创建讨论 (公开路由) */
|
|
547
|
-
async discussionCreate(title, initiator, participants, content) {
|
|
503
|
+
async discussionCreate(title, initiator, participants, content, msgSummary) {
|
|
548
504
|
return this.publicRequest('/api/discussions', {
|
|
549
505
|
method: 'POST',
|
|
550
|
-
body: JSON.stringify({ title, initiator, participants, content }),
|
|
506
|
+
body: JSON.stringify({ title, initiator, participants, content, msg_summary: msgSummary }),
|
|
551
507
|
});
|
|
552
508
|
}
|
|
553
|
-
/** 批量读取讨论 (
|
|
554
|
-
async discussionReadByIds(ids) {
|
|
509
|
+
/** 批量读取讨论 (公开路由, 支持已读追踪) */
|
|
510
|
+
async discussionReadByIds(ids, reader, mode) {
|
|
555
511
|
return this.publicRequest('/api/discussions/read', {
|
|
556
512
|
method: 'POST',
|
|
557
|
-
body: JSON.stringify({ ids }),
|
|
513
|
+
body: JSON.stringify({ ids, reader, mode }),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
/** 列出所有讨论 (含历史, 按参与方筛选) */
|
|
517
|
+
async discussionListAll(participant) {
|
|
518
|
+
return this.publicRequest('/api/discussions/all', {
|
|
519
|
+
method: 'POST',
|
|
520
|
+
body: JSON.stringify({ participant }),
|
|
558
521
|
});
|
|
559
522
|
}
|
|
560
523
|
/** 回复讨论 (公开路由) */
|
|
561
|
-
async discussionReply(id, sender, content, newSummary) {
|
|
524
|
+
async discussionReply(id, sender, content, msgSummary, newSummary) {
|
|
562
525
|
return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/reply`, {
|
|
563
526
|
method: 'POST',
|
|
564
|
-
body: JSON.stringify({ sender, content, new_summary: newSummary }),
|
|
527
|
+
body: JSON.stringify({ sender, content, msg_summary: msgSummary, new_summary: newSummary }),
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/** 完成讨论 (公开路由, 标记为 completed) */
|
|
531
|
+
async discussionComplete(id) {
|
|
532
|
+
return this.publicRequest(`/api/discussions/${encodeURIComponent(id)}/complete`, {
|
|
533
|
+
method: 'POST',
|
|
565
534
|
});
|
|
566
535
|
}
|
|
567
536
|
/** 删除讨论 (公开路由) */
|
|
@@ -577,6 +546,92 @@ export class PpdocsApiClient {
|
|
|
577
546
|
body: JSON.stringify({ conclusion }),
|
|
578
547
|
});
|
|
579
548
|
}
|
|
549
|
+
// ============ 公共文件池 ============
|
|
550
|
+
/** 列出公共文件 */
|
|
551
|
+
async publicFilesList(dir) {
|
|
552
|
+
const query = dir ? `?dir=${encodeURIComponent(dir)}` : '';
|
|
553
|
+
return this.publicRequest(`/api/public-files${query}`);
|
|
554
|
+
}
|
|
555
|
+
/** 读取公共文件 (文本) */
|
|
556
|
+
async publicFilesRead(filePath) {
|
|
557
|
+
return this.publicRequest(`/api/public-files/read/${cleanPath(filePath)}`);
|
|
558
|
+
}
|
|
559
|
+
/** 创建公共文件池子目录 */
|
|
560
|
+
async publicFilesMkdir(dirPath) {
|
|
561
|
+
return this.publicRequest(`/api/public-files/mkdir`, {
|
|
562
|
+
method: 'POST',
|
|
563
|
+
body: JSON.stringify({ path: dirPath }),
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
/** 重命名公共文件或目录 */
|
|
567
|
+
async publicFilesRename(filePath, newName) {
|
|
568
|
+
return this.publicRequest(`/api/public-files/rename`, {
|
|
569
|
+
method: 'POST',
|
|
570
|
+
body: JSON.stringify({ path: filePath, new_name: newName }),
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
/** 删除公共文件 */
|
|
574
|
+
async publicFilesDelete(filePath) {
|
|
575
|
+
return this.publicRequest(`/api/public-files/${cleanPath(filePath)}`, {
|
|
576
|
+
method: 'DELETE',
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
/** 下载公共文件或目录 */
|
|
580
|
+
async publicFilesDownload(remotePath, localPath) {
|
|
581
|
+
return fetchAndExtractZip(`${this.serverUrl}/api/public-files/download/${cleanPath(remotePath)}`, localPath);
|
|
582
|
+
}
|
|
583
|
+
/** 上传本地目录或单文件到公共文件池 */
|
|
584
|
+
async publicFilesUpload(localPath, remoteDir) {
|
|
585
|
+
const fs = await import('fs');
|
|
586
|
+
const path = await import('path');
|
|
587
|
+
const AdmZip = (await import('adm-zip')).default;
|
|
588
|
+
if (!fs.existsSync(localPath)) {
|
|
589
|
+
throw new Error(`本地路径不存在: ${localPath}`);
|
|
590
|
+
}
|
|
591
|
+
const zip = new AdmZip();
|
|
592
|
+
const stat = fs.statSync(localPath);
|
|
593
|
+
if (stat.isDirectory()) {
|
|
594
|
+
const addDir = (dirPath, zipPath) => {
|
|
595
|
+
for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
|
|
596
|
+
if (entry.name.startsWith('.'))
|
|
597
|
+
continue;
|
|
598
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
599
|
+
if (entry.isDirectory()) {
|
|
600
|
+
if (EXCLUDED_DIRS.has(entry.name))
|
|
601
|
+
continue;
|
|
602
|
+
addDir(fullPath, zipPath ? `${zipPath}/${entry.name}` : entry.name);
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
if (fs.statSync(fullPath).size > 10_485_760)
|
|
606
|
+
continue;
|
|
607
|
+
zip.addLocalFile(fullPath, zipPath || undefined);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
addDir(localPath, '');
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
if (stat.size > 10_485_760)
|
|
615
|
+
throw new Error('文件超过 10MB 限制');
|
|
616
|
+
zip.addLocalFile(localPath);
|
|
617
|
+
}
|
|
618
|
+
const buffer = zip.toBuffer();
|
|
619
|
+
if (buffer.length > 104_857_600) {
|
|
620
|
+
throw new Error('打包后超过 100MB 限制');
|
|
621
|
+
}
|
|
622
|
+
const query = remoteDir ? `?dir=${encodeURIComponent(remoteDir)}` : '';
|
|
623
|
+
const response = await fetch(`${this.serverUrl}/api/public-files/upload${query}`, {
|
|
624
|
+
method: 'POST',
|
|
625
|
+
headers: { 'Content-Type': 'application/octet-stream' },
|
|
626
|
+
body: new Uint8Array(buffer),
|
|
627
|
+
});
|
|
628
|
+
if (!response.ok) {
|
|
629
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
630
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
631
|
+
}
|
|
632
|
+
const result = await response.json();
|
|
633
|
+
return { fileCount: result.data?.fileCount || 0 };
|
|
634
|
+
}
|
|
580
635
|
// ============ 多AI协作会议 ============
|
|
581
636
|
async meetingJoin(agentId, agentType) {
|
|
582
637
|
return this.request('/meeting/join', {
|
package/dist/storage/types.d.ts
CHANGED
|
@@ -26,27 +26,6 @@ export interface DocNode {
|
|
|
26
26
|
isDir: boolean;
|
|
27
27
|
status?: string;
|
|
28
28
|
}
|
|
29
|
-
/** 文档搜索结果 */
|
|
30
|
-
export interface DocSearchResult {
|
|
31
|
-
path: string;
|
|
32
|
-
name: string;
|
|
33
|
-
summary: string;
|
|
34
|
-
score: number;
|
|
35
|
-
}
|
|
36
|
-
export interface ProjectMeta {
|
|
37
|
-
projectId: string;
|
|
38
|
-
projectName: string;
|
|
39
|
-
updatedAt: string;
|
|
40
|
-
password?: string;
|
|
41
|
-
}
|
|
42
|
-
export interface Project {
|
|
43
|
-
id: string;
|
|
44
|
-
name: string;
|
|
45
|
-
description?: string;
|
|
46
|
-
updatedAt: string;
|
|
47
|
-
createdAt?: string;
|
|
48
|
-
projectPath?: string;
|
|
49
|
-
}
|
|
50
29
|
export interface RuleMeta {
|
|
51
30
|
label: string;
|
|
52
31
|
keywords: string[];
|
package/dist/tools/analyzer.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 代码分析引擎工具 (
|
|
3
|
-
* code_scan,
|
|
2
|
+
* 代码分析引擎工具 (3个)
|
|
3
|
+
* code_scan, code_smart_context, code_full_path
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* code_smart_context: 代码+KG文档全关联 (含影响范围)
|
|
6
|
+
* code_full_path: 两点间代码链路+共享KG文档
|
|
6
7
|
*/
|
|
7
8
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
9
|
import { type McpContext } from './shared.js';
|
package/dist/tools/analyzer.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 代码分析引擎工具 (
|
|
3
|
-
* code_scan,
|
|
2
|
+
* 代码分析引擎工具 (3个)
|
|
3
|
+
* code_scan, code_smart_context, code_full_path
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* code_smart_context: 代码+KG文档全关联 (含影响范围)
|
|
6
|
+
* code_full_path: 两点间代码链路+共享KG文档
|
|
6
7
|
*/
|
|
7
8
|
import { z } from 'zod';
|
|
8
9
|
import { getClient } from '../storage/httpClient.js';
|
|
@@ -13,17 +14,14 @@ const SYMBOL_ICONS = {
|
|
|
13
14
|
type_alias: '𝐓', enum: '𝐄', struct: '𝐒', trait: '⚡',
|
|
14
15
|
constant: '𝐊', variable: '𝑣',
|
|
15
16
|
};
|
|
16
|
-
const SEVERITY_ICONS = {
|
|
17
|
-
critical: '🔴', warning: '🟡', info: '🟢',
|
|
18
|
-
};
|
|
19
17
|
export function registerAnalyzerTools(server, ctx) {
|
|
20
18
|
const client = () => getClient();
|
|
21
19
|
// ===== code_scan: 扫描项目代码 =====
|
|
22
|
-
server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用
|
|
23
|
-
projectPath: z.string().describe('项目源码的绝对路径(如"D:/projects/myapp")'),
|
|
20
|
+
server.tool('code_scan', '📡 扫描项目代码, 构建索引。返回文件数、符号数、语言统计。★首次使用 code_smart_context/code_full_path 前必须先执行★', {
|
|
21
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(如"D:/projects/myapp")。不传则自动从Beacon同步目录或项目配置解析'),
|
|
24
22
|
force: z.boolean().optional().describe('是否强制全量重建(默认false, 增量更新)'),
|
|
25
23
|
}, async (args) => safeTool(async () => {
|
|
26
|
-
const result = await client().analyzerScan(args.projectPath, args.force ?? false);
|
|
24
|
+
const result = await client().analyzerScan(args.projectPath || '', args.force ?? false);
|
|
27
25
|
return wrap([
|
|
28
26
|
`✅ 代码扫描完成`,
|
|
29
27
|
``,
|
|
@@ -34,238 +32,12 @@ export function registerAnalyzerTools(server, ctx) {
|
|
|
34
32
|
`- 索引时间: ${result.indexedAt}`,
|
|
35
33
|
].join('\n'));
|
|
36
34
|
}));
|
|
37
|
-
// ===== code_query: 搜索代码符号 =====
|
|
38
|
-
server.tool('code_query', '🔤 搜索代码符号(函数/类/方法/接口/类型)。返回匹配列表+文件路径+行号。定位代码位置的最快方式。需先运行 code_scan', {
|
|
39
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
40
|
-
query: z.string().describe('搜索关键词(函数名/类名/方法名等)'),
|
|
41
|
-
}, async (args) => safeTool(async () => {
|
|
42
|
-
const results = await client().analyzerQuery(args.projectPath, args.query);
|
|
43
|
-
if (!results || results.length === 0) {
|
|
44
|
-
return wrap(`未找到匹配 "${args.query}" 的符号。请确认已运行 code_scan`);
|
|
45
|
-
}
|
|
46
|
-
const lines = results.map(r => {
|
|
47
|
-
const icon = SYMBOL_ICONS[r.symbol.kind] || '?';
|
|
48
|
-
const exp = r.symbol.exported ? ' [export]' : '';
|
|
49
|
-
const parent = r.symbol.parent ? ` ◀ ${r.symbol.parent}` : '';
|
|
50
|
-
return ` ${icon} ${r.symbol.name}${parent}${exp} → ${r.filePath}:${r.symbol.lineStart}`;
|
|
51
|
-
});
|
|
52
|
-
return wrap([
|
|
53
|
-
`🔤 找到 ${results.length} 个匹配符号:`,
|
|
54
|
-
``,
|
|
55
|
-
...lines,
|
|
56
|
-
].join('\n'));
|
|
57
|
-
}));
|
|
58
|
-
// ===== code_impact: 分层影响分析 =====
|
|
59
|
-
server.tool('code_impact', '💥 爆炸半径分析 ★修改代码前必查★ 分析修改一个函数/类/类型会影响多少文件。BFS分层输出: L1🔴直接引用=必须检查, L2🟡间接引用=建议检查, L3🟢传递引用=注意。修改任何公共接口、函数签名、类型定义前务必先运行!', {
|
|
60
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
61
|
-
symbolName: z.string().describe('要分析的符号名称(如"AuthService", "handleLogin")'),
|
|
62
|
-
depth: z.number().optional().describe('分析深度层级(1-5, 默认2)。1=仅直接引用, 3=深度追踪'),
|
|
63
|
-
}, async (args) => safeTool(async () => {
|
|
64
|
-
const result = await client().analyzerImpactTree(args.projectPath, args.symbolName, args.depth ?? 2);
|
|
65
|
-
if (!result) {
|
|
66
|
-
return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
|
|
67
|
-
}
|
|
68
|
-
if (result.levels.length === 0) {
|
|
69
|
-
return wrap(`✅ "${args.symbolName}" 没有被其他文件引用, 修改安全`);
|
|
70
|
-
}
|
|
71
|
-
// 生成分层树形输出
|
|
72
|
-
const lines = [
|
|
73
|
-
`🎯 ${result.symbolName} (${result.symbolKind})`,
|
|
74
|
-
`📍 定义: ${result.definedIn}:${result.lineStart}`,
|
|
75
|
-
``,
|
|
76
|
-
`📊 ${result.summary}`,
|
|
77
|
-
``,
|
|
78
|
-
];
|
|
79
|
-
for (const level of result.levels) {
|
|
80
|
-
const icon = SEVERITY_ICONS[level.severity] || '⚪';
|
|
81
|
-
const label = level.depth === 1 ? '直接引用 (必须检查)' :
|
|
82
|
-
level.depth === 2 ? '间接引用 (建议检查)' :
|
|
83
|
-
'传递引用 (注意)';
|
|
84
|
-
lines.push(`### ${icon} L${level.depth} ${label} (${level.count}个)`);
|
|
85
|
-
for (const entry of level.entries) {
|
|
86
|
-
lines.push(` ${entry.filePath}:${entry.line} [${entry.refKind}] ${entry.context}`);
|
|
87
|
-
}
|
|
88
|
-
lines.push('');
|
|
89
|
-
}
|
|
90
|
-
return wrap(lines.join('\n'));
|
|
91
|
-
}));
|
|
92
|
-
// ===== code_context: 文件360°上下文 =====
|
|
93
|
-
server.tool('code_context', '🔍 文件360°上下文 — 定义了什么符号、导入了什么、被谁引用。修改文件前使用, 快速了解所有依赖关系, 避免遗漏', {
|
|
94
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
95
|
-
filePath: z.string().describe('目标文件的相对路径(如"src/services/auth.ts")'),
|
|
96
|
-
}, async (args) => safeTool(async () => {
|
|
97
|
-
const fileCtx = await client().analyzerContext(args.projectPath, args.filePath);
|
|
98
|
-
if (!fileCtx) {
|
|
99
|
-
return wrap(`未找到文件 "${args.filePath}"。请确认路径正确, 路径格式为相对路径(如 src/main.ts)`);
|
|
100
|
-
}
|
|
101
|
-
const lines = [
|
|
102
|
-
`📄 ${fileCtx.filePath}`,
|
|
103
|
-
`语言: ${fileCtx.language} | ${fileCtx.linesTotal} 行`,
|
|
104
|
-
``,
|
|
105
|
-
];
|
|
106
|
-
// 定义的符号
|
|
107
|
-
if (fileCtx.symbolsDefined.length > 0) {
|
|
108
|
-
lines.push(`### 🔤 定义的符号 (${fileCtx.symbolsDefined.length})`);
|
|
109
|
-
for (const sym of fileCtx.symbolsDefined) {
|
|
110
|
-
const icon = SYMBOL_ICONS[sym.kind] || '?';
|
|
111
|
-
const exp = sym.exported ? ' [export]' : '';
|
|
112
|
-
lines.push(` ${icon} ${sym.name}${exp} L${sym.lineStart}-L${sym.lineEnd}`);
|
|
113
|
-
}
|
|
114
|
-
lines.push('');
|
|
115
|
-
}
|
|
116
|
-
// 导入
|
|
117
|
-
if (fileCtx.imports.length > 0) {
|
|
118
|
-
lines.push(`### 📥 导入 (${fileCtx.imports.length})`);
|
|
119
|
-
for (const imp of fileCtx.imports) {
|
|
120
|
-
const specs = imp.specifiers.length > 0 ? `{ ${imp.specifiers.join(', ')} }` : '*';
|
|
121
|
-
lines.push(` → ${imp.source} ${specs} L${imp.line}`);
|
|
122
|
-
}
|
|
123
|
-
lines.push('');
|
|
124
|
-
}
|
|
125
|
-
// 被谁引用
|
|
126
|
-
if (fileCtx.importedBy.length > 0) {
|
|
127
|
-
lines.push(`### 📤 被引用 (${fileCtx.importedBy.length})`);
|
|
128
|
-
for (const by of fileCtx.importedBy) {
|
|
129
|
-
const specs = by.specifiers.length > 0 ? `{ ${by.specifiers.join(', ')} }` : '';
|
|
130
|
-
lines.push(` ← ${by.filePath} ${specs}`);
|
|
131
|
-
}
|
|
132
|
-
lines.push('');
|
|
133
|
-
}
|
|
134
|
-
return wrap(lines.join('\n'));
|
|
135
|
-
}));
|
|
136
|
-
// ===== code_path: 两点间链路追踪 =====
|
|
137
|
-
server.tool('code_path', '🔗 两点链路追踪 — 给定两个符号(如 fileA.funcA 和 fileB.funcB),自动搜索中间的引用链路,返回完整路径 + 绑定的知识图谱文档', {
|
|
138
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
139
|
-
symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
|
|
140
|
-
symbolB: z.string().describe('终点符号名(如 "AuthService")'),
|
|
141
|
-
maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
|
|
142
|
-
}, async (args) => safeTool(async () => {
|
|
143
|
-
const depth = Math.min(5, Math.max(1, args.maxDepth ?? 3));
|
|
144
|
-
// 1. 获取两个符号的影响树
|
|
145
|
-
const [treeA, treeB] = await Promise.all([
|
|
146
|
-
client().analyzerImpactTree(args.projectPath, args.symbolA, depth),
|
|
147
|
-
client().analyzerImpactTree(args.projectPath, args.symbolB, depth),
|
|
148
|
-
]);
|
|
149
|
-
if (!treeA)
|
|
150
|
-
return wrap(`❌ 未找到起点符号 "${args.symbolA}"。请确认名称正确且已运行 code_scan`);
|
|
151
|
-
if (!treeB)
|
|
152
|
-
return wrap(`❌ 未找到终点符号 "${args.symbolB}"。请确认名称正确且已运行 code_scan`);
|
|
153
|
-
const graphA = new Map(); // 从 A 出发可达的文件
|
|
154
|
-
const graphB = new Map(); // 从 B 出发可达的文件
|
|
155
|
-
const buildGraph = (tree, graph) => {
|
|
156
|
-
graph.set(tree.definedIn, []);
|
|
157
|
-
for (const level of tree.levels) {
|
|
158
|
-
for (const entry of level.entries) {
|
|
159
|
-
if (!graph.has(entry.filePath))
|
|
160
|
-
graph.set(entry.filePath, []);
|
|
161
|
-
graph.get(entry.filePath).push({
|
|
162
|
-
file: entry.filePath,
|
|
163
|
-
line: entry.line,
|
|
164
|
-
depth: level.depth,
|
|
165
|
-
from: tree.symbolName,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
buildGraph(treeA, graphA);
|
|
171
|
-
buildGraph(treeB, graphB);
|
|
172
|
-
// 3. 查找交集: A 和 B 都能到达的文件
|
|
173
|
-
const intersection = [];
|
|
174
|
-
for (const [fileA] of graphA) {
|
|
175
|
-
if (graphB.has(fileA)) {
|
|
176
|
-
const refsA = graphA.get(fileA);
|
|
177
|
-
const refsB = graphB.get(fileA);
|
|
178
|
-
const minDepthA = refsA.length > 0 ? Math.min(...refsA.map(r => r.depth)) : 0;
|
|
179
|
-
const minDepthB = refsB.length > 0 ? Math.min(...refsB.map(r => r.depth)) : 0;
|
|
180
|
-
intersection.push({ file: fileA, depthFromA: minDepthA, depthFromB: minDepthB });
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// 4. 尝试获取 KG 文档绑定
|
|
184
|
-
let docBindings = {};
|
|
185
|
-
try {
|
|
186
|
-
const docClient = client();
|
|
187
|
-
const allDocs = await docClient.listDocs();
|
|
188
|
-
for (const doc of allDocs) {
|
|
189
|
-
if (!doc.isDir && doc.summary) {
|
|
190
|
-
docBindings[doc.name] = doc.summary;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
catch { /* KG 不可用时忽略 */ }
|
|
195
|
-
// 5. 格式化输出
|
|
196
|
-
const lines = [
|
|
197
|
-
`🔗 两点链路追踪`,
|
|
198
|
-
``,
|
|
199
|
-
`📍 起点: ${treeA.symbolName} (${treeA.symbolKind}) → ${treeA.definedIn}:${treeA.lineStart}`,
|
|
200
|
-
`📍 终点: ${treeB.symbolName} (${treeB.symbolKind}) → ${treeB.definedIn}:${treeB.lineStart}`,
|
|
201
|
-
``,
|
|
202
|
-
];
|
|
203
|
-
// 直接引用关系
|
|
204
|
-
if (treeA.definedIn === treeB.definedIn) {
|
|
205
|
-
lines.push(`✅ 两个符号定义在同一文件中!`);
|
|
206
|
-
lines.push('');
|
|
207
|
-
}
|
|
208
|
-
// 检查 A 的影响树是否直接包含 B 的文件
|
|
209
|
-
const directAtoB = graphA.has(treeB.definedIn);
|
|
210
|
-
const directBtoA = graphB.has(treeA.definedIn);
|
|
211
|
-
if (directAtoB || directBtoA) {
|
|
212
|
-
lines.push(`### ✅ 发现直接链路`);
|
|
213
|
-
if (directAtoB) {
|
|
214
|
-
const refs = graphA.get(treeB.definedIn);
|
|
215
|
-
lines.push(` ${treeA.symbolName} → [L${refs[0]?.depth || 1}] → ${treeB.definedIn} (含 ${treeB.symbolName})`);
|
|
216
|
-
}
|
|
217
|
-
if (directBtoA) {
|
|
218
|
-
const refs = graphB.get(treeA.definedIn);
|
|
219
|
-
lines.push(` ${treeB.symbolName} → [L${refs[0]?.depth || 1}] → ${treeA.definedIn} (含 ${treeA.symbolName})`);
|
|
220
|
-
}
|
|
221
|
-
lines.push('');
|
|
222
|
-
}
|
|
223
|
-
if (intersection.length > 0) {
|
|
224
|
-
// 按总深度排序
|
|
225
|
-
intersection.sort((a, b) => (a.depthFromA + a.depthFromB) - (b.depthFromA + b.depthFromB));
|
|
226
|
-
lines.push(`### 🔗 共经文件 (${intersection.length}个)`);
|
|
227
|
-
lines.push(`| 文件 | 距${treeA.symbolName} | 距${treeB.symbolName} | 📚 绑定文档 |`);
|
|
228
|
-
lines.push(`|:---|:---:|:---:|:---|`);
|
|
229
|
-
for (const node of intersection.slice(0, 20)) {
|
|
230
|
-
const fileName = node.file.split('/').pop() || node.file;
|
|
231
|
-
const docBind = docBindings[fileName] || '—';
|
|
232
|
-
lines.push(`| ${node.file} | L${node.depthFromA} | L${node.depthFromB} | ${docBind} |`);
|
|
233
|
-
}
|
|
234
|
-
if (intersection.length > 20) {
|
|
235
|
-
lines.push(`| ... | | | (还有 ${intersection.length - 20} 个) |`);
|
|
236
|
-
}
|
|
237
|
-
lines.push('');
|
|
238
|
-
}
|
|
239
|
-
else if (!directAtoB && !directBtoA) {
|
|
240
|
-
lines.push(`⚠️ 在 ${depth} 层深度内未发现 ${treeA.symbolName} 与 ${treeB.symbolName} 的引用链路`);
|
|
241
|
-
lines.push(`建议: 增加 maxDepth 参数(当前=${depth}, 最大=5) 重试`);
|
|
242
|
-
lines.push('');
|
|
243
|
-
}
|
|
244
|
-
// 附加: 各符号的影响概要
|
|
245
|
-
lines.push(`### 📊 影响概要`);
|
|
246
|
-
lines.push(`- ${treeA.symbolName}: ${treeA.summary}`);
|
|
247
|
-
lines.push(`- ${treeB.symbolName}: ${treeB.summary}`);
|
|
248
|
-
// 附加: 绑定的 KG 文档
|
|
249
|
-
const relevantDocs = [];
|
|
250
|
-
const symbolNames = [treeA.symbolName, treeB.symbolName];
|
|
251
|
-
for (const [docName, docSummary] of Object.entries(docBindings)) {
|
|
252
|
-
if (symbolNames.some(s => docName.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(docName.toLowerCase()))) {
|
|
253
|
-
relevantDocs.push(` 📚 ${docName}: ${docSummary}`);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (relevantDocs.length > 0) {
|
|
257
|
-
lines.push('');
|
|
258
|
-
lines.push(`### 📚 相关知识图谱文档`);
|
|
259
|
-
lines.push(...relevantDocs);
|
|
260
|
-
}
|
|
261
|
-
return wrap(lines.join('\n'));
|
|
262
|
-
}));
|
|
263
35
|
// ===== code_smart_context: 代码+文档全关联上下文 =====
|
|
264
|
-
server.tool('code_smart_context', '🔍 代码+文档全关联上下文 —
|
|
265
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
36
|
+
server.tool('code_smart_context', '🔍 代码+文档全关联上下文 — 输入一个函数名,一次调用返回:代码依赖、关联文档、匹配规则、活跃任务、影响范围摘要。需先运行 code_scan', {
|
|
37
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
266
38
|
symbolName: z.string().describe('要查询的符号名称(如"handleLogin", "AuthService")'),
|
|
267
39
|
}, async (args) => safeTool(async () => {
|
|
268
|
-
const smartCtx = await client().analyzerSmartContext(args.projectPath, args.symbolName);
|
|
40
|
+
const smartCtx = await client().analyzerSmartContext(args.projectPath || '', args.symbolName);
|
|
269
41
|
if (!smartCtx) {
|
|
270
42
|
return wrap(`未找到符号 "${args.symbolName}"。请确认名称正确且已运行 code_scan`);
|
|
271
43
|
}
|
|
@@ -321,13 +93,13 @@ export function registerAnalyzerTools(server, ctx) {
|
|
|
321
93
|
return wrap(lines.join('\n'));
|
|
322
94
|
}));
|
|
323
95
|
// ===== code_full_path: 全关联路径 =====
|
|
324
|
-
server.tool('code_full_path', '🔗 全关联路径 —
|
|
325
|
-
projectPath: z.string().describe('项目源码的绝对路径'),
|
|
96
|
+
server.tool('code_full_path', '🔗 全关联路径 — 两个符号之间不仅返回代码引用链路,还返回共享的KG文档、共同导入、祖先模块。需先运行 code_scan', {
|
|
97
|
+
projectPath: z.string().optional().describe('项目源码的绝对路径(不传则自动解析)'),
|
|
326
98
|
symbolA: z.string().describe('起点符号名(如 "handleLogin")'),
|
|
327
99
|
symbolB: z.string().describe('终点符号名(如 "AuthService")'),
|
|
328
100
|
maxDepth: z.number().optional().describe('最大搜索深度(1-5, 默认3)'),
|
|
329
101
|
}, async (args) => safeTool(async () => {
|
|
330
|
-
const result = await client().analyzerFullPath(args.projectPath, args.symbolA, args.symbolB, args.maxDepth ?? 3);
|
|
102
|
+
const result = await client().analyzerFullPath(args.projectPath || '', args.symbolA, args.symbolB, args.maxDepth ?? 3);
|
|
331
103
|
if (!result) {
|
|
332
104
|
return wrap(`❌ 无法构建路径。请确认两个符号名称正确且已运行 code_scan`);
|
|
333
105
|
}
|
package/dist/tools/discussion.js
CHANGED
|
@@ -41,40 +41,52 @@ function formatList(items, ctx) {
|
|
|
41
41
|
}
|
|
42
42
|
return lines.join('\n');
|
|
43
43
|
}
|
|
44
|
-
function
|
|
44
|
+
function formatDetailView(d) {
|
|
45
45
|
const msgs = d.messages || [];
|
|
46
46
|
const lines = [
|
|
47
47
|
`💬 ${d.title}`,
|
|
48
48
|
`发起: ${d.initiator} | 参与: ${d.participants.length}方 | 状态: ${d.status}`,
|
|
49
49
|
`📌 ${d.summary}`,
|
|
50
|
+
`📊 总消息: ${d.totalCount} | 未读: ${d.unreadCount}`,
|
|
50
51
|
``,
|
|
51
52
|
];
|
|
52
53
|
msgs.forEach((m, i) => {
|
|
53
|
-
|
|
54
|
-
lines.push(m.
|
|
54
|
+
const readTag = m.isRead ? '📖' : '🆕';
|
|
55
|
+
lines.push(`--- ${readTag} 消息 ${i + 1}/${msgs.length} [${m.sender}] ${relativeTime(m.timestamp)} ---`);
|
|
56
|
+
// 已读且无 content → 显示摘要
|
|
57
|
+
if (m.isRead && !m.content) {
|
|
58
|
+
lines.push(`[摘要] ${m.summary}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
lines.push(m.content || m.summary || '(无内容)');
|
|
62
|
+
}
|
|
55
63
|
lines.push('');
|
|
56
64
|
});
|
|
57
65
|
return lines.join('\n');
|
|
58
66
|
}
|
|
59
67
|
export function registerDiscussionTools(server, ctx) {
|
|
60
68
|
const client = () => getClient();
|
|
61
|
-
server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(
|
|
62
|
-
action: z.enum(['list', 'read', 'create', 'reply', 'close', 'delete'])
|
|
69
|
+
server.tool('kg_discuss', '💬 跨项目讨论 — 发起、回复、归档协同讨论。action: list(列出活跃讨论)|read(读取详情,自动追踪已读)|create(发起)|reply(回复)|complete(标记完成)|close(结案归档)|delete(删除)|history(查看历史讨论)', {
|
|
70
|
+
action: z.enum(['list', 'read', 'create', 'reply', 'complete', 'close', 'delete', 'history'])
|
|
63
71
|
.describe('操作类型'),
|
|
64
72
|
id: z.string().optional()
|
|
65
|
-
.describe('讨论哈希ID (read/reply/close/delete)'),
|
|
73
|
+
.describe('讨论哈希ID (read/reply/complete/close/delete)'),
|
|
66
74
|
ids: z.array(z.string()).optional()
|
|
67
75
|
.describe('批量读取的讨论ID数组 (read)'),
|
|
68
76
|
title: z.string().optional()
|
|
69
77
|
.describe('讨论标题 (create)'),
|
|
70
78
|
participants: z.array(z.string()).optional()
|
|
71
79
|
.describe('参与项目ID数组 (create, 不含自己)'),
|
|
80
|
+
summary: z.string().optional()
|
|
81
|
+
.describe('消息摘要 (create/reply, 不传则自动截取content前50字)'),
|
|
72
82
|
content: z.string().optional()
|
|
73
|
-
.describe('
|
|
83
|
+
.describe('消息详细内容Markdown (create/reply)'),
|
|
74
84
|
conclusion: z.string().optional()
|
|
75
85
|
.describe('结案总结 (close)'),
|
|
76
86
|
newSummary: z.string().optional()
|
|
77
|
-
.describe('
|
|
87
|
+
.describe('更新讨论进展摘要 (reply)'),
|
|
88
|
+
mode: z.enum(['auto', 'full']).optional()
|
|
89
|
+
.describe('读取模式 (read, 默认auto: 已读消息仅返回摘要)'),
|
|
78
90
|
}, async (args) => safeTool(async () => {
|
|
79
91
|
const decoded = decodeObjectStrings(args);
|
|
80
92
|
const me = sender(ctx);
|
|
@@ -89,10 +101,11 @@ export function registerDiscussionTools(server, ctx) {
|
|
|
89
101
|
const readIds = decoded.ids || (decoded.id ? [decoded.id] : []);
|
|
90
102
|
if (readIds.length === 0)
|
|
91
103
|
return wrap('❌ read 需要 id 或 ids');
|
|
92
|
-
const
|
|
104
|
+
const readMode = decoded.mode || 'auto';
|
|
105
|
+
const discussions = await client().discussionReadByIds(readIds, me, readMode);
|
|
93
106
|
if (!Array.isArray(discussions) || discussions.length === 0)
|
|
94
107
|
return wrap('未找到对应的讨论记录');
|
|
95
|
-
return wrap(discussions.map(
|
|
108
|
+
return wrap(discussions.map(formatDetailView).join('\n\n━━━━━━━━━━━━━━━━━━━━\n\n'));
|
|
96
109
|
}
|
|
97
110
|
case 'create': {
|
|
98
111
|
if (!decoded.title)
|
|
@@ -101,7 +114,7 @@ export function registerDiscussionTools(server, ctx) {
|
|
|
101
114
|
return wrap('❌ create 需要 participants');
|
|
102
115
|
if (!decoded.content)
|
|
103
116
|
return wrap('❌ create 需要 content');
|
|
104
|
-
const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content);
|
|
117
|
+
const result = await client().discussionCreate(decoded.title, me, decoded.participants, decoded.content, decoded.summary);
|
|
105
118
|
return wrap(`✅ 讨论已发起\nID: ${result.id}\n发起方: ${me}\n参与方: ${decoded.participants.join(', ')}`);
|
|
106
119
|
}
|
|
107
120
|
case 'reply': {
|
|
@@ -109,9 +122,33 @@ export function registerDiscussionTools(server, ctx) {
|
|
|
109
122
|
return wrap('❌ reply 需要 id');
|
|
110
123
|
if (!decoded.content)
|
|
111
124
|
return wrap('❌ reply 需要 content');
|
|
112
|
-
await client().discussionReply(decoded.id, me, decoded.content, decoded.newSummary);
|
|
125
|
+
await client().discussionReply(decoded.id, me, decoded.content, decoded.summary, decoded.newSummary);
|
|
113
126
|
return wrap(`✅ 回复成功 (ID: ${decoded.id}, 身份: ${me})`);
|
|
114
127
|
}
|
|
128
|
+
case 'history': {
|
|
129
|
+
const all = await client().discussionListAll(me);
|
|
130
|
+
if (!Array.isArray(all) || all.length === 0)
|
|
131
|
+
return wrap(`暂无参与过的讨论记录 (身份: ${me})`);
|
|
132
|
+
const lines = [
|
|
133
|
+
`本项目: ${me}`,
|
|
134
|
+
``,
|
|
135
|
+
`📋 讨论历史 (${all.length}个)`,
|
|
136
|
+
``,
|
|
137
|
+
`| ID | 标题 | 发起方 | 状态 | 消息数 | 更新 |`,
|
|
138
|
+
`|:---|:---|:---|:---|:---:|:---|`,
|
|
139
|
+
];
|
|
140
|
+
for (const d of all) {
|
|
141
|
+
const statusIcon = d.status === 'active' ? '🟢' : '⚪';
|
|
142
|
+
lines.push(`| ${d.id} | ${d.title} | ${d.initiator} | ${statusIcon} ${d.status} | ${d.messageCount ?? 0} | ${relativeTime(d.updatedAt)} |`);
|
|
143
|
+
}
|
|
144
|
+
return wrap(lines.join('\n'));
|
|
145
|
+
}
|
|
146
|
+
case 'complete': {
|
|
147
|
+
if (!decoded.id)
|
|
148
|
+
return wrap('❌ complete 需要 id');
|
|
149
|
+
await client().discussionComplete(decoded.id);
|
|
150
|
+
return wrap(`✅ 讨论已标记完成 (ID: ${decoded.id})`);
|
|
151
|
+
}
|
|
115
152
|
case 'close': {
|
|
116
153
|
if (!decoded.id)
|
|
117
154
|
return wrap('❌ close 需要 id');
|
package/dist/tools/files.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 📁 kg_files (5→1)
|
|
3
3
|
* 合并: project_list_files, project_read_file, project_upload, project_download
|
|
4
|
-
*
|
|
4
|
+
* + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
|
|
5
5
|
*/
|
|
6
6
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
7
7
|
export declare function registerFileTools(server: McpServer): void;
|
package/dist/tools/files.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 📁 kg_files (5→1)
|
|
3
3
|
* 合并: project_list_files, project_read_file, project_upload, project_download
|
|
4
|
-
*
|
|
4
|
+
* + 公共文件池: public_list, public_read, public_upload, public_download, public_delete
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { getClient } from '../storage/httpClient.js';
|
|
8
8
|
import { wrap, safeTool, crossPrefix, formatFileSize } from './shared.js';
|
|
9
9
|
export function registerFileTools(server) {
|
|
10
10
|
const client = () => getClient();
|
|
11
|
-
server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(
|
|
12
|
-
action: z.enum(['list', 'read', 'upload', 'download'])
|
|
11
|
+
server.tool('kg_files', '📁 项目文件操作 — 浏览目录、读取文件、上传下载。action: list(目录浏览)|read(读取文件)|upload(上传)|download(下载文件)|public_list(公共文件池浏览)|public_read(读取公共文件)|public_upload(上传到公共池)|public_download(下载公共文件)|public_delete(删除公共文件)|public_mkdir(创建公共池子目录)|public_rename(重命名公共文件)', {
|
|
12
|
+
action: z.enum(['list', 'read', 'upload', 'download', 'public_list', 'public_read', 'public_upload', 'public_download', 'public_delete', 'public_mkdir', 'public_rename'])
|
|
13
13
|
.describe('操作类型'),
|
|
14
14
|
path: z.string().optional()
|
|
15
|
-
.describe('文件路径 (read/download, 如"src/main.ts")'),
|
|
15
|
+
.describe('文件路径 (read/download/public_read/public_download/public_delete/public_mkdir, 如"src/main.ts")'),
|
|
16
16
|
dir: z.string().optional()
|
|
17
|
-
.describe('子目录路径 (list, 如"src/components")'),
|
|
17
|
+
.describe('子目录路径 (list/public_list, 如"src/components")'),
|
|
18
18
|
localDir: z.string().optional()
|
|
19
|
-
.describe('
|
|
19
|
+
.describe('本地路径(目录或单文件) (upload/public_upload)'),
|
|
20
20
|
localPath: z.string().optional()
|
|
21
|
-
.describe('本地保存路径 (download)'),
|
|
21
|
+
.describe('本地保存路径 (download/public_download)'),
|
|
22
22
|
remoteDir: z.string().optional()
|
|
23
|
-
.describe('远程目标子目录 (upload)'),
|
|
23
|
+
.describe('远程目标子目录 (upload/public_upload)'),
|
|
24
|
+
newName: z.string().optional()
|
|
25
|
+
.describe('新名称 (public_rename)'),
|
|
24
26
|
targetProject: z.string().optional()
|
|
25
27
|
.describe('跨项目操作的目标项目ID'),
|
|
26
28
|
}, async (args) => safeTool(async () => {
|
|
@@ -64,6 +66,57 @@ export function registerFileTools(server) {
|
|
|
64
66
|
const prefix = args.targetProject ? crossPrefix(args.targetProject) : '';
|
|
65
67
|
return wrap(`${prefix}✅ 已下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
66
68
|
}
|
|
69
|
+
// ===== 公共文件池 =====
|
|
70
|
+
case 'public_list': {
|
|
71
|
+
const files = await client().publicFilesList(args.dir);
|
|
72
|
+
if (files.length === 0)
|
|
73
|
+
return wrap('📦 公共文件池' + (args.dir ? ` /${args.dir}` : '') + ' 为空');
|
|
74
|
+
const lines = files.map(f => {
|
|
75
|
+
const icon = f.isDir ? '📁' : '📄';
|
|
76
|
+
const size = f.isDir ? '' : ` (${formatFileSize(f.size)})`;
|
|
77
|
+
return `${icon} ${f.name}${size}`;
|
|
78
|
+
});
|
|
79
|
+
const dirLabel = args.dir || '/';
|
|
80
|
+
return wrap(`📦 公共文件池 ${dirLabel} (${files.length} 项)\n\n${lines.join('\n')}`);
|
|
81
|
+
}
|
|
82
|
+
case 'public_read': {
|
|
83
|
+
if (!args.path)
|
|
84
|
+
return wrap('❌ public_read 需要 path');
|
|
85
|
+
const content = await client().publicFilesRead(args.path);
|
|
86
|
+
return wrap(`📦 公共文件 ${args.path}\n\n\`\`\`\n${content}\n\`\`\``);
|
|
87
|
+
}
|
|
88
|
+
case 'public_upload': {
|
|
89
|
+
if (!args.localDir)
|
|
90
|
+
return wrap('❌ public_upload 需要 localDir');
|
|
91
|
+
const result = await client().publicFilesUpload(args.localDir, args.remoteDir);
|
|
92
|
+
return wrap(`✅ 已上传到公共文件池\n\n- 文件数量: ${result.fileCount}\n- 目标目录: ${args.remoteDir || '/'}`);
|
|
93
|
+
}
|
|
94
|
+
case 'public_download': {
|
|
95
|
+
if (!args.path)
|
|
96
|
+
return wrap('❌ public_download 需要 path');
|
|
97
|
+
const result = await client().publicFilesDownload(args.path, args.localPath);
|
|
98
|
+
return wrap(`✅ 已从公共文件池下载\n\n- 本地路径: ${result.localPath}\n- 文件数量: ${result.fileCount}`);
|
|
99
|
+
}
|
|
100
|
+
case 'public_delete': {
|
|
101
|
+
if (!args.path)
|
|
102
|
+
return wrap('❌ public_delete 需要 path');
|
|
103
|
+
await client().publicFilesDelete(args.path);
|
|
104
|
+
return wrap(`✅ 已从公共文件池删除: ${args.path}`);
|
|
105
|
+
}
|
|
106
|
+
case 'public_mkdir': {
|
|
107
|
+
if (!args.path)
|
|
108
|
+
return wrap('❌ public_mkdir 需要 path');
|
|
109
|
+
await client().publicFilesMkdir(args.path);
|
|
110
|
+
return wrap(`✅ 已创建公共文件池目录: /${args.path}`);
|
|
111
|
+
}
|
|
112
|
+
case 'public_rename': {
|
|
113
|
+
if (!args.path)
|
|
114
|
+
return wrap('❌ public_rename 需要 path');
|
|
115
|
+
if (!args.newName)
|
|
116
|
+
return wrap('❌ public_rename 需要 newName');
|
|
117
|
+
await client().publicFilesRename(args.path, args.newName);
|
|
118
|
+
return wrap(`✅ 已重命名: ${args.path} → ${args.newName}`);
|
|
119
|
+
}
|
|
67
120
|
default:
|
|
68
121
|
return wrap(`❌ 未知 action: ${args.action}`);
|
|
69
122
|
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
3
|
+
* 14 个工具, 7 个子模块
|
|
4
4
|
*
|
|
5
5
|
* 🔗 初始化: kg_init (1个)
|
|
6
6
|
* 📊 导航: kg_status, kg_tree (2个)
|
|
7
7
|
* 📚 知识: kg_doc, kg_projects, kg_rules (3个)
|
|
8
8
|
* 📝 工作流: kg_task, kg_files, kg_discuss (3个)
|
|
9
|
-
* 🔬 代码分析: code_scan,
|
|
9
|
+
* 🔬 代码分析: code_scan, code_smart_context, code_full_path (3个)
|
|
10
10
|
* 🏛️ 协作: kg_meeting (1个)
|
|
11
11
|
*/
|
|
12
12
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
package/dist/tools/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MCP 工具注册入口
|
|
3
|
-
*
|
|
3
|
+
* 14 个工具, 7 个子模块
|
|
4
4
|
*
|
|
5
5
|
* 🔗 初始化: kg_init (1个)
|
|
6
6
|
* 📊 导航: kg_status, kg_tree (2个)
|
|
7
7
|
* 📚 知识: kg_doc, kg_projects, kg_rules (3个)
|
|
8
8
|
* 📝 工作流: kg_task, kg_files, kg_discuss (3个)
|
|
9
|
-
* 🔬 代码分析: code_scan,
|
|
9
|
+
* 🔬 代码分析: code_scan, code_smart_context, code_full_path (3个)
|
|
10
10
|
* 🏛️ 协作: kg_meeting (1个)
|
|
11
11
|
*/
|
|
12
12
|
import { createContext } from './shared.js';
|
|
@@ -35,7 +35,7 @@ export function registerTools(server, projectId, user, onProjectChange) {
|
|
|
35
35
|
registerTaskTools(server, ctx);
|
|
36
36
|
registerFileTools(server);
|
|
37
37
|
registerDiscussionTools(server, ctx);
|
|
38
|
-
// 🔬 代码分析 (code_scan,
|
|
38
|
+
// 🔬 代码分析 (code_scan, code_smart_context, code_full_path)
|
|
39
39
|
registerAnalyzerTools(server, ctx);
|
|
40
40
|
// 🏛️ 多AI协作 (kg_meeting)
|
|
41
41
|
registerMeetingTools(server, ctx);
|