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.
Files changed (145) hide show
  1. package/README.md +38 -0
  2. package/apps/server/dist/broker/cc-broker.js +267 -0
  3. package/apps/server/dist/cli.js +296 -0
  4. package/apps/server/dist/config.js +78 -0
  5. package/apps/server/dist/daemon.js +51 -0
  6. package/apps/server/dist/franky/context-builder.js +161 -0
  7. package/apps/server/dist/franky/franky-mcp-server.js +110 -0
  8. package/apps/server/dist/franky/franky-orchestrator.js +629 -0
  9. package/apps/server/dist/franky/index.js +5 -0
  10. package/apps/server/dist/franky/topic-router.js +16 -0
  11. package/apps/server/dist/gateway/chat-sync.js +135 -0
  12. package/apps/server/dist/gateway/command-router.js +116 -0
  13. package/apps/server/dist/gateway/commands/cancel.js +32 -0
  14. package/apps/server/dist/gateway/commands/help.js +16 -0
  15. package/apps/server/dist/gateway/commands/index.js +26 -0
  16. package/apps/server/dist/gateway/commands/restart.js +43 -0
  17. package/apps/server/dist/gateway/commands/status.js +34 -0
  18. package/apps/server/dist/gateway/commands/tasks.js +33 -0
  19. package/apps/server/dist/gateway/feishu-sender.js +508 -0
  20. package/apps/server/dist/gateway/feishu-ws.js +353 -0
  21. package/apps/server/dist/gateway/health-monitor.js +154 -0
  22. package/apps/server/dist/gateway/http.js +1064 -0
  23. package/apps/server/dist/gateway/media-downloader.js +182 -0
  24. package/apps/server/dist/gateway/message-events.js +10 -0
  25. package/apps/server/dist/gateway/message-intake.js +72 -0
  26. package/apps/server/dist/gateway/message-queue.js +118 -0
  27. package/apps/server/dist/gateway/session-reader.js +142 -0
  28. package/apps/server/dist/gateway/ws-push.js +115 -0
  29. package/apps/server/dist/loid/brain.js +121 -0
  30. package/apps/server/dist/loid/clarifier.js +162 -0
  31. package/apps/server/dist/loid/context-builder.js +462 -0
  32. package/apps/server/dist/loid/mcp-server.js +119 -0
  33. package/apps/server/dist/loid/memory-settler.js +189 -0
  34. package/apps/server/dist/loid/opportunity-manager.js +148 -0
  35. package/apps/server/dist/loid/profile-updater.js +179 -0
  36. package/apps/server/dist/loid/project-registry.js +192 -0
  37. package/apps/server/dist/loid/reporter.js +148 -0
  38. package/apps/server/dist/loid/schemas.js +117 -0
  39. package/apps/server/dist/loid/self-calibrator.js +314 -0
  40. package/apps/server/dist/loid/session-manager.js +472 -0
  41. package/apps/server/dist/loid/session.js +276 -0
  42. package/apps/server/dist/main.js +528 -0
  43. package/apps/server/dist/tracing/index.js +2 -0
  44. package/apps/server/dist/tracing/trace-context.js +92 -0
  45. package/apps/server/dist/types/message.js +2 -0
  46. package/apps/server/dist/yor/yor-mcp-server.js +107 -0
  47. package/apps/server/dist/yor/yor-orchestrator.js +248 -0
  48. package/apps/web/dist/assets/index-BiiEB0qZ.css +1 -0
  49. package/apps/web/dist/assets/index-Dnb9LGZd.js +798 -0
  50. package/apps/web/dist/index.html +13 -0
  51. package/package.json +42 -0
  52. package/packages/cc-client/dist/claude-code-backend.js +792 -0
  53. package/packages/cc-client/dist/index.js +2 -0
  54. package/packages/cc-client/package.json +11 -0
  55. package/packages/core/dist/constants.js +60 -0
  56. package/packages/core/dist/errors.js +35 -0
  57. package/packages/core/dist/index.js +9 -0
  58. package/packages/core/dist/office-init.js +190 -0
  59. package/packages/core/dist/repo-cache.js +70 -0
  60. package/packages/core/dist/scope/checker.js +114 -0
  61. package/packages/core/dist/scope/defaults.js +55 -0
  62. package/packages/core/dist/scope/index.js +3 -0
  63. package/packages/core/dist/state-machine.js +86 -0
  64. package/packages/core/dist/types/audit.js +12 -0
  65. package/packages/core/dist/types/backend.js +2 -0
  66. package/packages/core/dist/types/commitment.js +17 -0
  67. package/packages/core/dist/types/communication.js +18 -0
  68. package/packages/core/dist/types/index.js +9 -0
  69. package/packages/core/dist/types/opportunity.js +27 -0
  70. package/packages/core/dist/types/org.js +26 -0
  71. package/packages/core/dist/types/task.js +46 -0
  72. package/packages/core/dist/types/workspace.js +39 -0
  73. package/packages/core/dist/workspace-manager.js +314 -0
  74. package/packages/core/package.json +10 -0
  75. package/packages/db/dist/client.js +69 -0
  76. package/packages/db/dist/index.js +756 -0
  77. package/packages/db/dist/schema/audit-events.js +13 -0
  78. package/packages/db/dist/schema/cc-sessions.js +14 -0
  79. package/packages/db/dist/schema/chats.js +35 -0
  80. package/packages/db/dist/schema/commitments.js +18 -0
  81. package/packages/db/dist/schema/communication-events.js +14 -0
  82. package/packages/db/dist/schema/index.js +14 -0
  83. package/packages/db/dist/schema/message-log.js +20 -0
  84. package/packages/db/dist/schema/opportunities.js +23 -0
  85. package/packages/db/dist/schema/org.js +36 -0
  86. package/packages/db/dist/schema/projects.js +23 -0
  87. package/packages/db/dist/schema/tasks.js +51 -0
  88. package/packages/db/dist/schema/topics.js +22 -0
  89. package/packages/db/dist/schema/trace-spans.js +19 -0
  90. package/packages/db/dist/schema/workspaces.js +15 -0
  91. package/packages/db/package.json +12 -0
  92. package/packages/db/src/migrations/0000_baseline.sql +251 -0
  93. package/packages/db/src/migrations/0001_workspaces.sql +19 -0
  94. package/packages/db/src/migrations/0002_workspace_parent.sql +1 -0
  95. package/packages/db/src/migrations/0003_chat_context.sql +3 -0
  96. package/packages/db/src/migrations/meta/_journal.json +34 -0
  97. package/packages/mcp-tools/dist/index.js +41 -0
  98. package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
  99. package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
  100. package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
  101. package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
  102. package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
  103. package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
  104. package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
  105. package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
  106. package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
  107. package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
  108. package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
  109. package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
  110. package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
  111. package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
  112. package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
  113. package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
  114. package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
  115. package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
  116. package/packages/mcp-tools/dist/layer2/franky/topic-checkpoint.js +43 -0
  117. package/packages/mcp-tools/dist/layer2/franky/topic-escalate.js +19 -0
  118. package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
  119. package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
  120. package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
  121. package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
  122. package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
  123. package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
  124. package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
  125. package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
  126. package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +206 -0
  127. package/packages/mcp-tools/dist/layer2/loid/task-escalate-to-topic.js +170 -0
  128. package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +45 -0
  129. package/packages/mcp-tools/dist/layer2/loid/topic-close.js +22 -0
  130. package/packages/mcp-tools/dist/layer2/loid/topic-create.js +60 -0
  131. package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
  132. package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
  133. package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
  134. package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +28 -0
  135. package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
  136. package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
  137. package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
  138. package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
  139. package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +203 -0
  140. package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
  141. package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
  142. package/packages/mcp-tools/dist/layer3/channel-send.js +75 -0
  143. package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
  144. package/packages/mcp-tools/dist/registry.js +911 -0
  145. package/packages/mcp-tools/package.json +13 -0
@@ -0,0 +1,462 @@
1
+ import { getTask, getTasksByStatus, getRecentMessages, getMessageLogBySourceRef, getRecentDoneTasksSince, } from "@team-anya/db";
2
+ import { TaskStatus } from "@team-anya/core";
3
+ import { memoryBrief } from "@team-anya/mcp-tools";
4
+ export class LoidContextBuilder {
5
+ db;
6
+ workspacePath;
7
+ projectRegistry;
8
+ constructor(deps) {
9
+ this.db = deps.db;
10
+ this.workspacePath = deps.workspacePath;
11
+ this.projectRegistry = deps.projectRegistry;
12
+ }
13
+ /**
14
+ * 构建消息事件上下文(包含产品/项目注册表)
15
+ */
16
+ async buildMessageContext(message) {
17
+ const activeTasks = this.getActiveTasks();
18
+ const recentMessages = this.getRecentMessages(message.chatId);
19
+ const registry = await this.getRegistry();
20
+ if (registry.length > 0) {
21
+ const ids = registry.map((r) => r.id).join(", ");
22
+ console.log(`[ContextBuilder] 注入 registry: ${registry.length} 条 (${ids})`);
23
+ }
24
+ // 查询近期完成的任务
25
+ const recentDoneTasks = this.getRecentDoneTasks();
26
+ // 注入记忆摘要
27
+ let brief;
28
+ try {
29
+ const briefResult = await memoryBrief(this.workspacePath, {
30
+ sender_id: message.sender ?? undefined,
31
+ token_budget: 1500,
32
+ });
33
+ if (briefResult.brief) {
34
+ brief = briefResult.brief;
35
+ }
36
+ }
37
+ catch {
38
+ // 记忆摘要获取失败不阻塞主流程
39
+ }
40
+ return {
41
+ type: "new_message",
42
+ message,
43
+ activeTasks,
44
+ recentMessages,
45
+ registry,
46
+ memoryBrief: brief,
47
+ recentDoneTasks: recentDoneTasks.length > 0 ? recentDoneTasks : undefined,
48
+ };
49
+ }
50
+ /**
51
+ * 构建交付验收上下文
52
+ */
53
+ buildDeliveryContext(taskId, exitCode, extras) {
54
+ const task = getTask(this.db, taskId);
55
+ const activeTasks = this.getActiveTasks();
56
+ return {
57
+ type: "delivery",
58
+ taskId,
59
+ briefContent: extras?.briefContent ?? null,
60
+ exitCode,
61
+ retryCount: task?.retry_count ?? 0,
62
+ maxRetries: task?.max_retries ?? 3,
63
+ prUrl: extras?.prUrl ?? task?.pr_url ?? null,
64
+ changedFiles: extras?.changedFiles ?? [],
65
+ testResults: extras?.testResults ?? null,
66
+ originalMessage: task?.context ?? null,
67
+ chatId: task?.source_ref
68
+ ? (getMessageLogBySourceRef(this.db, task.source_ref)?.chat_id ?? null)
69
+ : null,
70
+ conversationId: null,
71
+ activeTasks,
72
+ sourceChatId: task?.source_ref
73
+ ? (getMessageLogBySourceRef(this.db, task.source_ref)?.chat_id ??
74
+ undefined)
75
+ : undefined,
76
+ };
77
+ }
78
+ /**
79
+ * 获取活跃任务列表
80
+ */
81
+ getActiveTasks() {
82
+ const statuses = [
83
+ TaskStatus.IN_PROGRESS,
84
+ TaskStatus.READY,
85
+ TaskStatus.NEED_CLARIFICATION,
86
+ TaskStatus.DELIVERING,
87
+ TaskStatus.BLOCKED,
88
+ ];
89
+ const tasks = [];
90
+ for (const status of statuses) {
91
+ const statusTasks = getTasksByStatus(this.db, status);
92
+ for (const t of statusTasks) {
93
+ tasks.push({
94
+ task_id: t.task_id,
95
+ title: t.title,
96
+ status: t.status,
97
+ assignee: t.assignee,
98
+ });
99
+ }
100
+ }
101
+ return tasks;
102
+ }
103
+ /**
104
+ * 从项目注册表加载已注册项目
105
+ */
106
+ async getRegistry() {
107
+ try {
108
+ const config = await this.projectRegistry.loadConfig();
109
+ return config.projects.map((p) => ({
110
+ id: p.projectId,
111
+ name: p.name,
112
+ description: p.description,
113
+ repos: p.repos.map((r) => r.name),
114
+ claudeMd: p.claudeMd,
115
+ }));
116
+ }
117
+ catch {
118
+ return [];
119
+ }
120
+ }
121
+ /**
122
+ * 获取最近完成的任务(默认 30 分钟窗口)
123
+ */
124
+ getRecentDoneTasks(minutesAgo = 30) {
125
+ const sinceISO = new Date(Date.now() - minutesAgo * 60 * 1000).toISOString();
126
+ const doneTasks = getRecentDoneTasksSince(this.db, sinceISO);
127
+ return doneTasks.map((t) => ({
128
+ task_id: t.task_id,
129
+ title: t.title,
130
+ status: t.status,
131
+ assignee: t.assignee,
132
+ workspace_id: t.workspace_id ?? null,
133
+ escalated_to_topic_id: t.escalated_to_topic_id ?? null,
134
+ }));
135
+ }
136
+ /**
137
+ * 获取最近消息历史
138
+ */
139
+ getRecentMessages(chatId) {
140
+ try {
141
+ if (!chatId)
142
+ return [];
143
+ const rawMessages = getRecentMessages(this.db, {
144
+ limit: 50, // 多拉一些,过滤话题消息后保留足够上下文
145
+ chat_id: chatId,
146
+ });
147
+ // 过滤掉话题(thread)内的消息——那些由 Franky 负责,Loid 不需要重复看到
148
+ const messages = rawMessages.filter((m) => {
149
+ if (!m.metadata)
150
+ return true;
151
+ try {
152
+ const parsed = JSON.parse(m.metadata);
153
+ return !parsed.thread_id;
154
+ }
155
+ catch {
156
+ return true;
157
+ }
158
+ }).slice(0, 30);
159
+ const maxContentLen = 500;
160
+ return messages.map((m) => {
161
+ const raw = m.content ?? "";
162
+ // 从 metadata JSON 中提取 media、回复关系、sender_open_id
163
+ let media = [];
164
+ let replyTo = null;
165
+ let senderOpenId = null;
166
+ if (m.metadata) {
167
+ try {
168
+ const parsed = JSON.parse(m.metadata);
169
+ if (Array.isArray(parsed.media)) {
170
+ media = parsed.media.map((item) => ({
171
+ mediaType: String(item.mediaType ?? "file"),
172
+ originalName: String(item.originalName ?? ""),
173
+ localPath: String(item.localPath ?? ""),
174
+ }));
175
+ }
176
+ if (parsed.parent_message_id) {
177
+ replyTo = String(parsed.parent_message_id);
178
+ }
179
+ if (parsed.sender_open_id) {
180
+ senderOpenId = String(parsed.sender_open_id);
181
+ }
182
+ }
183
+ catch {
184
+ // metadata 解析失败,忽略
185
+ }
186
+ }
187
+ return {
188
+ message_id: m.source_ref ?? null,
189
+ sender: m.sender,
190
+ sender_open_id: senderOpenId,
191
+ content: raw.slice(0, maxContentLen),
192
+ direction: m.direction,
193
+ created_at: m.created_at,
194
+ message_type: m.message_type ?? null,
195
+ related_task_id: m.related_task_id ?? null,
196
+ reply_to: replyTo,
197
+ media,
198
+ truncated: raw.length > maxContentLen,
199
+ };
200
+ });
201
+ }
202
+ catch {
203
+ return [];
204
+ }
205
+ }
206
+ }
207
+ // ── 辅助函数 ──
208
+ function formatRecentDoneBlock(recentDoneTasks) {
209
+ if (!recentDoneTasks || recentDoneTasks.length === 0)
210
+ return [];
211
+ const lines = [];
212
+ lines.push("");
213
+ lines.push("--- 近期完成 ---");
214
+ lines.push("以下任务在最近 30 分钟内完成,用户可能要求续做:");
215
+ for (const t of recentDoneTasks) {
216
+ const ws = t.workspace_id ? ` (workspace: ${t.workspace_id})` : "";
217
+ lines.push(` - [DONE] ${t.task_id}: ${t.title}${ws}`);
218
+ }
219
+ lines.push("如果用户新消息是对某个已完成任务的后续要求,请调用 task.escalate_to_topic 将其升级为话题。");
220
+ return lines;
221
+ }
222
+ // ── Prompt 格式化 ──
223
+ export function formatMessageContextPrompt(ctx) {
224
+ const lines = [];
225
+ lines.push("强制执行步骤: 在生成任何回复之前,你必须先调用工具读取 CLAUDE.md 以及里面提到的所有的 md 文件。因为该文件中包含了决定你说话语气、你的灵魂,你的工作流程,以及你可以使用的工具(MCP)。即使你认为你知道答案,也严禁在未读取该文件的情况下直接回复。");
226
+ lines.push("--- 当前对话 ---");
227
+ const chatInfo = {
228
+ chat_id: ctx.message.chatId ?? null,
229
+ chat_type: ctx.message.isDirectMessage ? "p2p" : "group",
230
+ };
231
+ lines.push(JSON.stringify(chatInfo));
232
+ lines.push("");
233
+ lines.push("--- 当前事件 ---");
234
+ const event = {
235
+ type: "new_message",
236
+ message_id: ctx.message.metadata?.message_id ?? null,
237
+ sender_id: ctx.message.sender ?? "未知",
238
+ sender_name: ctx.message.senderName ?? null,
239
+ content: ctx.message.content,
240
+ };
241
+ if (ctx.message.mentionsAnya) {
242
+ event.mentions_anya = true;
243
+ }
244
+ if (ctx.message.metadata?.parent_message_id) {
245
+ event.reply_to = ctx.message.metadata.parent_message_id;
246
+ }
247
+ if (ctx.message.media.length > 0) {
248
+ event.media = ctx.message.media.map((m) => ({
249
+ type: m.mediaType,
250
+ name: m.originalName,
251
+ path: m.localPath,
252
+ }));
253
+ }
254
+ lines.push(JSON.stringify(event, null, 2));
255
+ if (ctx.message.media.length > 0) {
256
+ lines.push("");
257
+ lines.push("提示: 本条消息包含媒体附件,你可以通过 media.path 中的本地路径读取文件内容(图片可直接查看,文件可读取文本)。");
258
+ }
259
+ // 注入记忆摘要(在"当前状态"之前)
260
+ if (ctx.memoryBrief) {
261
+ lines.push("");
262
+ lines.push("--- 记忆摘要 ---");
263
+ lines.push(ctx.memoryBrief);
264
+ }
265
+ lines.push("");
266
+ lines.push("--- 当前状态 ---");
267
+ if (ctx.activeTasks.length > 0) {
268
+ lines.push("活跃任务:");
269
+ for (const t of ctx.activeTasks) {
270
+ lines.push(` - [${t.status}] ${t.task_id}: ${t.title}${t.assignee ? ` (${t.assignee})` : ""}`);
271
+ }
272
+ }
273
+ else {
274
+ lines.push("活跃任务: 无");
275
+ }
276
+ // 注入近期完成任务区块
277
+ lines.push(...formatRecentDoneBlock(ctx.recentDoneTasks));
278
+ if (ctx.recentMessages.length > 0) {
279
+ lines.push("");
280
+ lines.push("--- 对话历史 ---");
281
+ lines.push(`以下是当前对话最近 ${ctx.recentMessages.length} 条消息(时间倒序,最新在前):`);
282
+ const history = ctx.recentMessages.map((m) => {
283
+ const entry = {
284
+ message_id: m.message_id,
285
+ time: m.created_at ?? "",
286
+ sender_id: m.direction === "outbound" ? "anya" : (m.sender_open_id ?? m.sender ?? "未知"),
287
+ sender_name: m.direction === "outbound" ? "Anya" : (m.sender ?? null),
288
+ direction: m.direction,
289
+ content: m.content,
290
+ };
291
+ if (m.reply_to) {
292
+ entry.reply_to = m.reply_to;
293
+ }
294
+ if (m.message_type && m.message_type !== "text") {
295
+ entry.message_type = m.message_type;
296
+ }
297
+ if (m.related_task_id) {
298
+ entry.related_task_id = m.related_task_id;
299
+ }
300
+ if (m.media.length > 0) {
301
+ entry.media = m.media.map((a) => ({
302
+ type: a.mediaType,
303
+ name: a.originalName,
304
+ path: a.localPath,
305
+ }));
306
+ }
307
+ if (m.truncated) {
308
+ entry.truncated = true;
309
+ }
310
+ return entry;
311
+ });
312
+ lines.push(JSON.stringify(history, null, 2));
313
+ }
314
+ if (ctx.registry.length > 0) {
315
+ lines.push("");
316
+ lines.push("--- 可用项目 ---");
317
+ lines.push("派发任务时请通过 project_id 指定对应的项目:");
318
+ for (const entry of ctx.registry) {
319
+ const desc = entry.description ? ` — ${entry.description}` : "";
320
+ lines.push(` - ${entry.id}: ${entry.name}${desc}`);
321
+ if (entry.repos.length > 0) {
322
+ lines.push(` 仓库: ${entry.repos.join(", ")}`);
323
+ }
324
+ if (entry.claudeMd) {
325
+ lines.push(` 技术栈:`);
326
+ for (const line of entry.claudeMd.split("\n")) {
327
+ lines.push(` ${line}`);
328
+ }
329
+ }
330
+ }
331
+ }
332
+ return lines.join("\n");
333
+ }
334
+ /**
335
+ * 合并多条缓冲消息为一个 prompt,供 Session drain 时投递给 CC
336
+ */
337
+ export function formatBufferedMessages(messages) {
338
+ if (messages.length === 0)
339
+ return "";
340
+ const lines = [];
341
+ lines.push(`你在处理上一件事的过程中,该对话又收到了 ${messages.length} 条新消息:`);
342
+ lines.push("");
343
+ for (let i = 0; i < messages.length; i++) {
344
+ const msg = messages[i];
345
+ const time = msg.message.metadata?.timestamp ?? "";
346
+ const sender = msg.message.senderName ?? msg.message.sender ?? "未知";
347
+ lines.push(`--- 消息 ${i + 1} (${time} 来自 ${sender}) ---`);
348
+ lines.push(msg.message.content);
349
+ if (msg.message.media.length > 0) {
350
+ for (const m of msg.message.media) {
351
+ lines.push(` 附件: [${m.mediaType}] ${m.originalName} (${m.localPath})`);
352
+ }
353
+ }
354
+ lines.push("");
355
+ }
356
+ lines.push("请综合这些新消息,决定下一步行动。");
357
+ return lines.join("\n");
358
+ }
359
+ /**
360
+ * 崩溃恢复时从 DB 活跃任务状态生成上下文 prompt
361
+ */
362
+ export function formatRecoveryPrompt(activeTasks) {
363
+ const lines = [];
364
+ lines.push("--- 崩溃恢复 ---");
365
+ lines.push("上一个会话异常终止,以下是从数据库恢复的活跃任务状态:");
366
+ lines.push("");
367
+ if (activeTasks.length > 0) {
368
+ for (const t of activeTasks) {
369
+ lines.push(`- [${t.status}] ${t.task_id}: ${t.title}${t.assignee ? ` (负责人: ${t.assignee})` : ""}`);
370
+ }
371
+ }
372
+ else {
373
+ lines.push("当前无活跃任务。");
374
+ }
375
+ lines.push("");
376
+ lines.push("请根据以上状态,决定需要优先处理的事项。");
377
+ return lines.join("\n");
378
+ }
379
+ /**
380
+ * 后续消息精简格式:已有 session 时不重复注入对话历史、项目注册表、强制读 CLAUDE.md 指令
381
+ */
382
+ export function formatFollowUpPrompt(ctx) {
383
+ const lines = [];
384
+ // 当前事件(必须)
385
+ lines.push("--- 新消息 ---");
386
+ const event = {
387
+ message_id: ctx.message.metadata?.message_id ?? null,
388
+ sender_id: ctx.message.sender ?? "未知",
389
+ sender_name: ctx.message.senderName ?? null,
390
+ content: ctx.message.content,
391
+ };
392
+ if (ctx.message.mentionsAnya)
393
+ event.mentions_anya = true;
394
+ if (ctx.message.metadata?.parent_message_id)
395
+ event.reply_to = ctx.message.metadata.parent_message_id;
396
+ if (ctx.message.media.length > 0) {
397
+ event.media = ctx.message.media.map((m) => ({
398
+ type: m.mediaType,
399
+ name: m.originalName,
400
+ path: m.localPath,
401
+ }));
402
+ }
403
+ lines.push(JSON.stringify(event, null, 2));
404
+ if (ctx.message.media.length > 0) {
405
+ lines.push("");
406
+ lines.push("提示: 本条消息包含媒体附件,你可以通过 media.path 中的本地路径读取文件内容。");
407
+ }
408
+ // 简化版记忆摘要(只注入人的记忆 summary)
409
+ if (ctx.memoryBrief) {
410
+ lines.push("");
411
+ lines.push("--- 记忆提示 ---");
412
+ lines.push(ctx.memoryBrief);
413
+ }
414
+ // 活跃任务(简洁版)
415
+ if (ctx.activeTasks.length > 0) {
416
+ lines.push("");
417
+ lines.push("--- 活跃任务 ---");
418
+ for (const t of ctx.activeTasks) {
419
+ lines.push(` - [${t.status}] ${t.task_id}: ${t.title}${t.assignee ? ` (${t.assignee})` : ""}`);
420
+ }
421
+ }
422
+ // 注入近期完成任务区块
423
+ lines.push(...formatRecentDoneBlock(ctx.recentDoneTasks));
424
+ return lines.join("\n");
425
+ }
426
+ export function formatDeliveryContextPrompt(ctx) {
427
+ const lines = [];
428
+ lines.push("--- 当前事件 ---");
429
+ lines.push(`类型: Yor 交付`);
430
+ lines.push(`任务ID: ${ctx.taskId}`);
431
+ lines.push(`退出码: ${ctx.exitCode}`);
432
+ lines.push(`重试次数: ${ctx.retryCount}/${ctx.maxRetries}`);
433
+ if (ctx.prUrl) {
434
+ lines.push(`PR 链接: ${ctx.prUrl}`);
435
+ }
436
+ if (ctx.briefContent) {
437
+ lines.push("");
438
+ lines.push("原始 Brief:");
439
+ lines.push(ctx.briefContent);
440
+ }
441
+ if (ctx.changedFiles.length > 0) {
442
+ lines.push("");
443
+ lines.push("变更文件:");
444
+ for (const f of ctx.changedFiles) {
445
+ lines.push(` - ${f}`);
446
+ }
447
+ }
448
+ if (ctx.testResults) {
449
+ lines.push("");
450
+ lines.push("测试结果:");
451
+ lines.push(ctx.testResults);
452
+ }
453
+ if (ctx.originalMessage) {
454
+ lines.push("");
455
+ lines.push(`原始用户指令: "${ctx.originalMessage}"`);
456
+ }
457
+ if (ctx.chatId) {
458
+ lines.push(`对话ID: ${ctx.chatId}(用于回复人类)`);
459
+ }
460
+ return lines.join("\n");
461
+ }
462
+ //# sourceMappingURL=context-builder.js.map
@@ -0,0 +1,119 @@
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 createMcpServer(deps) {
10
+ const routerDeps = {
11
+ db: deps.db,
12
+ workspacePath: deps.workspacePath,
13
+ channelRegistry: deps.channelRegistry,
14
+ logger: deps.logger,
15
+ onMessageSent: deps.onMessageSent,
16
+ threadCreator: deps.threadCreator,
17
+ yorOrchestrator: deps.yorOrchestrator,
18
+ getProjectConfig: deps.getProjectConfig,
19
+ onProjectChanged: deps.onProjectChanged,
20
+ workspaceManager: deps.workspaceManager,
21
+ onTaskEscalated: deps.onTaskEscalated,
22
+ };
23
+ const router = createToolRouter('loid', routerDeps);
24
+ const server = new Server({ name: 'loid-mcp', version: '3.0.0' }, { capabilities: { tools: {} } });
25
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
26
+ const tools = router.tools.map(def => ({
27
+ name: def.name,
28
+ description: def.description,
29
+ inputSchema: zodToJsonSchema(def.inputSchema),
30
+ }));
31
+ return { tools };
32
+ });
33
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
34
+ const { name, arguments: args } = request.params;
35
+ deps.onToolCall?.(name);
36
+ return router.handleToolCall(name, args ?? {});
37
+ });
38
+ return server;
39
+ }
40
+ /**
41
+ * 启动 Loid 指挥型 MCP server(薄路由层)
42
+ *
43
+ * 所有工具实现来自 @team-anya/mcp-tools,
44
+ * 这里只负责 HTTP 传输和 session 管理。
45
+ */
46
+ /** Loid MCP server 固定端口 */
47
+ export const LOID_MCP_PORT = 19510;
48
+ export async function startLoidMcpServer(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
+ // CC 会先探测 OAuth well-known 端点,本地 MCP 无需认证,返回 404 JSON
53
+ // 让 CC 正确 fallback 到无认证模式(纯文本 404 会导致 JSON 解析失败)
54
+ if (url.pathname.startsWith('/.well-known/')) {
55
+ res.writeHead(404, { 'Content-Type': 'application/json' });
56
+ res.end(JSON.stringify({ error: 'not_found' }));
57
+ return;
58
+ }
59
+ if (url.pathname !== '/mcp') {
60
+ res.writeHead(404, { 'Content-Type': 'application/json' });
61
+ res.end(JSON.stringify({ error: 'not_found' }));
62
+ return;
63
+ }
64
+ const sessionId = req.headers['mcp-session-id'];
65
+ if (sessionId && sessions.has(sessionId)) {
66
+ const { transport } = sessions.get(sessionId);
67
+ await transport.handleRequest(req, res);
68
+ return;
69
+ }
70
+ // 无 session 的 GET/DELETE:返回 405(MCP 规范要求)
71
+ if (req.method !== 'POST') {
72
+ res.writeHead(405, { 'Content-Type': 'application/json', 'Allow': 'POST' });
73
+ res.end(JSON.stringify({ error: 'method_not_allowed', message: 'Use POST to initialize a session' }));
74
+ return;
75
+ }
76
+ const transport = new StreamableHTTPServerTransport({
77
+ sessionIdGenerator: () => randomUUID(),
78
+ });
79
+ const server = createMcpServer(deps);
80
+ transport.onclose = () => {
81
+ if (transport.sessionId) {
82
+ sessions.delete(transport.sessionId);
83
+ }
84
+ // 不在这里调用 server.close(),避免与 close() 函数形成循环调用导致栈溢出
85
+ };
86
+ await server.connect(transport);
87
+ await transport.handleRequest(req, res);
88
+ if (transport.sessionId) {
89
+ sessions.set(transport.sessionId, { server, transport });
90
+ }
91
+ });
92
+ await new Promise((resolve) => {
93
+ httpServer.listen(LOID_MCP_PORT, '127.0.0.1', resolve);
94
+ });
95
+ const addr = httpServer.address();
96
+ const port = typeof addr === 'object' && addr !== null ? addr.port : 0;
97
+ const serverUrl = `http://127.0.0.1:${port}/mcp`;
98
+ deps.logger?.info(`Loid MCP server 已启动: ${serverUrl}`);
99
+ return {
100
+ port,
101
+ url: serverUrl,
102
+ close: async () => {
103
+ for (const { server, transport } of sessions.values()) {
104
+ await transport.close();
105
+ await server.close();
106
+ }
107
+ sessions.clear();
108
+ await new Promise((resolve, reject) => {
109
+ httpServer.close((err) => {
110
+ if (err)
111
+ reject(err);
112
+ else
113
+ resolve();
114
+ });
115
+ });
116
+ },
117
+ };
118
+ }
119
+ //# sourceMappingURL=mcp-server.js.map