team-anya 0.2.2 → 0.2.3

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.
@@ -1,10 +1,4 @@
1
- import { getRecentMessages } from '@team-anya/db';
2
1
  // ── 话术池 ──
3
- const SILENT_RESULT_REPLIES = [
4
- '刚才处理完了但好像漏说了结果,你再问我一下?',
5
- '不好意思,刚才干完活忘了汇报,你可以再@我一下',
6
- '处理完了但我好像没回你,抱歉,你再说一声我接上',
7
- ];
8
2
  const STUCK_REPLIES = [
9
3
  '我这边好像卡住了,正在尝试恢复,如果一直没反应你踢我一下',
10
4
  '抱歉,我可能卡在某个环节了,你可以发条消息唤醒我试试',
@@ -17,37 +11,28 @@ function pickRandom(pool) {
17
11
  /**
18
12
  * 静默健康监控器
19
13
  *
20
- * 正常流程零感知,仅在异常时主动通知用户:
21
- * 1. CC result 到了但没回复用户 → 兜底通知
22
- * 2. CC 实例卡死(executing 但长时间无 progress) → 告警
14
+ * 正常流程零感知,仅在 CC 实例卡死(executing 但长时间无 progress)时主动通知用户。
23
15
  */
24
16
  export class HealthMonitor {
25
17
  deps;
26
18
  config;
27
19
  logger;
28
20
  scanTimer = null;
29
- pendingChecks = new Map(); // instanceId → timer
30
21
  /** 已通知过卡死的实例,防止重复告警 */
31
22
  stuckNotified = new Set();
32
23
  constructor(deps, config = {}) {
33
24
  this.deps = deps;
34
25
  this.logger = deps.logger ?? { info: console.log, error: console.error };
35
26
  this.config = {
36
- resultCheckDelayMs: config.resultCheckDelayMs ?? 10_000,
37
27
  stuckThresholdMs: config.stuckThresholdMs ?? 3 * 60_000,
38
28
  scanIntervalMs: config.scanIntervalMs ?? 30_000,
39
- recentOutboundWindowMs: config.recentOutboundWindowMs ?? 30_000,
40
29
  };
41
30
  }
42
31
  /**
43
32
  * 启动监控:监听 broker 事件 + 启动定时扫描
44
33
  */
45
34
  start() {
46
- // 场景 2:监听 result 事件,延迟检查是否有回复
47
- this.deps.broker.on('instance.result', (instanceId, role, _result) => {
48
- this.scheduleResultCheck(instanceId, role);
49
- });
50
- // 场景 3:定时扫描卡死实例
35
+ // 定时扫描卡死实例
51
36
  this.scanTimer = setInterval(() => this.checkStuckInstances(), this.config.scanIntervalMs);
52
37
  // 实例退出时清理追踪状态
53
38
  this.deps.broker.on('instance.exited', (instanceId) => {
@@ -66,36 +51,10 @@ export class HealthMonitor {
66
51
  clearInterval(this.scanTimer);
67
52
  this.scanTimer = null;
68
53
  }
69
- for (const timer of this.pendingChecks.values()) {
70
- clearTimeout(timer);
71
- }
72
- this.pendingChecks.clear();
73
54
  this.stuckNotified.clear();
74
55
  this.logger.info('[HealthMonitor] 已停止');
75
56
  }
76
- // ── 场景 2:result 到了但没回复 ──
77
- scheduleResultCheck(instanceId, role) {
78
- // 如果已有待检查的 timer,先清掉(同一实例连续 result)
79
- const existing = this.pendingChecks.get(instanceId);
80
- if (existing)
81
- clearTimeout(existing);
82
- const timer = setTimeout(() => {
83
- this.pendingChecks.delete(instanceId);
84
- this.checkResultReply(instanceId, role);
85
- }, this.config.resultCheckDelayMs);
86
- this.pendingChecks.set(instanceId, timer);
87
- }
88
- checkResultReply(instanceId, role) {
89
- const chatId = this.deps.resolveChatId(instanceId, role);
90
- if (!chatId)
91
- return; // 无法定位用户,跳过
92
- if (this.hasRecentOutbound(chatId))
93
- return; // 已回复,正常
94
- // 没回复 → 发兜底通知
95
- this.notify(chatId, 'silent_result');
96
- this.logger.info(`[HealthMonitor] result 无回复兜底通知 (instance: ${instanceId}, chat: ${chatId})`);
97
- }
98
- // ── 场景 3:卡死检测 ──
57
+ // ── 卡死检测 ──
99
58
  checkStuckInstances() {
100
59
  const now = Date.now();
101
60
  const instances = this.deps.broker.status();
@@ -115,25 +74,13 @@ export class HealthMonitor {
115
74
  if (!chatId)
116
75
  continue;
117
76
  this.stuckNotified.add(inst.id);
118
- this.notify(chatId, 'stuck');
77
+ this.notify(chatId);
119
78
  this.logger.info(`[HealthMonitor] 卡死告警 (instance: ${inst.id}, idle: ${Math.round(elapsed / 1000)}s)`);
120
79
  }
121
80
  }
122
81
  // ── 工具方法 ──
123
- hasRecentOutbound(chatId) {
124
- const since = new Date(Date.now() - this.config.recentOutboundWindowMs).toISOString();
125
- const messages = getRecentMessages(this.deps.db, {
126
- direction: 'outbound',
127
- chat_id: chatId,
128
- since,
129
- limit: 1,
130
- });
131
- return messages.length > 0;
132
- }
133
- notify(chatId, type) {
134
- const text = type === 'silent_result'
135
- ? pickRandom(SILENT_RESULT_REPLIES)
136
- : pickRandom(STUCK_REPLIES);
82
+ notify(chatId) {
83
+ const text = pickRandom(STUCK_REPLIES);
137
84
  this.deps.feishuSender.sendText({
138
85
  receiveIdType: 'chat_id',
139
86
  receiveId: chatId,
@@ -143,11 +90,6 @@ export class HealthMonitor {
143
90
  });
144
91
  }
145
92
  cleanup(instanceId) {
146
- const timer = this.pendingChecks.get(instanceId);
147
- if (timer) {
148
- clearTimeout(timer);
149
- this.pendingChecks.delete(instanceId);
150
- }
151
93
  this.stuckNotified.delete(instanceId);
152
94
  }
153
95
  }
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## 铁律
4
4
 
5
+ ### 禁止删除工作区
6
+
7
+ **绝对不要执行 `rm -rf` 或任何方式删除工作区目录。** 任务完成后工作区由系统管理,不由你清理。原因:
8
+ - 已完成的任务可能被 `task.escalate_to_topic` 升级为话题,需要 fork 源工作区
9
+ - 工作区保留用于审计追溯
10
+
5
11
  ### 定向消息必须回应
6
12
 
7
13
  @Anya 或私聊的消息就是定向消息。收到后必须有明确的下一步动作,**且所有回复必须通过 `channel.send` 发出**(纯文本输出对方看不到):
@@ -127,14 +133,11 @@ dispatch 返回 status=blocked 时,不执行 yor.spawn——通知人类工作
127
133
 
128
134
  Yor 完成任务后调用 `task.deliver`,CC 实例保持存活等待审核:
129
135
 
130
- - **通过** → `yor.approve()` + `delivery.submit()` + `task.update({ status: "DONE" })` + `channel.send()` + 清理工作区
136
+ - **通过** → `yor.approve()` + `delivery.submit()` + `task.update({ status: "DONE" })` + `channel.send()`
131
137
  - **返工** → `yor.rework({ task_id, feedback })` — Yor 在原工作区继续,修改完后再次 deliver
132
138
  - **升级** → `task.update({ status: "BLOCKED" })` + `yor.kill()` + `channel.send()` 告知人类
133
139
 
134
- 清理规则:
135
- - 通过:`yor.approve` 自动关闭 Yor;`rm -rf` 清理工作区,分支保留等 PR 合并
136
- - 返工:不清理工作区
137
- - 升级:`yor.kill` 关闭 Yor;不清理工作区
140
+ ⚠️ **任何审核结果都不清理工作区。** `yor.approve` 自动关闭 Yor CC 实例即可,工作区目录保留。
138
141
 
139
142
  ## 场景打法
140
143
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-anya",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "Team Anya - AI 数字员工系统",
6
6
  "bin": {