team-anya-cli 0.1.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 (163) hide show
  1. package/README.md +38 -0
  2. package/anya/prompts/execution-guides/git-delivery.md +38 -0
  3. package/anya/prompts/execution-guides/testing-and-self-heal.md +28 -0
  4. package/anya/prompts/protocols/brief-assembly.md +55 -0
  5. package/anya/prompts/protocols/report.md +175 -0
  6. package/anya/prompts/protocols/review.md +90 -0
  7. package/anya/prompts/task-claude-md.template.md +32 -0
  8. package/apps/server/dist/broker/cc-broker.js +257 -0
  9. package/apps/server/dist/cli.js +296 -0
  10. package/apps/server/dist/config.js +76 -0
  11. package/apps/server/dist/daemon.js +51 -0
  12. package/apps/server/dist/gateway/chat-sync.js +135 -0
  13. package/apps/server/dist/gateway/command-router.js +114 -0
  14. package/apps/server/dist/gateway/commands/cancel.js +32 -0
  15. package/apps/server/dist/gateway/commands/help.js +16 -0
  16. package/apps/server/dist/gateway/commands/index.js +26 -0
  17. package/apps/server/dist/gateway/commands/restart.js +34 -0
  18. package/apps/server/dist/gateway/commands/status.js +34 -0
  19. package/apps/server/dist/gateway/commands/tasks.js +33 -0
  20. package/apps/server/dist/gateway/feishu-sender.js +346 -0
  21. package/apps/server/dist/gateway/feishu-ws.js +254 -0
  22. package/apps/server/dist/gateway/http.js +994 -0
  23. package/apps/server/dist/gateway/media-downloader.js +149 -0
  24. package/apps/server/dist/gateway/message-events.js +10 -0
  25. package/apps/server/dist/gateway/message-intake.js +50 -0
  26. package/apps/server/dist/gateway/message-queue.js +104 -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 +104 -0
  30. package/apps/server/dist/loid/clarifier.js +162 -0
  31. package/apps/server/dist/loid/context-builder.js +413 -0
  32. package/apps/server/dist/loid/mcp-server.js +104 -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/reporter.js +148 -0
  37. package/apps/server/dist/loid/schemas.js +117 -0
  38. package/apps/server/dist/loid/self-calibrator.js +314 -0
  39. package/apps/server/dist/loid/session-manager.js +217 -0
  40. package/apps/server/dist/loid/session.js +271 -0
  41. package/apps/server/dist/loid/worktree-manager.js +191 -0
  42. package/apps/server/dist/main.js +337 -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 +104 -0
  47. package/apps/server/dist/yor/yor-orchestrator.js +233 -0
  48. package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
  49. package/apps/web/dist/assets/index-CJzAjoVH.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 +664 -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 +59 -0
  56. package/packages/core/dist/errors.js +35 -0
  57. package/packages/core/dist/index.js +7 -0
  58. package/packages/core/dist/office-init.js +97 -0
  59. package/packages/core/dist/scope/checker.js +114 -0
  60. package/packages/core/dist/scope/defaults.js +40 -0
  61. package/packages/core/dist/scope/index.js +3 -0
  62. package/packages/core/dist/state-machine.js +85 -0
  63. package/packages/core/dist/types/audit.js +12 -0
  64. package/packages/core/dist/types/backend.js +2 -0
  65. package/packages/core/dist/types/commitment.js +17 -0
  66. package/packages/core/dist/types/communication.js +18 -0
  67. package/packages/core/dist/types/index.js +8 -0
  68. package/packages/core/dist/types/opportunity.js +27 -0
  69. package/packages/core/dist/types/org.js +26 -0
  70. package/packages/core/dist/types/task.js +46 -0
  71. package/packages/core/package.json +10 -0
  72. package/packages/db/dist/client.js +69 -0
  73. package/packages/db/dist/index.js +603 -0
  74. package/packages/db/dist/schema/audit-events.js +13 -0
  75. package/packages/db/dist/schema/cc-sessions.js +14 -0
  76. package/packages/db/dist/schema/chats.js +33 -0
  77. package/packages/db/dist/schema/commitments.js +18 -0
  78. package/packages/db/dist/schema/communication-events.js +14 -0
  79. package/packages/db/dist/schema/index.js +12 -0
  80. package/packages/db/dist/schema/message-log.js +20 -0
  81. package/packages/db/dist/schema/opportunities.js +23 -0
  82. package/packages/db/dist/schema/org.js +36 -0
  83. package/packages/db/dist/schema/projects.js +23 -0
  84. package/packages/db/dist/schema/tasks.js +46 -0
  85. package/packages/db/dist/schema/trace-spans.js +19 -0
  86. package/packages/db/package.json +12 -0
  87. package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
  88. package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
  89. package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
  90. package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
  91. package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
  92. package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
  93. package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
  94. package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
  95. package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
  96. package/packages/db/src/migrations/meta/_journal.json +41 -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/loid/decision-log.js +15 -0
  117. package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
  118. package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
  119. package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
  120. package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
  121. package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
  122. package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
  123. package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
  124. package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
  125. package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
  126. package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
  127. package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
  128. package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
  129. package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
  130. package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
  131. package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
  132. package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
  133. package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
  134. package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
  135. package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
  136. package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
  137. package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
  138. package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
  139. package/packages/mcp-tools/dist/registry.js +779 -0
  140. package/packages/mcp-tools/package.json +13 -0
  141. package/workspace/.claude/settings.local.json +9 -0
  142. package/workspace/.mcp.json +12 -0
  143. package/workspace/CHARTER.md +73 -0
  144. package/workspace/CLAUDE.md +49 -0
  145. package/workspace/PROTOCOL.md +126 -0
  146. package/workspace/TOOLS.md +464 -0
  147. package/workspace/audit/.gitkeep +0 -0
  148. package/workspace/loid/CLAUDE.md +12 -0
  149. package/workspace/loid/PLAYBOOK.md +198 -0
  150. package/workspace/loid/PROFILE.md +78 -0
  151. package/workspace/memory/commitments/.gitkeep +0 -0
  152. package/workspace/memory/execution/.gitkeep +0 -0
  153. package/workspace/memory/people/.gitkeep +0 -0
  154. package/workspace/memory/projects/.gitkeep +0 -0
  155. package/workspace/memory/self/.gitkeep +0 -0
  156. package/workspace/reference/identity/.gitkeep +0 -0
  157. package/workspace/reference/org/escalation.yaml +24 -0
  158. package/workspace/reference/org/ownership.yaml +28 -0
  159. package/workspace/reports/.gitkeep +0 -0
  160. package/workspace/yor/CLAUDE.md +22 -0
  161. package/workspace/yor/PLAYBOOK.md +73 -0
  162. package/workspace/yor/PROFILE.md +52 -0
  163. package/workspace/yor/SELF-HEAL.md +39 -0
@@ -0,0 +1,162 @@
1
+ import { getTask, updateTask, addClarification, getClarifications, answerClarification } from '@team-anya/db';
2
+ import { TaskStatus } from '@team-anya/core';
3
+ // ── 问题模板 ──
4
+ const QUESTION_TEMPLATES = {
5
+ objective: [
6
+ '这个任务的具体目标是什么?希望达到什么效果?',
7
+ ],
8
+ acceptance_criteria: [
9
+ '这个任务的验收标准是什么?怎样算完成?',
10
+ ],
11
+ context: [
12
+ '能否提供更多背景信息?比如触发场景、影响范围、相关日志等?',
13
+ ],
14
+ scope: [
15
+ '这个任务涉及哪个项目/仓库?影响范围有多大?',
16
+ ],
17
+ };
18
+ // 精确型 sender 只问关键项(objective 和 acceptance_criteria 优先)
19
+ const CRITICAL_FIELDS = ['objective', 'acceptance_criteria'];
20
+ // ── Clarifier ──
21
+ export class Clarifier {
22
+ db;
23
+ getProfileStyle;
24
+ constructor(deps) {
25
+ this.db = deps.db;
26
+ this.getProfileStyle = deps.getProfileStyle;
27
+ }
28
+ /**
29
+ * 检查任务需求的完整性(四要素:objective, acceptance_criteria, context, scope)
30
+ */
31
+ checkCompleteness(taskData) {
32
+ const missing = [];
33
+ if (!taskData.objective || taskData.objective.trim() === '') {
34
+ missing.push('objective');
35
+ }
36
+ if (!taskData.acceptance_criteria || taskData.acceptance_criteria.trim() === '') {
37
+ missing.push('acceptance_criteria');
38
+ }
39
+ else {
40
+ // 检查 JSON 数组是否为空
41
+ try {
42
+ const parsed = JSON.parse(taskData.acceptance_criteria);
43
+ if (Array.isArray(parsed) && parsed.length === 0) {
44
+ missing.push('acceptance_criteria');
45
+ }
46
+ }
47
+ catch {
48
+ // 非 JSON 格式,视为有值
49
+ }
50
+ }
51
+ if (!taskData.context || taskData.context.trim() === '') {
52
+ missing.push('context');
53
+ }
54
+ if (!taskData.project_id || taskData.project_id.trim() === '') {
55
+ missing.push('scope');
56
+ }
57
+ return {
58
+ isComplete: missing.length === 0,
59
+ missing,
60
+ };
61
+ }
62
+ /**
63
+ * 根据缺失项生成澄清问题
64
+ * @param missing 缺失的要素名
65
+ * @param style sender 的沟通风格(verbose=全问, precise=只问关键项)
66
+ */
67
+ generateQuestions(missing, style = 'verbose') {
68
+ if (missing.length === 0)
69
+ return [];
70
+ const fieldsToAsk = style === 'precise'
71
+ ? missing.filter(m => CRITICAL_FIELDS.includes(m))
72
+ : missing;
73
+ // 精确型如果没有关键项缺失,仍至少问第一个缺失项
74
+ const effectiveFields = fieldsToAsk.length > 0 ? fieldsToAsk : [missing[0]];
75
+ const questions = [];
76
+ for (const field of effectiveFields) {
77
+ const templates = QUESTION_TEMPLATES[field];
78
+ if (templates) {
79
+ questions.push(...templates);
80
+ }
81
+ }
82
+ return questions;
83
+ }
84
+ /**
85
+ * 对任务进行需求澄清
86
+ * - 完整 → 直接转为 READY
87
+ * - 不完整 → 转为 NEED_CLARIFICATION,生成问题并写入 DB
88
+ */
89
+ async clarifyTask(taskId, senderId) {
90
+ const task = getTask(this.db, taskId);
91
+ if (!task) {
92
+ throw new Error(`任务不存在: ${taskId}`);
93
+ }
94
+ if (task.status !== TaskStatus.NEW) {
95
+ throw new Error(`任务状态为 ${task.status},只有 NEW 状态才能进行澄清`);
96
+ }
97
+ const check = this.checkCompleteness({
98
+ objective: task.objective,
99
+ acceptance_criteria: task.acceptance_criteria,
100
+ context: task.context,
101
+ project_id: task.project_id,
102
+ });
103
+ if (check.isComplete) {
104
+ // 直接转为 READY
105
+ updateTask(this.db, taskId, { status: TaskStatus.READY });
106
+ return {
107
+ needsClarification: false,
108
+ questions: [],
109
+ missing: [],
110
+ };
111
+ }
112
+ // 获取 sender 的沟通风格
113
+ let style = 'verbose';
114
+ if (senderId && this.getProfileStyle) {
115
+ try {
116
+ style = await this.getProfileStyle(senderId);
117
+ }
118
+ catch {
119
+ // 降级为 verbose
120
+ style = 'verbose';
121
+ }
122
+ }
123
+ const questions = this.generateQuestions(check.missing, style);
124
+ // 转为 NEED_CLARIFICATION
125
+ updateTask(this.db, taskId, { status: TaskStatus.NEED_CLARIFICATION });
126
+ // 将问题写入 clarifications 表
127
+ for (const question of questions) {
128
+ addClarification(this.db, {
129
+ task_id: taskId,
130
+ question,
131
+ asked_by: 'loid',
132
+ });
133
+ }
134
+ return {
135
+ needsClarification: true,
136
+ questions,
137
+ missing: check.missing,
138
+ };
139
+ }
140
+ /**
141
+ * 处理澄清回答
142
+ * - 更新 clarification 记录
143
+ * - 如果所有问题都已回答,将任务转为 READY
144
+ */
145
+ async handleAnswer(clarificationId, answer, answeredBy) {
146
+ const updated = answerClarification(this.db, clarificationId, answer, answeredBy);
147
+ if (!updated) {
148
+ throw new Error(`澄清记录不存在: ${clarificationId}`);
149
+ }
150
+ const taskId = updated.task_id;
151
+ // 检查是否所有问题都已回答
152
+ const allClarifications = getClarifications(this.db, taskId);
153
+ const allAnswered = allClarifications.every(c => c.answer !== null);
154
+ if (allAnswered) {
155
+ const task = getTask(this.db, taskId);
156
+ if (task && task.status === TaskStatus.NEED_CLARIFICATION) {
157
+ updateTask(this.db, taskId, { status: TaskStatus.READY });
158
+ }
159
+ }
160
+ }
161
+ }
162
+ //# sourceMappingURL=clarifier.js.map
@@ -0,0 +1,413 @@
1
+ import { getTask, getTasksByStatus, getRecentMessages, getMessageLogBySourceRef, } 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
+ worktreeManager;
8
+ constructor(deps) {
9
+ this.db = deps.db;
10
+ this.workspacePath = deps.workspacePath;
11
+ this.worktreeManager = deps.worktreeManager;
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
+ let brief;
26
+ try {
27
+ const briefResult = await memoryBrief(this.workspacePath, {
28
+ sender_id: message.sender ?? undefined,
29
+ token_budget: 1500,
30
+ });
31
+ if (briefResult.brief) {
32
+ brief = briefResult.brief;
33
+ }
34
+ }
35
+ catch {
36
+ // 记忆摘要获取失败不阻塞主流程
37
+ }
38
+ return {
39
+ type: "new_message",
40
+ message,
41
+ activeTasks,
42
+ recentMessages,
43
+ registry,
44
+ memoryBrief: brief,
45
+ };
46
+ }
47
+ /**
48
+ * 构建交付验收上下文
49
+ */
50
+ buildDeliveryContext(taskId, exitCode, extras) {
51
+ const task = getTask(this.db, taskId);
52
+ const activeTasks = this.getActiveTasks();
53
+ return {
54
+ type: "delivery",
55
+ taskId,
56
+ briefContent: extras?.briefContent ?? null,
57
+ exitCode,
58
+ retryCount: task?.retry_count ?? 0,
59
+ maxRetries: task?.max_retries ?? 3,
60
+ prUrl: extras?.prUrl ?? task?.pr_url ?? null,
61
+ changedFiles: extras?.changedFiles ?? [],
62
+ testResults: extras?.testResults ?? null,
63
+ originalMessage: task?.context ?? null,
64
+ chatId: task?.source_ref
65
+ ? (getMessageLogBySourceRef(this.db, task.source_ref)?.chat_id ?? null)
66
+ : null,
67
+ conversationId: null,
68
+ activeTasks,
69
+ sourceChatId: task?.source_ref
70
+ ? (getMessageLogBySourceRef(this.db, task.source_ref)?.chat_id ??
71
+ undefined)
72
+ : undefined,
73
+ };
74
+ }
75
+ /**
76
+ * 获取活跃任务列表
77
+ */
78
+ getActiveTasks() {
79
+ const statuses = [
80
+ TaskStatus.IN_PROGRESS,
81
+ TaskStatus.READY,
82
+ TaskStatus.NEED_CLARIFICATION,
83
+ TaskStatus.DELIVERING,
84
+ TaskStatus.BLOCKED,
85
+ ];
86
+ const tasks = [];
87
+ for (const status of statuses) {
88
+ const statusTasks = getTasksByStatus(this.db, status);
89
+ for (const t of statusTasks) {
90
+ tasks.push({
91
+ task_id: t.task_id,
92
+ title: t.title,
93
+ status: t.status,
94
+ assignee: t.assignee,
95
+ });
96
+ }
97
+ }
98
+ return tasks;
99
+ }
100
+ /**
101
+ * 从 WorktreeManager 加载项目注册表
102
+ */
103
+ async getRegistry() {
104
+ try {
105
+ const config = await this.worktreeManager.loadConfig();
106
+ return config.projects.map((p) => ({
107
+ id: p.projectId,
108
+ name: p.name,
109
+ description: p.description,
110
+ repos: p.repos.map((r) => r.name),
111
+ claudeMd: p.claudeMd,
112
+ }));
113
+ }
114
+ catch {
115
+ return [];
116
+ }
117
+ }
118
+ /**
119
+ * 获取最近消息历史
120
+ */
121
+ getRecentMessages(chatId) {
122
+ try {
123
+ if (!chatId)
124
+ return [];
125
+ const messages = getRecentMessages(this.db, {
126
+ limit: 30,
127
+ chat_id: chatId,
128
+ });
129
+ const maxContentLen = 500;
130
+ return messages.map((m) => {
131
+ const raw = m.content ?? "";
132
+ // 从 metadata JSON 中提取 media、回复关系、sender_open_id
133
+ let media = [];
134
+ let replyTo = null;
135
+ let senderOpenId = null;
136
+ if (m.metadata) {
137
+ try {
138
+ const parsed = JSON.parse(m.metadata);
139
+ if (Array.isArray(parsed.media)) {
140
+ media = parsed.media.map((item) => ({
141
+ mediaType: String(item.mediaType ?? "file"),
142
+ originalName: String(item.originalName ?? ""),
143
+ localPath: String(item.localPath ?? ""),
144
+ }));
145
+ }
146
+ if (parsed.parent_message_id) {
147
+ replyTo = String(parsed.parent_message_id);
148
+ }
149
+ if (parsed.sender_open_id) {
150
+ senderOpenId = String(parsed.sender_open_id);
151
+ }
152
+ }
153
+ catch {
154
+ // metadata 解析失败,忽略
155
+ }
156
+ }
157
+ return {
158
+ message_id: m.source_ref ?? null,
159
+ sender: m.sender,
160
+ sender_open_id: senderOpenId,
161
+ content: raw.slice(0, maxContentLen),
162
+ direction: m.direction,
163
+ created_at: m.created_at,
164
+ message_type: m.message_type ?? null,
165
+ related_task_id: m.related_task_id ?? null,
166
+ reply_to: replyTo,
167
+ media,
168
+ truncated: raw.length > maxContentLen,
169
+ };
170
+ });
171
+ }
172
+ catch {
173
+ return [];
174
+ }
175
+ }
176
+ }
177
+ // ── Prompt 格式化 ──
178
+ export function formatMessageContextPrompt(ctx) {
179
+ const lines = [];
180
+ lines.push("强制执行步骤: 在生成任何回复之前,你必须先调用工具读取 CLAUDE.md 以及里面提到的所有的 md 文件。因为该文件中包含了决定你说话语气、你的灵魂,你的工作流程,以及你可以使用的工具(MCP)。即使你认为你知道答案,也严禁在未读取该文件的情况下直接回复。");
181
+ lines.push("--- 当前对话 ---");
182
+ const chatInfo = {
183
+ chat_id: ctx.message.chatId ?? null,
184
+ chat_type: ctx.message.isDirectMessage ? "p2p" : "group",
185
+ };
186
+ lines.push(JSON.stringify(chatInfo));
187
+ lines.push("");
188
+ lines.push("--- 当前事件 ---");
189
+ const event = {
190
+ type: "new_message",
191
+ message_id: ctx.message.metadata?.message_id ?? null,
192
+ sender_id: ctx.message.sender ?? "未知",
193
+ sender_name: ctx.message.senderName ?? null,
194
+ content: ctx.message.content,
195
+ };
196
+ if (ctx.message.mentionsAnya) {
197
+ event.mentions_anya = true;
198
+ }
199
+ if (ctx.message.metadata?.parent_message_id) {
200
+ event.reply_to = ctx.message.metadata.parent_message_id;
201
+ }
202
+ if (ctx.message.media.length > 0) {
203
+ event.media = ctx.message.media.map((m) => ({
204
+ type: m.mediaType,
205
+ name: m.originalName,
206
+ path: m.localPath,
207
+ }));
208
+ }
209
+ lines.push(JSON.stringify(event, null, 2));
210
+ if (ctx.message.media.length > 0) {
211
+ lines.push("");
212
+ lines.push("提示: 本条消息包含媒体附件,你可以通过 media.path 中的本地路径读取文件内容(图片可直接查看,文件可读取文本)。");
213
+ }
214
+ // 注入记忆摘要(在"当前状态"之前)
215
+ if (ctx.memoryBrief) {
216
+ lines.push("");
217
+ lines.push("--- 记忆摘要 ---");
218
+ lines.push(ctx.memoryBrief);
219
+ }
220
+ lines.push("");
221
+ lines.push("--- 当前状态 ---");
222
+ if (ctx.activeTasks.length > 0) {
223
+ lines.push("活跃任务:");
224
+ for (const t of ctx.activeTasks) {
225
+ lines.push(` - [${t.status}] ${t.task_id}: ${t.title}${t.assignee ? ` (${t.assignee})` : ""}`);
226
+ }
227
+ }
228
+ else {
229
+ lines.push("活跃任务: 无");
230
+ }
231
+ if (ctx.recentMessages.length > 0) {
232
+ lines.push("");
233
+ lines.push("--- 对话历史 ---");
234
+ lines.push(`以下是当前对话最近 ${ctx.recentMessages.length} 条消息(时间倒序,最新在前):`);
235
+ const history = ctx.recentMessages.map((m) => {
236
+ const entry = {
237
+ message_id: m.message_id,
238
+ time: m.created_at ?? "",
239
+ sender_id: m.direction === "outbound" ? "anya" : (m.sender_open_id ?? m.sender ?? "未知"),
240
+ sender_name: m.direction === "outbound" ? "Anya" : (m.sender ?? null),
241
+ direction: m.direction,
242
+ content: m.content,
243
+ };
244
+ if (m.reply_to) {
245
+ entry.reply_to = m.reply_to;
246
+ }
247
+ if (m.message_type && m.message_type !== "text") {
248
+ entry.message_type = m.message_type;
249
+ }
250
+ if (m.related_task_id) {
251
+ entry.related_task_id = m.related_task_id;
252
+ }
253
+ if (m.media.length > 0) {
254
+ entry.media = m.media.map((a) => ({
255
+ type: a.mediaType,
256
+ name: a.originalName,
257
+ path: a.localPath,
258
+ }));
259
+ }
260
+ if (m.truncated) {
261
+ entry.truncated = true;
262
+ }
263
+ return entry;
264
+ });
265
+ lines.push(JSON.stringify(history, null, 2));
266
+ }
267
+ if (ctx.registry.length > 0) {
268
+ lines.push("");
269
+ lines.push("--- 可用项目 ---");
270
+ lines.push("派发任务时请通过 project_id 指定对应的项目:");
271
+ for (const entry of ctx.registry) {
272
+ const desc = entry.description ? ` — ${entry.description}` : "";
273
+ lines.push(` - ${entry.id}: ${entry.name}${desc}`);
274
+ if (entry.repos.length > 0) {
275
+ lines.push(` 仓库: ${entry.repos.join(", ")}`);
276
+ }
277
+ if (entry.claudeMd) {
278
+ lines.push(` 技术栈:`);
279
+ for (const line of entry.claudeMd.split("\n")) {
280
+ lines.push(` ${line}`);
281
+ }
282
+ }
283
+ }
284
+ }
285
+ return lines.join("\n");
286
+ }
287
+ /**
288
+ * 合并多条缓冲消息为一个 prompt,供 Session drain 时投递给 CC
289
+ */
290
+ export function formatBufferedMessages(messages) {
291
+ if (messages.length === 0)
292
+ return "";
293
+ const lines = [];
294
+ lines.push(`你在处理上一件事的过程中,该对话又收到了 ${messages.length} 条新消息:`);
295
+ lines.push("");
296
+ for (let i = 0; i < messages.length; i++) {
297
+ const msg = messages[i];
298
+ const time = msg.message.metadata?.timestamp ?? "";
299
+ const sender = msg.message.senderName ?? msg.message.sender ?? "未知";
300
+ lines.push(`--- 消息 ${i + 1} (${time} 来自 ${sender}) ---`);
301
+ lines.push(msg.message.content);
302
+ if (msg.message.media.length > 0) {
303
+ for (const m of msg.message.media) {
304
+ lines.push(` 附件: [${m.mediaType}] ${m.originalName} (${m.localPath})`);
305
+ }
306
+ }
307
+ lines.push("");
308
+ }
309
+ lines.push("请综合这些新消息,决定下一步行动。");
310
+ return lines.join("\n");
311
+ }
312
+ /**
313
+ * 崩溃恢复时从 DB 活跃任务状态生成上下文 prompt
314
+ */
315
+ export function formatRecoveryPrompt(activeTasks) {
316
+ const lines = [];
317
+ lines.push("--- 崩溃恢复 ---");
318
+ lines.push("上一个会话异常终止,以下是从数据库恢复的活跃任务状态:");
319
+ lines.push("");
320
+ if (activeTasks.length > 0) {
321
+ for (const t of activeTasks) {
322
+ lines.push(`- [${t.status}] ${t.task_id}: ${t.title}${t.assignee ? ` (负责人: ${t.assignee})` : ""}`);
323
+ }
324
+ }
325
+ else {
326
+ lines.push("当前无活跃任务。");
327
+ }
328
+ lines.push("");
329
+ lines.push("请根据以上状态,决定需要优先处理的事项。");
330
+ return lines.join("\n");
331
+ }
332
+ /**
333
+ * 后续消息精简格式:已有 session 时不重复注入对话历史、项目注册表、强制读 CLAUDE.md 指令
334
+ */
335
+ export function formatFollowUpPrompt(ctx) {
336
+ const lines = [];
337
+ // 当前事件(必须)
338
+ lines.push("--- 新消息 ---");
339
+ const event = {
340
+ message_id: ctx.message.metadata?.message_id ?? null,
341
+ sender_id: ctx.message.sender ?? "未知",
342
+ sender_name: ctx.message.senderName ?? null,
343
+ content: ctx.message.content,
344
+ };
345
+ if (ctx.message.mentionsAnya)
346
+ event.mentions_anya = true;
347
+ if (ctx.message.metadata?.parent_message_id)
348
+ event.reply_to = ctx.message.metadata.parent_message_id;
349
+ if (ctx.message.media.length > 0) {
350
+ event.media = ctx.message.media.map((m) => ({
351
+ type: m.mediaType,
352
+ name: m.originalName,
353
+ path: m.localPath,
354
+ }));
355
+ }
356
+ lines.push(JSON.stringify(event, null, 2));
357
+ if (ctx.message.media.length > 0) {
358
+ lines.push("");
359
+ lines.push("提示: 本条消息包含媒体附件,你可以通过 media.path 中的本地路径读取文件内容。");
360
+ }
361
+ // 简化版记忆摘要(只注入人的记忆 summary)
362
+ if (ctx.memoryBrief) {
363
+ lines.push("");
364
+ lines.push("--- 记忆提示 ---");
365
+ lines.push(ctx.memoryBrief);
366
+ }
367
+ // 活跃任务(简洁版)
368
+ if (ctx.activeTasks.length > 0) {
369
+ lines.push("");
370
+ lines.push("--- 活跃任务 ---");
371
+ for (const t of ctx.activeTasks) {
372
+ lines.push(` - [${t.status}] ${t.task_id}: ${t.title}${t.assignee ? ` (${t.assignee})` : ""}`);
373
+ }
374
+ }
375
+ return lines.join("\n");
376
+ }
377
+ export function formatDeliveryContextPrompt(ctx) {
378
+ const lines = [];
379
+ lines.push("--- 当前事件 ---");
380
+ lines.push(`类型: Yor 交付`);
381
+ lines.push(`任务ID: ${ctx.taskId}`);
382
+ lines.push(`退出码: ${ctx.exitCode}`);
383
+ lines.push(`重试次数: ${ctx.retryCount}/${ctx.maxRetries}`);
384
+ if (ctx.prUrl) {
385
+ lines.push(`PR 链接: ${ctx.prUrl}`);
386
+ }
387
+ if (ctx.briefContent) {
388
+ lines.push("");
389
+ lines.push("原始 Brief:");
390
+ lines.push(ctx.briefContent);
391
+ }
392
+ if (ctx.changedFiles.length > 0) {
393
+ lines.push("");
394
+ lines.push("变更文件:");
395
+ for (const f of ctx.changedFiles) {
396
+ lines.push(` - ${f}`);
397
+ }
398
+ }
399
+ if (ctx.testResults) {
400
+ lines.push("");
401
+ lines.push("测试结果:");
402
+ lines.push(ctx.testResults);
403
+ }
404
+ if (ctx.originalMessage) {
405
+ lines.push("");
406
+ lines.push(`原始用户指令: "${ctx.originalMessage}"`);
407
+ }
408
+ if (ctx.chatId) {
409
+ lines.push(`对话ID: ${ctx.chatId}(用于回复人类)`);
410
+ }
411
+ return lines.join("\n");
412
+ }
413
+ //# sourceMappingURL=context-builder.js.map
@@ -0,0 +1,104 @@
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
+ yorOrchestrator: deps.yorOrchestrator,
17
+ getProjectConfig: deps.getProjectConfig,
18
+ };
19
+ const router = createToolRouter('loid', routerDeps);
20
+ const server = new Server({ name: 'loid-mcp', version: '3.0.0' }, { capabilities: { tools: {} } });
21
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
22
+ const tools = router.tools.map(def => ({
23
+ name: def.name,
24
+ description: def.description,
25
+ inputSchema: zodToJsonSchema(def.inputSchema),
26
+ }));
27
+ return { tools };
28
+ });
29
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
30
+ const { name, arguments: args } = request.params;
31
+ deps.onToolCall?.(name);
32
+ return router.handleToolCall(name, args ?? {});
33
+ });
34
+ return server;
35
+ }
36
+ /**
37
+ * 启动 Loid 指挥型 MCP server(薄路由层)
38
+ *
39
+ * 所有工具实现来自 @team-anya/mcp-tools,
40
+ * 这里只负责 HTTP 传输和 session 管理。
41
+ */
42
+ export async function startLoidMcpServer(deps) {
43
+ const sessions = new Map();
44
+ const httpServer = createServer(async (req, res) => {
45
+ const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
46
+ if (url.pathname !== '/mcp') {
47
+ res.writeHead(404);
48
+ res.end('Not Found');
49
+ return;
50
+ }
51
+ const sessionId = req.headers['mcp-session-id'];
52
+ if (sessionId && sessions.has(sessionId)) {
53
+ const { transport } = sessions.get(sessionId);
54
+ await transport.handleRequest(req, res);
55
+ return;
56
+ }
57
+ if (req.method === 'POST') {
58
+ const transport = new StreamableHTTPServerTransport({
59
+ sessionIdGenerator: () => randomUUID(),
60
+ });
61
+ const server = createMcpServer(deps);
62
+ transport.onclose = () => {
63
+ if (transport.sessionId) {
64
+ sessions.delete(transport.sessionId);
65
+ }
66
+ // 不在这里调用 server.close(),避免与 close() 函数形成循环调用导致栈溢出
67
+ };
68
+ await server.connect(transport);
69
+ await transport.handleRequest(req, res);
70
+ if (transport.sessionId) {
71
+ sessions.set(transport.sessionId, { server, transport });
72
+ }
73
+ return;
74
+ }
75
+ res.writeHead(400);
76
+ res.end('Bad Request: No valid session');
77
+ });
78
+ await new Promise((resolve) => {
79
+ httpServer.listen(0, '127.0.0.1', resolve);
80
+ });
81
+ const addr = httpServer.address();
82
+ const port = typeof addr === 'object' && addr !== null ? addr.port : 0;
83
+ const serverUrl = `http://127.0.0.1:${port}/mcp`;
84
+ return {
85
+ port,
86
+ url: serverUrl,
87
+ close: async () => {
88
+ for (const { server, transport } of sessions.values()) {
89
+ await transport.close();
90
+ await server.close();
91
+ }
92
+ sessions.clear();
93
+ await new Promise((resolve, reject) => {
94
+ httpServer.close((err) => {
95
+ if (err)
96
+ reject(err);
97
+ else
98
+ resolve();
99
+ });
100
+ });
101
+ },
102
+ };
103
+ }
104
+ //# sourceMappingURL=mcp-server.js.map