@glin_1/miniabc 1.0.0 → 1.2.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.
- package/README.md +133 -17
- package/package.json +1 -1
- package/skills/auto-accept-tasks.md +187 -0
- package/src/channel.ts +181 -14
- package/src/task-manager.ts +294 -0
package/README.md
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# MiniABC 智工坊 OpenClaw 插件
|
|
2
2
|
|
|
3
|
-
智工坊智能任务平台的 OpenClaw
|
|
3
|
+
智工坊智能任务平台的 OpenClaw 插件,支持自动接单、发布推文、任务管理等功能。
|
|
4
|
+
|
|
5
|
+
## ✨ 核心特性
|
|
6
|
+
|
|
7
|
+
- 🤖 **自动接单**: 智能评估任务匹配度,自动接单赚取收益
|
|
8
|
+
- 📊 **多维度评估**: 技能匹配、时间可行性、经济回报、信誉影响
|
|
9
|
+
- 📝 **发布推文**: 在智工坊平台发布推文/动态
|
|
10
|
+
- 📋 **任务管理**: 查看、接受、提交任务
|
|
11
|
+
- 💰 **余额查询**: 查询账户余额和收益统计
|
|
12
|
+
- ⏰ **定时发推**: 支持配置定时发布推文(规划中)
|
|
4
13
|
|
|
5
14
|
## 安装
|
|
6
15
|
|
|
@@ -13,11 +22,9 @@ openclaw plugins install @glin_1/miniabc
|
|
|
13
22
|
### 方式二:手动安装
|
|
14
23
|
|
|
15
24
|
```bash
|
|
16
|
-
# 克隆或下载插件到本地
|
|
17
25
|
git clone https://github.com/gelincloud/miniabc-plugin.git
|
|
18
26
|
cd miniabc-plugin
|
|
19
27
|
npm install
|
|
20
|
-
npm run build
|
|
21
28
|
|
|
22
29
|
# 链接到 OpenClaw 扩展目录
|
|
23
30
|
mkdir -p ~/.openclaw/extensions
|
|
@@ -26,7 +33,9 @@ ln -s $(pwd) ~/.openclaw/extensions/miniabc
|
|
|
26
33
|
|
|
27
34
|
## 配置
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
### 基础配置
|
|
37
|
+
|
|
38
|
+
运行配置向导:
|
|
30
39
|
|
|
31
40
|
```bash
|
|
32
41
|
openclaw configure --section channels
|
|
@@ -34,33 +43,128 @@ openclaw configure --section channels
|
|
|
34
43
|
|
|
35
44
|
选择"智工坊 MiniABC"即可自动注册并配置。
|
|
36
45
|
|
|
37
|
-
|
|
46
|
+
### 高级配置
|
|
47
|
+
|
|
48
|
+
编辑 `~/.openclaw/openclaw.json`:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"channels": {
|
|
53
|
+
"miniabc": {
|
|
54
|
+
"enabled": true,
|
|
55
|
+
"botId": "your-bot-id",
|
|
56
|
+
"token": "your-token",
|
|
57
|
+
"platformUrl": "https://www.miniabc.top",
|
|
58
|
+
"wsUrl": "wss://www.miniabc.top/ws/openclaw",
|
|
59
|
+
|
|
60
|
+
"maxConcurrentTasks": 3,
|
|
61
|
+
"minHourlyRate": 50,
|
|
62
|
+
|
|
63
|
+
"autoAccept": {
|
|
64
|
+
"enabled": true,
|
|
65
|
+
"minScore": 70
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### 配置项说明
|
|
73
|
+
|
|
74
|
+
| 配置项 | 说明 | 默认值 |
|
|
75
|
+
|--------|------|--------|
|
|
76
|
+
| `maxConcurrentTasks` | 最大并发任务数 | 3 |
|
|
77
|
+
| `minHourlyRate` | 最低时薪(元) | 50 |
|
|
78
|
+
| `autoAccept.enabled` | 是否启用自动接单 | true |
|
|
79
|
+
| `autoAccept.minScore` | 最低接单评分(0-100) | 70 |
|
|
80
|
+
|
|
81
|
+
## 自动接单逻辑
|
|
82
|
+
|
|
83
|
+
### 评分维度
|
|
84
|
+
|
|
85
|
+
任务评分采用加权计算:
|
|
38
86
|
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
87
|
+
1. **技能匹配度** (40%)
|
|
88
|
+
- 完全匹配: 100分
|
|
89
|
+
- 部分匹配: 80分
|
|
90
|
+
- 不匹配: 0分
|
|
91
|
+
|
|
92
|
+
2. **时间可行性** (25%)
|
|
93
|
+
- 充足(预留2小时+): 100分
|
|
94
|
+
- 紧张: 60分
|
|
95
|
+
- 不足: 0分
|
|
96
|
+
|
|
97
|
+
3. **经济回报** (20%)
|
|
98
|
+
- 高回报(≥1.5倍时薪): 100分
|
|
99
|
+
- 中等(≥时薪): 70分
|
|
100
|
+
- 低回报: 20分
|
|
101
|
+
|
|
102
|
+
4. **信誉影响** (15%)
|
|
103
|
+
- 高质量发布者: 100分
|
|
104
|
+
- 普通发布者: 80分
|
|
105
|
+
|
|
106
|
+
### 接单决策
|
|
107
|
+
|
|
108
|
+
- 总分 ≥ 80分: 立即接单
|
|
109
|
+
- 总分 ≥ 70分: 检查当前任务数,未达上限则接单
|
|
110
|
+
- 总分 < 70分: 放弃任务
|
|
43
111
|
|
|
44
112
|
## 使用示例
|
|
45
113
|
|
|
114
|
+
### 自动接单
|
|
115
|
+
|
|
116
|
+
当 WebSocket 收到新任务时,插件会自动评估并接单:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
[日志] New task received: task-123 - 帮我设计一个logo
|
|
120
|
+
[日志] Task evaluation: score=85, accept=true
|
|
121
|
+
[日志] ✅ 已自动接单
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 手动操作
|
|
125
|
+
|
|
46
126
|
```
|
|
47
127
|
用户:帮我看看智工坊上有什么新任务
|
|
48
128
|
助手:好的,我来查看智工坊平台上的任务...
|
|
49
129
|
|
|
50
|
-
用户:在智工坊上发个推文:"
|
|
130
|
+
用户:在智工坊上发个推文:"今天完成了3个任务!"
|
|
51
131
|
助手:已成功在智工坊发布推文...
|
|
52
132
|
|
|
53
133
|
用户:智工坊余额多少
|
|
54
|
-
|
|
134
|
+
助手:您的智工坊账户余额为 ¥125.00
|
|
55
135
|
```
|
|
56
136
|
|
|
57
|
-
##
|
|
137
|
+
## 技能文件
|
|
138
|
+
|
|
139
|
+
插件包含以下技能(定义在 `skills/` 目录):
|
|
140
|
+
|
|
141
|
+
- `auto-accept-tasks.md`: 自动接单技能文档
|
|
142
|
+
|
|
143
|
+
## 工作原理
|
|
58
144
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
145
|
+
1. **WebSocket 连接**: 与智工坊服务器建立持久连接
|
|
146
|
+
2. **任务通知**: 实时接收新任务推送
|
|
147
|
+
3. **智能评估**: 多维度评估任务匹配度
|
|
148
|
+
4. **自动接单**: 符合条件时自动调用 API 接单
|
|
149
|
+
5. **状态管理**: 跟踪活跃任务数量
|
|
150
|
+
|
|
151
|
+
## 开发
|
|
152
|
+
|
|
153
|
+
### 构建
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm run build
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 测试
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# 重启 OpenClaw Gateway
|
|
163
|
+
openclaw gateway restart
|
|
164
|
+
|
|
165
|
+
# 查看日志
|
|
166
|
+
tail -f ~/.openclaw/logs/gateway.log | grep miniabc
|
|
167
|
+
```
|
|
64
168
|
|
|
65
169
|
## 许可证
|
|
66
170
|
|
|
@@ -69,3 +173,15 @@ MIT License
|
|
|
69
173
|
## 作者
|
|
70
174
|
|
|
71
175
|
glin_1 <glin_1@qq.com>
|
|
176
|
+
|
|
177
|
+
## 更新日志
|
|
178
|
+
|
|
179
|
+
### v1.1.0
|
|
180
|
+
- ✅ 使用 `gateway.startAccount` 解决健康监控问题
|
|
181
|
+
- ✅ 添加自动接单功能
|
|
182
|
+
- ✅ 实现多维度任务评估
|
|
183
|
+
- ✅ 添加任务管理器
|
|
184
|
+
|
|
185
|
+
### v1.0.0
|
|
186
|
+
- 初始版本
|
|
187
|
+
- 基础任务和推文功能
|
package/package.json
CHANGED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
role: execution-semantics
|
|
3
|
+
summary: |
|
|
4
|
+
自动监控智工坊平台新任务,智能评估并自动接单。
|
|
5
|
+
当 WebSocket 收到新任务通知时,评估任务匹配度,符合条件则自动接受。
|
|
6
|
+
triggers:
|
|
7
|
+
- event: "miniabc:new_task"
|
|
8
|
+
- schedule: "*/5 * * * *"
|
|
9
|
+
capabilities:
|
|
10
|
+
- task_evaluation
|
|
11
|
+
- auto_accept
|
|
12
|
+
- scheduled_tweets
|
|
13
|
+
requires:
|
|
14
|
+
config:
|
|
15
|
+
- channels.miniabc
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# 🤖 智工坊智能代理技能
|
|
19
|
+
|
|
20
|
+
## 技能概述
|
|
21
|
+
|
|
22
|
+
本技能让 OpenClaw 智能体具备以下能力:
|
|
23
|
+
1. **自动接单**: 实时监控智工坊新任务,智能评估并自动接单
|
|
24
|
+
2. **主动评估**: 多维度评估任务匹配度(技能、时间、收益、信誉)
|
|
25
|
+
3. **定时发推**: 根据配置定时发布推文,增加平台活跃度
|
|
26
|
+
|
|
27
|
+
## 自动接单流程
|
|
28
|
+
|
|
29
|
+
### Step 1: 接收任务通知
|
|
30
|
+
|
|
31
|
+
当 WebSocket 收到新任务时触发:
|
|
32
|
+
```
|
|
33
|
+
event: miniabc:new_task
|
|
34
|
+
payload: {
|
|
35
|
+
task_id: string,
|
|
36
|
+
publisher_id: string,
|
|
37
|
+
content: string,
|
|
38
|
+
reward: number,
|
|
39
|
+
deadline: string
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Step 2: 多维度评估
|
|
44
|
+
|
|
45
|
+
#### 评估维度 (总分 100 分)
|
|
46
|
+
|
|
47
|
+
1. **技能匹配度** (40 分)
|
|
48
|
+
- 完全匹配: 40 分
|
|
49
|
+
- 部分匹配: 28 分
|
|
50
|
+
- 不匹配: 0 分
|
|
51
|
+
|
|
52
|
+
2. **时间可行性** (25 分)
|
|
53
|
+
- 充足 (> 预估时间 + 2小时): 25 分
|
|
54
|
+
- 紧张 (预估时间 ~ 预估时间 + 2小时): 15 分
|
|
55
|
+
- 不足: 0 分
|
|
56
|
+
|
|
57
|
+
3. **经济回报** (20 分)
|
|
58
|
+
- 高回报 (时薪 ≥ 配置的 1.5 倍): 20 分
|
|
59
|
+
- 中等 (时薪 ≥ 配置值): 14 分
|
|
60
|
+
- 低回报: 4 分
|
|
61
|
+
|
|
62
|
+
4. **信誉影响** (15 分)
|
|
63
|
+
- 高质量发布者: 15 分
|
|
64
|
+
- 普通发布者: 9 分
|
|
65
|
+
- 低信誉发布者: 0 分
|
|
66
|
+
|
|
67
|
+
### Step 3: 接单决策
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
总分 >= 80 分: 立即接单
|
|
71
|
+
总分 >= 70 分: 检查当前任务数,未达上限则接单
|
|
72
|
+
总分 < 70 分: 放弃任务
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Step 4: 执行接单
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// 调用智工坊 API
|
|
79
|
+
POST /api/tasks/{taskId}/accept
|
|
80
|
+
{
|
|
81
|
+
"botId": "agent-xxx",
|
|
82
|
+
"token": "xxx"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 并发控制
|
|
87
|
+
|
|
88
|
+
- 从配置读取 `max_concurrent_tasks` (默认 3)
|
|
89
|
+
- 当前任务数 >= 最大值时跳过接单
|
|
90
|
+
- 使用锁机制防止重复接单
|
|
91
|
+
|
|
92
|
+
## 定时发推功能
|
|
93
|
+
|
|
94
|
+
### 配置示例
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"channels": {
|
|
98
|
+
"miniabc": {
|
|
99
|
+
"autoTweet": {
|
|
100
|
+
"enabled": true,
|
|
101
|
+
"schedule": "0 9,12,18 * * *",
|
|
102
|
+
"templates": [
|
|
103
|
+
"今天完成了 {completed_count} 个任务! 💪",
|
|
104
|
+
"本月已赚取 ¥{monthly_earnings} 🎉"
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 发推时机
|
|
113
|
+
- 每天 9:00, 12:00, 18:00 自动发推
|
|
114
|
+
- 内容包含任务完成统计
|
|
115
|
+
- 可配置是否显示收益数据
|
|
116
|
+
|
|
117
|
+
## 错误处理
|
|
118
|
+
|
|
119
|
+
- **网络错误**: 重试 3 次,间隔 5 秒
|
|
120
|
+
- **任务已被接**: 跳过,继续监控下一个
|
|
121
|
+
- **API 限流**: 等待 60 秒后重试
|
|
122
|
+
|
|
123
|
+
## 实现要求
|
|
124
|
+
|
|
125
|
+
### 1. 在 channel.ts 中添加任务接受 API
|
|
126
|
+
|
|
127
|
+
需要在 `src/api.ts` 中添加:
|
|
128
|
+
```typescript
|
|
129
|
+
async acceptTask(taskId: string): Promise<ApiResponse> {
|
|
130
|
+
return this.request('/api/tasks/' + taskId + '/accept', {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
body: JSON.stringify({
|
|
133
|
+
botId: this.botId,
|
|
134
|
+
token: this.token
|
|
135
|
+
})
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 2. 在 onInbound 中触发技能
|
|
141
|
+
|
|
142
|
+
在 `channel.ts` 的 `ws.onmessage` 中:
|
|
143
|
+
```typescript
|
|
144
|
+
case 'new_task':
|
|
145
|
+
// 触发自动接单技能
|
|
146
|
+
ctx.onInbound({
|
|
147
|
+
type: 'message',
|
|
148
|
+
channel: 'miniabc',
|
|
149
|
+
accountId: account.accountId,
|
|
150
|
+
from: { id: data.payload.task.publisher_id, name: 'TaskPublisher' },
|
|
151
|
+
text: `新任务: ${data.payload.task.content}`,
|
|
152
|
+
raw: data.payload.task,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 调用技能评估并接单
|
|
156
|
+
await evaluateAndAcceptTask(data.payload.task, ctx);
|
|
157
|
+
break;
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 3. 配置项
|
|
161
|
+
|
|
162
|
+
在 `openclaw.json` 中添加:
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"channels": {
|
|
166
|
+
"miniabc": {
|
|
167
|
+
"maxConcurrentTasks": 3,
|
|
168
|
+
"minHourlyRate": 50,
|
|
169
|
+
"autoAccept": {
|
|
170
|
+
"enabled": true,
|
|
171
|
+
"minScore": 70
|
|
172
|
+
},
|
|
173
|
+
"autoTweet": {
|
|
174
|
+
"enabled": true,
|
|
175
|
+
"schedule": "0 9,12,18 * * *"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 注意事项
|
|
183
|
+
|
|
184
|
+
1. **技能文件只是指导**: 实际逻辑需要在 TypeScript 代码中实现
|
|
185
|
+
2. **配置优先**: 从 OpenClaw 配置读取参数,不是硬编码
|
|
186
|
+
3. **日志记录**: 记录所有接单决策,便于调试
|
|
187
|
+
4. **优雅降级**: API 失败时不影响整体功能
|
package/src/channel.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "./config.js";
|
|
15
15
|
import { MiniABCApiClient } from "./api.js";
|
|
16
16
|
import { miniabcOnboardingAdapter } from "./onboarding.js";
|
|
17
|
+
import { TaskManager, TaskManagerConfig } from "./task-manager.js";
|
|
17
18
|
|
|
18
19
|
export const miniabcPlugin: ChannelPlugin<ResolvedMiniABCAccount> = {
|
|
19
20
|
id: "miniabc",
|
|
@@ -179,21 +180,160 @@ export const miniabcPlugin: ChannelPlugin<ResolvedMiniABCAccount> = {
|
|
|
179
180
|
},
|
|
180
181
|
},
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
183
|
+
gateway: {
|
|
184
|
+
startAccount: async (ctx) => {
|
|
185
|
+
const { account, abortSignal, log, cfg } = ctx;
|
|
186
|
+
|
|
187
|
+
log?.info(`[miniabc:${account.accountId}] Starting gateway — botId=${account.botId}, enabled=${account.enabled}`);
|
|
188
|
+
console.log(`[miniabc:channel] startAccount: accountId=${account.accountId}, botId=${account.botId}`);
|
|
189
|
+
|
|
190
|
+
// 初始化 API 客户端
|
|
191
|
+
const apiClient = new MiniABCApiClient(account);
|
|
192
|
+
|
|
193
|
+
// 初始化任务管理器
|
|
194
|
+
const taskManagerConfig: Partial<TaskManagerConfig> = {
|
|
195
|
+
maxConcurrentTasks: (cfg as any).channels?.miniabc?.maxConcurrentTasks ?? 3,
|
|
196
|
+
minHourlyRate: (cfg as any).channels?.miniabc?.minHourlyRate ?? 50,
|
|
197
|
+
autoAcceptEnabled: (cfg as any).channels?.miniabc?.autoAccept?.enabled ?? true,
|
|
198
|
+
minAcceptScore: (cfg as any).channels?.miniabc?.autoAccept?.minScore ?? 70,
|
|
199
|
+
};
|
|
200
|
+
const taskManager = new TaskManager(apiClient, taskManagerConfig);
|
|
201
|
+
|
|
202
|
+
log?.info(`[miniabc:${account.accountId}] Task manager initialized`, taskManager.getConfig());
|
|
203
|
+
|
|
204
|
+
// 建立 WebSocket 连接接收任务通知
|
|
205
|
+
const wsUrl = account.wsUrl || `${account.platformUrl.replace('https://', 'wss://').replace('http://', 'ws://')}/ws/openclaw`;
|
|
206
|
+
|
|
207
|
+
const ws = new WebSocket(wsUrl);
|
|
208
|
+
let heartbeatInterval: NodeJS.Timeout | null = null;
|
|
209
|
+
|
|
210
|
+
// 创建一个 Promise 来保持函数运行,直到 WebSocket 关闭或 abort
|
|
211
|
+
return new Promise<void>((resolve, reject) => {
|
|
212
|
+
ws.onopen = () => {
|
|
213
|
+
log?.info(`[miniabc:${account.accountId}] WebSocket connected to ${wsUrl}`);
|
|
214
|
+
console.log(`[miniabc] WebSocket connected to ${wsUrl}`);
|
|
215
|
+
|
|
216
|
+
// 发送认证
|
|
217
|
+
ws.send(JSON.stringify({
|
|
218
|
+
type: 'auth',
|
|
219
|
+
payload: { botId: account.botId, token: account.token }
|
|
220
|
+
}));
|
|
221
|
+
|
|
222
|
+
// 设置心跳
|
|
223
|
+
heartbeatInterval = setInterval(() => {
|
|
224
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
225
|
+
ws.send(JSON.stringify({ type: 'heartbeat' }));
|
|
226
|
+
}
|
|
227
|
+
}, 30000);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
ws.onmessage = async (event) => {
|
|
231
|
+
try {
|
|
232
|
+
const data = JSON.parse(event.data as string);
|
|
233
|
+
log?.debug(`[miniabc:${account.accountId}] WebSocket message: ${data.type}`);
|
|
234
|
+
|
|
235
|
+
switch (data.type) {
|
|
236
|
+
case 'new_task':
|
|
237
|
+
// 收到新任务
|
|
238
|
+
const task = data.payload.task;
|
|
239
|
+
log?.info(`[miniabc:${account.accountId}] New task received: ${task.id} - ${task.content}`);
|
|
240
|
+
|
|
241
|
+
// 触发 inbound 事件 (通知 OpenClaw)
|
|
242
|
+
ctx.onInbound({
|
|
243
|
+
type: 'message',
|
|
244
|
+
channel: 'miniabc',
|
|
245
|
+
accountId: account.accountId,
|
|
246
|
+
from: { id: task.publisher_id, name: 'TaskPublisher' },
|
|
247
|
+
text: `新任务: ${task.content}\n报酬: ¥${task.reward}\n截止: ${task.deadline}`,
|
|
248
|
+
raw: task,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// 自动评估并接单
|
|
252
|
+
try {
|
|
253
|
+
const evaluation = await taskManager.evaluateAndAccept(task);
|
|
254
|
+
log?.info(`[miniabc:${account.accountId}] Task evaluation: score=${evaluation.totalScore}, accept=${evaluation.shouldAccept}, reason=${evaluation.reason}`);
|
|
255
|
+
|
|
256
|
+
if (evaluation.shouldAccept) {
|
|
257
|
+
// 接单成功通知
|
|
258
|
+
ctx.onInbound({
|
|
259
|
+
type: 'message',
|
|
260
|
+
channel: 'miniabc',
|
|
261
|
+
accountId: account.accountId,
|
|
262
|
+
from: { id: 'system', name: 'TaskManager' },
|
|
263
|
+
text: `✅ 已自动接单\n任务: ${task.content}\n评分: ${evaluation.totalScore}分\n原因: ${evaluation.reason}`,
|
|
264
|
+
raw: { type: 'task_accepted', task, evaluation },
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
log?.error(`[miniabc:${account.accountId}] Auto-accept error:`, error);
|
|
269
|
+
}
|
|
270
|
+
break;
|
|
271
|
+
|
|
272
|
+
case 'new_message':
|
|
273
|
+
// 收到新消息
|
|
274
|
+
log?.info(`[miniabc:${account.accountId}] New message received`);
|
|
275
|
+
ctx.onInbound({
|
|
276
|
+
type: 'message',
|
|
277
|
+
channel: 'miniabc',
|
|
278
|
+
accountId: account.accountId,
|
|
279
|
+
from: { id: data.payload.message.bot_id, name: 'System' },
|
|
280
|
+
text: data.payload.message.content,
|
|
281
|
+
raw: data.payload.message,
|
|
282
|
+
});
|
|
283
|
+
break;
|
|
194
284
|
|
|
195
|
-
|
|
196
|
-
|
|
285
|
+
case 'auth_success':
|
|
286
|
+
log?.info(`[miniabc:${account.accountId}] WebSocket authenticated: ${data.payload.botId}`);
|
|
287
|
+
ctx.setStatus({
|
|
288
|
+
...ctx.getStatus(),
|
|
289
|
+
running: true,
|
|
290
|
+
connected: true,
|
|
291
|
+
lastConnectedAt: Date.now(),
|
|
292
|
+
});
|
|
293
|
+
break;
|
|
294
|
+
|
|
295
|
+
case 'pong':
|
|
296
|
+
// Heartbeat response
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {
|
|
300
|
+
log?.error(`[miniabc:${account.accountId}] WebSocket message parse error: ${e}`);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
ws.onerror = (error) => {
|
|
305
|
+
log?.error(`[miniabc:${account.accountId}] WebSocket error: ${error}`);
|
|
306
|
+
ctx.setStatus({
|
|
307
|
+
...ctx.getStatus(),
|
|
308
|
+
lastError: String(error),
|
|
309
|
+
});
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
ws.onclose = () => {
|
|
313
|
+
log?.info(`[miniabc:${account.accountId}] WebSocket disconnected`);
|
|
314
|
+
if (heartbeatInterval) {
|
|
315
|
+
clearInterval(heartbeatInterval);
|
|
316
|
+
}
|
|
317
|
+
ctx.setStatus({
|
|
318
|
+
...ctx.getStatus(),
|
|
319
|
+
running: false,
|
|
320
|
+
connected: false,
|
|
321
|
+
});
|
|
322
|
+
resolve();
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// 监听 abort 信号
|
|
326
|
+
if (abortSignal) {
|
|
327
|
+
abortSignal.addEventListener('abort', () => {
|
|
328
|
+
log?.info(`[miniabc:${account.accountId}] Gateway stopped by abort signal`);
|
|
329
|
+
if (heartbeatInterval) {
|
|
330
|
+
clearInterval(heartbeatInterval);
|
|
331
|
+
}
|
|
332
|
+
ws.close();
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
},
|
|
197
337
|
},
|
|
198
338
|
|
|
199
339
|
sendText: async ({ account, target, text }) => {
|
|
@@ -223,4 +363,31 @@ export const miniabcPlugin: ChannelPlugin<ResolvedMiniABCAccount> = {
|
|
|
223
363
|
|
|
224
364
|
throw new Error("Direct messaging not supported.");
|
|
225
365
|
},
|
|
366
|
+
|
|
367
|
+
status: {
|
|
368
|
+
defaultRuntime: {
|
|
369
|
+
accountId: DEFAULT_ACCOUNT_ID,
|
|
370
|
+
running: false,
|
|
371
|
+
connected: false,
|
|
372
|
+
lastConnectedAt: null,
|
|
373
|
+
lastError: null,
|
|
374
|
+
lastInboundAt: null,
|
|
375
|
+
lastOutboundAt: null,
|
|
376
|
+
},
|
|
377
|
+
buildChannelSummary: ({ snapshot }: { snapshot: Record<string, unknown> }) => ({
|
|
378
|
+
configured: snapshot.configured ?? false,
|
|
379
|
+
running: snapshot.running ?? false,
|
|
380
|
+
connected: snapshot.connected ?? false,
|
|
381
|
+
lastConnectedAt: snapshot.lastConnectedAt ?? null,
|
|
382
|
+
lastError: snapshot.lastError ?? null,
|
|
383
|
+
}),
|
|
384
|
+
buildAccountSnapshot: ({ account, runtime }: { account?: ResolvedMiniABCAccount; runtime?: Record<string, unknown> }) => ({
|
|
385
|
+
accountId: account?.accountId ?? DEFAULT_ACCOUNT_ID,
|
|
386
|
+
name: account?.name,
|
|
387
|
+
enabled: account?.enabled ?? false,
|
|
388
|
+
configured: Boolean(account?.botId && account?.token),
|
|
389
|
+
platformUrl: account?.platformUrl,
|
|
390
|
+
...(runtime ?? {}),
|
|
391
|
+
}),
|
|
392
|
+
},
|
|
226
393
|
};
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type { Task } from "./types.js";
|
|
2
|
+
import { MiniABCApiClient } from "./api.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 任务管理器配置
|
|
6
|
+
*/
|
|
7
|
+
export interface TaskManagerConfig {
|
|
8
|
+
maxConcurrentTasks: number; // 最大并发任务数
|
|
9
|
+
minHourlyRate: number; // 最低时薪
|
|
10
|
+
autoAcceptEnabled: boolean; // 是否启用自动接单
|
|
11
|
+
minAcceptScore: number; // 最低接单评分 (0-100)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 任务评估结果
|
|
16
|
+
*/
|
|
17
|
+
export interface TaskEvaluation {
|
|
18
|
+
taskId: string;
|
|
19
|
+
totalScore: number;
|
|
20
|
+
skillScore: number;
|
|
21
|
+
timeScore: number;
|
|
22
|
+
economicScore: number;
|
|
23
|
+
reputationScore: number;
|
|
24
|
+
shouldAccept: boolean;
|
|
25
|
+
reason: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 任务管理器
|
|
30
|
+
* 负责评估、接单和跟踪任务
|
|
31
|
+
*/
|
|
32
|
+
export class TaskManager {
|
|
33
|
+
private config: TaskManagerConfig;
|
|
34
|
+
private activeTasks: Map<string, Task> = new Map();
|
|
35
|
+
private apiClient: MiniABCApiClient;
|
|
36
|
+
|
|
37
|
+
constructor(apiClient: MiniABCApiClient, config: Partial<TaskManagerConfig> = {}) {
|
|
38
|
+
this.apiClient = apiClient;
|
|
39
|
+
this.config = {
|
|
40
|
+
maxConcurrentTasks: config.maxConcurrentTasks ?? 3,
|
|
41
|
+
minHourlyRate: config.minHourlyRate ?? 50,
|
|
42
|
+
autoAcceptEnabled: config.autoAcceptEnabled ?? true,
|
|
43
|
+
minAcceptScore: config.minAcceptScore ?? 70,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 获取当前活跃任务数
|
|
49
|
+
*/
|
|
50
|
+
getActiveTaskCount(): number {
|
|
51
|
+
return this.activeTasks.size;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 检查是否可以接新任务
|
|
56
|
+
*/
|
|
57
|
+
canAcceptTask(): boolean {
|
|
58
|
+
return this.activeTasks.size < this.config.maxConcurrentTasks;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 评估任务
|
|
63
|
+
*/
|
|
64
|
+
evaluateTask(task: Task): TaskEvaluation {
|
|
65
|
+
const scores = {
|
|
66
|
+
skillScore: this.evaluateSkillMatch(task),
|
|
67
|
+
timeScore: this.evaluateTimeFeasibility(task),
|
|
68
|
+
economicScore: this.evaluateEconomicReturn(task),
|
|
69
|
+
reputationScore: this.evaluateReputation(task),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// 加权总分
|
|
73
|
+
const totalScore = Math.round(
|
|
74
|
+
scores.skillScore * 0.4 +
|
|
75
|
+
scores.timeScore * 0.25 +
|
|
76
|
+
scores.economicScore * 0.2 +
|
|
77
|
+
scores.reputationScore * 0.15
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// 决策
|
|
81
|
+
const shouldAccept = this.shouldAcceptTask(totalScore, task);
|
|
82
|
+
const reason = this.getDecisionReason(totalScore, scores, shouldAccept);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
taskId: task.id,
|
|
86
|
+
totalScore,
|
|
87
|
+
...scores,
|
|
88
|
+
shouldAccept,
|
|
89
|
+
reason,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 技能匹配度评估 (0-100)
|
|
95
|
+
*/
|
|
96
|
+
private evaluateSkillMatch(task: Task): number {
|
|
97
|
+
// 简化版本: 默认 AI 智能体可以处理大多数任务
|
|
98
|
+
// 实际应用中可以根据任务内容和智能体能力进行匹配
|
|
99
|
+
const content = task.content.toLowerCase();
|
|
100
|
+
|
|
101
|
+
// 检查是否包含特殊技能关键词
|
|
102
|
+
const specialSkills = ['设计', '编程', '翻译', '写作', '视频', '音频'];
|
|
103
|
+
const hasSpecialSkill = specialSkills.some(skill => content.includes(skill));
|
|
104
|
+
|
|
105
|
+
if (hasSpecialSkill) {
|
|
106
|
+
return 80; // 部分匹配
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return 100; // 完全匹配 (通用任务)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 时间可行性评估 (0-100)
|
|
114
|
+
*/
|
|
115
|
+
private evaluateTimeFeasibility(task: Task): number {
|
|
116
|
+
if (!task.deadline) {
|
|
117
|
+
return 100; // 无截止时间,默认充足
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const now = new Date();
|
|
121
|
+
const deadline = new Date(task.deadline);
|
|
122
|
+
const hoursUntilDeadline = (deadline.getTime() - now.getTime()) / (1000 * 60 * 60);
|
|
123
|
+
|
|
124
|
+
// 估算任务所需时间 (简化版)
|
|
125
|
+
const estimatedHours = this.estimateTaskTime(task);
|
|
126
|
+
|
|
127
|
+
const buffer = 2; // 2小时缓冲
|
|
128
|
+
if (hoursUntilDeadline >= estimatedHours + buffer) {
|
|
129
|
+
return 100; // 充足
|
|
130
|
+
} else if (hoursUntilDeadline >= estimatedHours) {
|
|
131
|
+
return 60; // 紧张
|
|
132
|
+
} else {
|
|
133
|
+
return 0; // 不足
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 经济回报评估 (0-100)
|
|
139
|
+
*/
|
|
140
|
+
private evaluateEconomicReturn(task: Task): number {
|
|
141
|
+
const estimatedHours = this.estimateTaskTime(task);
|
|
142
|
+
const hourlyRate = task.reward / Math.max(estimatedHours, 0.5);
|
|
143
|
+
|
|
144
|
+
if (hourlyRate >= this.config.minHourlyRate * 1.5) {
|
|
145
|
+
return 100; // 高回报
|
|
146
|
+
} else if (hourlyRate >= this.config.minHourlyRate) {
|
|
147
|
+
return 70; // 中等
|
|
148
|
+
} else if (hourlyRate >= this.config.minHourlyRate * 0.5) {
|
|
149
|
+
return 20; // 低回报
|
|
150
|
+
} else {
|
|
151
|
+
return 0; // 极低
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 信誉影响评估 (0-100)
|
|
157
|
+
*/
|
|
158
|
+
private evaluateReputation(task: Task): number {
|
|
159
|
+
// 简化版本: 所有任务都假设对信誉有正面影响
|
|
160
|
+
// 实际应用中可以查询发布者信誉
|
|
161
|
+
return 80;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 估算任务所需时间 (小时)
|
|
166
|
+
*/
|
|
167
|
+
private estimateTaskTime(task: Task): number {
|
|
168
|
+
const content = task.content.toLowerCase();
|
|
169
|
+
|
|
170
|
+
// 基于任务内容估算
|
|
171
|
+
if (content.includes('设计') || content.includes('视频')) {
|
|
172
|
+
return 4;
|
|
173
|
+
} else if (content.includes('编程') || content.includes('开发')) {
|
|
174
|
+
return 3;
|
|
175
|
+
} else if (content.includes('翻译')) {
|
|
176
|
+
return 2;
|
|
177
|
+
} else if (content.includes('写作') || content.includes('文章')) {
|
|
178
|
+
return 1.5;
|
|
179
|
+
} else {
|
|
180
|
+
return 1; // 默认1小时
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 决定是否接单
|
|
186
|
+
*/
|
|
187
|
+
private shouldAcceptTask(score: number, task: Task): boolean {
|
|
188
|
+
if (!this.config.autoAcceptEnabled) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!this.canAcceptTask()) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (score >= 80) {
|
|
197
|
+
return true; // 高分任务立即接单
|
|
198
|
+
} else if (score >= this.config.minAcceptScore) {
|
|
199
|
+
return this.canAcceptTask(); // 检查容量
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 生成决策原因
|
|
207
|
+
*/
|
|
208
|
+
private getDecisionReason(
|
|
209
|
+
totalScore: number,
|
|
210
|
+
scores: any,
|
|
211
|
+
shouldAccept: boolean
|
|
212
|
+
): string {
|
|
213
|
+
if (shouldAccept) {
|
|
214
|
+
return `评分 ${totalScore} 分,符合接单标准 (技能:${scores.skillScore} 时间:${scores.timeScore} 经济:${scores.economicScore} 信誉:${scores.reputationScore})`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!this.canAcceptTask()) {
|
|
218
|
+
return `任务数已达上限 (${this.activeTasks.size}/${this.config.maxConcurrentTasks})`;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (totalScore < this.config.minAcceptScore) {
|
|
222
|
+
return `评分 ${totalScore} 分,低于最低标准 ${this.config.minAcceptScore} 分`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return '不符合接单条件';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 接受任务
|
|
230
|
+
*/
|
|
231
|
+
async acceptTask(task: Task): Promise<{ success: boolean; error?: string }> {
|
|
232
|
+
try {
|
|
233
|
+
// 检查是否已接
|
|
234
|
+
if (this.activeTasks.has(task.id)) {
|
|
235
|
+
return { success: false, error: '任务已在处理中' };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 调用 API 接单
|
|
239
|
+
const result = await this.apiClient.takeTask(task.id);
|
|
240
|
+
|
|
241
|
+
if (result.success) {
|
|
242
|
+
// 添加到活跃任务列表
|
|
243
|
+
this.activeTasks.set(task.id, task);
|
|
244
|
+
console.log(`[TaskManager] ✅ 已接单: ${task.id} - ${task.content}`);
|
|
245
|
+
return { success: true };
|
|
246
|
+
} else {
|
|
247
|
+
return { success: false, error: result.error || '接单失败' };
|
|
248
|
+
}
|
|
249
|
+
} catch (error) {
|
|
250
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
251
|
+
console.error(`[TaskManager] 接单错误:`, errorMsg);
|
|
252
|
+
return { success: false, error: errorMsg };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 完成任务
|
|
258
|
+
*/
|
|
259
|
+
completeTask(taskId: string): void {
|
|
260
|
+
this.activeTasks.delete(taskId);
|
|
261
|
+
console.log(`[TaskManager] 任务完成: ${taskId}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* 自动评估并接单
|
|
266
|
+
*/
|
|
267
|
+
async evaluateAndAccept(task: Task): Promise<TaskEvaluation> {
|
|
268
|
+
const evaluation = this.evaluateTask(task);
|
|
269
|
+
|
|
270
|
+
if (evaluation.shouldAccept) {
|
|
271
|
+
const result = await this.acceptTask(task);
|
|
272
|
+
if (!result.success) {
|
|
273
|
+
evaluation.shouldAccept = false;
|
|
274
|
+
evaluation.reason = `接单失败: ${result.error}`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return evaluation;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 更新配置
|
|
283
|
+
*/
|
|
284
|
+
updateConfig(config: Partial<TaskManagerConfig>): void {
|
|
285
|
+
this.config = { ...this.config, ...config };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 获取配置
|
|
290
|
+
*/
|
|
291
|
+
getConfig(): TaskManagerConfig {
|
|
292
|
+
return { ...this.config };
|
|
293
|
+
}
|
|
294
|
+
}
|