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,301 +0,0 @@
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
- /** 崩溃恢复中的话术池(像同事重启电脑一样自然) */
13
- static CRASH_RECOVERY_REPLIES = [
14
- '抱歉,刚才脑子卡了一下,我重新捋一下',
15
- '不好意思,刚才断片了,我重新接上',
16
- '刚才出了点状况,我重新来过',
17
- '抱歉打断了,我重新整理一下思路',
18
- '不好意思,刚才掉线了,马上接上',
19
- ];
20
- lastCrashRecoveryIndex = -1;
21
- /** 崩溃恢复失败 / 启动失败的话术池 */
22
- static STARTUP_FAILURE_REPLIES = [
23
- '抱歉,我这边遇到了点问题暂时处理不了,稍后我再试试,或者你可以再发一次',
24
- '不好意思,出了点技术问题我暂时响应不了,你可以稍后再@我试试',
25
- '抱歉,我这边卡住了,短时间内可能没法响应,你可以过一会儿再找我',
26
- ];
27
- lastStartupFailureIndex = -1;
28
- constructor(config, deps) {
29
- this.config = config;
30
- this.deps = deps;
31
- this.logger = config.logger ?? { info: console.log, error: console.error };
32
- }
33
- /** 从话术池随机取一句,避免连续重复 */
34
- pickRandom(pool, lastIndex) {
35
- let idx;
36
- do {
37
- idx = Math.floor(Math.random() * pool.length);
38
- } while (idx === lastIndex && pool.length > 1);
39
- return { text: pool[idx], index: idx };
40
- }
41
- pickCrashRecoveryReply() {
42
- const { text, index } = this.pickRandom(LoidSessionManager.CRASH_RECOVERY_REPLIES, this.lastCrashRecoveryIndex);
43
- this.lastCrashRecoveryIndex = index;
44
- return text;
45
- }
46
- pickStartupFailureReply() {
47
- const { text, index } = this.pickRandom(LoidSessionManager.STARTUP_FAILURE_REPLIES, this.lastStartupFailureIndex);
48
- this.lastStartupFailureIndex = index;
49
- return text;
50
- }
51
- // ── 公开方法 ──
52
- /**
53
- * 处理新消息:有活跃 session 就 enqueue,没有就创建
54
- */
55
- async handleMessage(chatId, ctx) {
56
- const key = chatId;
57
- const existing = this.sessions.get(key);
58
- if (existing) {
59
- const state = existing.getState();
60
- if (state !== 'disposed') {
61
- const prompt = formatFollowUpPrompt(ctx);
62
- existing.enqueue({ type: 'message', prompt, enqueuedAt: new Date() });
63
- return;
64
- }
65
- // session 已销毁,删除并新建
66
- this.sessions.delete(key);
67
- }
68
- // 新建 session
69
- try {
70
- const session = await this.createSession(key);
71
- const contextPrompt = formatMessageContextPrompt(ctx);
72
- session.enqueue({ type: 'message', prompt: contextPrompt, enqueuedAt: new Date() });
73
- }
74
- catch (err) {
75
- this.logger.error(`[SessionManager] 创建 session 失败 (${key}):`, err);
76
- this.sendChatNotification(key, this.pickStartupFailureReply());
77
- }
78
- }
79
- /**
80
- * 处理 Yor 交付验收:查找来源 session,活着就复用,否则新建
81
- */
82
- async handleDelivery(ctx) {
83
- const sourceChatId = ctx.sourceChatId;
84
- // 尝试复用来源 session
85
- if (sourceChatId && this.sessions.has(sourceChatId)) {
86
- const session = this.sessions.get(sourceChatId);
87
- const state = session.getState();
88
- if (state !== 'disposed') {
89
- const prompt = formatDeliveryContextPrompt(ctx);
90
- session.enqueue({ type: 'delivery', prompt, enqueuedAt: new Date() });
91
- return;
92
- }
93
- this.sessions.delete(sourceChatId);
94
- }
95
- // 来源 session 已回收,新建独立 session
96
- const key = `delivery:${ctx.taskId}`;
97
- try {
98
- const session = await this.createSession(key);
99
- const protocol = this.config.protocols['review'] ?? '';
100
- const prompt = [
101
- protocol ? protocol + '\n\n---\n\n' : '',
102
- formatDeliveryContextPrompt(ctx),
103
- ].join('');
104
- session.enqueue({ type: 'delivery', prompt, enqueuedAt: new Date() });
105
- }
106
- catch (err) {
107
- this.logger.error(`[SessionManager] 创建 delivery session 失败 (${key}):`, err);
108
- // delivery 场景通知来源群
109
- if (ctx.sourceChatId) {
110
- this.sendChatNotification(ctx.sourceChatId, this.pickStartupFailureReply());
111
- }
112
- }
113
- }
114
- /**
115
- * 获取当前活跃 session 数量
116
- */
117
- getSessionCount() {
118
- return this.sessions.size;
119
- }
120
- /**
121
- * 获取指定 session 的状态(用于诊断)
122
- */
123
- getSessionState(key) {
124
- return this.sessions.get(key)?.getState() ?? null;
125
- }
126
- /**
127
- * 关闭所有 session,释放资源
128
- */
129
- async dispose() {
130
- const disposePromises = [];
131
- for (const [key, session] of this.sessions) {
132
- this.logger.info(`[SessionManager] 正在关闭 session: ${key}`);
133
- disposePromises.push(session.dispose());
134
- }
135
- await Promise.all(disposePromises);
136
- this.sessions.clear();
137
- this.logger.info('[SessionManager] 所有 session 已关闭');
138
- }
139
- // ── 私有方法 ──
140
- /**
141
- * 创建新 LoidSession,注册生命周期回调
142
- * 如果 broker 槽位满,尝试 LRU 淘汰后重试
143
- */
144
- async createSession(key) {
145
- this.logger.info(`[SessionManager] 创建新 session: ${key}`);
146
- const instanceId = `loid:${key}`;
147
- const logFile = this.config.logDir
148
- ? join(this.config.logDir, `loid-${key.replace(/[/:]/g, '_')}-${Date.now()}.log`)
149
- : undefined;
150
- const sessionConfig = {
151
- broker: this.config.broker,
152
- instanceId,
153
- backendConfig: this.config.backendConfig,
154
- idleTimeoutMs: this.config.idleTimeoutMs,
155
- maxTurns: this.config.maxTurnsPerSession,
156
- logFile,
157
- db: this.deps.db,
158
- chatId: key,
159
- logger: this.logger,
160
- };
161
- const session = new LoidSession(sessionConfig, {
162
- onCrash: (orphanMessages) => {
163
- this.onSessionCrash(key, orphanMessages);
164
- },
165
- onDisposed: () => {
166
- this.logger.info(`[SessionManager] session 自然结束: ${key}`);
167
- this.sessions.delete(key);
168
- },
169
- });
170
- try {
171
- await session.connect();
172
- }
173
- catch (err) {
174
- // 槽位满时尝试 LRU 淘汰
175
- if (err instanceof Error && err.message.includes('实例已满')) {
176
- const evicted = await this.evictLRU();
177
- if (evicted) {
178
- this.logger.info(`[SessionManager] LRU 淘汰 ${evicted},重试创建 ${key}`);
179
- await session.connect();
180
- }
181
- else {
182
- throw err;
183
- }
184
- }
185
- else {
186
- throw err;
187
- }
188
- }
189
- this.sessions.set(key, session);
190
- return session;
191
- }
192
- /**
193
- * LRU 淘汰:找到最久没活动且处于 IDLE 的 session,dispose 它
194
- * 返回被淘汰的 session key,无可淘汰时返回 null
195
- */
196
- async evictLRU() {
197
- let oldestKey = null;
198
- let oldestTime = Infinity;
199
- for (const [key, session] of this.sessions) {
200
- if (session.getState() === 'idle') {
201
- const activityTime = session.getLastActivityAt().getTime();
202
- if (activityTime < oldestTime) {
203
- oldestTime = activityTime;
204
- oldestKey = key;
205
- }
206
- }
207
- }
208
- if (oldestKey) {
209
- const session = this.sessions.get(oldestKey);
210
- await session.dispose();
211
- this.sessions.delete(oldestKey);
212
- this.logger.info(`[SessionManager] LRU 淘汰 session: ${oldestKey}`);
213
- return oldestKey;
214
- }
215
- return null;
216
- }
217
- /**
218
- * Session 崩溃恢复:删旧 session,通知用户,新建处理孤儿消息
219
- */
220
- async onSessionCrash(key, orphanedMessages) {
221
- this.logger.error(`[SessionManager] session 崩溃: ${key} (${orphanedMessages.length} 条孤儿消息)`);
222
- // 删除旧 session
223
- this.sessions.delete(key);
224
- // 通知用户:正在恢复
225
- const chatId = this.extractChatId(key);
226
- if (chatId) {
227
- this.sendChatNotification(chatId, this.pickCrashRecoveryReply());
228
- }
229
- // 从 DB 加载活跃任务状态作为恢复上下文
230
- const activeTasks = this.getActiveTasks();
231
- const recoveryPrompt = formatRecoveryPrompt(activeTasks);
232
- try {
233
- const newSession = await this.createSession(key);
234
- // 先投递恢复上下文
235
- newSession.enqueue({ type: 'recovery', prompt: recoveryPrompt, enqueuedAt: new Date() });
236
- // 再投递孤儿消息
237
- for (const msg of orphanedMessages) {
238
- newSession.enqueue(msg);
239
- }
240
- }
241
- catch (err) {
242
- this.logger.error(`[SessionManager] 崩溃恢复失败 (${key}):`, err);
243
- // 恢复失败:告知用户
244
- if (chatId) {
245
- this.sendChatNotification(chatId, this.pickStartupFailureReply());
246
- }
247
- }
248
- }
249
- /**
250
- * 从 session key 提取 chatId(过滤掉 delivery: 等前缀的内部 key)
251
- * 只有来自用户对话的 session 才需要通知
252
- */
253
- extractChatId(key) {
254
- // delivery:ANYA-xxx 等内部 session 不直接通知用户
255
- if (key.startsWith('delivery:'))
256
- return null;
257
- return key;
258
- }
259
- /**
260
- * 向飞书群发送通知(fire-and-forget),同时清除"处理中"表情
261
- */
262
- sendChatNotification(chatId, text) {
263
- if (!this.deps.feishuSender)
264
- return;
265
- // 清除 typing reaction(通知本身就是反馈)
266
- this.deps.feishuSender.clearTypingReaction(chatId).catch(() => { });
267
- this.deps.feishuSender.sendText({
268
- receiveIdType: 'chat_id',
269
- receiveId: chatId,
270
- text,
271
- }).catch(err => {
272
- this.logger.error('[SessionManager] 崩溃通知发送失败:', err);
273
- });
274
- }
275
- /**
276
- * 获取活跃任务列表(用于崩溃恢复上下文)
277
- */
278
- getActiveTasks() {
279
- const statuses = [
280
- TaskStatus.IN_PROGRESS,
281
- TaskStatus.READY,
282
- TaskStatus.NEED_CLARIFICATION,
283
- TaskStatus.DELIVERING,
284
- TaskStatus.BLOCKED,
285
- ];
286
- const tasks = [];
287
- for (const status of statuses) {
288
- const statusTasks = getTasksByStatus(this.deps.db, status);
289
- for (const t of statusTasks) {
290
- tasks.push({
291
- task_id: t.task_id,
292
- title: t.title,
293
- status: t.status,
294
- assignee: t.assignee,
295
- });
296
- }
297
- }
298
- return tasks;
299
- }
300
- }
301
- //# sourceMappingURL=session-manager.js.map
@@ -1,271 +0,0 @@
1
- import { insertCCSession, updateCCSessionEnded } from '@team-anya/db';
2
- // ── 常量 ──
3
- const DEFAULT_IDLE_TIMEOUT_MS = 2 * 60 * 60 * 1000; // 2 小时
4
- const DEFAULT_MAX_TURNS = 50;
5
- // ── LoidSession ──
6
- export class LoidSession {
7
- state = 'idle';
8
- broker;
9
- instanceId;
10
- config;
11
- callbacks;
12
- pendingMessages = [];
13
- turnCount = 0;
14
- maxTurns;
15
- idleTimer = null;
16
- lastActivityAt = new Date();
17
- connected = false;
18
- cliSessionId = null;
19
- /** Broker 事件监听器引用(用于 dispose 时清理) */
20
- boundResultListener = null;
21
- boundCrashListener = null;
22
- boundExitedListener = null;
23
- constructor(config, callbacks) {
24
- this.config = config;
25
- this.callbacks = callbacks;
26
- this.maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
27
- this.broker = config.broker;
28
- this.instanceId = config.instanceId;
29
- }
30
- // ── 公开方法 ──
31
- /**
32
- * 通过 CCBroker 启动 CC 进程并建立连接
33
- */
34
- async connect() {
35
- // 注册 broker 事件监听(过滤当前 instanceId)
36
- this.boundResultListener = (id, _role, result) => {
37
- if (id === this.instanceId) {
38
- this.handleResult(result);
39
- }
40
- };
41
- this.boundCrashListener = (id, _role, _error) => {
42
- if (id === this.instanceId) {
43
- this.handleCrash();
44
- }
45
- };
46
- this.boundExitedListener = (id) => {
47
- if (id === this.instanceId && this.connected) {
48
- if (this.state !== 'disposed') {
49
- this.config.logger?.error('[LoidSession] CC 进程异常退出,触发崩溃恢复');
50
- this.handleCrash();
51
- }
52
- }
53
- };
54
- this.broker.on('instance.result', this.boundResultListener);
55
- this.broker.on('instance.crash', this.boundCrashListener);
56
- this.broker.on('instance.exited', this.boundExitedListener);
57
- // 监听 CC session ID
58
- const sessionIdListener = (id, cliSessionId) => {
59
- if (id === this.instanceId && this.config.db) {
60
- this.cliSessionId = cliSessionId;
61
- insertCCSession(this.config.db, {
62
- session_id: cliSessionId,
63
- role: 'loid',
64
- instance_id: this.instanceId,
65
- chat_id: this.config.chatId,
66
- project_path: this.config.backendConfig.workingDir,
67
- });
68
- this.config.logger?.info(`[LoidSession] 记录 CC session: ${cliSessionId}`);
69
- // 一次性监听,获取后移除
70
- this.broker.removeListener('instance.sessionId', sessionIdListener);
71
- }
72
- };
73
- this.broker.on('instance.sessionId', sessionIdListener);
74
- await this.broker.spawn({
75
- id: this.instanceId,
76
- role: 'loid',
77
- backendConfig: this.config.backendConfig,
78
- logFile: this.config.logFile,
79
- });
80
- this.connected = true;
81
- this.resetIdleTimer();
82
- }
83
- /**
84
- * 投递消息:IDLE 时直接发送,EXECUTING 时缓冲
85
- * 返回 false 表示 session 不接受消息(DISPOSED)
86
- */
87
- enqueue(msg) {
88
- if (this.state === 'disposed') {
89
- return false;
90
- }
91
- this.lastActivityAt = new Date();
92
- if (this.state === 'idle') {
93
- this.state = 'executing';
94
- this.clearIdleTimer();
95
- try {
96
- this.broker.sendPrompt(this.instanceId, msg.prompt);
97
- }
98
- catch (err) {
99
- this.config.logger?.error('[LoidSession] sendPrompt 失败:', err);
100
- this.handleCrash();
101
- }
102
- }
103
- else {
104
- // EXECUTING → 缓冲
105
- this.pendingMessages.push(msg);
106
- }
107
- return true;
108
- }
109
- /**
110
- * 获取当前状态
111
- */
112
- getState() {
113
- return this.state;
114
- }
115
- /**
116
- * 获取当前轮次数
117
- */
118
- getTurnCount() {
119
- return this.turnCount;
120
- }
121
- /**
122
- * 获取缓冲区消息数
123
- */
124
- getPendingCount() {
125
- return this.pendingMessages.length;
126
- }
127
- /**
128
- * 获取最后活跃时间(供 LRU 淘汰使用)
129
- */
130
- getLastActivityAt() {
131
- return this.lastActivityAt;
132
- }
133
- /**
134
- * 销毁 session,释放资源
135
- */
136
- async dispose() {
137
- if (this.state === 'disposed')
138
- return;
139
- this.state = 'disposed';
140
- this.clearIdleTimer();
141
- this.connected = false;
142
- // 更新 cc_sessions.ended_at
143
- if (this.cliSessionId && this.config.db) {
144
- updateCCSessionEnded(this.config.db, this.cliSessionId);
145
- }
146
- this.removeBrokerListeners();
147
- await this.broker.dispose(this.instanceId).catch(() => { });
148
- }
149
- // ── 私有方法 ──
150
- /**
151
- * CC 执行完一轮后的 drain loop
152
- */
153
- handleResult(result) {
154
- this.callbacks.onResult?.(result);
155
- this.turnCount++;
156
- this.lastActivityAt = new Date();
157
- if (this.state === 'disposed')
158
- return;
159
- // drain: 缓冲区有消息 → 合并发送
160
- if (this.pendingMessages.length > 0) {
161
- const buffered = this.pendingMessages.splice(0);
162
- const prompt = this.formatBufferedMessages(buffered);
163
- try {
164
- this.broker.sendPrompt(this.instanceId, prompt);
165
- }
166
- catch (err) {
167
- this.config.logger?.error('[LoidSession] drain sendPrompt 失败:', err);
168
- this.handleCrash();
169
- }
170
- return;
171
- }
172
- // 达到硬上限 → 直接 dispose(不生成摘要)
173
- if (this.turnCount >= this.maxTurns) {
174
- this.config.logger?.info(`[LoidSession] 达到硬上限 (${this.maxTurns} 轮),销毁 session`);
175
- this.disposeAndNotify();
176
- return;
177
- }
178
- // 无待处理消息 → 回 IDLE,启动空闲计时器
179
- this.state = 'idle';
180
- this.resetIdleTimer();
181
- }
182
- /**
183
- * 崩溃处理:交还未处理消息,标记 disposed
184
- */
185
- handleCrash() {
186
- if (this.state === 'disposed')
187
- return;
188
- this.state = 'disposed';
189
- this.clearIdleTimer();
190
- this.connected = false;
191
- this.removeBrokerListeners();
192
- const orphans = this.pendingMessages.splice(0);
193
- this.callbacks.onCrash(orphans);
194
- }
195
- /**
196
- * dispose 并通知回调
197
- */
198
- async disposeAndNotify() {
199
- await this.dispose();
200
- this.callbacks.onDisposed();
201
- }
202
- /**
203
- * 重置空闲计时器
204
- */
205
- resetIdleTimer() {
206
- this.clearIdleTimer();
207
- const timeout = this.config.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
208
- this.idleTimer = setTimeout(() => {
209
- if (this.state === 'idle') {
210
- this.config.logger?.info(`[LoidSession] 空闲超时 (${timeout}ms),销毁 session`);
211
- this.disposeAndNotify();
212
- }
213
- }, timeout);
214
- }
215
- /**
216
- * 清除空闲计时器
217
- */
218
- clearIdleTimer() {
219
- if (this.idleTimer) {
220
- clearTimeout(this.idleTimer);
221
- this.idleTimer = null;
222
- }
223
- }
224
- /**
225
- * 移除 broker 事件监听器
226
- */
227
- removeBrokerListeners() {
228
- if (this.boundResultListener) {
229
- this.broker.removeListener('instance.result', this.boundResultListener);
230
- this.boundResultListener = null;
231
- }
232
- if (this.boundCrashListener) {
233
- this.broker.removeListener('instance.crash', this.boundCrashListener);
234
- this.boundCrashListener = null;
235
- }
236
- if (this.boundExitedListener) {
237
- this.broker.removeListener('instance.exited', this.boundExitedListener);
238
- this.boundExitedListener = null;
239
- }
240
- }
241
- /**
242
- * 合并缓冲消息为单个 prompt
243
- * 临时内联实现(正式版本由 context-builder 的 formatBufferedMessages 提供)
244
- */
245
- formatBufferedMessages(messages) {
246
- if (messages.length === 1) {
247
- return messages[0].prompt;
248
- }
249
- const lines = [];
250
- lines.push(`你在处理上一件事的过程中,该对话又收到了 ${messages.length} 条新消息:`);
251
- lines.push('');
252
- for (let i = 0; i < messages.length; i++) {
253
- const msg = messages[i];
254
- const time = msg.enqueuedAt.toLocaleTimeString('zh-CN', {
255
- hour: '2-digit',
256
- minute: '2-digit',
257
- second: '2-digit',
258
- hour12: false,
259
- });
260
- const typeLabel = msg.type === 'delivery' ? ' [交付]'
261
- : msg.type === 'recovery' ? ' [恢复]'
262
- : '';
263
- lines.push(`--- 消息 ${i + 1} (${time}${typeLabel}) ---`);
264
- lines.push(msg.prompt);
265
- lines.push('');
266
- }
267
- lines.push('请综合这些新消息,决定下一步行动。');
268
- return lines.join('\n');
269
- }
270
- }
271
- //# sourceMappingURL=session.js.map