team-anya-cli 0.1.8 → 1.0.1

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 (168) hide show
  1. package/README.md +1 -37
  2. package/package.json +6 -37
  3. package/anya/prompts/execution-guides/git-delivery.md +0 -38
  4. package/anya/prompts/execution-guides/testing-and-self-heal.md +0 -28
  5. package/anya/prompts/protocols/brief-assembly.md +0 -55
  6. package/anya/prompts/protocols/report.md +0 -175
  7. package/anya/prompts/protocols/review.md +0 -90
  8. package/anya/prompts/task-claude-md.template.md +0 -32
  9. package/apps/server/dist/broker/cc-broker.js +0 -261
  10. package/apps/server/dist/cli.js +0 -296
  11. package/apps/server/dist/config.js +0 -76
  12. package/apps/server/dist/daemon.js +0 -51
  13. package/apps/server/dist/franky/context-builder.js +0 -160
  14. package/apps/server/dist/franky/franky-mcp-server.js +0 -107
  15. package/apps/server/dist/franky/franky-orchestrator.js +0 -450
  16. package/apps/server/dist/franky/index.js +0 -5
  17. package/apps/server/dist/franky/topic-router.js +0 -16
  18. package/apps/server/dist/gateway/chat-sync.js +0 -135
  19. package/apps/server/dist/gateway/command-router.js +0 -114
  20. package/apps/server/dist/gateway/commands/cancel.js +0 -32
  21. package/apps/server/dist/gateway/commands/help.js +0 -16
  22. package/apps/server/dist/gateway/commands/index.js +0 -26
  23. package/apps/server/dist/gateway/commands/restart.js +0 -34
  24. package/apps/server/dist/gateway/commands/status.js +0 -34
  25. package/apps/server/dist/gateway/commands/tasks.js +0 -33
  26. package/apps/server/dist/gateway/feishu-sender.js +0 -410
  27. package/apps/server/dist/gateway/feishu-ws.js +0 -256
  28. package/apps/server/dist/gateway/http.js +0 -1019
  29. package/apps/server/dist/gateway/media-downloader.js +0 -149
  30. package/apps/server/dist/gateway/message-events.js +0 -10
  31. package/apps/server/dist/gateway/message-intake.js +0 -67
  32. package/apps/server/dist/gateway/message-queue.js +0 -118
  33. package/apps/server/dist/gateway/session-reader.js +0 -142
  34. package/apps/server/dist/gateway/ws-push.js +0 -115
  35. package/apps/server/dist/loid/brain.js +0 -105
  36. package/apps/server/dist/loid/clarifier.js +0 -162
  37. package/apps/server/dist/loid/context-builder.js +0 -413
  38. package/apps/server/dist/loid/mcp-server.js +0 -106
  39. package/apps/server/dist/loid/memory-settler.js +0 -189
  40. package/apps/server/dist/loid/opportunity-manager.js +0 -148
  41. package/apps/server/dist/loid/profile-updater.js +0 -179
  42. package/apps/server/dist/loid/reporter.js +0 -148
  43. package/apps/server/dist/loid/schemas.js +0 -117
  44. package/apps/server/dist/loid/self-calibrator.js +0 -314
  45. package/apps/server/dist/loid/session-manager.js +0 -301
  46. package/apps/server/dist/loid/session.js +0 -271
  47. package/apps/server/dist/loid/worktree-manager.js +0 -191
  48. package/apps/server/dist/main.js +0 -393
  49. package/apps/server/dist/tracing/index.js +0 -2
  50. package/apps/server/dist/tracing/trace-context.js +0 -92
  51. package/apps/server/dist/types/message.js +0 -2
  52. package/apps/server/dist/yor/yor-mcp-server.js +0 -104
  53. package/apps/server/dist/yor/yor-orchestrator.js +0 -233
  54. package/apps/web/dist/assets/index-BiiEB0qZ.css +0 -1
  55. package/apps/web/dist/assets/index-D1AK5ZEE.js +0 -798
  56. package/apps/web/dist/index.html +0 -13
  57. package/packages/cc-client/dist/claude-code-backend.js +0 -664
  58. package/packages/cc-client/dist/index.js +0 -2
  59. package/packages/cc-client/package.json +0 -11
  60. package/packages/core/dist/constants.js +0 -59
  61. package/packages/core/dist/errors.js +0 -35
  62. package/packages/core/dist/index.js +0 -7
  63. package/packages/core/dist/office-init.js +0 -101
  64. package/packages/core/dist/scope/checker.js +0 -114
  65. package/packages/core/dist/scope/defaults.js +0 -55
  66. package/packages/core/dist/scope/index.js +0 -3
  67. package/packages/core/dist/state-machine.js +0 -85
  68. package/packages/core/dist/types/audit.js +0 -12
  69. package/packages/core/dist/types/backend.js +0 -2
  70. package/packages/core/dist/types/commitment.js +0 -17
  71. package/packages/core/dist/types/communication.js +0 -18
  72. package/packages/core/dist/types/index.js +0 -8
  73. package/packages/core/dist/types/opportunity.js +0 -27
  74. package/packages/core/dist/types/org.js +0 -26
  75. package/packages/core/dist/types/task.js +0 -46
  76. package/packages/core/package.json +0 -10
  77. package/packages/db/dist/client.js +0 -69
  78. package/packages/db/dist/index.js +0 -691
  79. package/packages/db/dist/schema/audit-events.js +0 -13
  80. package/packages/db/dist/schema/cc-sessions.js +0 -14
  81. package/packages/db/dist/schema/chats.js +0 -33
  82. package/packages/db/dist/schema/commitments.js +0 -18
  83. package/packages/db/dist/schema/communication-events.js +0 -14
  84. package/packages/db/dist/schema/index.js +0 -13
  85. package/packages/db/dist/schema/message-log.js +0 -20
  86. package/packages/db/dist/schema/opportunities.js +0 -23
  87. package/packages/db/dist/schema/org.js +0 -36
  88. package/packages/db/dist/schema/projects.js +0 -23
  89. package/packages/db/dist/schema/tasks.js +0 -48
  90. package/packages/db/dist/schema/topics.js +0 -20
  91. package/packages/db/dist/schema/trace-spans.js +0 -19
  92. package/packages/db/package.json +0 -12
  93. package/packages/db/src/migrations/0000_baseline.sql +0 -251
  94. package/packages/db/src/migrations/meta/_journal.json +0 -13
  95. package/packages/mcp-tools/dist/index.js +0 -41
  96. package/packages/mcp-tools/dist/layer1/audit-append.js +0 -38
  97. package/packages/mcp-tools/dist/layer1/audit-query.js +0 -51
  98. package/packages/mcp-tools/dist/layer1/memory-brief.js +0 -168
  99. package/packages/mcp-tools/dist/layer1/memory-context.js +0 -124
  100. package/packages/mcp-tools/dist/layer1/memory-digest.js +0 -126
  101. package/packages/mcp-tools/dist/layer1/memory-forget.js +0 -108
  102. package/packages/mcp-tools/dist/layer1/memory-learn.js +0 -63
  103. package/packages/mcp-tools/dist/layer1/memory-recall.js +0 -287
  104. package/packages/mcp-tools/dist/layer1/memory-reflect.js +0 -80
  105. package/packages/mcp-tools/dist/layer1/memory-remember.js +0 -119
  106. package/packages/mcp-tools/dist/layer1/memory-search.js +0 -263
  107. package/packages/mcp-tools/dist/layer1/memory-write.js +0 -21
  108. package/packages/mcp-tools/dist/layer1/org-lookup.js +0 -47
  109. package/packages/mcp-tools/dist/layer1/project-get.js +0 -28
  110. package/packages/mcp-tools/dist/layer1/project-list.js +0 -20
  111. package/packages/mcp-tools/dist/layer1/report-daily.js +0 -68
  112. package/packages/mcp-tools/dist/layer1/task-get.js +0 -29
  113. package/packages/mcp-tools/dist/layer1/task-update.js +0 -34
  114. package/packages/mcp-tools/dist/layer2/franky/topic-checkpoint.js +0 -43
  115. package/packages/mcp-tools/dist/layer2/franky/topic-escalate.js +0 -19
  116. package/packages/mcp-tools/dist/layer2/loid/decision-log.js +0 -15
  117. package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +0 -15
  118. package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +0 -30
  119. package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +0 -12
  120. package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +0 -77
  121. package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +0 -18
  122. package/packages/mcp-tools/dist/layer2/loid/project-remove.js +0 -16
  123. package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +0 -33
  124. package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +0 -196
  125. package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +0 -38
  126. package/packages/mcp-tools/dist/layer2/loid/topic-close.js +0 -22
  127. package/packages/mcp-tools/dist/layer2/loid/topic-create.js +0 -56
  128. package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +0 -8
  129. package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +0 -7
  130. package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +0 -7
  131. package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +0 -15
  132. package/packages/mcp-tools/dist/layer2/loid/yor-status.js +0 -8
  133. package/packages/mcp-tools/dist/layer2/yor/task-block.js +0 -11
  134. package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +0 -35
  135. package/packages/mcp-tools/dist/layer2/yor/task-progress.js +0 -21
  136. package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +0 -192
  137. package/packages/mcp-tools/dist/layer3/adapters/types.js +0 -28
  138. package/packages/mcp-tools/dist/layer3/channel-receive.js +0 -11
  139. package/packages/mcp-tools/dist/layer3/channel-send.js +0 -91
  140. package/packages/mcp-tools/dist/layer3/file-upload.js +0 -44
  141. package/packages/mcp-tools/dist/registry.js +0 -871
  142. package/packages/mcp-tools/package.json +0 -13
  143. package/workspace/.claude/settings.local.json +0 -9
  144. package/workspace/.mcp.json +0 -12
  145. package/workspace/CHARTER.md +0 -76
  146. package/workspace/CLAUDE.md +0 -58
  147. package/workspace/PROTOCOL.md +0 -160
  148. package/workspace/TOOLS.md +0 -470
  149. package/workspace/audit/.gitkeep +0 -0
  150. package/workspace/franky/CLAUDE.md +0 -37
  151. package/workspace/franky/PLAYBOOK.md +0 -215
  152. package/workspace/franky/PROFILE.md +0 -80
  153. package/workspace/loid/CLAUDE.md +0 -12
  154. package/workspace/loid/PLAYBOOK.md +0 -198
  155. package/workspace/loid/PROFILE.md +0 -78
  156. package/workspace/memory/commitments/.gitkeep +0 -0
  157. package/workspace/memory/execution/.gitkeep +0 -0
  158. package/workspace/memory/people/.gitkeep +0 -0
  159. package/workspace/memory/projects/.gitkeep +0 -0
  160. package/workspace/memory/self/.gitkeep +0 -0
  161. package/workspace/reference/identity/.gitkeep +0 -0
  162. package/workspace/reference/org/escalation.yaml +0 -24
  163. package/workspace/reference/org/ownership.yaml +0 -28
  164. package/workspace/reports/.gitkeep +0 -0
  165. package/workspace/yor/CLAUDE.md +0 -22
  166. package/workspace/yor/PLAYBOOK.md +0 -73
  167. package/workspace/yor/PROFILE.md +0 -52
  168. package/workspace/yor/SELF-HEAL.md +0 -39
@@ -1,450 +0,0 @@
1
- import { readFile, mkdir, writeFile, copyFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { existsSync } from 'node:fs';
4
- import { execFile as execFileCb } from 'node:child_process';
5
- import { promisify } from 'node:util';
6
- import { getTopic, insertAuditEvent, insertCCSession, updateCCSessionEnded, upsertTopic } from '@team-anya/db';
7
- import { FRANKY_DEFAULT_SCOPE } from '@team-anya/core';
8
- import { startFrankyMcpServer } from './franky-mcp-server.js';
9
- const execFile = promisify(execFileCb);
10
- // ── FrankyOrchestrator ──
11
- /**
12
- * FrankyOrchestrator:专项协作者编排层
13
- *
14
- * 职责:
15
- * - 管理每个 Topic 对应的 Franky CC 实例(最多一个)
16
- * - 消息驱动:用户发消息时,实例存在则注入消息,不存在则自动创建
17
- * - 监听 CC 实例的 result / exited / crash 事件
18
- * - Idle 超时回收(预留接口,Phase 3 细化)
19
- *
20
- * 与 YorOrchestrator 的核心区别:
21
- * - Yor:一次性任务执行(brief -> 执行 -> deliver -> 销毁)
22
- * - Franky:持续协作(专项开启 -> 消息驱动对话 -> 专项关闭)
23
- */
24
- export class FrankyOrchestrator {
25
- broker;
26
- config;
27
- instances = new Map(); // key = topicId
28
- spawning = new Map(); // 防止并发 spawn
29
- logger;
30
- idleCheckInterval = null;
31
- idleTimeoutMs;
32
- constructor(broker, config) {
33
- this.broker = broker;
34
- this.config = config;
35
- this.logger = config.logger ?? { info: console.log, error: console.error };
36
- this.idleTimeoutMs = config.idleTimeoutMs ?? 30 * 60 * 1000; // 默认 30 分钟
37
- // 启动 idle 检查定时器
38
- this.idleCheckInterval = setInterval(() => this.checkIdleInstances(), 5 * 60 * 1000);
39
- // 监听 CC session ID 事件(Franky 实例)
40
- this.broker.on('instance.sessionId', (instanceId, cliSessionId) => {
41
- const brokerInstance = this.broker.getInstance(instanceId);
42
- if (brokerInstance?.role !== 'franky')
43
- return;
44
- const topicId = this.findTopicByInstanceId(instanceId);
45
- if (topicId) {
46
- insertCCSession(this.config.db, {
47
- session_id: cliSessionId,
48
- role: 'franky',
49
- instance_id: instanceId,
50
- task_id: topicId,
51
- project_path: brokerInstance.config.workingDir,
52
- });
53
- this.logger.info(`[FrankyOrchestrator] 记录 CC session: ${cliSessionId} (topic: ${topicId})`);
54
- }
55
- });
56
- // 监听 CC 实例事件
57
- this.broker.on('instance.result', (instanceId, role, _result) => {
58
- if (role !== 'franky')
59
- return;
60
- const topicId = this.findTopicByInstanceId(instanceId);
61
- if (topicId) {
62
- const instance = this.instances.get(topicId);
63
- if (instance)
64
- instance.lastActivityAt = new Date();
65
- this.logger.info(`[FrankyOrchestrator] 实例 ${instanceId} 执行完成 (topic: ${topicId})`);
66
- }
67
- });
68
- this.broker.on('instance.exited', (instanceId, role) => {
69
- if (role !== 'franky')
70
- return;
71
- const topicId = this.findTopicByInstanceId(instanceId);
72
- if (topicId) {
73
- // 更新 cc_sessions.ended_at
74
- const brokerInstance = this.broker.getInstance(instanceId);
75
- if (brokerInstance?.cliSessionId) {
76
- updateCCSessionEnded(this.config.db, brokerInstance.cliSessionId);
77
- }
78
- this.logger.info(`[FrankyOrchestrator] 实例 ${instanceId} 已退出 (topic: ${topicId})`);
79
- this.cleanupInstance(topicId);
80
- }
81
- });
82
- this.broker.on('instance.crash', (instanceId, role, error) => {
83
- if (role !== 'franky')
84
- return;
85
- const topicId = this.findTopicByInstanceId(instanceId);
86
- if (topicId) {
87
- // 更新 cc_sessions.ended_at
88
- const brokerInstance = this.broker.getInstance(instanceId);
89
- if (brokerInstance?.cliSessionId) {
90
- updateCCSessionEnded(this.config.db, brokerInstance.cliSessionId);
91
- }
92
- this.logger.error(`[FrankyOrchestrator] 实例 ${instanceId} 崩溃 (topic: ${topicId}):`, error);
93
- this.cleanupInstance(topicId);
94
- }
95
- });
96
- }
97
- /**
98
- * 向专项发送消息。如果 Franky 实例不存在,自动创建。
99
- */
100
- async sendMessage(topicId, message) {
101
- let instance = this.instances.get(topicId);
102
- if (!instance) {
103
- // 防止并发 spawn:多条消息同时到达时共享同一个 spawn promise
104
- let pending = this.spawning.get(topicId);
105
- if (!pending) {
106
- pending = this.spawnForTopic(topicId);
107
- this.spawning.set(topicId, pending);
108
- try {
109
- instance = await pending;
110
- }
111
- finally {
112
- this.spawning.delete(topicId);
113
- }
114
- }
115
- else {
116
- instance = await pending;
117
- }
118
- }
119
- // 格式化用户消息为 prompt
120
- const prompt = this.formatUserMessage(message);
121
- // 注入消息到 CC 实例
122
- try {
123
- this.broker.sendPrompt(instance.instanceId, prompt);
124
- instance.lastActivityAt = new Date();
125
- }
126
- catch (err) {
127
- this.logger.error(`[FrankyOrchestrator] 发送消息失败 (topic: ${topicId}):`, err);
128
- // 实例可能已死,清理后重试一次
129
- this.cleanupInstance(topicId);
130
- const newInstance = await this.spawnForTopic(topicId);
131
- this.broker.sendPrompt(newInstance.instanceId, prompt);
132
- }
133
- }
134
- /**
135
- * 关闭指定 Topic 的 Franky 实例
136
- */
137
- async closeTopic(topicId) {
138
- await this.cleanupInstance(topicId);
139
- this.logger.info(`[FrankyOrchestrator] 关闭专项 ${topicId} 的 Franky 实例`);
140
- }
141
- /**
142
- * 获取所有活跃的 Franky 实例状态
143
- */
144
- status() {
145
- return [...this.instances.values()].map(i => ({
146
- topicId: i.topicId,
147
- instanceId: i.instanceId,
148
- lastActivityAt: i.lastActivityAt.toISOString(),
149
- }));
150
- }
151
- /**
152
- * 优雅关闭所有实例
153
- */
154
- async shutdown() {
155
- if (this.idleCheckInterval) {
156
- clearInterval(this.idleCheckInterval);
157
- this.idleCheckInterval = null;
158
- }
159
- const topicIds = [...this.instances.keys()];
160
- for (const topicId of topicIds) {
161
- await this.cleanupInstance(topicId);
162
- }
163
- }
164
- // ── 私有方法 ──
165
- /**
166
- * 为指定 Topic 创建 Franky CC 实例
167
- */
168
- async spawnForTopic(topicId) {
169
- const topic = getTopic(this.config.db, topicId);
170
- if (!topic)
171
- throw new Error(`专项 ${topicId} 不存在`);
172
- const instanceId = `franky:${topicId}`;
173
- const frankyRoot = join(this.config.workspacePath, 'franky');
174
- const topicWorkDir = join(frankyRoot, 'topics', topicId);
175
- // 确保 topic 目录存在
176
- await mkdir(topicWorkDir, { recursive: true });
177
- // 准备 git worktree(如果关联了项目)
178
- const worktreeResult = await this.prepareWorktrees(topic, topicWorkDir);
179
- // 复制启动文件到 topic 目录(CLAUDE.md 的相对路径基于 topicWorkDir)
180
- await this.prepareTopicWorkspace(frankyRoot, topicWorkDir);
181
- // 生成/重建 context.md
182
- const contextPath = join(topicWorkDir, 'context.md');
183
- const isRebuild = existsSync(contextPath);
184
- if (isRebuild) {
185
- // 重建模式:从 DB + checkpoints + message_log 重建上下文
186
- try {
187
- const { writeTopicContext } = await import('./context-builder.js');
188
- await writeTopicContext({ db: this.config.db, workspacePath: this.config.workspacePath, logger: this.logger }, topicId);
189
- this.logger.info(`[FrankyOrchestrator] 重建 context.md (topic: ${topicId})`);
190
- }
191
- catch (err) {
192
- this.logger.error(`[FrankyOrchestrator] context.md 重建失败,使用现有文件:`, err);
193
- }
194
- }
195
- else {
196
- // 新建模式:生成初始 context(含 worktree 信息)
197
- await this.generateInitialContext(topic, contextPath, worktreeResult?.worktreeInfo);
198
- }
199
- // 如果创建了 worktree,回写 workspace_path 和 branch_name 到 DB
200
- if (worktreeResult) {
201
- upsertTopic(this.config.db, {
202
- id: topic.id,
203
- title: topic.title,
204
- status: topic.status,
205
- description: topic.description ?? undefined,
206
- project_id: topic.project_id ?? undefined,
207
- thread_id: topic.thread_id ?? undefined,
208
- root_message_id: topic.root_message_id ?? undefined,
209
- chat_id: topic.chat_id ?? undefined,
210
- created_by: topic.created_by ?? undefined,
211
- workspace_path: topicWorkDir,
212
- branch_name: worktreeResult.branchName,
213
- });
214
- }
215
- // 启动 Franky MCP Server
216
- const mcpDeps = {
217
- db: this.config.db,
218
- workspacePath: topicWorkDir,
219
- topicId,
220
- replyToThreadId: topic.thread_id ?? undefined,
221
- channelRegistry: this.config.channelRegistry,
222
- onMessageSent: this.config.onMessageSent,
223
- onTopicClosed: (tid) => this.closeTopic(tid),
224
- onTopicEscalated: this.config.onTopicEscalated,
225
- logger: this.logger,
226
- };
227
- const mcpServer = await startFrankyMcpServer(mcpDeps);
228
- // 准备启动 prompt(读取 context.md)
229
- let startupPrompt;
230
- try {
231
- startupPrompt = await readFile(contextPath, 'utf-8');
232
- }
233
- catch {
234
- startupPrompt = `# 专项: ${topic.title}\n\n目标: ${topic.description ?? '(未设定)'}`;
235
- }
236
- // 通过 CCBroker 启动实例
237
- const logFile = join(topicWorkDir, `cc-backend.log`);
238
- await this.broker.spawn({
239
- id: instanceId,
240
- role: 'franky',
241
- backendConfig: {
242
- type: 'claude-code',
243
- binary: this.config.binary,
244
- workingDir: topicWorkDir,
245
- permissionMode: 'bypassPermissions',
246
- mcpServers: [{ name: 'franky-tools', url: mcpServer.url }],
247
- },
248
- scope: FRANKY_DEFAULT_SCOPE,
249
- mcpServer,
250
- logFile,
251
- });
252
- // 发送启动 prompt
253
- this.broker.sendPrompt(instanceId, startupPrompt);
254
- const instance = {
255
- topicId,
256
- instanceId,
257
- mcpServer,
258
- lastActivityAt: new Date(),
259
- };
260
- this.instances.set(topicId, instance);
261
- insertAuditEvent(this.config.db, {
262
- event_type: 'franky_spawned',
263
- actor: 'system',
264
- summary: `启动 Franky 实例: ${topicId}`,
265
- detail: JSON.stringify({ instanceId, topicId }),
266
- });
267
- this.logger.info(`[FrankyOrchestrator] 启动 Franky 实例 ${instanceId} (topic: ${topicId})`);
268
- return instance;
269
- }
270
- /**
271
- * 准备 git worktree(如果 topic 关联了项目)
272
- *
273
- * 与 Yor 的 task-dispatch 类似,但额外处理 respawn 场景:
274
- * - worktree 目录已存在时跳过创建(复用)
275
- * - 分支已存在时不带 -b 参数附加
276
- * - 创建失败时 graceful degradation(不阻塞 spawn)
277
- */
278
- async prepareWorktrees(topic, topicWorkDir) {
279
- if (!topic.project_id || !this.config.getProjectConfig)
280
- return null;
281
- let projectConfig;
282
- try {
283
- projectConfig = await this.config.getProjectConfig(topic.project_id);
284
- }
285
- catch (err) {
286
- this.logger.error(`[FrankyOrchestrator] 获取项目配置失败 (${topic.project_id}):`, err);
287
- return null;
288
- }
289
- if (projectConfig.mode !== 'project' || !projectConfig.repos?.length)
290
- return null;
291
- const branchName = topic.branch_name ?? `feat/topic-${topic.id}`;
292
- const lines = ['## 代码工作区', ''];
293
- const createdWorktrees = [];
294
- for (const repo of projectConfig.repos) {
295
- const wtPath = join(topicWorkDir, repo.name);
296
- const defaultBranch = repo.default_branch ?? 'main';
297
- if (existsSync(wtPath)) {
298
- this.logger.info(`[FrankyOrchestrator] worktree 已存在,复用: ${wtPath}`);
299
- }
300
- else {
301
- try {
302
- await execFile('git', ['-C', repo.repo_path, 'fetch', 'origin']);
303
- // 检查分支是否已存在(之前创建后被手动清理 worktree 的场景)
304
- let branchExists = false;
305
- try {
306
- const { stdout } = await execFile('git', ['-C', repo.repo_path, 'branch', '--list', branchName]);
307
- branchExists = stdout.trim().length > 0;
308
- }
309
- catch { /* ignore */ }
310
- if (branchExists) {
311
- await execFile('git', ['-C', repo.repo_path, 'worktree', 'add', wtPath, branchName]);
312
- }
313
- else {
314
- await execFile('git', ['-C', repo.repo_path, 'worktree', 'add', wtPath, '-b', branchName, `origin/${defaultBranch}`]);
315
- }
316
- createdWorktrees.push({ repoPath: repo.repo_path, wtPath });
317
- this.logger.info(`[FrankyOrchestrator] worktree 已创建: ${wtPath} (branch: ${branchName})`);
318
- }
319
- catch (err) {
320
- const msg = err instanceof Error ? err.message : String(err);
321
- this.logger.error(`[FrankyOrchestrator] worktree 创建失败 (${repo.name}): ${msg}`);
322
- // 回滚已创建的 worktree
323
- for (const wt of createdWorktrees) {
324
- try {
325
- await execFile('git', ['-C', wt.repoPath, 'worktree', 'remove', wt.wtPath, '--force']);
326
- }
327
- catch { /* ignore rollback failure */ }
328
- }
329
- return null;
330
- }
331
- }
332
- lines.push(`- \`${repo.name}/\` → 分支 \`${branchName}\`,路径 \`${wtPath}\``);
333
- }
334
- lines.push('');
335
- lines.push('> 代码在上述 worktree 子目录中,git 操作请进入对应子目录执行。');
336
- return { branchName, worktreeInfo: lines.join('\n') };
337
- }
338
- /**
339
- * 准备 topic 工作目录:复制启动文件,使 CLAUDE.md 的相对路径正确解析
340
- *
341
- * 目录结构:
342
- * topics/TOPIC-xxx/ ← workingDir(CC 实例在此启动)
343
- * ├── CLAUDE.md ← 复制自 franky/CLAUDE.md
344
- * ├── PROFILE.md ← 复制自 franky/PROFILE.md
345
- * ├── PLAYBOOK.md ← 复制自 franky/PLAYBOOK.md
346
- * ├── ../CHARTER.md ← topics/CHARTER.md(复制自 office/CHARTER.md)
347
- * ├── ../PROTOCOL.md ← topics/PROTOCOL.md
348
- * ├── ../TOOLS.md ← topics/TOOLS.md
349
- * └── context.md ← 系统生成
350
- */
351
- async prepareTopicWorkspace(frankyRoot, topicWorkDir) {
352
- const officeRoot = this.config.workspacePath;
353
- const topicsDir = join(frankyRoot, 'topics');
354
- // CLAUDE.md 中 `../CHARTER.md` 从 topic dir 解析到 topics/ 目录
355
- const parentFiles = ['CHARTER.md', 'PROTOCOL.md', 'TOOLS.md'];
356
- for (const file of parentFiles) {
357
- const src = join(officeRoot, file);
358
- const dest = join(topicsDir, file);
359
- if (existsSync(src))
360
- await copyFile(src, dest);
361
- }
362
- // CLAUDE.md 中 `PROFILE.md` / `PLAYBOOK.md` 在 topic dir 同级
363
- const localFiles = ['CLAUDE.md', 'PROFILE.md', 'PLAYBOOK.md'];
364
- for (const file of localFiles) {
365
- const src = join(frankyRoot, file);
366
- const dest = join(topicWorkDir, file);
367
- if (existsSync(src))
368
- await copyFile(src, dest);
369
- }
370
- }
371
- /**
372
- * 生成初始 context.md
373
- */
374
- async generateInitialContext(topic, contextPath, worktreeInfo) {
375
- const lines = [
376
- `# 专项上下文:${topic.title} (${topic.id})`,
377
- '',
378
- '## 目标',
379
- topic.description ?? '(用户未设定详细描述)',
380
- '',
381
- '## 通讯信息',
382
- ...(topic.chat_id ? [`- 群聊 ID:\`${topic.chat_id}\``] : []),
383
- ...(topic.root_message_id ? [`- 话题根消息 ID:\`${topic.root_message_id}\``] : []),
384
- ...(topic.thread_id ? [`- 话题 ID:\`${topic.thread_id}\`(仅供参考,channel.send 的 reply_to 请用根消息 ID)`] : []),
385
- '',
386
- '## 项目信息',
387
- `- 专项 ID:${topic.id}`,
388
- ...(topic.project_id ? [`- 关联项目:${topic.project_id}`] : []),
389
- ...(topic.branch_name ? [`- 分支:${topic.branch_name}`] : []),
390
- '',
391
- ];
392
- if (worktreeInfo) {
393
- lines.push(worktreeInfo);
394
- lines.push('');
395
- }
396
- await writeFile(contextPath, lines.join('\n'), 'utf-8');
397
- }
398
- /**
399
- * 格式化用户消息为 CC prompt
400
- */
401
- formatUserMessage(message) {
402
- const sender = message.senderName ?? message.sender ?? '用户';
403
- return `[${sender}]: ${message.content}`;
404
- }
405
- /**
406
- * 检查并回收空闲实例
407
- */
408
- async checkIdleInstances() {
409
- const now = Date.now();
410
- for (const [topicId, instance] of this.instances) {
411
- const idle = now - instance.lastActivityAt.getTime();
412
- if (idle > this.idleTimeoutMs) {
413
- this.logger.info(`[FrankyOrchestrator] 回收空闲实例 ${instance.instanceId} (idle: ${Math.round(idle / 60000)}min)`);
414
- await this.cleanupInstance(topicId);
415
- }
416
- }
417
- }
418
- /**
419
- * 清理指定 Topic 的 Franky 实例
420
- */
421
- async cleanupInstance(topicId) {
422
- const instance = this.instances.get(topicId);
423
- if (!instance)
424
- return;
425
- try {
426
- await instance.mcpServer.close();
427
- }
428
- catch (err) {
429
- this.logger.error(`[FrankyOrchestrator] MCP server 关闭失败:`, err);
430
- }
431
- try {
432
- await this.broker.dispose(instance.instanceId);
433
- }
434
- catch {
435
- // 可能已经退出
436
- }
437
- this.instances.delete(topicId);
438
- }
439
- /**
440
- * 通过 instanceId 反查 topicId
441
- */
442
- findTopicByInstanceId(instanceId) {
443
- for (const [topicId, instance] of this.instances) {
444
- if (instance.instanceId === instanceId)
445
- return topicId;
446
- }
447
- return undefined;
448
- }
449
- }
450
- //# sourceMappingURL=franky-orchestrator.js.map
@@ -1,5 +0,0 @@
1
- export { FrankyOrchestrator } from './franky-orchestrator.js';
2
- export { startFrankyMcpServer } from './franky-mcp-server.js';
3
- export { FrankyTopicRouter } from './topic-router.js';
4
- export { buildTopicContext, writeTopicContext } from './context-builder.js';
5
- //# sourceMappingURL=index.js.map
@@ -1,16 +0,0 @@
1
- /**
2
- * TopicRouter 实现:将消息路由给对应 Topic 的 Franky 实例
3
- */
4
- export class FrankyTopicRouter {
5
- orchestrator;
6
- logger;
7
- constructor(orchestrator, logger) {
8
- this.orchestrator = orchestrator;
9
- this.logger = logger ?? { info: console.log, error: console.error };
10
- }
11
- async routeToTopic(topicId, msg) {
12
- this.logger.info(`[TopicRouter] 路由消息到专项 ${topicId}: ${msg.sender}`);
13
- await this.orchestrator.sendMessage(topicId, msg);
14
- }
15
- }
16
- //# sourceMappingURL=topic-router.js.map
@@ -1,135 +0,0 @@
1
- import { getChat, upsertChat, upsertChatMember, removeChatMembers, upsertOrgMember } from '@team-anya/db';
2
- const SYNC_INTERVAL_MS = 60 * 60 * 1000; // 1 小时内不重复同步
3
- export class ChatSyncService {
4
- larkClient;
5
- db;
6
- syncing = new Set(); // 防并发重复同步
7
- constructor(larkClient, db) {
8
- this.larkClient = larkClient;
9
- this.db = db;
10
- }
11
- /** 检查是否需要同步(不存在或超过同步间隔) */
12
- needsSync(chatId) {
13
- const chat = getChat(this.db, chatId);
14
- if (!chat)
15
- return true;
16
- if (!chat.last_synced_at)
17
- return true;
18
- const elapsed = Date.now() - new Date(chat.last_synced_at).getTime();
19
- return elapsed > SYNC_INTERVAL_MS;
20
- }
21
- /** 同步单个群信息 */
22
- async syncChat(chatId) {
23
- try {
24
- const resp = await this.larkClient.im.chat.get({
25
- path: { chat_id: chatId },
26
- });
27
- const chatData = resp?.data;
28
- if (!chatData)
29
- return;
30
- upsertChat(this.db, {
31
- chat_id: chatId,
32
- platform: 'feishu',
33
- name: chatData.name ?? null,
34
- description: chatData.description ?? null,
35
- avatar: chatData.avatar ?? null,
36
- owner_id: chatData.owner_id ?? null,
37
- chat_mode: chatData.chat_mode ?? null,
38
- chat_type: chatData.chat_type ?? null,
39
- chat_tag: chatData.chat_tag ?? null,
40
- external: chatData.external ? 1 : 0,
41
- tenant_key: chatData.tenant_key ?? null,
42
- user_count: typeof chatData.user_count === 'string'
43
- ? parseInt(chatData.user_count, 10)
44
- : (chatData.user_count ?? null),
45
- bot_count: typeof chatData.bot_count === 'string'
46
- ? parseInt(chatData.bot_count, 10)
47
- : (chatData.bot_count ?? null),
48
- last_synced_at: new Date().toISOString(),
49
- });
50
- console.log(`[chat-sync] 群信息同步完成: ${chatId} (${chatData.name ?? 'unnamed'})`);
51
- }
52
- catch (err) {
53
- console.warn(`[chat-sync] 同步群信息失败 (${chatId}):`, err);
54
- }
55
- }
56
- /** 同步群成员(全量替换) */
57
- async syncChatMembers(chatId) {
58
- try {
59
- // 清除旧成员数据
60
- removeChatMembers(this.db, chatId);
61
- let pageToken;
62
- let memberCount = 0;
63
- do {
64
- const resp = await this.larkClient.im.chatMembers.get({
65
- path: { chat_id: chatId },
66
- params: {
67
- member_id_type: 'open_id',
68
- ...(pageToken ? { page_token: pageToken } : {}),
69
- },
70
- });
71
- const items = resp?.data?.items ?? [];
72
- for (const item of items) {
73
- const openId = item.member_id;
74
- if (!openId)
75
- continue;
76
- // 同步用户完整信息到 org_members
77
- await this.resolveAndUpsertMember(openId);
78
- // 写入 chat_members 关联
79
- upsertChatMember(this.db, {
80
- chat_id: chatId,
81
- member_id: openId,
82
- role: item.member_id_type === 'owner' ? 'owner' : 'member',
83
- });
84
- memberCount++;
85
- }
86
- pageToken = resp?.data?.page_token || undefined;
87
- } while (pageToken);
88
- console.log(`[chat-sync] 群成员同步完成: ${chatId}, 共 ${memberCount} 人`);
89
- }
90
- catch (err) {
91
- console.warn(`[chat-sync] 同步群成员失败 (${chatId}):`, err);
92
- }
93
- }
94
- /** 完整同步(群信息 + 成员) */
95
- async syncChatFull(chatId) {
96
- if (this.syncing.has(chatId))
97
- return;
98
- this.syncing.add(chatId);
99
- try {
100
- await this.syncChat(chatId);
101
- await this.syncChatMembers(chatId);
102
- }
103
- finally {
104
- this.syncing.delete(chatId);
105
- }
106
- }
107
- /** 解析飞书用户完整信息并 upsert 到 org_members */
108
- async resolveAndUpsertMember(openId) {
109
- try {
110
- const resp = await this.larkClient.contact.user.get({
111
- path: { user_id: openId },
112
- params: { user_id_type: 'open_id' },
113
- });
114
- const user = resp?.data?.user;
115
- if (!user?.name)
116
- return;
117
- upsertOrgMember(this.db, {
118
- member_id: openId,
119
- name: user.name,
120
- platform: 'feishu',
121
- union_id: user.union_id ?? undefined,
122
- user_id: user.user_id ?? undefined,
123
- en_name: user.en_name ?? undefined,
124
- email: user.email ?? undefined,
125
- employee_no: user.employee_no ?? undefined,
126
- avatar_url: user.avatar?.avatar_origin ?? user.avatar?.avatar_72 ?? undefined,
127
- last_synced_at: new Date().toISOString(),
128
- });
129
- }
130
- catch (err) {
131
- console.warn(`[chat-sync] 解析用户信息失败 (${openId}):`, err);
132
- }
133
- }
134
- }
135
- //# sourceMappingURL=chat-sync.js.map