@f2a/openclaw-f2a 0.2.20

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 (71) hide show
  1. package/README.md +510 -0
  2. package/dist/agent-manager.d.ts +78 -0
  3. package/dist/agent-manager.d.ts.map +1 -0
  4. package/dist/agent-manager.js +206 -0
  5. package/dist/agent-manager.js.map +1 -0
  6. package/dist/announcement-queue.d.ts +152 -0
  7. package/dist/announcement-queue.d.ts.map +1 -0
  8. package/dist/announcement-queue.js +307 -0
  9. package/dist/announcement-queue.js.map +1 -0
  10. package/dist/capability-detector.d.ts +21 -0
  11. package/dist/capability-detector.d.ts.map +1 -0
  12. package/dist/capability-detector.js +178 -0
  13. package/dist/capability-detector.js.map +1 -0
  14. package/dist/claim-handlers.d.ts +75 -0
  15. package/dist/claim-handlers.d.ts.map +1 -0
  16. package/dist/claim-handlers.js +368 -0
  17. package/dist/claim-handlers.js.map +1 -0
  18. package/dist/connector.d.ts +174 -0
  19. package/dist/connector.d.ts.map +1 -0
  20. package/dist/connector.js +1284 -0
  21. package/dist/connector.js.map +1 -0
  22. package/dist/index.d.ts +16 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +45 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/logger.d.ts +28 -0
  27. package/dist/logger.d.ts.map +1 -0
  28. package/dist/logger.js +44 -0
  29. package/dist/logger.js.map +1 -0
  30. package/dist/network-client.d.ts +73 -0
  31. package/dist/network-client.d.ts.map +1 -0
  32. package/dist/network-client.js +202 -0
  33. package/dist/network-client.js.map +1 -0
  34. package/dist/node-manager.d.ts +79 -0
  35. package/dist/node-manager.d.ts.map +1 -0
  36. package/dist/node-manager.js +374 -0
  37. package/dist/node-manager.js.map +1 -0
  38. package/dist/plugin.d.ts +22 -0
  39. package/dist/plugin.d.ts.map +1 -0
  40. package/dist/plugin.js +148 -0
  41. package/dist/plugin.js.map +1 -0
  42. package/dist/reputation.d.ts +156 -0
  43. package/dist/reputation.d.ts.map +1 -0
  44. package/dist/reputation.js +432 -0
  45. package/dist/reputation.js.map +1 -0
  46. package/dist/task-guard.d.ts +159 -0
  47. package/dist/task-guard.d.ts.map +1 -0
  48. package/dist/task-guard.js +763 -0
  49. package/dist/task-guard.js.map +1 -0
  50. package/dist/task-queue.d.ts +130 -0
  51. package/dist/task-queue.d.ts.map +1 -0
  52. package/dist/task-queue.js +592 -0
  53. package/dist/task-queue.js.map +1 -0
  54. package/dist/tool-handlers.d.ts +158 -0
  55. package/dist/tool-handlers.d.ts.map +1 -0
  56. package/dist/tool-handlers.js +727 -0
  57. package/dist/tool-handlers.js.map +1 -0
  58. package/dist/types.d.ts +417 -0
  59. package/dist/types.d.ts.map +1 -0
  60. package/dist/types.js +29 -0
  61. package/dist/types.js.map +1 -0
  62. package/dist/webhook-pusher.d.ts +71 -0
  63. package/dist/webhook-pusher.d.ts.map +1 -0
  64. package/dist/webhook-pusher.js +175 -0
  65. package/dist/webhook-pusher.js.map +1 -0
  66. package/dist/webhook-server.d.ts +70 -0
  67. package/dist/webhook-server.d.ts.map +1 -0
  68. package/dist/webhook-server.js +191 -0
  69. package/dist/webhook-server.js.map +1 -0
  70. package/openclaw.plugin.json +107 -0
  71. package/package.json +53 -0
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Manager
4
+ *
5
+ * 管理 Agent 身份和生命周期。
6
+ * Phase 1: 引入 AgentID,独立于 Node 的 PeerID。
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.AgentManager = void 0;
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const crypto_1 = require("crypto");
13
+ const logger_js_1 = require("./logger.js");
14
+ /** Agent 数据文件名 */
15
+ const AGENT_DATA_FILE = 'agent.json';
16
+ /**
17
+ * Agent Manager
18
+ *
19
+ * 职责:
20
+ * - 生成或加载 Agent 身份
21
+ * - 持久化 Agent 数据
22
+ * - 提供 Agent 相关 API
23
+ */
24
+ class AgentManager {
25
+ dataDir;
26
+ identity = null;
27
+ config;
28
+ constructor(dataDir, config = {}) {
29
+ this.dataDir = dataDir;
30
+ this.config = config;
31
+ // 同步初始化(避免插件异步注册问题)
32
+ this.initializeSync();
33
+ }
34
+ /**
35
+ * 同步初始化 Agent 身份
36
+ * - 如果已存在则加载
37
+ * - 如果不存在则创建
38
+ */
39
+ initializeSync() {
40
+ // 尝试加载已有身份
41
+ const existing = this.loadIdentity();
42
+ if (existing) {
43
+ this.identity = existing;
44
+ logger_js_1.pluginLogger.info('[F2A:Agent] 已加载 Agent 身份: %s', existing.agentId);
45
+ return;
46
+ }
47
+ // 创建新身份
48
+ this.identity = this.createIdentity();
49
+ this.saveIdentity(this.identity);
50
+ logger_js_1.pluginLogger.info('[F2A:Agent] 已创建新 Agent 身份: %s', this.identity.agentId);
51
+ }
52
+ /**
53
+ * 异步初始化(兼容旧 API)
54
+ * @deprecated 使用构造函数自动初始化
55
+ */
56
+ async initialize() {
57
+ if (!this.identity) {
58
+ this.initializeSync();
59
+ }
60
+ return this.identity;
61
+ }
62
+ /**
63
+ * 获取当前 Agent 身份
64
+ */
65
+ getIdentity() {
66
+ return this.identity;
67
+ }
68
+ /**
69
+ * 获取 AgentID
70
+ */
71
+ getAgentId() {
72
+ return this.identity?.agentId || null;
73
+ }
74
+ /**
75
+ * 获取 Agent 名称
76
+ */
77
+ getAgentName() {
78
+ return this.identity?.name || 'Unknown Agent';
79
+ }
80
+ /**
81
+ * 创建新 Agent 身份
82
+ */
83
+ createIdentity() {
84
+ const agentId = this.config.id || this.generateAgentId();
85
+ const name = this.config.name || this.generateDefaultName();
86
+ return {
87
+ agentId,
88
+ name,
89
+ createdAt: Date.now()
90
+ };
91
+ }
92
+ /**
93
+ * 生成 AgentID
94
+ * 格式: agent-{timestamp}-{random}
95
+ */
96
+ generateAgentId() {
97
+ const timestamp = Date.now().toString(36);
98
+ const random = (0, crypto_1.randomBytes)(6).toString('hex');
99
+ return `agent-${timestamp}-${random}`;
100
+ }
101
+ /**
102
+ * 生成默认 Agent 名称
103
+ */
104
+ generateDefaultName() {
105
+ const names = ['Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta', 'Eta', 'Theta'];
106
+ const randomName = names[Math.floor(Math.random() * names.length)];
107
+ return `Agent-${randomName}`;
108
+ }
109
+ /**
110
+ * 加载已存在的 Agent 身份
111
+ */
112
+ loadIdentity() {
113
+ const filePath = (0, path_1.join)(this.dataDir, AGENT_DATA_FILE);
114
+ if (!(0, fs_1.existsSync)(filePath)) {
115
+ return null;
116
+ }
117
+ try {
118
+ const data = (0, fs_1.readFileSync)(filePath, 'utf-8');
119
+ const identity = JSON.parse(data);
120
+ // 验证必要字段
121
+ if (!identity.agentId || !identity.name || !identity.createdAt) {
122
+ logger_js_1.pluginLogger.warn('[F2A:Agent] 身份文件格式无效,将创建新身份');
123
+ return null;
124
+ }
125
+ return identity;
126
+ }
127
+ catch (error) {
128
+ logger_js_1.pluginLogger.warn('[F2A:Agent] 加载身份文件失败: %s', error);
129
+ return null;
130
+ }
131
+ }
132
+ /**
133
+ * 保存 Agent 身份到文件
134
+ */
135
+ saveIdentity(identity) {
136
+ // 确保目录存在
137
+ if (!(0, fs_1.existsSync)(this.dataDir)) {
138
+ (0, fs_1.mkdirSync)(this.dataDir, { recursive: true });
139
+ }
140
+ const filePath = (0, path_1.join)(this.dataDir, AGENT_DATA_FILE);
141
+ const data = JSON.stringify(identity, null, 2);
142
+ (0, fs_1.writeFileSync)(filePath, data, { mode: 0o600 }); // 仅所有者可读写
143
+ logger_js_1.pluginLogger.debug('[F2A:Agent] 身份已保存到: %s', filePath);
144
+ }
145
+ /**
146
+ * 导出 Agent 身份(用于迁移)
147
+ */
148
+ exportIdentity() {
149
+ if (!this.identity) {
150
+ throw new Error('No agent identity loaded');
151
+ }
152
+ return JSON.stringify(this.identity);
153
+ }
154
+ /**
155
+ * 导入 Agent 身份(用于迁移)
156
+ */
157
+ importIdentity(data) {
158
+ // P1-3 修复:输入验证
159
+ // 1. 长度限制:防止过大的输入导致内存问题
160
+ const MAX_IDENTITY_SIZE = 4096; // 4KB 足够存储 AgentIdentity
161
+ if (data.length > MAX_IDENTITY_SIZE) {
162
+ throw new Error(`Identity data too large: ${data.length} bytes (max: ${MAX_IDENTITY_SIZE})`);
163
+ }
164
+ // 2. 基础格式校验:确保是有效的 JSON
165
+ let identity;
166
+ try {
167
+ identity = JSON.parse(data);
168
+ }
169
+ catch (parseError) {
170
+ throw new Error('Invalid JSON format for identity data');
171
+ }
172
+ // 3. 类型验证:确保解析结果是一个对象
173
+ if (typeof identity !== 'object' || identity === null || Array.isArray(identity)) {
174
+ throw new Error('Identity data must be a JSON object');
175
+ }
176
+ // 4. 验证必要字段
177
+ if (!identity.agentId || !identity.name || !identity.createdAt) {
178
+ throw new Error('Invalid agent identity data: missing required fields (agentId, name, createdAt)');
179
+ }
180
+ // 5. 字段格式验证
181
+ if (typeof identity.agentId !== 'string' || identity.agentId.length === 0) {
182
+ throw new Error('Invalid agentId: must be a non-empty string');
183
+ }
184
+ if (typeof identity.name !== 'string' || identity.name.length === 0) {
185
+ throw new Error('Invalid name: must be a non-empty string');
186
+ }
187
+ if (typeof identity.createdAt !== 'number' || identity.createdAt <= 0) {
188
+ throw new Error('Invalid createdAt: must be a positive number');
189
+ }
190
+ this.identity = identity;
191
+ this.saveIdentity(identity);
192
+ logger_js_1.pluginLogger.info('[F2A:Agent] 已导入 Agent 身份: %s', identity.agentId);
193
+ return identity;
194
+ }
195
+ /**
196
+ * 重置 Agent 身份(创建新的)
197
+ */
198
+ resetIdentity() {
199
+ this.identity = this.createIdentity();
200
+ this.saveIdentity(this.identity);
201
+ logger_js_1.pluginLogger.info('[F2A:Agent] 已重置 Agent 身份: %s', this.identity.agentId);
202
+ return this.identity;
203
+ }
204
+ }
205
+ exports.AgentManager = AgentManager;
206
+ //# sourceMappingURL=agent-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-manager.js","sourceRoot":"","sources":["../src/agent-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * F2A Announcement Queue
3
+ * 管理任务广播和认领(Claim Pattern)
4
+ *
5
+ * 设计说明:
6
+ * - 默认导出类而非单例,便于测试和依赖注入
7
+ * - createAnnouncementQueue() 工厂函数提供默认实例
8
+ */
9
+ import type { TaskAnnouncement, TaskClaim } from './types.js';
10
+ import { EventEmitter } from 'eventemitter3';
11
+ /** Logger 接口 */
12
+ interface Logger {
13
+ info(message: string, ...args: unknown[]): void;
14
+ warn(message: string, ...args: unknown[]): void;
15
+ error(message: string, ...args: unknown[]): void;
16
+ debug?(message: string, ...args: unknown[]): void;
17
+ }
18
+ /**
19
+ * 任务广播队列统计信息
20
+ *
21
+ * 提供任务广播各状态的数量统计,用于监控队列健康状态和任务处理进度。
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const stats: AnnouncementQueueStats = {
26
+ * open: 5, // 5 个开放认领的任务
27
+ * claimed: 3, // 3 个已被认领
28
+ * delegated: 12, // 12 个已委托执行
29
+ * expired: 2, // 2 个已过期
30
+ * total: 22 // 总计 22 个广播
31
+ * };
32
+ *
33
+ * // 监控队列健康状态
34
+ * if (stats.open > 10) {
35
+ * console.warn('待认领任务过多,考虑通知更多 Agent');
36
+ * }
37
+ * ```
38
+ */
39
+ export interface AnnouncementQueueStats {
40
+ /** 开放认领中的任务数量 */
41
+ open: number;
42
+ /** 已被认领的任务数量 */
43
+ claimed: number;
44
+ /** 已委托执行的任务数量 */
45
+ delegated: number;
46
+ /** 已过期的任务数量 */
47
+ expired: number;
48
+ /** 任务广播总数 */
49
+ total: number;
50
+ }
51
+ export interface AnnouncementQueueOptions {
52
+ maxSize?: number;
53
+ maxAgeMs?: number;
54
+ }
55
+ /** 过期事件载荷 */
56
+ export interface AnnouncementExpiredEvent {
57
+ announcementId: string;
58
+ taskType: string;
59
+ from: string;
60
+ timestamp: number;
61
+ reason: 'timeout' | 'manual';
62
+ }
63
+ /** 事件类型定义 */
64
+ export interface AnnouncementQueueEvents {
65
+ 'announcement:expired': (event: AnnouncementExpiredEvent) => void;
66
+ 'announcement:created': (announcement: TaskAnnouncement) => void;
67
+ 'announcement:claimed': (announcement: TaskAnnouncement, claim: TaskClaim) => void;
68
+ }
69
+ export declare class AnnouncementQueue extends EventEmitter<AnnouncementQueueEvents> {
70
+ private announcements;
71
+ private maxSize;
72
+ private maxAgeMs;
73
+ /** 正在处理的广播 ID 集合,用于防止并发操作 */
74
+ private processingLocks;
75
+ private logger;
76
+ constructor(options?: {
77
+ maxSize?: number;
78
+ maxAgeMs?: number;
79
+ logger?: Logger;
80
+ });
81
+ /**
82
+ * 创建任务广播
83
+ */
84
+ create(announcement: Omit<TaskAnnouncement, 'announcementId' | 'timestamp' | 'status' | 'claims'>): TaskAnnouncement;
85
+ /**
86
+ * 获取所有开放的广播
87
+ */
88
+ getOpen(): TaskAnnouncement[];
89
+ /**
90
+ * 获取特定广播
91
+ */
92
+ get(announcementId: string): TaskAnnouncement | undefined;
93
+ /**
94
+ * 提交认领
95
+ */
96
+ submitClaim(announcementId: string, claim: Omit<TaskClaim, 'claimId' | 'timestamp' | 'status' | 'announcementId'>): TaskClaim | null;
97
+ /**
98
+ * 接受认领
99
+ * 使用锁机制防止竞态条件
100
+ *
101
+ * P1 修复:确保锁在任何情况下都能被释放,包括异常情况
102
+ */
103
+ acceptClaim(announcementId: string, claimId: string): TaskClaim | null;
104
+ /**
105
+ * 拒绝认领
106
+ */
107
+ rejectClaim(announcementId: string, claimId: string): TaskClaim | null;
108
+ /**
109
+ * 标记为已委托
110
+ */
111
+ markDelegated(announcementId: string): boolean;
112
+ /**
113
+ * 获取我的认领(作为认领方)
114
+ */
115
+ getMyClaims(claimantId: string): TaskClaim[];
116
+ /**
117
+ * 获取我的广播(作为发布方)
118
+ */
119
+ getMyAnnouncements(fromId: string): TaskAnnouncement[];
120
+ /**
121
+ * 获取统计
122
+ */
123
+ getStats(): AnnouncementQueueStats;
124
+ /**
125
+ * 清理过期
126
+ */
127
+ cleanup(): void;
128
+ /**
129
+ * 清空
130
+ */
131
+ clear(): void;
132
+ /**
133
+ * 强制清除孤立锁
134
+ * P1 修复:用于处理异常导致的锁未释放情况
135
+ *
136
+ * @param maxLockAgeMs 锁的最大存活时间(毫秒),默认 30 秒
137
+ * @returns 清除的锁数量
138
+ */
139
+ forceClearOrphanLocks(maxLockAgeMs?: number): number;
140
+ /**
141
+ * 检查是否有孤立锁
142
+ * 用于监控和调试
143
+ */
144
+ hasOrphanLocks(): boolean;
145
+ }
146
+ export declare function createAnnouncementQueue(options?: {
147
+ maxSize?: number;
148
+ maxAgeMs?: number;
149
+ }): AnnouncementQueue;
150
+ export declare const announcementQueue: AnnouncementQueue;
151
+ export {};
152
+ //# sourceMappingURL=announcement-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"announcement-queue.d.ts","sourceRoot":"","sources":["../src/announcement-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,307 @@
1
+ "use strict";
2
+ /**
3
+ * F2A Announcement Queue
4
+ * 管理任务广播和认领(Claim Pattern)
5
+ *
6
+ * 设计说明:
7
+ * - 默认导出类而非单例,便于测试和依赖注入
8
+ * - createAnnouncementQueue() 工厂函数提供默认实例
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.announcementQueue = exports.AnnouncementQueue = void 0;
12
+ exports.createAnnouncementQueue = createAnnouncementQueue;
13
+ const crypto_1 = require("crypto");
14
+ const eventemitter3_1 = require("eventemitter3");
15
+ class AnnouncementQueue extends eventemitter3_1.EventEmitter {
16
+ announcements = new Map();
17
+ maxSize;
18
+ maxAgeMs;
19
+ /** 正在处理的广播 ID 集合,用于防止并发操作 */
20
+ processingLocks = new Set();
21
+ logger;
22
+ constructor(options) {
23
+ super();
24
+ this.maxSize = options?.maxSize || 100;
25
+ this.maxAgeMs = options?.maxAgeMs || 30 * 60 * 1000; // 30分钟
26
+ this.logger = options?.logger || console;
27
+ }
28
+ /**
29
+ * 创建任务广播
30
+ */
31
+ create(announcement) {
32
+ // 清理过期
33
+ this.cleanup();
34
+ // 检查容量
35
+ if (this.announcements.size >= this.maxSize) {
36
+ this.logger.error('[F2A:Announce] create: queue is full, size=%d, maxSize=%d', this.announcements.size, this.maxSize);
37
+ throw new Error('Announcement queue is full');
38
+ }
39
+ // 使用 crypto.randomUUID() 生成唯一 ID,避免碰撞
40
+ const id = `ann-${(0, crypto_1.randomUUID)()}`;
41
+ const created = {
42
+ ...announcement,
43
+ announcementId: id,
44
+ timestamp: Date.now(),
45
+ status: 'open',
46
+ claims: []
47
+ };
48
+ this.announcements.set(id, created);
49
+ this.logger.info('[F2A:Announce] create: announcementId=%s, from=%s, taskType=%s', id, announcement.from, announcement.taskType);
50
+ // 发出创建事件
51
+ this.emit('announcement:created', created);
52
+ return created;
53
+ }
54
+ /**
55
+ * 获取所有开放的广播
56
+ */
57
+ getOpen() {
58
+ // 在获取开放广播前先清理过期数据,避免返回过期数据
59
+ this.cleanup();
60
+ return Array.from(this.announcements.values())
61
+ .filter(a => a.status === 'open')
62
+ .sort((a, b) => a.timestamp - b.timestamp);
63
+ }
64
+ /**
65
+ * 获取特定广播
66
+ */
67
+ get(announcementId) {
68
+ return this.announcements.get(announcementId);
69
+ }
70
+ /**
71
+ * 提交认领
72
+ */
73
+ submitClaim(announcementId, claim) {
74
+ const announcement = this.announcements.get(announcementId);
75
+ if (!announcement) {
76
+ this.logger.warn('[F2A:Announce] submitClaim: announcement not found, id=%s, claimant=%s', announcementId, claim.claimant);
77
+ return null;
78
+ }
79
+ if (announcement.status !== 'open') {
80
+ this.logger.warn('[F2A:Announce] submitClaim: announcement not open, id=%s, status=%s, claimant=%s', announcementId, announcement.status, claim.claimant);
81
+ return null;
82
+ }
83
+ // 检查该 claimant 是否已经提交过认领(防止重复认领)
84
+ const existingClaim = announcement.claims?.find(c => c.claimant === claim.claimant);
85
+ if (existingClaim) {
86
+ this.logger.info('[F2A:Announce] submitClaim: duplicate claim ignored, id=%s, claimant=%s, existingClaimId=%s', announcementId, claim.claimant, existingClaim.claimId);
87
+ // 返回已存在的认领,而不是创建新的
88
+ return existingClaim;
89
+ }
90
+ // 使用 crypto.randomUUID() 生成唯一 ID
91
+ const claimId = `claim-${(0, crypto_1.randomUUID)()}`;
92
+ const created = {
93
+ ...claim,
94
+ claimId,
95
+ announcementId,
96
+ timestamp: Date.now(),
97
+ status: 'pending'
98
+ };
99
+ if (!announcement.claims) {
100
+ announcement.claims = [];
101
+ }
102
+ announcement.claims.push(created);
103
+ this.logger.info('[F2A:Announce] submitClaim: claimId=%s, announcementId=%s, claimant=%s', claimId, announcementId, claim.claimant);
104
+ return created;
105
+ }
106
+ /**
107
+ * 接受认领
108
+ * 使用锁机制防止竞态条件
109
+ *
110
+ * P1 修复:确保锁在任何情况下都能被释放,包括异常情况
111
+ */
112
+ acceptClaim(announcementId, claimId) {
113
+ const announcement = this.announcements.get(announcementId);
114
+ if (!announcement) {
115
+ this.logger.warn('[F2A:Announce] acceptClaim: announcement not found, id=%s, claimId=%s', announcementId, claimId);
116
+ return null;
117
+ }
118
+ // 检查是否已被锁定(正在被其他操作处理)
119
+ if (this.processingLocks.has(announcementId)) {
120
+ this.logger.warn('[F2A:Announce] acceptClaim: announcement is being processed, id=%s, claimId=%s', announcementId, claimId);
121
+ return null;
122
+ }
123
+ // 检查广播状态
124
+ if (announcement.status !== 'open') {
125
+ this.logger.warn('[F2A:Announce] acceptClaim: announcement not open, id=%s, status=%s, claimId=%s', announcementId, announcement.status, claimId);
126
+ return null;
127
+ }
128
+ const claim = announcement.claims?.find(c => c.claimId === claimId);
129
+ if (!claim) {
130
+ this.logger.warn('[F2A:Announce] acceptClaim: claim not found, announcementId=%s, claimId=%s', announcementId, claimId);
131
+ return null;
132
+ }
133
+ // 获取锁
134
+ this.processingLocks.add(announcementId);
135
+ try {
136
+ // 再次检查广播状态(双重检查)
137
+ if (announcement.status !== 'open') {
138
+ this.logger.warn('[F2A:Announce] acceptClaim: race condition detected, announcement status changed, id=%s, status=%s, claimId=%s', announcementId, announcement.status, claimId);
139
+ return null;
140
+ }
141
+ // 标记该认领为接受
142
+ claim.status = 'accepted';
143
+ // 拒绝其他认领(幂等操作,已拒绝的保持不变)
144
+ const rejectedCount = announcement.claims?.filter(c => {
145
+ if (c.claimId !== claimId && c.status !== 'rejected') {
146
+ c.status = 'rejected';
147
+ return true;
148
+ }
149
+ return false;
150
+ }).length || 0;
151
+ // 标记广播为已认领
152
+ announcement.status = 'claimed';
153
+ this.logger.info('[F2A:Announce] acceptClaim: claimId=%s, announcementId=%s, claimant=%s, rejectedCount=%d', claimId, announcementId, claim.claimant, rejectedCount);
154
+ // 发出认领事件(在锁内发出,确保状态一致)
155
+ this.emit('announcement:claimed', announcement, claim);
156
+ return claim;
157
+ }
158
+ catch (error) {
159
+ // P1 修复:记录异常并返回 null,但不改变状态
160
+ this.logger.error('[F2A:Announce] acceptClaim: unexpected error, id=%s, claimId=%s, error=%s', announcementId, claimId, error);
161
+ // 注意:不恢复状态,因为操作可能已部分完成
162
+ // 但锁会在 finally 中释放
163
+ return null;
164
+ }
165
+ finally {
166
+ // 确保锁在任何情况下都能被释放
167
+ this.processingLocks.delete(announcementId);
168
+ }
169
+ }
170
+ /**
171
+ * 拒绝认领
172
+ */
173
+ rejectClaim(announcementId, claimId) {
174
+ const announcement = this.announcements.get(announcementId);
175
+ if (!announcement) {
176
+ this.logger.warn('[F2A:Announce] rejectClaim: announcement not found, id=%s, claimId=%s', announcementId, claimId);
177
+ return null;
178
+ }
179
+ const claim = announcement.claims?.find(c => c.claimId === claimId);
180
+ if (!claim) {
181
+ this.logger.warn('[F2A:Announce] rejectClaim: claim not found, announcementId=%s, claimId=%s', announcementId, claimId);
182
+ return null;
183
+ }
184
+ claim.status = 'rejected';
185
+ this.logger.info('[F2A:Announce] rejectClaim: claimId=%s, announcementId=%s, claimant=%s', claimId, announcementId, claim.claimant);
186
+ return claim;
187
+ }
188
+ /**
189
+ * 标记为已委托
190
+ */
191
+ markDelegated(announcementId) {
192
+ const announcement = this.announcements.get(announcementId);
193
+ if (!announcement)
194
+ return false;
195
+ announcement.status = 'delegated';
196
+ return true;
197
+ }
198
+ /**
199
+ * 获取我的认领(作为认领方)
200
+ */
201
+ getMyClaims(claimantId) {
202
+ const claims = [];
203
+ for (const announcement of this.announcements.values()) {
204
+ const myClaims = announcement.claims?.filter(c => c.claimant === claimantId);
205
+ if (myClaims) {
206
+ claims.push(...myClaims);
207
+ }
208
+ }
209
+ return claims.sort((a, b) => b.timestamp - a.timestamp);
210
+ }
211
+ /**
212
+ * 获取我的广播(作为发布方)
213
+ */
214
+ getMyAnnouncements(fromId) {
215
+ return Array.from(this.announcements.values())
216
+ .filter(a => a.from === fromId)
217
+ .sort((a, b) => b.timestamp - a.timestamp);
218
+ }
219
+ /**
220
+ * 获取统计
221
+ */
222
+ getStats() {
223
+ const all = Array.from(this.announcements.values());
224
+ return {
225
+ open: all.filter(a => a.status === 'open').length,
226
+ claimed: all.filter(a => a.status === 'claimed').length,
227
+ delegated: all.filter(a => a.status === 'delegated').length,
228
+ expired: all.filter(a => a.status === 'expired').length,
229
+ total: all.length
230
+ };
231
+ }
232
+ /**
233
+ * 清理过期
234
+ */
235
+ cleanup() {
236
+ const now = Date.now();
237
+ let expiredCount = 0;
238
+ let deletedCount = 0;
239
+ for (const [id, announcement] of this.announcements) {
240
+ const age = now - announcement.timestamp;
241
+ if (age > this.maxAgeMs) {
242
+ if (announcement.status === 'open') {
243
+ announcement.status = 'expired';
244
+ expiredCount++;
245
+ // 发出过期事件,通知外部系统
246
+ const expiredEvent = {
247
+ announcementId: announcement.announcementId,
248
+ taskType: announcement.taskType,
249
+ from: announcement.from,
250
+ timestamp: announcement.timestamp,
251
+ reason: 'timeout'
252
+ };
253
+ this.emit('announcement:expired', expiredEvent);
254
+ this.logger.info('[F2A:Announce] cleanup: announcement expired, id=%s, taskType=%s, from=%s', announcement.announcementId, announcement.taskType, announcement.from);
255
+ }
256
+ // 删除已过期一段时间的
257
+ if (age > this.maxAgeMs * 2) {
258
+ this.announcements.delete(id);
259
+ deletedCount++;
260
+ }
261
+ }
262
+ }
263
+ if (expiredCount > 0 || deletedCount > 0) {
264
+ this.logger.info('[F2A:Announce] cleanup: expired=%d, deleted=%d, remaining=%d', expiredCount, deletedCount, this.announcements.size);
265
+ }
266
+ }
267
+ /**
268
+ * 清空
269
+ */
270
+ clear() {
271
+ this.announcements.clear();
272
+ // P1 修复:同时清理孤立锁
273
+ this.processingLocks.clear();
274
+ }
275
+ /**
276
+ * 强制清除孤立锁
277
+ * P1 修复:用于处理异常导致的锁未释放情况
278
+ *
279
+ * @param maxLockAgeMs 锁的最大存活时间(毫秒),默认 30 秒
280
+ * @returns 清除的锁数量
281
+ */
282
+ forceClearOrphanLocks(maxLockAgeMs = 30000) {
283
+ // 由于当前实现没有记录锁的获取时间,我们只能清除所有锁
284
+ // 这是一个安全操作,因为锁只是为了防止并发,不会丢失数据
285
+ const count = this.processingLocks.size;
286
+ this.processingLocks.clear();
287
+ if (count > 0) {
288
+ this.logger.warn('[F2A:Announce] forceClearOrphanLocks: cleared %d orphan locks', count);
289
+ }
290
+ return count;
291
+ }
292
+ /**
293
+ * 检查是否有孤立锁
294
+ * 用于监控和调试
295
+ */
296
+ hasOrphanLocks() {
297
+ return this.processingLocks.size > 0;
298
+ }
299
+ }
300
+ exports.AnnouncementQueue = AnnouncementQueue;
301
+ // 工厂函数:创建新的 AnnouncementQueue 实例
302
+ function createAnnouncementQueue(options) {
303
+ return new AnnouncementQueue(options);
304
+ }
305
+ // 默认实例(向后兼容,但建议使用依赖注入)
306
+ exports.announcementQueue = new AnnouncementQueue();
307
+ //# sourceMappingURL=announcement-queue.js.map