team-anya 0.2.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.
- package/README.md +38 -0
- package/apps/server/dist/broker/cc-broker.js +267 -0
- package/apps/server/dist/cli.js +296 -0
- package/apps/server/dist/config.js +78 -0
- package/apps/server/dist/daemon.js +51 -0
- package/apps/server/dist/franky/context-builder.js +161 -0
- package/apps/server/dist/franky/franky-mcp-server.js +110 -0
- package/apps/server/dist/franky/franky-orchestrator.js +629 -0
- package/apps/server/dist/franky/index.js +5 -0
- package/apps/server/dist/franky/topic-router.js +16 -0
- package/apps/server/dist/gateway/chat-sync.js +135 -0
- package/apps/server/dist/gateway/command-router.js +116 -0
- package/apps/server/dist/gateway/commands/cancel.js +32 -0
- package/apps/server/dist/gateway/commands/help.js +16 -0
- package/apps/server/dist/gateway/commands/index.js +26 -0
- package/apps/server/dist/gateway/commands/restart.js +43 -0
- package/apps/server/dist/gateway/commands/status.js +34 -0
- package/apps/server/dist/gateway/commands/tasks.js +33 -0
- package/apps/server/dist/gateway/feishu-sender.js +508 -0
- package/apps/server/dist/gateway/feishu-ws.js +353 -0
- package/apps/server/dist/gateway/health-monitor.js +154 -0
- package/apps/server/dist/gateway/http.js +1064 -0
- package/apps/server/dist/gateway/media-downloader.js +182 -0
- package/apps/server/dist/gateway/message-events.js +10 -0
- package/apps/server/dist/gateway/message-intake.js +72 -0
- package/apps/server/dist/gateway/message-queue.js +118 -0
- package/apps/server/dist/gateway/session-reader.js +142 -0
- package/apps/server/dist/gateway/ws-push.js +115 -0
- package/apps/server/dist/loid/brain.js +121 -0
- package/apps/server/dist/loid/clarifier.js +162 -0
- package/apps/server/dist/loid/context-builder.js +462 -0
- package/apps/server/dist/loid/mcp-server.js +119 -0
- package/apps/server/dist/loid/memory-settler.js +189 -0
- package/apps/server/dist/loid/opportunity-manager.js +148 -0
- package/apps/server/dist/loid/profile-updater.js +179 -0
- package/apps/server/dist/loid/project-registry.js +192 -0
- package/apps/server/dist/loid/reporter.js +148 -0
- package/apps/server/dist/loid/schemas.js +117 -0
- package/apps/server/dist/loid/self-calibrator.js +314 -0
- package/apps/server/dist/loid/session-manager.js +472 -0
- package/apps/server/dist/loid/session.js +276 -0
- package/apps/server/dist/main.js +528 -0
- package/apps/server/dist/tracing/index.js +2 -0
- package/apps/server/dist/tracing/trace-context.js +92 -0
- package/apps/server/dist/types/message.js +2 -0
- package/apps/server/dist/yor/yor-mcp-server.js +107 -0
- package/apps/server/dist/yor/yor-orchestrator.js +248 -0
- package/apps/web/dist/assets/index-BiiEB0qZ.css +1 -0
- package/apps/web/dist/assets/index-Dnb9LGZd.js +798 -0
- package/apps/web/dist/index.html +13 -0
- package/package.json +42 -0
- package/packages/cc-client/dist/claude-code-backend.js +792 -0
- package/packages/cc-client/dist/index.js +2 -0
- package/packages/cc-client/package.json +11 -0
- package/packages/core/dist/constants.js +60 -0
- package/packages/core/dist/errors.js +35 -0
- package/packages/core/dist/index.js +9 -0
- package/packages/core/dist/office-init.js +190 -0
- package/packages/core/dist/repo-cache.js +70 -0
- package/packages/core/dist/scope/checker.js +114 -0
- package/packages/core/dist/scope/defaults.js +55 -0
- package/packages/core/dist/scope/index.js +3 -0
- package/packages/core/dist/state-machine.js +86 -0
- package/packages/core/dist/types/audit.js +12 -0
- package/packages/core/dist/types/backend.js +2 -0
- package/packages/core/dist/types/commitment.js +17 -0
- package/packages/core/dist/types/communication.js +18 -0
- package/packages/core/dist/types/index.js +9 -0
- package/packages/core/dist/types/opportunity.js +27 -0
- package/packages/core/dist/types/org.js +26 -0
- package/packages/core/dist/types/task.js +46 -0
- package/packages/core/dist/types/workspace.js +39 -0
- package/packages/core/dist/workspace-manager.js +314 -0
- package/packages/core/package.json +10 -0
- package/packages/db/dist/client.js +69 -0
- package/packages/db/dist/index.js +756 -0
- package/packages/db/dist/schema/audit-events.js +13 -0
- package/packages/db/dist/schema/cc-sessions.js +14 -0
- package/packages/db/dist/schema/chats.js +35 -0
- package/packages/db/dist/schema/commitments.js +18 -0
- package/packages/db/dist/schema/communication-events.js +14 -0
- package/packages/db/dist/schema/index.js +14 -0
- package/packages/db/dist/schema/message-log.js +20 -0
- package/packages/db/dist/schema/opportunities.js +23 -0
- package/packages/db/dist/schema/org.js +36 -0
- package/packages/db/dist/schema/projects.js +23 -0
- package/packages/db/dist/schema/tasks.js +51 -0
- package/packages/db/dist/schema/topics.js +22 -0
- package/packages/db/dist/schema/trace-spans.js +19 -0
- package/packages/db/dist/schema/workspaces.js +15 -0
- package/packages/db/package.json +12 -0
- package/packages/db/src/migrations/0000_baseline.sql +251 -0
- package/packages/db/src/migrations/0001_workspaces.sql +19 -0
- package/packages/db/src/migrations/0002_workspace_parent.sql +1 -0
- package/packages/db/src/migrations/0003_chat_context.sql +3 -0
- package/packages/db/src/migrations/meta/_journal.json +34 -0
- package/packages/mcp-tools/dist/index.js +41 -0
- package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
- package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
- package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
- package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
- package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
- package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
- package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
- package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
- package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
- package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
- package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
- package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
- package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
- package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
- package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
- package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
- package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
- package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
- package/packages/mcp-tools/dist/layer2/franky/topic-checkpoint.js +43 -0
- package/packages/mcp-tools/dist/layer2/franky/topic-escalate.js +19 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
- package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
- package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
- package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +206 -0
- package/packages/mcp-tools/dist/layer2/loid/task-escalate-to-topic.js +170 -0
- package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +45 -0
- package/packages/mcp-tools/dist/layer2/loid/topic-close.js +22 -0
- package/packages/mcp-tools/dist/layer2/loid/topic-create.js +60 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +28 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
- package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
- package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
- package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
- package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +203 -0
- package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
- package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
- package/packages/mcp-tools/dist/layer3/channel-send.js +75 -0
- package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
- package/packages/mcp-tools/dist/registry.js +911 -0
- package/packages/mcp-tools/package.json +13 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon 管理:PID 文件读写、进程检测、健康检查
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
function resolveHome(p) {
|
|
8
|
+
if (p.startsWith('~/') || p === '~')
|
|
9
|
+
return join(homedir(), p.slice(2));
|
|
10
|
+
return p;
|
|
11
|
+
}
|
|
12
|
+
function getPidFile(anyaHome) {
|
|
13
|
+
const dataDir = join(resolveHome(anyaHome), 'data');
|
|
14
|
+
mkdirSync(dataDir, { recursive: true });
|
|
15
|
+
return join(dataDir, 'anya.pid');
|
|
16
|
+
}
|
|
17
|
+
export function writePid(anyaHome, pid) {
|
|
18
|
+
writeFileSync(getPidFile(anyaHome), String(pid), 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
export function readPid(anyaHome) {
|
|
21
|
+
const pidFile = getPidFile(anyaHome);
|
|
22
|
+
if (!existsSync(pidFile))
|
|
23
|
+
return null;
|
|
24
|
+
const raw = readFileSync(pidFile, 'utf-8').trim();
|
|
25
|
+
const pid = parseInt(raw, 10);
|
|
26
|
+
return Number.isNaN(pid) ? null : pid;
|
|
27
|
+
}
|
|
28
|
+
export function removePid(anyaHome) {
|
|
29
|
+
const pidFile = getPidFile(anyaHome);
|
|
30
|
+
if (existsSync(pidFile))
|
|
31
|
+
unlinkSync(pidFile);
|
|
32
|
+
}
|
|
33
|
+
export function isProcessAlive(pid) {
|
|
34
|
+
try {
|
|
35
|
+
process.kill(pid, 0);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function healthCheck(port) {
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(`http://localhost:${port}/api/dashboard`);
|
|
45
|
+
return { ok: res.ok, status: res.status };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return { ok: false };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { readFile, readdir, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { getTopic, getRecentMessages } from '@team-anya/db';
|
|
6
|
+
/**
|
|
7
|
+
* 为 Franky CC 实例重建上下文
|
|
8
|
+
* 所有 checkpoint 标题 + 最近 2 个详情 + 最近 30 条对话
|
|
9
|
+
*/
|
|
10
|
+
export async function buildTopicContext(deps, topicId) {
|
|
11
|
+
const topic = getTopic(deps.db, topicId);
|
|
12
|
+
if (!topic)
|
|
13
|
+
throw new Error(`话题 ${topicId} 不存在`);
|
|
14
|
+
const topicDir = deps.workspacePath;
|
|
15
|
+
const sections = [];
|
|
16
|
+
// 1. Topic 元信息
|
|
17
|
+
sections.push(`# 话题上下文:${topic.title} (${topic.id})`);
|
|
18
|
+
sections.push('');
|
|
19
|
+
sections.push('## 目标');
|
|
20
|
+
sections.push(topic.description ?? '(未设定)');
|
|
21
|
+
sections.push('');
|
|
22
|
+
sections.push('## 通讯信息');
|
|
23
|
+
if (topic.chat_id)
|
|
24
|
+
sections.push(`- 群聊 ID:\`${topic.chat_id}\``);
|
|
25
|
+
if (topic.root_message_id)
|
|
26
|
+
sections.push(`- 话题根消息 ID:\`${topic.root_message_id}\``);
|
|
27
|
+
if (topic.thread_id)
|
|
28
|
+
sections.push(`- 话题 ID:\`${topic.thread_id}\`(仅供参考,channel.send 的 reply_to 请用根消息 ID)`);
|
|
29
|
+
sections.push('');
|
|
30
|
+
sections.push('## 项目信息');
|
|
31
|
+
sections.push(`- 话题 ID:${topic.id}`);
|
|
32
|
+
sections.push(`- 状态:${topic.status}`);
|
|
33
|
+
if (topic.project_id)
|
|
34
|
+
sections.push(`- 关联项目:${topic.project_id}`);
|
|
35
|
+
if (topic.branch_name)
|
|
36
|
+
sections.push(`- 分支:${topic.branch_name}`);
|
|
37
|
+
sections.push('');
|
|
38
|
+
// 1.5 代码工作区(仓库信息)
|
|
39
|
+
if (topic.project_id && topic.workspace_path) {
|
|
40
|
+
try {
|
|
41
|
+
const entries = await readdir(topic.workspace_path, { withFileTypes: true });
|
|
42
|
+
const repoDirs = entries
|
|
43
|
+
.filter(e => e.isDirectory() && !e.name.startsWith('.') && e.name !== 'checkpoints' && e.name !== 'feedback')
|
|
44
|
+
.map(e => e.name);
|
|
45
|
+
if (repoDirs.length > 0) {
|
|
46
|
+
sections.push('## 代码工作区');
|
|
47
|
+
sections.push('');
|
|
48
|
+
for (const dir of repoDirs) {
|
|
49
|
+
const fullPath = join(topic.workspace_path, dir);
|
|
50
|
+
const branchInfo = topic.branch_name ? `分支 \`${topic.branch_name}\`,` : '';
|
|
51
|
+
sections.push(`- \`${dir}/\` → ${branchInfo}路径 \`${fullPath}\``);
|
|
52
|
+
}
|
|
53
|
+
sections.push('');
|
|
54
|
+
sections.push('> 代码在上述子目录中(独立 clone),git 操作请进入对应子目录执行。');
|
|
55
|
+
sections.push('');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// workspace 不可读,跳过
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// 2. Checkpoints
|
|
63
|
+
const checkpointsDir = join(topicDir, 'checkpoints');
|
|
64
|
+
let checkpointFiles = [];
|
|
65
|
+
if (existsSync(checkpointsDir)) {
|
|
66
|
+
const files = await readdir(checkpointsDir);
|
|
67
|
+
checkpointFiles = files.filter(f => f.match(/^\d{3}-.*\.md$/)).sort();
|
|
68
|
+
}
|
|
69
|
+
if (checkpointFiles.length > 0) {
|
|
70
|
+
sections.push('## Checkpoint 概览');
|
|
71
|
+
for (const file of checkpointFiles) {
|
|
72
|
+
const num = file.split('-')[0];
|
|
73
|
+
const name = file.replace(/^\d{3}-/, '').replace(/\.md$/, '');
|
|
74
|
+
sections.push(`${parseInt(num, 10)}. ${name}`);
|
|
75
|
+
}
|
|
76
|
+
sections.push('');
|
|
77
|
+
// 最近 2 个 checkpoint 的完整内容
|
|
78
|
+
const recentCheckpoints = checkpointFiles.slice(-2);
|
|
79
|
+
sections.push('## 最近 Checkpoint 详情');
|
|
80
|
+
sections.push('');
|
|
81
|
+
for (const file of recentCheckpoints) {
|
|
82
|
+
try {
|
|
83
|
+
const content = await readFile(join(checkpointsDir, file), 'utf-8');
|
|
84
|
+
sections.push(content.trim());
|
|
85
|
+
sections.push('');
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// 文件读取失败,跳过
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 3. Git log(如果有代码仓库)
|
|
93
|
+
const gitDir = join(topicDir);
|
|
94
|
+
try {
|
|
95
|
+
const gitLog = execSync('git log --oneline -10 2>/dev/null || true', {
|
|
96
|
+
cwd: gitDir,
|
|
97
|
+
encoding: 'utf-8',
|
|
98
|
+
timeout: 5000,
|
|
99
|
+
}).trim();
|
|
100
|
+
if (gitLog) {
|
|
101
|
+
sections.push('## 近期 Git 记录');
|
|
102
|
+
sections.push('```');
|
|
103
|
+
sections.push(gitLog);
|
|
104
|
+
sections.push('```');
|
|
105
|
+
sections.push('');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// 不是 git 仓库或命令失败,跳过
|
|
110
|
+
}
|
|
111
|
+
// 4. 近期对话(始终拉取最近 30 条,不受 checkpoint 截断)
|
|
112
|
+
if (topic.chat_id) {
|
|
113
|
+
try {
|
|
114
|
+
const messages = getRecentMessages(deps.db, {
|
|
115
|
+
chat_id: topic.chat_id,
|
|
116
|
+
limit: 30,
|
|
117
|
+
sort: 'asc',
|
|
118
|
+
});
|
|
119
|
+
if (messages.length > 0) {
|
|
120
|
+
sections.push('## 近期对话');
|
|
121
|
+
sections.push(`以下是最近 ${messages.length} 条消息(时间正序):`);
|
|
122
|
+
sections.push('');
|
|
123
|
+
const history = messages.map(msg => {
|
|
124
|
+
const senderRaw = msg.sender ?? '未知';
|
|
125
|
+
const senderName = msg.sender_name ?? senderRaw;
|
|
126
|
+
const entry = {
|
|
127
|
+
time: msg.created_at ?? '',
|
|
128
|
+
sender: msg.direction === 'outbound' ? 'Anya' : senderName,
|
|
129
|
+
direction: msg.direction,
|
|
130
|
+
content: msg.content.length > 500 ? msg.content.slice(0, 500) + '...' : msg.content,
|
|
131
|
+
};
|
|
132
|
+
if (msg.source_ref) {
|
|
133
|
+
entry.message_id = msg.source_ref;
|
|
134
|
+
}
|
|
135
|
+
if (msg.message_type && msg.message_type !== 'text') {
|
|
136
|
+
entry.message_type = msg.message_type;
|
|
137
|
+
}
|
|
138
|
+
if (msg.related_task_id) {
|
|
139
|
+
entry.related_task_id = msg.related_task_id;
|
|
140
|
+
}
|
|
141
|
+
return entry;
|
|
142
|
+
});
|
|
143
|
+
sections.push(JSON.stringify(history, null, 2));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
deps.logger?.error('[TopicContextBuilder] 查询对话历史失败:', err);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return sections.join('\n');
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 生成并写入 context.md 文件
|
|
154
|
+
*/
|
|
155
|
+
export async function writeTopicContext(deps, topicId) {
|
|
156
|
+
const content = await buildTopicContext(deps, topicId);
|
|
157
|
+
const contextPath = join(deps.workspacePath, 'context.md');
|
|
158
|
+
await writeFile(contextPath, content, 'utf-8');
|
|
159
|
+
return contextPath;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=context-builder.js.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server';
|
|
4
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
5
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
6
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
7
|
+
import { createToolRouter } from '@team-anya/mcp-tools';
|
|
8
|
+
// ── MCP Server 创建(薄路由层) ──
|
|
9
|
+
function createFrankyMcpServer(deps) {
|
|
10
|
+
const logger = deps.logger ?? { info: console.log, error: console.error };
|
|
11
|
+
const routerDeps = {
|
|
12
|
+
db: deps.db,
|
|
13
|
+
workspacePath: deps.workspacePath,
|
|
14
|
+
currentTopicId: deps.topicId,
|
|
15
|
+
replyToThreadId: deps.replyToThreadId,
|
|
16
|
+
channelRegistry: deps.channelRegistry,
|
|
17
|
+
onMessageSent: deps.onMessageSent,
|
|
18
|
+
onTopicClosed: deps.onTopicClosed,
|
|
19
|
+
onTopicEscalated: deps.onTopicEscalated,
|
|
20
|
+
logger,
|
|
21
|
+
};
|
|
22
|
+
const router = createToolRouter('franky', routerDeps);
|
|
23
|
+
const server = new Server({ name: 'franky-mcp', version: '1.0.0' }, { capabilities: { tools: {} } });
|
|
24
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
25
|
+
const tools = router.tools.map(def => ({
|
|
26
|
+
name: def.name,
|
|
27
|
+
description: def.description,
|
|
28
|
+
inputSchema: zodToJsonSchema(def.inputSchema),
|
|
29
|
+
}));
|
|
30
|
+
return { tools };
|
|
31
|
+
});
|
|
32
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
33
|
+
const { name, arguments: args } = request.params;
|
|
34
|
+
return router.handleToolCall(name, args ?? {});
|
|
35
|
+
});
|
|
36
|
+
return server;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 启动 Franky MCP server(薄路由层)
|
|
40
|
+
*
|
|
41
|
+
* Franky 可以使用 Layer 1 全部共享工具 + Layer 3 Franky 专属工具(channel.send 等)。
|
|
42
|
+
* 不需要 Yor 的 deliver/block 工具。
|
|
43
|
+
*
|
|
44
|
+
* 所有工具实现来自 @team-anya/mcp-tools。
|
|
45
|
+
*/
|
|
46
|
+
/** Franky MCP server 固定端口 */
|
|
47
|
+
export const FRANKY_MCP_PORT = 19530;
|
|
48
|
+
export async function startFrankyMcpServer(deps) {
|
|
49
|
+
const sessions = new Map();
|
|
50
|
+
const httpServer = createServer(async (req, res) => {
|
|
51
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
52
|
+
if (url.pathname !== '/mcp') {
|
|
53
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
54
|
+
res.end(JSON.stringify({ error: 'not_found' }));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const sessionId = req.headers['mcp-session-id'];
|
|
58
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
59
|
+
const { transport } = sessions.get(sessionId);
|
|
60
|
+
await transport.handleRequest(req, res);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (req.method !== 'POST') {
|
|
64
|
+
res.writeHead(405, { 'Content-Type': 'application/json', 'Allow': 'POST' });
|
|
65
|
+
res.end(JSON.stringify({ error: 'method_not_allowed', message: 'Use POST to initialize a session' }));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const transport = new StreamableHTTPServerTransport({
|
|
69
|
+
sessionIdGenerator: () => randomUUID(),
|
|
70
|
+
});
|
|
71
|
+
const server = createFrankyMcpServer(deps);
|
|
72
|
+
transport.onclose = () => {
|
|
73
|
+
if (transport.sessionId) {
|
|
74
|
+
sessions.delete(transport.sessionId);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
await server.connect(transport);
|
|
78
|
+
await transport.handleRequest(req, res);
|
|
79
|
+
if (transport.sessionId) {
|
|
80
|
+
sessions.set(transport.sessionId, { server, transport });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
await new Promise((resolve) => {
|
|
84
|
+
httpServer.listen(FRANKY_MCP_PORT, '127.0.0.1', resolve);
|
|
85
|
+
});
|
|
86
|
+
const addr = httpServer.address();
|
|
87
|
+
const port = typeof addr === 'object' && addr !== null ? addr.port : 0;
|
|
88
|
+
const serverUrl = `http://127.0.0.1:${port}/mcp`;
|
|
89
|
+
deps.logger?.info(`Franky MCP server 已启动: ${serverUrl}`);
|
|
90
|
+
return {
|
|
91
|
+
port,
|
|
92
|
+
url: serverUrl,
|
|
93
|
+
close: async () => {
|
|
94
|
+
for (const { server, transport } of sessions.values()) {
|
|
95
|
+
await transport.close();
|
|
96
|
+
await server.close();
|
|
97
|
+
}
|
|
98
|
+
sessions.clear();
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
httpServer.close((err) => {
|
|
101
|
+
if (err)
|
|
102
|
+
reject(err);
|
|
103
|
+
else
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=franky-mcp-server.js.map
|