team-anya 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.

Potentially problematic release.


This version of team-anya might be problematic. Click here for more details.

Files changed (177) 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/brief-assembler.js +156 -0
  31. package/apps/server/dist/loid/clarifier.js +162 -0
  32. package/apps/server/dist/loid/commitment-tracker.js +236 -0
  33. package/apps/server/dist/loid/context-builder.js +413 -0
  34. package/apps/server/dist/loid/dispatcher.js +544 -0
  35. package/apps/server/dist/loid/intent-classifier.js +158 -0
  36. package/apps/server/dist/loid/mcp-server.js +104 -0
  37. package/apps/server/dist/loid/memory-settler.js +189 -0
  38. package/apps/server/dist/loid/opportunity-manager.js +148 -0
  39. package/apps/server/dist/loid/process-manager.js +186 -0
  40. package/apps/server/dist/loid/profile-updater.js +179 -0
  41. package/apps/server/dist/loid/reporter.js +148 -0
  42. package/apps/server/dist/loid/schemas.js +117 -0
  43. package/apps/server/dist/loid/self-calibrator.js +314 -0
  44. package/apps/server/dist/loid/session-manager.js +217 -0
  45. package/apps/server/dist/loid/session.js +271 -0
  46. package/apps/server/dist/loid/worktree-manager.js +191 -0
  47. package/apps/server/dist/main.js +337 -0
  48. package/apps/server/dist/tracing/index.js +2 -0
  49. package/apps/server/dist/tracing/trace-context.js +92 -0
  50. package/apps/server/dist/types/message.js +2 -0
  51. package/apps/server/dist/yor/yor-mcp-server.js +104 -0
  52. package/apps/server/dist/yor/yor-orchestrator.js +233 -0
  53. package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
  54. package/apps/web/dist/assets/index-CJzAjoVH.js +798 -0
  55. package/apps/web/dist/index.html +13 -0
  56. package/package.json +42 -0
  57. package/packages/cc-client/dist/claude-code-backend.js +664 -0
  58. package/packages/cc-client/dist/index.js +2 -0
  59. package/packages/cc-client/package.json +11 -0
  60. package/packages/core/dist/constants.js +59 -0
  61. package/packages/core/dist/errors.js +35 -0
  62. package/packages/core/dist/index.js +7 -0
  63. package/packages/core/dist/office-init.js +97 -0
  64. package/packages/core/dist/scope/checker.js +114 -0
  65. package/packages/core/dist/scope/defaults.js +40 -0
  66. package/packages/core/dist/scope/index.js +3 -0
  67. package/packages/core/dist/state-machine.js +85 -0
  68. package/packages/core/dist/types/audit.js +12 -0
  69. package/packages/core/dist/types/backend.js +2 -0
  70. package/packages/core/dist/types/commitment.js +17 -0
  71. package/packages/core/dist/types/communication.js +18 -0
  72. package/packages/core/dist/types/index.js +8 -0
  73. package/packages/core/dist/types/opportunity.js +27 -0
  74. package/packages/core/dist/types/org.js +26 -0
  75. package/packages/core/dist/types/task.js +46 -0
  76. package/packages/core/package.json +10 -0
  77. package/packages/db/dist/client.js +69 -0
  78. package/packages/db/dist/index.js +603 -0
  79. package/packages/db/dist/schema/audit-events.js +13 -0
  80. package/packages/db/dist/schema/cc-sessions.js +14 -0
  81. package/packages/db/dist/schema/chats.js +33 -0
  82. package/packages/db/dist/schema/commitments.js +18 -0
  83. package/packages/db/dist/schema/communication-events.js +14 -0
  84. package/packages/db/dist/schema/index.js +12 -0
  85. package/packages/db/dist/schema/message-log.js +20 -0
  86. package/packages/db/dist/schema/opportunities.js +23 -0
  87. package/packages/db/dist/schema/org.js +36 -0
  88. package/packages/db/dist/schema/projects.js +23 -0
  89. package/packages/db/dist/schema/tasks.js +46 -0
  90. package/packages/db/dist/schema/trace-spans.js +19 -0
  91. package/packages/db/package.json +12 -0
  92. package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
  93. package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
  94. package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
  95. package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
  96. package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
  97. package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
  98. package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
  99. package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
  100. package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
  101. package/packages/db/src/migrations/meta/_journal.json +41 -0
  102. package/packages/mcp-tools/dist/index.js +41 -0
  103. package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
  104. package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
  105. package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
  106. package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
  107. package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
  108. package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
  109. package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
  110. package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
  111. package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
  112. package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
  113. package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
  114. package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
  115. package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
  116. package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
  117. package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
  118. package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
  119. package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
  120. package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
  121. package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
  122. package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
  123. package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
  124. package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
  125. package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
  126. package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
  127. package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
  128. package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
  129. package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
  130. package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
  131. package/packages/mcp-tools/dist/layer2/loid/task-review.js +151 -0
  132. package/packages/mcp-tools/dist/layer2/loid/workspace-cleanup.js +7 -0
  133. package/packages/mcp-tools/dist/layer2/loid/workspace-info.js +31 -0
  134. package/packages/mcp-tools/dist/layer2/loid/workspace-prepare.js +12 -0
  135. package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
  136. package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
  137. package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
  138. package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
  139. package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
  140. package/packages/mcp-tools/dist/layer2/yor/code-lint.js +47 -0
  141. package/packages/mcp-tools/dist/layer2/yor/code-test.js +52 -0
  142. package/packages/mcp-tools/dist/layer2/yor/git-add.js +24 -0
  143. package/packages/mcp-tools/dist/layer2/yor/git-commit.js +24 -0
  144. package/packages/mcp-tools/dist/layer2/yor/git-push.js +64 -0
  145. package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
  146. package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
  147. package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
  148. package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
  149. package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
  150. package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
  151. package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
  152. package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
  153. package/packages/mcp-tools/dist/registry.js +779 -0
  154. package/packages/mcp-tools/package.json +13 -0
  155. package/workspace/.claude/settings.local.json +9 -0
  156. package/workspace/.mcp.json +12 -0
  157. package/workspace/CHARTER.md +73 -0
  158. package/workspace/CLAUDE.md +49 -0
  159. package/workspace/PROTOCOL.md +126 -0
  160. package/workspace/TOOLS.md +464 -0
  161. package/workspace/audit/.gitkeep +0 -0
  162. package/workspace/loid/CLAUDE.md +12 -0
  163. package/workspace/loid/PLAYBOOK.md +198 -0
  164. package/workspace/loid/PROFILE.md +78 -0
  165. package/workspace/memory/commitments/.gitkeep +0 -0
  166. package/workspace/memory/execution/.gitkeep +0 -0
  167. package/workspace/memory/people/.gitkeep +0 -0
  168. package/workspace/memory/projects/.gitkeep +0 -0
  169. package/workspace/memory/self/.gitkeep +0 -0
  170. package/workspace/reference/identity/.gitkeep +0 -0
  171. package/workspace/reference/org/escalation.yaml +24 -0
  172. package/workspace/reference/org/ownership.yaml +28 -0
  173. package/workspace/reports/.gitkeep +0 -0
  174. package/workspace/yor/CLAUDE.md +22 -0
  175. package/workspace/yor/PLAYBOOK.md +73 -0
  176. package/workspace/yor/PROFILE.md +52 -0
  177. package/workspace/yor/SELF-HEAL.md +39 -0
@@ -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
@@ -0,0 +1,189 @@
1
+ // ── MemorySettler ──
2
+ /**
3
+ * 记忆沉淀器
4
+ *
5
+ * 任务完成(DONE)后自动触发,分析 execution-log.jsonl 和 result.md,
6
+ * 将经过验证的结论写入长期记忆。
7
+ *
8
+ * 沉淀规则:
9
+ * - self_heal 且 result=fixed → pitfalls.md
10
+ * - decision 事件 → decisions.md
11
+ * - collaborator_discovery → people/{ref}.md
12
+ * - result.md 中的"经验教训"section → pitfalls.md
13
+ */
14
+ export class MemorySettler {
15
+ deps;
16
+ constructor(deps) {
17
+ this.deps = deps;
18
+ }
19
+ async settle(taskId, taskDir) {
20
+ const result = {
21
+ pitfalls: [],
22
+ decisions: [],
23
+ collaborators: [],
24
+ totalSettled: 0,
25
+ errors: [],
26
+ };
27
+ // 1. 读取 execution-log.jsonl
28
+ const logEntries = await this.readExecutionLog(taskDir);
29
+ // 2. 读取 result.md
30
+ const resultMd = await this.deps.readFile(`${taskDir}/result.md`);
31
+ // 3. 提取 pitfalls(自愈记录,仅 result=fixed)
32
+ const pitfalls = this.extractPitfalls(logEntries);
33
+ // 4. 从 result.md 提取额外经验教训
34
+ const lessonsFromResult = this.extractLessonsFromResult(resultMd);
35
+ for (const lesson of lessonsFromResult) {
36
+ if (!pitfalls.includes(lesson)) {
37
+ pitfalls.push(lesson);
38
+ }
39
+ }
40
+ // 5. 提取 decisions
41
+ const decisions = this.extractDecisions(logEntries);
42
+ // 6. 提取 collaborator discoveries
43
+ const collaborators = this.extractCollaborators(logEntries);
44
+ // 7. 写入记忆(使用新版 memoryRemember)
45
+ for (const pitfall of pitfalls) {
46
+ await this.safeRemember(result, {
47
+ category: 'project',
48
+ target: this.deps.projectId,
49
+ sub_type: 'pitfall',
50
+ content: pitfall,
51
+ source: taskId,
52
+ importance: 0.7,
53
+ });
54
+ }
55
+ for (const decision of decisions) {
56
+ await this.safeRemember(result, {
57
+ category: 'project',
58
+ target: this.deps.projectId,
59
+ sub_type: 'decision',
60
+ content: decision,
61
+ source: taskId,
62
+ importance: 0.6,
63
+ });
64
+ }
65
+ for (const collab of collaborators) {
66
+ await this.safeRemember(result, {
67
+ category: 'people',
68
+ target: collab.ref,
69
+ content: collab.detail,
70
+ source: taskId,
71
+ importance: 0.5,
72
+ });
73
+ }
74
+ result.pitfalls = pitfalls;
75
+ result.decisions = decisions;
76
+ result.collaborators = collaborators;
77
+ result.totalSettled = pitfalls.length + decisions.length + collaborators.length;
78
+ // 8. 审计记录
79
+ try {
80
+ await this.deps.auditAppend({
81
+ event_type: 'memory_settled',
82
+ actor: 'loid',
83
+ task_id: taskId,
84
+ summary: `沉淀 ${result.totalSettled} 条记忆 (${pitfalls.length} pitfalls, ${decisions.length} decisions, ${collaborators.length} collaborators)`,
85
+ detail: {
86
+ pitfalls: pitfalls.length,
87
+ decisions: decisions.length,
88
+ collaborators: collaborators.length,
89
+ },
90
+ });
91
+ }
92
+ catch {
93
+ result.errors.push('审计记录写入失败');
94
+ }
95
+ return result;
96
+ }
97
+ // ── 内部方法 ──
98
+ async readExecutionLog(taskDir) {
99
+ const raw = await this.deps.readFile(`${taskDir}/execution-log.jsonl`);
100
+ if (!raw)
101
+ return [];
102
+ const entries = [];
103
+ for (const line of raw.split('\n')) {
104
+ const trimmed = line.trim();
105
+ if (!trimmed)
106
+ continue;
107
+ try {
108
+ entries.push(JSON.parse(trimmed));
109
+ }
110
+ catch {
111
+ // 解析失败的行跳过
112
+ }
113
+ }
114
+ return entries;
115
+ }
116
+ extractPitfalls(entries) {
117
+ const seen = new Set();
118
+ const pitfalls = [];
119
+ for (const entry of entries) {
120
+ if (entry.event !== 'self_heal')
121
+ continue;
122
+ if (entry.result !== 'fixed')
123
+ continue;
124
+ if (!entry.detail)
125
+ continue;
126
+ const key = entry.detail;
127
+ if (seen.has(key))
128
+ continue;
129
+ seen.add(key);
130
+ pitfalls.push(entry.detail);
131
+ }
132
+ return pitfalls;
133
+ }
134
+ extractLessonsFromResult(resultMd) {
135
+ if (!resultMd)
136
+ return [];
137
+ const lessons = [];
138
+ // 查找 "## 经验教训" section
139
+ const sectionPattern = /##\s*经验教训\s*\n([\s\S]*?)(?=\n##\s|\n$|$)/;
140
+ const match = sectionPattern.exec(resultMd);
141
+ if (!match)
142
+ return [];
143
+ const sectionContent = match[1];
144
+ // 解析列表项
145
+ const listPattern = /^[-*]\s+(.+)$/gm;
146
+ let listMatch;
147
+ while ((listMatch = listPattern.exec(sectionContent)) !== null) {
148
+ const lesson = listMatch[1].trim();
149
+ if (lesson) {
150
+ lessons.push(lesson);
151
+ }
152
+ }
153
+ return lessons;
154
+ }
155
+ extractDecisions(entries) {
156
+ const decisions = [];
157
+ for (const entry of entries) {
158
+ if (entry.event !== 'decision')
159
+ continue;
160
+ if (!entry.detail)
161
+ continue;
162
+ decisions.push(entry.detail);
163
+ }
164
+ return decisions;
165
+ }
166
+ extractCollaborators(entries) {
167
+ const collaborators = [];
168
+ for (const entry of entries) {
169
+ if (entry.event !== 'collaborator_discovery')
170
+ continue;
171
+ if (!entry.ref || !entry.detail)
172
+ continue;
173
+ collaborators.push({
174
+ ref: entry.ref,
175
+ detail: entry.detail,
176
+ });
177
+ }
178
+ return collaborators;
179
+ }
180
+ async safeRemember(result, input) {
181
+ try {
182
+ await this.deps.memoryRemember(input);
183
+ }
184
+ catch (e) {
185
+ result.errors.push(`写入记忆 [${input.category}/${input.target ?? ''}] 失败: ${e.message}`);
186
+ }
187
+ }
188
+ }
189
+ //# sourceMappingURL=memory-settler.js.map
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @deprecated 此模块已废弃,核心流程改为 Loid 自主判断。
3
+ * 保留此文件仅为兼容 HTTP API,新代码请勿使用。
4
+ */
5
+ import { randomUUID } from 'node:crypto';
6
+ import { createOpportunity, createTask, getOpportunity, getAllOpportunities, getOpportunitiesByStatus, updateOpportunity, getTodayMaxSequence, } from '@team-anya/db';
7
+ import { TaskStatus, IntentLevel, generateTaskId } from '@team-anya/core';
8
+ // ── 关键词列表(复用 intent-classifier 的关键词) ──
9
+ const RISK_WORDS = [
10
+ '故障', '报警', '错误', '崩溃', '超时', '500', 'oom', 'panic',
11
+ '宕机', '挂了', '异常', '告警', '失败', 'error', 'crash', 'timeout',
12
+ 'fatal', 'outage', 'down',
13
+ ];
14
+ const TASK_VERBS = [
15
+ '修复', '优化', '排查', '添加', '实现', '删除', '重构', '升级',
16
+ 'fix', 'add', 'implement', 'delete', 'remove', 'refactor', 'upgrade',
17
+ 'optimize', 'debug', 'resolve', 'migrate',
18
+ ];
19
+ const IMPACT_WORDS = [
20
+ '用户', '线上', '生产', '核心', '关键', '影响', '所有', '全部',
21
+ 'production', 'critical', 'users', 'all', 'major',
22
+ ];
23
+ // ── OpportunityManager ──
24
+ export class OpportunityManager {
25
+ db;
26
+ workspacePath;
27
+ logger;
28
+ constructor(deps) {
29
+ this.db = deps.db;
30
+ this.workspacePath = deps.workspacePath;
31
+ this.logger = deps.logger ?? { info: console.log, error: console.error };
32
+ }
33
+ /**
34
+ * 从意图分级结果创建机会
35
+ * 仅处理 L2_OPPORTUNITY 级别的消息
36
+ */
37
+ createFromIntent(input) {
38
+ if (input.intentLevel !== IntentLevel.L2_OPPORTUNITY) {
39
+ return null;
40
+ }
41
+ const scores = this.scoreOpportunity(input.content);
42
+ const id = `opp-${randomUUID().slice(0, 8)}`;
43
+ const opp = createOpportunity(this.db, {
44
+ id,
45
+ summary: input.content,
46
+ status: 'detected',
47
+ source_ref: input.sourceRef ?? null,
48
+ score_impact: scores.impact,
49
+ score_urgency: scores.urgency,
50
+ score_feasibility: scores.feasibility,
51
+ score_permission: scores.permission,
52
+ total_score: scores.total,
53
+ });
54
+ this.logger.info(`机会创建: ${id} (score=${scores.total.toFixed(2)})`);
55
+ return opp;
56
+ }
57
+ /**
58
+ * 4 维评分
59
+ * - impact: 影响范围(是否涉及用户/线上/核心功能)
60
+ * - urgency: 紧急程度(是否有风险词/故障词)
61
+ * - feasibility: 可行性(是否有明确的任务动词)
62
+ * - permission: 权限适配性(Anya 是否能处理,默认中等)
63
+ */
64
+ scoreOpportunity(content) {
65
+ const lower = content.toLowerCase();
66
+ // impact: 影响范围
67
+ const impactHits = IMPACT_WORDS.filter(w => lower.includes(w.toLowerCase())).length;
68
+ const impact = Math.min(1, 0.3 + impactHits * 0.15);
69
+ // urgency: 紧急程度
70
+ const urgencyHits = RISK_WORDS.filter(w => lower.includes(w.toLowerCase())).length;
71
+ const urgency = Math.min(1, 0.1 + urgencyHits * 0.25);
72
+ // feasibility: 可行性
73
+ const feasibilityHits = TASK_VERBS.filter(w => lower.includes(w.toLowerCase())).length;
74
+ const feasibility = Math.min(1, 0.3 + feasibilityHits * 0.2);
75
+ // permission: 默认中等
76
+ const permission = 0.5;
77
+ // total: 加权平均
78
+ const total = impact * 0.3 + urgency * 0.3 + feasibility * 0.25 + permission * 0.15;
79
+ return { impact, urgency, feasibility, permission, total };
80
+ }
81
+ /**
82
+ * 认领机会:转为 confirmed,创建关联 task
83
+ */
84
+ confirm(opportunityId) {
85
+ const opp = getOpportunity(this.db, opportunityId);
86
+ if (!opp) {
87
+ throw new Error(`机会 ${opportunityId} 不存在`);
88
+ }
89
+ if (opp.status !== 'detected') {
90
+ throw new Error(`只有 detected 状态的机会可以认领,当前状态: ${opp.status}`);
91
+ }
92
+ // 创建关联任务
93
+ const seq = getTodayMaxSequence(this.db) + 1;
94
+ const taskId = generateTaskId(seq);
95
+ const task = createTask(this.db, {
96
+ task_id: taskId,
97
+ title: opp.summary,
98
+ status: TaskStatus.NEW,
99
+ source_type: 'opportunity',
100
+ source_ref: opportunityId,
101
+ });
102
+ // 更新 opportunity
103
+ const updated = updateOpportunity(this.db, opportunityId, {
104
+ status: 'confirmed',
105
+ converted_task_id: taskId,
106
+ claimed_at: new Date().toISOString(),
107
+ });
108
+ this.logger.info(`机会认领: ${opportunityId} → task ${taskId}`);
109
+ return { opportunity: updated, task };
110
+ }
111
+ /**
112
+ * 拒绝机会
113
+ */
114
+ reject(opportunityId, _reason) {
115
+ const opp = getOpportunity(this.db, opportunityId);
116
+ if (!opp) {
117
+ throw new Error(`机会 ${opportunityId} 不存在`);
118
+ }
119
+ const updated = updateOpportunity(this.db, opportunityId, {
120
+ status: 'rejected',
121
+ });
122
+ this.logger.info(`机会拒绝: ${opportunityId}`);
123
+ return updated;
124
+ }
125
+ /**
126
+ * 查询机会列表(支持 status 和 min_score 过滤)
127
+ */
128
+ getOpportunities(filter) {
129
+ let results;
130
+ if (filter.status) {
131
+ results = getOpportunitiesByStatus(this.db, filter.status);
132
+ }
133
+ else {
134
+ results = getAllOpportunities(this.db);
135
+ }
136
+ if (filter.min_score !== undefined) {
137
+ results = results.filter(o => (o.total_score ?? 0) >= filter.min_score);
138
+ }
139
+ return results;
140
+ }
141
+ /**
142
+ * 获取单个机会
143
+ */
144
+ getOpportunity(opportunityId) {
145
+ return getOpportunity(this.db, opportunityId);
146
+ }
147
+ }
148
+ //# sourceMappingURL=opportunity-manager.js.map
@@ -0,0 +1,186 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { EventEmitter } from 'node:events';
3
+ /**
4
+ * ProcessManager: 纯进程管理器
5
+ *
6
+ * 只负责 Yor 进程的生命周期管理:
7
+ * - spawn / kill / 监听退出
8
+ * - 槽位管理(并发控制)
9
+ * - 退出时通过 EventEmitter 发送事件通知
10
+ *
11
+ * 不包含任何业务逻辑(排序、恢复、工作区准备、brief 组装、DB 操作等)。
12
+ */
13
+ export class ProcessManager extends EventEmitter {
14
+ slots;
15
+ maxConcurrency;
16
+ yorEntry;
17
+ logger;
18
+ spawnFn;
19
+ shuttingDown = false;
20
+ constructor(config, spawnFn) {
21
+ super();
22
+ this.maxConcurrency = config.maxConcurrency;
23
+ this.yorEntry = config.yorEntry;
24
+ this.logger = config.logger ?? { info: console.log, error: console.error };
25
+ this.spawnFn = spawnFn ?? spawn;
26
+ this.slots = Array.from({ length: config.maxConcurrency }, (_, i) => ({
27
+ slotId: `slot-${i}`,
28
+ busy: false,
29
+ taskId: null,
30
+ process: null,
31
+ pid: null,
32
+ startedAt: null,
33
+ }));
34
+ }
35
+ /**
36
+ * 启动一个 Yor 子进程
37
+ * 调用方负责工作区准备和 brief 组装,ProcessManager 只负责 spawn。
38
+ */
39
+ async spawn(params) {
40
+ if (this.shuttingDown) {
41
+ throw new Error('ProcessManager 正在关闭,无法 spawn 新进程');
42
+ }
43
+ const slot = this.findFreeSlot();
44
+ if (!slot) {
45
+ throw new Error(`无空闲槽位,当前并发上限 ${this.maxConcurrency}`);
46
+ }
47
+ const { taskId, workingDir, briefPath, env } = params;
48
+ const child = this.spawnFn('npx', [
49
+ 'tsx', this.yorEntry,
50
+ '--task', taskId,
51
+ '--workspace', workingDir,
52
+ '--brief', briefPath,
53
+ ], {
54
+ cwd: workingDir,
55
+ env: { ...process.env, ...env },
56
+ stdio: 'pipe',
57
+ });
58
+ const pid = child.pid ?? 0;
59
+ slot.busy = true;
60
+ slot.taskId = taskId;
61
+ slot.process = child;
62
+ slot.pid = pid;
63
+ slot.startedAt = new Date().toISOString();
64
+ // stdout/stderr 日志转发
65
+ child.stdout?.on('data', (data) => {
66
+ const lines = data.toString().trimEnd();
67
+ if (lines) {
68
+ this.logger.info(`[Yor:${taskId}] ${lines}`);
69
+ }
70
+ });
71
+ let stderrBuf = '';
72
+ child.stderr?.on('data', (data) => {
73
+ const chunk = data.toString();
74
+ stderrBuf += chunk;
75
+ const lines = chunk.trimEnd();
76
+ if (lines) {
77
+ this.logger.error(`[Yor:${taskId}:stderr] ${lines}`);
78
+ }
79
+ });
80
+ child.on('exit', (code) => {
81
+ if (code !== 0 && stderrBuf.trim()) {
82
+ this.logger.error(`[ProcessManager] stderr (${taskId}): ${stderrBuf.trim().slice(0, 500)}`);
83
+ }
84
+ this.handleExit(slot, code);
85
+ });
86
+ child.on('error', (err) => {
87
+ this.logger.error(`[ProcessManager] 进程异常 (${taskId}):`, err);
88
+ this.handleExit(slot, 1);
89
+ });
90
+ this.logger.info(`[ProcessManager] 启动 ${taskId} | ${slot.slotId} pid=${pid}`);
91
+ return { slotId: slot.slotId, pid };
92
+ }
93
+ /**
94
+ * 终止指定槽位的 Yor 进程
95
+ */
96
+ async kill(slotId) {
97
+ const slot = this.slots.find(s => s.slotId === slotId);
98
+ if (!slot || !slot.busy || !slot.process) {
99
+ return { success: false };
100
+ }
101
+ try {
102
+ slot.process.kill('SIGTERM');
103
+ return { success: true };
104
+ }
105
+ catch (err) {
106
+ this.logger.error(`[ProcessManager] kill 失败 (${slotId}):`, err);
107
+ return { success: false };
108
+ }
109
+ }
110
+ /**
111
+ * 返回所有槽位的状态
112
+ */
113
+ status() {
114
+ return this.slots.map(s => ({
115
+ slotId: s.slotId,
116
+ taskId: s.taskId,
117
+ pid: s.pid,
118
+ startedAt: s.startedAt,
119
+ status: s.busy ? 'busy' : 'idle',
120
+ }));
121
+ }
122
+ /**
123
+ * 优雅关闭:终止所有运行中的进程
124
+ */
125
+ async shutdown(timeoutMs = 30_000) {
126
+ this.shuttingDown = true;
127
+ const busySlots = this.slots.filter(s => s.busy);
128
+ if (busySlots.length === 0) {
129
+ return { unfinished: [] };
130
+ }
131
+ return new Promise((resolve) => {
132
+ const timer = setTimeout(() => {
133
+ const unfinished = [];
134
+ for (const slot of this.slots) {
135
+ if (slot.busy && slot.process) {
136
+ slot.process.kill('SIGTERM');
137
+ if (slot.taskId) {
138
+ unfinished.push(slot.taskId);
139
+ }
140
+ }
141
+ }
142
+ resolve({ unfinished });
143
+ }, timeoutMs);
144
+ this.shutdownResolve = () => {
145
+ clearTimeout(timer);
146
+ resolve({ unfinished: [] });
147
+ };
148
+ });
149
+ }
150
+ shutdownResolve = null;
151
+ findFreeSlot() {
152
+ for (const slot of this.slots) {
153
+ if (!slot.busy) {
154
+ return slot;
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ handleExit(slot, code) {
160
+ const taskId = slot.taskId;
161
+ const slotId = slot.slotId;
162
+ if (taskId) {
163
+ const label = code === 0 ? '正常退出' : `异常退出 code=${code}`;
164
+ this.logger.info(`[ProcessManager] 结束 ${taskId} | ${label}`);
165
+ }
166
+ // 释放槽位
167
+ slot.busy = false;
168
+ slot.taskId = null;
169
+ slot.process = null;
170
+ slot.pid = null;
171
+ slot.startedAt = null;
172
+ // 发送退出事件(业务处理交给监听方)
173
+ if (taskId) {
174
+ this.emit('yor.exited', { slotId, taskId, exitCode: code });
175
+ }
176
+ // 检查 shutdown 等待
177
+ if (this.shuttingDown && this.shutdownResolve) {
178
+ const stillBusy = this.slots.some(s => s.busy);
179
+ if (!stillBusy) {
180
+ this.shutdownResolve();
181
+ this.shutdownResolve = null;
182
+ }
183
+ }
184
+ }
185
+ }
186
+ //# sourceMappingURL=process-manager.js.map