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,337 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import Fastify from 'fastify';
5
+ import fastifyCors from '@fastify/cors';
6
+ import fastifyStatic from '@fastify/static';
7
+ import { createDB } from '@team-anya/db';
8
+ import { memoryRemember, auditAppend, ChannelRegistry, FeishuAdapter } from '@team-anya/mcp-tools';
9
+ import { ensureOffice, TaskStatus } from '@team-anya/core';
10
+ import { loadConfig } from './config.js';
11
+ import { registerRoutes } from './gateway/http.js';
12
+ import { CCBroker } from './broker/cc-broker.js';
13
+ import { YorOrchestrator } from './yor/yor-orchestrator.js';
14
+ import { FeishuWSClient } from './gateway/feishu-ws.js';
15
+ import { MediaDownloader } from './gateway/media-downloader.js';
16
+ import { ChatSyncService } from './gateway/chat-sync.js';
17
+ import { FeishuSender } from './gateway/feishu-sender.js';
18
+ import { MessageIntake } from './gateway/message-intake.js';
19
+ import { CommandRouter } from './gateway/command-router.js';
20
+ import { registerBuiltinCommands } from './gateway/commands/index.js';
21
+ import { MessageQueue } from './gateway/message-queue.js';
22
+ import { MemorySettler } from './loid/memory-settler.js';
23
+ import { LoidBrain } from './loid/brain.js';
24
+ import { LoidContextBuilder } from './loid/context-builder.js';
25
+ import { Clarifier } from './loid/clarifier.js';
26
+ import { WorktreeManager } from './loid/worktree-manager.js';
27
+ export async function buildServer() {
28
+ const config = loadConfig();
29
+ // 初始化 office 目录(从模板源复制缺失的文件)
30
+ await ensureOffice(config.WORKSPACE_PATH, resolve(config.OFFICE_TEMPLATE_DIR), { info: (msg) => console.log(`[office-init] ${msg}`) });
31
+ const db = createDB(config.SQLITE_PATH);
32
+ // 日志配置:stdout + 文件双写(按日期+大小分割)
33
+ let loggerConfig = false;
34
+ if (config.NODE_ENV !== 'test') {
35
+ const logDir = resolve(config.LOG_DIR);
36
+ mkdirSync(logDir, { recursive: true });
37
+ loggerConfig = {
38
+ level: 'info',
39
+ transport: {
40
+ targets: [
41
+ { target: 'pino-pretty', options: { destination: 1 }, level: 'info' },
42
+ {
43
+ target: 'pino-roll',
44
+ options: {
45
+ file: join(logDir, 'anya'),
46
+ frequency: 'daily',
47
+ limit: { count: 14 },
48
+ mkdir: true,
49
+ },
50
+ level: 'info',
51
+ },
52
+ ],
53
+ },
54
+ };
55
+ }
56
+ const app = Fastify({
57
+ logger: loggerConfig,
58
+ disableRequestLogging: true,
59
+ });
60
+ // CORS
61
+ if (config.NODE_ENV === 'development') {
62
+ await app.register(fastifyCors, { origin: 'http://localhost:5173' });
63
+ }
64
+ // 静态文件(兼容 monorepo 开发 + npm 全局安装两种目录结构)
65
+ const webDistCandidates = [
66
+ resolve(import.meta.dirname, '../../web/dist'), // npm 包: apps/server/dist/ → apps/web/dist/
67
+ resolve(import.meta.dirname, '../../../apps/web/dist'), // monorepo 开发: src/ 下通过 tsx 运行
68
+ ];
69
+ const webDistPath = webDistCandidates.find(p => existsSync(p)) ?? webDistCandidates[0];
70
+ const serveStatic = config.NODE_ENV !== 'test' && existsSync(webDistPath);
71
+ if (serveStatic) {
72
+ await app.register(fastifyStatic, {
73
+ root: webDistPath,
74
+ prefix: '/',
75
+ });
76
+ }
77
+ // FeishuSender + 共享 Lark Client
78
+ let larkClient;
79
+ const feishuSender = (config.FEISHU_APP_ID && config.FEISHU_APP_SECRET)
80
+ ? new FeishuSender({ appId: config.FEISHU_APP_ID, appSecret: config.FEISHU_APP_SECRET, db })
81
+ : null;
82
+ if (config.FEISHU_APP_ID && config.FEISHU_APP_SECRET) {
83
+ const Lark = await import('@larksuiteoapi/node-sdk');
84
+ larkClient = new Lark.Client({
85
+ appId: config.FEISHU_APP_ID,
86
+ appSecret: config.FEISHU_APP_SECRET,
87
+ });
88
+ }
89
+ // ChatSyncService(飞书群信息同步)
90
+ const chatSyncService = larkClient ? new ChatSyncService(larkClient, db) : null;
91
+ // ChannelRegistry(通道注册表)
92
+ const channelRegistry = new ChannelRegistry();
93
+ if (feishuSender) {
94
+ channelRegistry.register(new FeishuAdapter(feishuSender));
95
+ app.log.info('已注册飞书通道适配器');
96
+ }
97
+ // MemorySettler
98
+ const memorySettler = new MemorySettler({
99
+ readFile: async (path) => {
100
+ try {
101
+ return await readFile(path, 'utf-8');
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ },
107
+ memoryRemember: (input) => memoryRemember(db, config.WORKSPACE_PATH, input),
108
+ auditAppend: (input) => auditAppend(db, config.WORKSPACE_PATH, input).then(() => { }),
109
+ });
110
+ // 任务完成后处理回调
111
+ const onTaskComplete = async (taskId, exitCode) => {
112
+ if (exitCode !== 0)
113
+ return;
114
+ try {
115
+ const taskDir = join(config.WORKSPACE_PATH, 'yor', 'tasks', taskId);
116
+ await memorySettler.settle(taskId, taskDir);
117
+ }
118
+ catch (err) {
119
+ console.error('经验沉淀失败:', err);
120
+ }
121
+ };
122
+ // Clarifier(保留用于 HTTP API)
123
+ const clarifier = new Clarifier({ db });
124
+ // WorktreeManager
125
+ const worktreeManager = new WorktreeManager({
126
+ workspacePath: resolve(config.WORKSPACE_PATH),
127
+ projectsConfigPath: resolve(config.PROJECTS_CONFIG_PATH),
128
+ reposPath: resolve(config.REPOS_PATH),
129
+ db,
130
+ });
131
+ // 自动初始化仓库(首次使用时 clone 缺失的仓库)
132
+ const ensureResult = await worktreeManager.ensureRepos();
133
+ if (ensureResult.cloned.length > 0) {
134
+ app.log.info(`自动 clone 仓库: ${ensureResult.cloned.join(', ')}`);
135
+ }
136
+ if (ensureResult.failed.length > 0) {
137
+ for (const f of ensureResult.failed) {
138
+ app.log.error(`仓库初始化失败 (${f.name}): ${f.error}`);
139
+ }
140
+ }
141
+ // ContextBuilder
142
+ const contextBuilder = new LoidContextBuilder({
143
+ db,
144
+ workspacePath: config.WORKSPACE_PATH,
145
+ worktreeManager,
146
+ });
147
+ // CCBroker — 统一 CC 进程管理器
148
+ const broker = new CCBroker({
149
+ maxLoidInstances: 2,
150
+ maxYorInstances: config.MAX_YOR_CONCURRENCY,
151
+ logger: app.log,
152
+ });
153
+ // YorOrchestrator — 替代 Yor 子进程,通过 CCBroker 管理 Yor CC
154
+ const yorOrchestrator = new YorOrchestrator(broker, {
155
+ binary: config.CLAUDE_CODE_BINARY,
156
+ workspacePath: config.WORKSPACE_PATH,
157
+ db,
158
+ logger: app.log,
159
+ });
160
+ // LoidBrain(指挥模式)
161
+ const loidBrain = new LoidBrain({
162
+ binary: config.CLAUDE_CODE_BINARY,
163
+ loidWorkDir: resolve(config.LOID_WORK_DIR),
164
+ protocolsDir: resolve(config.LOID_PROTOCOLS_DIR),
165
+ maxTurnsPerSession: 50,
166
+ logger: app.log,
167
+ }, {
168
+ broker,
169
+ mcpDeps: {
170
+ db,
171
+ channelRegistry,
172
+ workspacePath: config.WORKSPACE_PATH,
173
+ logger: app.log,
174
+ yorOrchestrator,
175
+ getProjectConfig: (projectId) => worktreeManager.getProjectConfig(projectId),
176
+ },
177
+ });
178
+ await loidBrain.init();
179
+ app.log.info('LoidBrain (指挥模式) 已启动');
180
+ // YorOrchestrator 回调 → 状态变更 + 通知 LoidBrain
181
+ yorOrchestrator.setCallbacks({
182
+ onDelivery: (taskId, _data) => {
183
+ // IN_PROGRESS → DELIVERING
184
+ yorOrchestrator.transitionTask(taskId, TaskStatus.DELIVERING, 'yor', 'Yor 提交交付');
185
+ const ctx = contextBuilder.buildDeliveryContext(taskId, 0);
186
+ loidBrain.handleDelivery(ctx).catch(err => {
187
+ app.log.error({ err, taskId }, 'Yor 交付验收触发失败');
188
+ });
189
+ },
190
+ onBlocker: (taskId, data) => {
191
+ // IN_PROGRESS → BLOCKED
192
+ yorOrchestrator.transitionTask(taskId, TaskStatus.BLOCKED, 'yor', `阻塞: ${data.category} - ${data.reason}`);
193
+ app.log.info({ taskId, category: data.category }, 'Yor 报告阻塞,已更新状态');
194
+ },
195
+ onInstanceExited: (taskId) => {
196
+ onTaskComplete(taskId, 0).catch(err => {
197
+ app.log.error({ err, taskId }, '经验沉淀失败');
198
+ });
199
+ },
200
+ onInstanceCrash: (taskId, error) => {
201
+ app.log.error({ taskId, error }, 'Yor 实例崩溃');
202
+ // 崩溃 → BLOCKED(非终态才变更)
203
+ yorOrchestrator.transitionTask(taskId, TaskStatus.BLOCKED, 'system', `Yor 实例崩溃: ${error}`);
204
+ const ctx = contextBuilder.buildDeliveryContext(taskId, 1);
205
+ loidBrain.handleDelivery(ctx).catch(err => {
206
+ app.log.error({ err, taskId }, 'Yor 崩溃后验收触发失败');
207
+ });
208
+ },
209
+ });
210
+ // 重启回调:释放 Loid 会话 + Broker 全部实例
211
+ const onRestart = async () => {
212
+ // 先让 SessionManager 清理 session map(会同时 dispose broker 中的 Loid 实例)
213
+ if (loidBrain) {
214
+ await loidBrain.disposeSessions();
215
+ }
216
+ // 再释放剩余实例(Yor 等)
217
+ const ids = await broker.disposeAll();
218
+ app.log.info(`[restart] 已释放 ${ids.length} 个 CC 实例`);
219
+ return { disposed: ids.length };
220
+ };
221
+ // CommandRouter(斜杠命令拦截)
222
+ let commandRouter;
223
+ if (feishuSender) {
224
+ commandRouter = new CommandRouter({ db, feishuSender, broker, onRestart, logger: app.log });
225
+ registerBuiltinCommands(commandRouter);
226
+ app.log.info('CommandRouter 已注册内置命令');
227
+ }
228
+ // MessageIntake(事件转发器)
229
+ const messageIntake = new MessageIntake({
230
+ db,
231
+ loidBrain,
232
+ contextBuilder,
233
+ commandRouter,
234
+ });
235
+ // MessageQueue
236
+ const messageQueue = new MessageQueue({
237
+ handler: async (msg) => {
238
+ try {
239
+ await messageIntake.handle(msg);
240
+ }
241
+ catch (err) {
242
+ app.log.error({ err, msg: msg.content.slice(0, 50) }, '消息处理失败');
243
+ }
244
+ },
245
+ logger: {
246
+ info: (...args) => app.log.info(args.join(' ')),
247
+ warn: (...args) => app.log.warn(args.join(' ')),
248
+ error: (...args) => app.log.error(args.join(' ')),
249
+ },
250
+ });
251
+ // HTTP 路由
252
+ await registerRoutes(app, { db, broker, clarifier, messageQueue, larkClient, chatSyncService, onRestart });
253
+ // SPA fallback
254
+ if (serveStatic) {
255
+ app.setNotFoundHandler(async (request, reply) => {
256
+ if (request.url.startsWith('/api/')) {
257
+ return reply.status(404).send({ error: 'Not Found' });
258
+ }
259
+ return reply.sendFile('index.html', webDistPath);
260
+ });
261
+ }
262
+ return { app, db, config, broker, yorOrchestrator, messageIntake, messageQueue, feishuSender, loidBrain, larkClient, chatSyncService };
263
+ }
264
+ /**
265
+ * 完整启动流程:buildServer + listen + 飞书 WS + graceful shutdown
266
+ * CLI 和直接运行都调用这个函数
267
+ */
268
+ export async function startServer() {
269
+ const serverCtx = await buildServer();
270
+ const { app, config, broker, messageQueue, feishuSender, loidBrain, larkClient, chatSyncService, db } = serverCtx;
271
+ await app.listen({ port: config.PORT, host: '0.0.0.0' });
272
+ app.log.info(`Anya server 已启动,端口 ${config.PORT}`);
273
+ // 飞书 WebSocket 长连接
274
+ let feishuWs = null;
275
+ if (config.FEISHU_APP_ID && config.FEISHU_APP_SECRET && larkClient) {
276
+ const mediaDownloader = new MediaDownloader(larkClient, {
277
+ mediaDir: config.MEDIA_PATH,
278
+ });
279
+ await mediaDownloader.ensureDir();
280
+ feishuWs = new FeishuWSClient({ appId: config.FEISHU_APP_ID, appSecret: config.FEISHU_APP_SECRET }, {
281
+ onMessage: async (msg) => {
282
+ messageQueue.enqueue(msg);
283
+ },
284
+ mediaDownloader,
285
+ client: larkClient,
286
+ db,
287
+ chatSyncService: chatSyncService ?? undefined,
288
+ feishuSender: feishuSender ?? undefined,
289
+ logger: app.log,
290
+ });
291
+ feishuWs.connect().then(() => {
292
+ app.log.info('飞书 WebSocket 连接已建立');
293
+ }).catch((err) => {
294
+ app.log.error({ err }, '飞书 WebSocket 连接失败');
295
+ });
296
+ }
297
+ else {
298
+ app.log.info('飞书凭证未配置,跳过 WebSocket 连接');
299
+ }
300
+ // ── Graceful shutdown ──
301
+ let shuttingDown = false;
302
+ const shutdown = async (signal) => {
303
+ if (shuttingDown)
304
+ return;
305
+ shuttingDown = true;
306
+ app.log.info(`收到 ${signal},开始优雅关闭...`);
307
+ if (feishuWs)
308
+ await feishuWs.close();
309
+ messageQueue.dispose();
310
+ await app.close();
311
+ if (loidBrain) {
312
+ try {
313
+ await loidBrain.dispose();
314
+ }
315
+ catch (err) {
316
+ app.log.error({ err }, 'LoidBrain 关闭失败');
317
+ }
318
+ }
319
+ const result = await broker.shutdown(30_000);
320
+ if (result.unfinished.length > 0) {
321
+ app.log.info(`优雅关闭: ${result.unfinished.length} 个实例未完成`);
322
+ }
323
+ app.log.info('Anya server 已关闭');
324
+ };
325
+ process.on('SIGINT', () => { shutdown('SIGINT').then(() => process.exit(0)); });
326
+ process.on('SIGTERM', () => { shutdown('SIGTERM').then(() => process.exit(0)); });
327
+ return { app, config };
328
+ }
329
+ // 仅在直接运行时启动
330
+ const isDirectRun = process.argv[1]?.endsWith('main.js') || process.argv[1]?.endsWith('main.ts');
331
+ if (isDirectRun) {
332
+ startServer().catch((err) => {
333
+ console.error('启动失败:', err);
334
+ process.exit(1);
335
+ });
336
+ }
337
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1,2 @@
1
+ export { TraceContext } from './trace-context.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,92 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { insertTraceSpan, updateTraceSpan } from '@team-anya/db';
3
+ export class TraceContext {
4
+ traceId;
5
+ db;
6
+ logger;
7
+ spanStack = [];
8
+ constructor(config, traceId) {
9
+ this.traceId = traceId ?? randomUUID();
10
+ this.db = config.db;
11
+ this.logger = config.logger;
12
+ }
13
+ startSpan(operation, input) {
14
+ const spanId = randomUUID();
15
+ const parentSpanId = this.spanStack.length > 0
16
+ ? this.spanStack[this.spanStack.length - 1].spanId
17
+ : null;
18
+ this.spanStack.push({ spanId, operation, startedAt: Date.now() });
19
+ // Fire-and-forget DB write
20
+ try {
21
+ insertTraceSpan(this.db, {
22
+ trace_id: this.traceId,
23
+ span_id: spanId,
24
+ parent_span_id: parentSpanId,
25
+ operation,
26
+ status: 'in_progress',
27
+ started_at: new Date().toISOString(),
28
+ input: input !== undefined ? JSON.stringify(input) : null,
29
+ });
30
+ }
31
+ catch (err) {
32
+ this.logger?.error('TraceContext: insertTraceSpan 失败', err);
33
+ }
34
+ return spanId;
35
+ }
36
+ endSpan(opts) {
37
+ const targetSpanId = opts?.spanId;
38
+ let entry;
39
+ if (targetSpanId) {
40
+ // Find and remove the specific span from the stack
41
+ const idx = this.spanStack.findIndex(s => s.spanId === targetSpanId);
42
+ if (idx >= 0) {
43
+ entry = this.spanStack[idx];
44
+ this.spanStack.splice(idx, 1);
45
+ }
46
+ }
47
+ else {
48
+ // Pop the top of the stack
49
+ entry = this.spanStack.pop();
50
+ }
51
+ if (!entry) {
52
+ this.logger?.error('TraceContext: endSpan 未找到对应 span');
53
+ return;
54
+ }
55
+ const endedAt = Date.now();
56
+ const durationMs = endedAt - entry.startedAt;
57
+ try {
58
+ updateTraceSpan(this.db, entry.spanId, {
59
+ status: opts?.status ?? 'success',
60
+ ended_at: new Date().toISOString(),
61
+ duration_ms: durationMs,
62
+ output: opts?.output !== undefined ? JSON.stringify(opts.output) : null,
63
+ error: opts?.error ?? null,
64
+ metadata: opts?.metadata ? JSON.stringify(opts.metadata) : null,
65
+ });
66
+ }
67
+ catch (err) {
68
+ this.logger?.error('TraceContext: updateTraceSpan 失败', err);
69
+ }
70
+ }
71
+ async withSpan(operation, input, fn) {
72
+ const spanId = this.startSpan(operation, input);
73
+ try {
74
+ const result = await fn();
75
+ this.endSpan({
76
+ spanId,
77
+ status: 'success',
78
+ output: result,
79
+ });
80
+ return result;
81
+ }
82
+ catch (err) {
83
+ this.endSpan({
84
+ spanId,
85
+ status: 'error',
86
+ error: err instanceof Error ? err.message : String(err),
87
+ });
88
+ throw err;
89
+ }
90
+ }
91
+ }
92
+ //# sourceMappingURL=trace-context.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=message.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 createYorMcpServer(deps) {
10
+ const logger = deps.collector.logger ?? { info: console.log, error: console.error };
11
+ const routerDeps = {
12
+ db: deps.db,
13
+ workspacePath: deps.workspacePath,
14
+ currentTaskId: deps.currentTaskId,
15
+ yorCollector: deps.collector,
16
+ logger,
17
+ };
18
+ const router = createToolRouter('yor', routerDeps);
19
+ const server = new Server({ name: 'yor-mcp', version: '2.0.0' }, { capabilities: { tools: {} } });
20
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
21
+ const tools = router.tools.map(def => ({
22
+ name: def.name,
23
+ description: def.description,
24
+ inputSchema: zodToJsonSchema(def.inputSchema),
25
+ }));
26
+ return { tools };
27
+ });
28
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29
+ const { name, arguments: args } = request.params;
30
+ return router.handleToolCall(name, args ?? {});
31
+ });
32
+ return server;
33
+ }
34
+ /**
35
+ * 启动 Yor MCP server(薄路由层)
36
+ *
37
+ * Yor 现在可以使用 Layer 1 全部共享工具(memory/org/task.get/audit)
38
+ * + Layer 2 Yor 专属工具(task.deliver/task.block/task.progress)。
39
+ *
40
+ * 所有工具实现来自 @team-anya/mcp-tools。
41
+ */
42
+ export async function startYorMcpServer(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 = createYorMcpServer(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=yor-mcp-server.js.map