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,117 @@
1
+ import { z } from 'zod';
2
+ // ── Loid 行动工具 Input Schemas ──
3
+ export const DispatchTaskInputSchema = z.object({
4
+ title: z.string().describe('任务标题'),
5
+ brief: z.string().describe('执行 brief(Markdown 格式,只写做什么和标准,不写怎么做)'),
6
+ project_id: z.string().optional().describe('目标项目/仓库 ID'),
7
+ objective: z.string().describe('任务目标(一句话概括要达成什么)'),
8
+ acceptance_criteria: z.array(z.string()).describe('验收标准列表(至少一条可验证的完成条件)'),
9
+ context: z.string().optional().describe('背景信息(业务上下文、技术约束等)'),
10
+ conversation_id: z.string().optional().describe('关联的对话 ID(用于后续回复)'),
11
+ source_message_id: z.string().optional().describe('触发消息 ID'),
12
+ created_by: z.string().optional().describe('任务创建者(从当前事件的 sender_name 或 sender_id 提取)'),
13
+ source_chat_id: z.string().optional().describe('触发任务的聊天 ID(从当前对话的 chat_id 提取)'),
14
+ });
15
+ export const AskHumanInputSchema = z.object({
16
+ message: z.string().describe('要发送给人类的消息内容'),
17
+ chat_id: z.string().optional().describe('目标群聊 ID'),
18
+ reply_to_message_id: z.string().optional().describe('回复的消息 ID'),
19
+ task_id: z.string().optional().describe('关联任务 ID'),
20
+ intent: z.enum(['clarification', 'report', 'notification']).describe('消息意图'),
21
+ });
22
+ export const ApproveDeliveryInputSchema = z.object({
23
+ task_id: z.string().describe('要验收的任务 ID'),
24
+ summary: z.string().optional().describe('验收通过的简评'),
25
+ notify_human: z.boolean().default(true).describe('是否通知人类'),
26
+ });
27
+ export const RejectDeliveryInputSchema = z.object({
28
+ task_id: z.string().describe('要打回的任务 ID'),
29
+ reason: z.string().describe('打回原因'),
30
+ feedback: z.string().describe('给执行者的修改意见'),
31
+ });
32
+ export const LookupTaskInputSchema = z.object({
33
+ task_id: z.string().optional().describe('指定任务 ID'),
34
+ status: z.string().optional().describe('按状态过滤'),
35
+ });
36
+ export const LogDecisionInputSchema = z.object({
37
+ decision: z.string().describe('决策内容'),
38
+ reasoning: z.string().describe('决策理由'),
39
+ task_id: z.string().optional().describe('关联任务 ID'),
40
+ });
41
+ export const NoActionInputSchema = z.object({
42
+ reason: z.string().describe('不处理的原因'),
43
+ });
44
+ // ── Output Schemas(工具执行后返回给 Loid 的结果)──
45
+ export const DispatchTaskOutputSchema = z.object({
46
+ task_id: z.string(),
47
+ status: z.literal('dispatched'),
48
+ });
49
+ export const AskHumanOutputSchema = z.object({
50
+ sent: z.boolean(),
51
+ message_id: z.string().optional(),
52
+ });
53
+ export const ApproveDeliveryOutputSchema = z.object({
54
+ approved: z.boolean(),
55
+ task_id: z.string(),
56
+ });
57
+ export const RejectDeliveryOutputSchema = z.object({
58
+ rejected: z.boolean(),
59
+ task_id: z.string(),
60
+ retry_count: z.number(),
61
+ max_retries: z.number(),
62
+ });
63
+ export const LookupTaskOutputSchema = z.object({
64
+ tasks: z.array(z.object({
65
+ task_id: z.string(),
66
+ title: z.string(),
67
+ status: z.string(),
68
+ assignee: z.string().nullable(),
69
+ created_at: z.string().nullable(),
70
+ pr_url: z.string().nullable().optional(),
71
+ })),
72
+ });
73
+ export const LogDecisionOutputSchema = z.object({
74
+ logged: z.boolean(),
75
+ });
76
+ export const NoActionOutputSchema = z.object({
77
+ acknowledged: z.boolean(),
78
+ });
79
+ // ── Tool 注册表 ──
80
+ export const LOID_TOOL_REGISTRY = {
81
+ dispatch_task: {
82
+ description: '派工给执行者(Yor)。创建任务、写入 brief、启动执行。',
83
+ inputSchema: DispatchTaskInputSchema,
84
+ outputSchema: DispatchTaskOutputSchema,
85
+ },
86
+ ask_human: {
87
+ description: '向人类发送消息(提问、汇报结果、通知)。',
88
+ inputSchema: AskHumanInputSchema,
89
+ outputSchema: AskHumanOutputSchema,
90
+ },
91
+ approve_delivery: {
92
+ description: '验收通过执行者的交付成果。标记任务完成,可选通知人类。',
93
+ inputSchema: ApproveDeliveryInputSchema,
94
+ outputSchema: ApproveDeliveryOutputSchema,
95
+ },
96
+ reject_delivery: {
97
+ description: '打回执行者的交付,附带修改意见并重新派工。最多返工 3 次。',
98
+ inputSchema: RejectDeliveryInputSchema,
99
+ outputSchema: RejectDeliveryOutputSchema,
100
+ },
101
+ lookup_task: {
102
+ description: '查询任务状态和历史。可按 task_id 或 status 过滤。',
103
+ inputSchema: LookupTaskInputSchema,
104
+ outputSchema: LookupTaskOutputSchema,
105
+ },
106
+ log_decision: {
107
+ description: '记录决策理由到审计日志。',
108
+ inputSchema: LogDecisionInputSchema,
109
+ outputSchema: LogDecisionOutputSchema,
110
+ },
111
+ no_action: {
112
+ description: '明确表示不处理此消息。记录原因到日志。',
113
+ inputSchema: NoActionInputSchema,
114
+ outputSchema: NoActionOutputSchema,
115
+ },
116
+ };
117
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1,314 @@
1
+ /**
2
+ * SelfCalibrator - 自我校准模块
3
+ *
4
+ * 从执行结果中自动校准能力评估:
5
+ * - 统计成功率(按项目/类型分组)
6
+ * - 识别常见失败模式
7
+ * - 计算自愈成功率
8
+ * - 更新 capability-notes.md
9
+ * - 提供项目风险提示
10
+ */
11
+ import { getAllTasks, getAuditEventsByType, insertAuditEvent } from '@team-anya/db';
12
+ import { writeFile } from 'node:fs/promises';
13
+ import { join } from 'node:path';
14
+ // ── 终态状态集 ──
15
+ const TERMINAL_STATUSES = new Set(['DONE', 'CANCELLED']);
16
+ const SUCCESS_STATUSES = new Set(['DONE']);
17
+ // ── 实现 ──
18
+ export class SelfCalibrator {
19
+ db;
20
+ workspacePath;
21
+ _lastStatus = {
22
+ lastCalibratedAt: null,
23
+ report: null,
24
+ deviationFactors: null,
25
+ warnings: [],
26
+ };
27
+ constructor(opts) {
28
+ this.db = opts.db;
29
+ this.workspacePath = opts.workspacePath;
30
+ }
31
+ /**
32
+ * 计算执行统计数据
33
+ */
34
+ computeStats() {
35
+ const allTasks = getAllTasks(this.db);
36
+ const terminalTasks = allTasks.filter(t => TERMINAL_STATUSES.has(t.status));
37
+ const successTasks = terminalTasks.filter(t => SUCCESS_STATUSES.has(t.status));
38
+ const totalCompleted = terminalTasks.length;
39
+ const successCount = successTasks.length;
40
+ const successRate = totalCompleted > 0 ? successCount / totalCompleted : 0;
41
+ // 平均重试次数
42
+ const avgRetryCount = totalCompleted > 0
43
+ ? terminalTasks.reduce((sum, t) => sum + (t.retry_count ?? 0), 0) / totalCompleted
44
+ : 0;
45
+ // 按项目分组
46
+ const byProject = {};
47
+ for (const task of terminalTasks) {
48
+ const pid = task.project_id;
49
+ if (!pid)
50
+ continue;
51
+ if (!byProject[pid]) {
52
+ byProject[pid] = { total: 0, success: 0, successRate: 0 };
53
+ }
54
+ byProject[pid].total++;
55
+ if (SUCCESS_STATUSES.has(task.status)) {
56
+ byProject[pid].success++;
57
+ }
58
+ }
59
+ for (const group of Object.values(byProject)) {
60
+ group.successRate = group.total > 0 ? group.success / group.total : 0;
61
+ }
62
+ // 按 source_type 分组
63
+ const bySourceType = {};
64
+ for (const task of terminalTasks) {
65
+ const st = task.source_type;
66
+ if (!bySourceType[st]) {
67
+ bySourceType[st] = { total: 0, success: 0, successRate: 0 };
68
+ }
69
+ bySourceType[st].total++;
70
+ if (SUCCESS_STATUSES.has(task.status)) {
71
+ bySourceType[st].success++;
72
+ }
73
+ }
74
+ for (const group of Object.values(bySourceType)) {
75
+ group.successRate = group.total > 0 ? group.success / group.total : 0;
76
+ }
77
+ return { totalCompleted, successCount, successRate, avgRetryCount, byProject, bySourceType };
78
+ }
79
+ /**
80
+ * 分析失败模式(从 task_blocked 审计事件中提取)
81
+ */
82
+ analyzeFailurePatterns() {
83
+ const blockedEvents = getAuditEventsByType(this.db, 'task_blocked');
84
+ const patternMap = new Map();
85
+ for (const event of blockedEvents) {
86
+ if (!event.detail)
87
+ continue;
88
+ try {
89
+ const detail = JSON.parse(event.detail);
90
+ const reason = detail.reason;
91
+ if (!reason)
92
+ continue;
93
+ const existing = patternMap.get(reason);
94
+ if (existing) {
95
+ existing.count++;
96
+ }
97
+ else {
98
+ patternMap.set(reason, { count: 1, type: detail.type });
99
+ }
100
+ }
101
+ catch {
102
+ // detail 不是有效 JSON,跳过
103
+ }
104
+ }
105
+ const patterns = [];
106
+ for (const [pattern, data] of patternMap) {
107
+ patterns.push({ pattern, count: data.count, type: data.type });
108
+ }
109
+ // 按出现次数降序排列
110
+ patterns.sort((a, b) => b.count - a.count);
111
+ return patterns;
112
+ }
113
+ /**
114
+ * 计算自愈成功率
115
+ */
116
+ computeSelfHealRate() {
117
+ const healEvents = getAuditEventsByType(this.db, 'self_heal_attempt');
118
+ if (healEvents.length === 0) {
119
+ return { total: 0, succeeded: 0, rate: 0 };
120
+ }
121
+ let succeeded = 0;
122
+ for (const event of healEvents) {
123
+ if (!event.detail)
124
+ continue;
125
+ try {
126
+ const detail = JSON.parse(event.detail);
127
+ if (detail.success)
128
+ succeeded++;
129
+ }
130
+ catch {
131
+ // 跳过
132
+ }
133
+ }
134
+ return {
135
+ total: healEvents.length,
136
+ succeeded,
137
+ rate: healEvents.length > 0 ? succeeded / healEvents.length : 0,
138
+ };
139
+ }
140
+ /**
141
+ * 生成完整的校准报告
142
+ */
143
+ generateReport() {
144
+ return {
145
+ stats: this.computeStats(),
146
+ failurePatterns: this.analyzeFailurePatterns(),
147
+ selfHealRate: this.computeSelfHealRate(),
148
+ generatedAt: new Date().toISOString(),
149
+ };
150
+ }
151
+ /**
152
+ * 更新 capability-notes.md 文件
153
+ */
154
+ async updateCapabilityNotes() {
155
+ const report = this.generateReport();
156
+ const content = this.formatCapabilityNotes(report);
157
+ const notesPath = join(this.workspacePath, 'memory', 'self', 'capability-notes.md');
158
+ await writeFile(notesPath, content, 'utf-8');
159
+ }
160
+ /**
161
+ * 获取指定项目的风险提示
162
+ */
163
+ getRiskHints(projectId) {
164
+ const stats = this.computeStats();
165
+ const projectStats = stats.byProject[projectId];
166
+ const hints = [];
167
+ if (!projectStats)
168
+ return hints;
169
+ // 低成功率警告(< 50%)
170
+ if (projectStats.successRate < 0.5) {
171
+ hints.push(`该项目成功率偏低 (${(projectStats.successRate * 100).toFixed(0)}%),建议关注失败原因`);
172
+ }
173
+ // 高重试率警告(平均重试 >= 2)
174
+ const projectTasks = getAllTasks(this.db).filter(t => t.project_id === projectId && TERMINAL_STATUSES.has(t.status));
175
+ if (projectTasks.length > 0) {
176
+ const avgRetry = projectTasks.reduce((sum, t) => sum + (t.retry_count ?? 0), 0) / projectTasks.length;
177
+ if (avgRetry >= 2) {
178
+ hints.push(`该项目平均重试次数较高 (${avgRetry.toFixed(1)}次),可能存在系统性问题`);
179
+ }
180
+ }
181
+ return hints;
182
+ }
183
+ /**
184
+ * 计算偏差校正因子
185
+ */
186
+ computeDeviationFactors() {
187
+ const allTasks = getAllTasks(this.db);
188
+ const terminalTasks = allTasks.filter(t => TERMINAL_STATUSES.has(t.status));
189
+ // 重试偏差:平均 retry_count / max_retries
190
+ let retryDeviation = 0;
191
+ if (terminalTasks.length > 0) {
192
+ const ratios = terminalTasks.map(t => {
193
+ const maxRetries = t.max_retries ?? 3;
194
+ if (maxRetries === 0)
195
+ return 0;
196
+ return (t.retry_count ?? 0) / maxRetries;
197
+ });
198
+ retryDeviation = ratios.reduce((a, b) => a + b, 0) / ratios.length;
199
+ }
200
+ // 承诺达成率
201
+ const fulfilledEvents = getAuditEventsByType(this.db, 'commitment_fulfilled');
202
+ const brokenEvents = getAuditEventsByType(this.db, 'commitment_broken');
203
+ const totalCommitmentEvents = fulfilledEvents.length + brokenEvents.length;
204
+ const commitmentFulfillRate = totalCommitmentEvents > 0
205
+ ? fulfilledEvents.length / totalCommitmentEvents
206
+ : 0;
207
+ // 校正因子:retryDeviation 越高说明低估复杂度
208
+ // 0 偏差 → 1.0(无需校正),1.0 偏差 → 1.5(需要 50% 更多缓冲)
209
+ const correctionFactor = terminalTasks.length > 0
210
+ ? 1.0 + retryDeviation * 0.5
211
+ : 1.0;
212
+ return { retryDeviation, commitmentFulfillRate, correctionFactor };
213
+ }
214
+ /**
215
+ * 执行一次完整校准,记录审计事件并缓存状态
216
+ */
217
+ runCalibration() {
218
+ const report = this.generateReport();
219
+ const deviationFactors = this.computeDeviationFactors();
220
+ const now = new Date().toISOString();
221
+ // 生成偏差警告
222
+ const warnings = [];
223
+ const RETRY_DEVIATION_THRESHOLD = 0.7;
224
+ if (deviationFactors.retryDeviation >= RETRY_DEVIATION_THRESHOLD) {
225
+ warnings.push(`重试偏差过高 (${(deviationFactors.retryDeviation * 100).toFixed(0)}%),建议调整复杂度预估`);
226
+ }
227
+ if (report.stats.successRate < 0.5 && report.stats.totalCompleted > 0) {
228
+ warnings.push(`整体成功率偏低 (${(report.stats.successRate * 100).toFixed(0)}%),需要关注系统性问题`);
229
+ }
230
+ // 记录审计事件
231
+ insertAuditEvent(this.db, {
232
+ event_type: 'calibration',
233
+ actor: 'loid',
234
+ summary: `自我校准完成: 成功率 ${(report.stats.successRate * 100).toFixed(1)}%, 校正因子 ${deviationFactors.correctionFactor.toFixed(2)}`,
235
+ detail: JSON.stringify({
236
+ stats: report.stats,
237
+ deviationFactors,
238
+ warnings,
239
+ }),
240
+ });
241
+ // 缓存状态
242
+ this._lastStatus = {
243
+ lastCalibratedAt: now,
244
+ report,
245
+ deviationFactors,
246
+ warnings,
247
+ };
248
+ return this._lastStatus;
249
+ }
250
+ /**
251
+ * 获取当前校准状态(返回上次 runCalibration 的缓存)
252
+ */
253
+ getCalibrationStatus() {
254
+ return this._lastStatus;
255
+ }
256
+ // ── 私有方法 ──
257
+ formatCapabilityNotes(report) {
258
+ const { stats, failurePatterns, selfHealRate } = report;
259
+ if (stats.totalCompleted === 0 && failurePatterns.length === 0 && selfHealRate.total === 0) {
260
+ return `# Anya Capability Notes\n\n> 自动生成于 ${report.generatedAt}\n\n无数据 — 尚无已完成的任务记录。\n`;
261
+ }
262
+ const lines = [
263
+ '# Anya Capability Notes',
264
+ '',
265
+ `> 自动生成于 ${report.generatedAt}`,
266
+ '',
267
+ '## 执行统计',
268
+ '',
269
+ `- 已完成任务: ${stats.totalCompleted}`,
270
+ `- 成功数: ${stats.successCount}`,
271
+ `- 整体成功率: ${(stats.successRate * 100).toFixed(1)}%`,
272
+ `- 平均重试次数: ${stats.avgRetryCount.toFixed(1)}`,
273
+ '',
274
+ ];
275
+ // 项目分组统计
276
+ const projectIds = Object.keys(stats.byProject);
277
+ if (projectIds.length > 0) {
278
+ lines.push('### 按项目', '');
279
+ for (const pid of projectIds) {
280
+ const g = stats.byProject[pid];
281
+ lines.push(`- **${pid}**: ${g.success}/${g.total} (成功率 ${(g.successRate * 100).toFixed(1)}%)`);
282
+ }
283
+ lines.push('');
284
+ }
285
+ // source_type 分组统计
286
+ const sourceTypes = Object.keys(stats.bySourceType);
287
+ if (sourceTypes.length > 0) {
288
+ lines.push('### 按来源类型', '');
289
+ for (const st of sourceTypes) {
290
+ const g = stats.bySourceType[st];
291
+ lines.push(`- **${st}**: ${g.success}/${g.total} (成功率 ${(g.successRate * 100).toFixed(1)}%)`);
292
+ }
293
+ lines.push('');
294
+ }
295
+ // 失败模式
296
+ if (failurePatterns.length > 0) {
297
+ lines.push('## 失败模式', '');
298
+ for (const p of failurePatterns) {
299
+ lines.push(`- **${p.pattern}**: ${p.count}次${p.type ? ` (类型: ${p.type})` : ''}`);
300
+ }
301
+ lines.push('');
302
+ }
303
+ // 自愈成功率
304
+ if (selfHealRate.total > 0) {
305
+ lines.push('## 自愈能力', '');
306
+ lines.push(`- 自愈尝试: ${selfHealRate.total}次`);
307
+ lines.push(`- 自愈成功: ${selfHealRate.succeeded}次`);
308
+ lines.push(`- 自愈成功率: ${(selfHealRate.rate * 100).toFixed(1)}%`);
309
+ lines.push('');
310
+ }
311
+ return lines.join('\n');
312
+ }
313
+ }
314
+ //# sourceMappingURL=self-calibrator.js.map
@@ -0,0 +1,217 @@
1
+ import { join } from 'node:path';
2
+ import { getTasksByStatus } from '@team-anya/db';
3
+ import { TaskStatus } from '@team-anya/core';
4
+ import { LoidSession, } from './session.js';
5
+ import { formatMessageContextPrompt, formatFollowUpPrompt, formatDeliveryContextPrompt, formatRecoveryPrompt, } from './context-builder.js';
6
+ // ── LoidSessionManager ──
7
+ export class LoidSessionManager {
8
+ sessions = new Map();
9
+ config;
10
+ deps;
11
+ logger;
12
+ constructor(config, deps) {
13
+ this.config = config;
14
+ this.deps = deps;
15
+ this.logger = config.logger ?? { info: console.log, error: console.error };
16
+ }
17
+ // ── 公开方法 ──
18
+ /**
19
+ * 处理新消息:有活跃 session 就 enqueue,没有就创建
20
+ */
21
+ async handleMessage(chatId, ctx) {
22
+ const key = chatId;
23
+ const existing = this.sessions.get(key);
24
+ if (existing) {
25
+ const state = existing.getState();
26
+ if (state !== 'disposed') {
27
+ const prompt = formatFollowUpPrompt(ctx);
28
+ existing.enqueue({ type: 'message', prompt, enqueuedAt: new Date() });
29
+ return;
30
+ }
31
+ // session 已销毁,删除并新建
32
+ this.sessions.delete(key);
33
+ }
34
+ // 新建 session
35
+ const session = await this.createSession(key);
36
+ const contextPrompt = formatMessageContextPrompt(ctx);
37
+ session.enqueue({ type: 'message', prompt: contextPrompt, enqueuedAt: new Date() });
38
+ }
39
+ /**
40
+ * 处理 Yor 交付验收:查找来源 session,活着就复用,否则新建
41
+ */
42
+ async handleDelivery(ctx) {
43
+ const sourceChatId = ctx.sourceChatId;
44
+ // 尝试复用来源 session
45
+ if (sourceChatId && this.sessions.has(sourceChatId)) {
46
+ const session = this.sessions.get(sourceChatId);
47
+ const state = session.getState();
48
+ if (state !== 'disposed') {
49
+ const prompt = formatDeliveryContextPrompt(ctx);
50
+ session.enqueue({ type: 'delivery', prompt, enqueuedAt: new Date() });
51
+ return;
52
+ }
53
+ this.sessions.delete(sourceChatId);
54
+ }
55
+ // 来源 session 已回收,新建独立 session
56
+ const key = `delivery:${ctx.taskId}`;
57
+ const session = await this.createSession(key);
58
+ const protocol = this.config.protocols['review'] ?? '';
59
+ const prompt = [
60
+ protocol ? protocol + '\n\n---\n\n' : '',
61
+ formatDeliveryContextPrompt(ctx),
62
+ ].join('');
63
+ session.enqueue({ type: 'delivery', prompt, enqueuedAt: new Date() });
64
+ }
65
+ /**
66
+ * 获取当前活跃 session 数量
67
+ */
68
+ getSessionCount() {
69
+ return this.sessions.size;
70
+ }
71
+ /**
72
+ * 获取指定 session 的状态(用于诊断)
73
+ */
74
+ getSessionState(key) {
75
+ return this.sessions.get(key)?.getState() ?? null;
76
+ }
77
+ /**
78
+ * 关闭所有 session,释放资源
79
+ */
80
+ async dispose() {
81
+ const disposePromises = [];
82
+ for (const [key, session] of this.sessions) {
83
+ this.logger.info(`[SessionManager] 正在关闭 session: ${key}`);
84
+ disposePromises.push(session.dispose());
85
+ }
86
+ await Promise.all(disposePromises);
87
+ this.sessions.clear();
88
+ this.logger.info('[SessionManager] 所有 session 已关闭');
89
+ }
90
+ // ── 私有方法 ──
91
+ /**
92
+ * 创建新 LoidSession,注册生命周期回调
93
+ * 如果 broker 槽位满,尝试 LRU 淘汰后重试
94
+ */
95
+ async createSession(key) {
96
+ this.logger.info(`[SessionManager] 创建新 session: ${key}`);
97
+ const instanceId = `loid:${key}`;
98
+ const logFile = this.config.logDir
99
+ ? join(this.config.logDir, `loid-${key.replace(/[/:]/g, '_')}-${Date.now()}.log`)
100
+ : undefined;
101
+ const sessionConfig = {
102
+ broker: this.config.broker,
103
+ instanceId,
104
+ backendConfig: this.config.backendConfig,
105
+ idleTimeoutMs: this.config.idleTimeoutMs,
106
+ maxTurns: this.config.maxTurnsPerSession,
107
+ logFile,
108
+ db: this.deps.db,
109
+ chatId: key,
110
+ logger: this.logger,
111
+ };
112
+ const session = new LoidSession(sessionConfig, {
113
+ onCrash: (orphanMessages) => {
114
+ this.onSessionCrash(key, orphanMessages);
115
+ },
116
+ onDisposed: () => {
117
+ this.logger.info(`[SessionManager] session 自然结束: ${key}`);
118
+ this.sessions.delete(key);
119
+ },
120
+ });
121
+ try {
122
+ await session.connect();
123
+ }
124
+ catch (err) {
125
+ // 槽位满时尝试 LRU 淘汰
126
+ if (err instanceof Error && err.message.includes('实例已满')) {
127
+ const evicted = await this.evictLRU();
128
+ if (evicted) {
129
+ this.logger.info(`[SessionManager] LRU 淘汰 ${evicted},重试创建 ${key}`);
130
+ await session.connect();
131
+ }
132
+ else {
133
+ throw err;
134
+ }
135
+ }
136
+ else {
137
+ throw err;
138
+ }
139
+ }
140
+ this.sessions.set(key, session);
141
+ return session;
142
+ }
143
+ /**
144
+ * LRU 淘汰:找到最久没活动且处于 IDLE 的 session,dispose 它
145
+ * 返回被淘汰的 session key,无可淘汰时返回 null
146
+ */
147
+ async evictLRU() {
148
+ let oldestKey = null;
149
+ let oldestTime = Infinity;
150
+ for (const [key, session] of this.sessions) {
151
+ if (session.getState() === 'idle') {
152
+ const activityTime = session.getLastActivityAt().getTime();
153
+ if (activityTime < oldestTime) {
154
+ oldestTime = activityTime;
155
+ oldestKey = key;
156
+ }
157
+ }
158
+ }
159
+ if (oldestKey) {
160
+ const session = this.sessions.get(oldestKey);
161
+ await session.dispose();
162
+ this.sessions.delete(oldestKey);
163
+ this.logger.info(`[SessionManager] LRU 淘汰 session: ${oldestKey}`);
164
+ return oldestKey;
165
+ }
166
+ return null;
167
+ }
168
+ /**
169
+ * Session 崩溃恢复:删旧 session,新建处理孤儿消息
170
+ */
171
+ async onSessionCrash(key, orphanedMessages) {
172
+ this.logger.error(`[SessionManager] session 崩溃: ${key} (${orphanedMessages.length} 条孤儿消息)`);
173
+ // 删除旧 session
174
+ this.sessions.delete(key);
175
+ // 从 DB 加载活跃任务状态作为恢复上下文
176
+ const activeTasks = this.getActiveTasks();
177
+ const recoveryPrompt = formatRecoveryPrompt(activeTasks);
178
+ try {
179
+ const newSession = await this.createSession(key);
180
+ // 先投递恢复上下文
181
+ newSession.enqueue({ type: 'recovery', prompt: recoveryPrompt, enqueuedAt: new Date() });
182
+ // 再投递孤儿消息
183
+ for (const msg of orphanedMessages) {
184
+ newSession.enqueue(msg);
185
+ }
186
+ }
187
+ catch (err) {
188
+ this.logger.error(`[SessionManager] 崩溃恢复失败 (${key}):`, err);
189
+ }
190
+ }
191
+ /**
192
+ * 获取活跃任务列表(用于崩溃恢复上下文)
193
+ */
194
+ getActiveTasks() {
195
+ const statuses = [
196
+ TaskStatus.IN_PROGRESS,
197
+ TaskStatus.READY,
198
+ TaskStatus.NEED_CLARIFICATION,
199
+ TaskStatus.DELIVERING,
200
+ TaskStatus.BLOCKED,
201
+ ];
202
+ const tasks = [];
203
+ for (const status of statuses) {
204
+ const statusTasks = getTasksByStatus(this.deps.db, status);
205
+ for (const t of statusTasks) {
206
+ tasks.push({
207
+ task_id: t.task_id,
208
+ title: t.title,
209
+ status: t.status,
210
+ assignee: t.assignee,
211
+ });
212
+ }
213
+ }
214
+ return tasks;
215
+ }
216
+ }
217
+ //# sourceMappingURL=session-manager.js.map