@myassis/gateway 1.0.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 (65) hide show
  1. package/README.md +194 -0
  2. package/dist/.env +6 -0
  3. package/dist/api/index.js +182 -0
  4. package/dist/config/index.js +41 -0
  5. package/dist/index.js +183 -0
  6. package/dist/middleware/auth.js +53 -0
  7. package/dist/middleware/errorHandler.js +20 -0
  8. package/dist/routes/agent.js +513 -0
  9. package/dist/routes/auth.js +172 -0
  10. package/dist/routes/chat.js +45 -0
  11. package/dist/routes/config.js +21 -0
  12. package/dist/routes/models.js +123 -0
  13. package/dist/routes/service.js +240 -0
  14. package/dist/routes/settings.js +101 -0
  15. package/dist/routes/skillHub.js +126 -0
  16. package/dist/routes/skills.js +159 -0
  17. package/dist/routes/tasks.js +149 -0
  18. package/dist/routes/upload.js +129 -0
  19. package/dist/routes/version.js +66 -0
  20. package/dist/services/HMSPushService.js +24 -0
  21. package/dist/services/LocalTaskService.js +223 -0
  22. package/dist/services/NotificationService.js +242 -0
  23. package/dist/services/ServiceManager.js +348 -0
  24. package/dist/services/TaskSchedulerService.js +195 -0
  25. package/dist/services/TaskService.js +240 -0
  26. package/dist/services/WebSocketService.js +236 -0
  27. package/dist/services/agent/Agent.js +120 -0
  28. package/dist/services/agent/AgentManager.js +265 -0
  29. package/dist/services/agent/AgentStore.js +73 -0
  30. package/dist/services/dataService.js +293 -0
  31. package/dist/services/index.js +15 -0
  32. package/dist/services/llm/LLMClient.js +724 -0
  33. package/dist/services/memory/MemoryManager.js +117 -0
  34. package/dist/services/model/ModelCapabilities.js +141 -0
  35. package/dist/services/model/index.js +4 -0
  36. package/dist/services/models.js +16 -0
  37. package/dist/services/session/MigrationManager.js +176 -0
  38. package/dist/services/session/Session.js +733 -0
  39. package/dist/services/session/SessionManager.js +255 -0
  40. package/dist/services/session/SessionStore.js +186 -0
  41. package/dist/services/session/index.js +3 -0
  42. package/dist/services/skills.js +34 -0
  43. package/dist/services/systemPrompt.js +150 -0
  44. package/dist/services/task/PushTokenStore.js +124 -0
  45. package/dist/services/task/TaskStore.js +143 -0
  46. package/dist/services/tools/calculator.js +27 -0
  47. package/dist/services/tools/edit.js +318 -0
  48. package/dist/services/tools/exec.js +119 -0
  49. package/dist/services/tools/fetch.js +155 -0
  50. package/dist/services/tools/file.js +315 -0
  51. package/dist/services/tools/index.js +48 -0
  52. package/dist/services/tools/keyboard.js +145 -0
  53. package/dist/services/tools/model.js +86 -0
  54. package/dist/services/tools/mouse.js +55 -0
  55. package/dist/services/tools/screenshot.js +19 -0
  56. package/dist/services/tools/search.js +53 -0
  57. package/dist/services/tools/skill.js +108 -0
  58. package/dist/services/tools/task.js +110 -0
  59. package/dist/services/tools/types.js +1 -0
  60. package/dist/services/tools/webFetch.js +34 -0
  61. package/dist/stores/authStore.js +178 -0
  62. package/dist/stores/index.js +6 -0
  63. package/dist/stores/memoryStore.js +191 -0
  64. package/dist/stores/persistStore.js +317 -0
  65. package/package.json +94 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Gateway 本地任务服务
3
+ * 提供本地任务的 CRUD 操作
4
+ *
5
+ * 使用 SQLite 数据库存储任务
6
+ */
7
+ import { v4 as uuidv4 } from 'uuid';
8
+ import { TaskStore } from './task/TaskStore';
9
+ import { getLogger, sharedTaskService, formatUTCForLog } from '@pocketclaw/shared';
10
+ const logger = getLogger('LocalTaskService');
11
+ class LocalTaskService {
12
+ store;
13
+ constructor() {
14
+ this.store = new TaskStore();
15
+ }
16
+ /**
17
+ * 创建本地任务
18
+ */
19
+ createTask(data) {
20
+ const now = Date.now();
21
+ const task = {
22
+ id: uuidv4(),
23
+ userId: data.userId,
24
+ title: data.title,
25
+ description: data.description,
26
+ taskType: data.taskType,
27
+ recurrenceRule: data.recurrenceRule,
28
+ intervalValue: data.intervalValue,
29
+ intervalUnit: data.intervalUnit,
30
+ scheduledAt: toTimestamp(data.scheduledAt) || now,
31
+ startTime: toTimestamp(data.startTime),
32
+ endTime: toTimestamp(data.endTime),
33
+ status: 'pending',
34
+ platformApply: data.platformApply,
35
+ pushToken: data.pushToken,
36
+ createdAt: now,
37
+ updatedAt: now,
38
+ };
39
+ this.store.insert(task);
40
+ logger.info('[LocalTaskService] 创建本地任务', {
41
+ taskId: task.id,
42
+ title: task.title,
43
+ platformApply: task.platformApply,
44
+ });
45
+ return task;
46
+ }
47
+ /**
48
+ * 获取本地任务
49
+ */
50
+ getTask(taskId) {
51
+ return this.store.findById(taskId);
52
+ }
53
+ /**
54
+ * 获取用户的所有本地任务
55
+ */
56
+ getUserTasks(userId) {
57
+ return this.store.findByUserId(userId);
58
+ }
59
+ /**
60
+ * 获取所有本地任务
61
+ */
62
+ getAllTasks() {
63
+ return this.store.findAll();
64
+ }
65
+ /**
66
+ * 更新本地任务
67
+ */
68
+ updateTask(taskId, data) {
69
+ const task = this.store.findById(taskId);
70
+ if (!task) {
71
+ logger.warn('[LocalTaskService] 任务不存在', { taskId });
72
+ return null;
73
+ }
74
+ // 更新字段
75
+ if (data.title !== undefined)
76
+ task.title = data.title;
77
+ if (data.description !== undefined)
78
+ task.description = data.description;
79
+ if (data.taskType !== undefined)
80
+ task.taskType = data.taskType;
81
+ if (data.recurrenceRule !== undefined)
82
+ task.recurrenceRule = data.recurrenceRule;
83
+ if (data.intervalValue !== undefined)
84
+ task.intervalValue = data.intervalValue;
85
+ if (data.intervalUnit !== undefined)
86
+ task.intervalUnit = data.intervalUnit;
87
+ if (data.scheduledAt !== undefined)
88
+ task.scheduledAt = toTimestamp(data.scheduledAt);
89
+ if (data.startTime !== undefined)
90
+ task.startTime = toTimestamp(data.startTime);
91
+ if (data.endTime !== undefined)
92
+ task.endTime = toTimestamp(data.endTime);
93
+ if (data.status !== undefined)
94
+ task.status = data.status;
95
+ if (data.platformApply !== undefined)
96
+ task.platformApply = data.platformApply;
97
+ if (data.pushToken !== undefined)
98
+ task.pushToken = data.pushToken;
99
+ task.updatedAt = Date.now();
100
+ this.store.update(task);
101
+ logger.info('[LocalTaskService] 更新本地任务', {
102
+ taskId: task.id,
103
+ updates: Object.keys(data),
104
+ });
105
+ return task;
106
+ }
107
+ /**
108
+ * 更新任务状态
109
+ */
110
+ async updateTaskStatus(taskId, status) {
111
+ const task = this.getTask(taskId);
112
+ if (!task) {
113
+ return;
114
+ }
115
+ if (['expired', 'completed', 'error'].includes(status)) {
116
+ await this.complete(task.id);
117
+ }
118
+ else {
119
+ this.store.updateStatus(taskId, status);
120
+ }
121
+ logger.info('[LocalTaskService] 更新任务状态', { taskId, status });
122
+ }
123
+ //完成任务
124
+ async complete(taskId) {
125
+ try {
126
+ const task = this.getTask(taskId);
127
+ if (!task) {
128
+ return { success: false, error: 'Task not found' };
129
+ }
130
+ const now = Date.now();
131
+ // 如果是重复任务,计算下次执行时间
132
+ if (task.taskType === 'recurring') {
133
+ const result = await sharedTaskService.calculateNextOccurrence(now, task.recurrenceRule, task.endTime, task.intervalValue, task.intervalUnit);
134
+ if (result) {
135
+ const newTask = {
136
+ ...task,
137
+ id: task.id,
138
+ scheduledAt: result,
139
+ status: 'pending',
140
+ createdAt: Date.now(),
141
+ updatedAt: Date.now(),
142
+ };
143
+ this.store.update(newTask);
144
+ logger.info(`Created recurring task instance`, {
145
+ originalId: task.id,
146
+ newId: newTask.id,
147
+ nextScheduledAt: formatUTCForLog(result),
148
+ });
149
+ }
150
+ else {
151
+ this.store.updateStatus(task.id, 'completed');
152
+ }
153
+ }
154
+ // 一次性任务标记为完成
155
+ if (task.taskType === 'one_time') {
156
+ this.store.updateStatus(task.id, 'completed');
157
+ logger.info(`Task completed`, { taskId: task.id });
158
+ }
159
+ return { success: true };
160
+ }
161
+ catch (error) {
162
+ logger.error('[tasksService] Complete failed', { taskId, error });
163
+ return { success: false, error: error.message };
164
+ }
165
+ }
166
+ /**
167
+ * 删除本地任务
168
+ */
169
+ deleteTask(taskId) {
170
+ const task = this.store.findById(taskId);
171
+ if (!task) {
172
+ logger.warn('[LocalTaskService] 任务不存在', { taskId });
173
+ return false;
174
+ }
175
+ this.store.delete(taskId);
176
+ logger.info('[LocalTaskService] 删除本地任务', { taskId });
177
+ return true;
178
+ }
179
+ /**
180
+ * 批量获取任务(用于列表)
181
+ */
182
+ listTasks(params) {
183
+ let tasks;
184
+ if (params?.userId && params?.status) {
185
+ tasks = this.store.findByUserAndStatus(params.userId, params.status);
186
+ }
187
+ else if (params?.userId) {
188
+ tasks = this.store.findByUserId(params.userId);
189
+ }
190
+ else if (params?.status) {
191
+ tasks = this.store.findByStatus(params.status);
192
+ }
193
+ else {
194
+ tasks = this.store.findAll();
195
+ }
196
+ const total = tasks.length;
197
+ // 排序(按执行时间倒序)
198
+ tasks.sort((a, b) => b.scheduledAt - a.scheduledAt);
199
+ // 分页
200
+ if (params?.page && params?.pageSize) {
201
+ const start = (params.page - 1) * params.pageSize;
202
+ tasks = tasks.slice(start, start + params.pageSize);
203
+ }
204
+ return { tasks, total };
205
+ }
206
+ /**
207
+ * 获取任务存储实例
208
+ */
209
+ getStore() {
210
+ return this.store;
211
+ }
212
+ }
213
+ export const localTaskService = new LocalTaskService();
214
+ /**
215
+ * 将时间转换为时间戳
216
+ */
217
+ function toTimestamp(time) {
218
+ if (!time)
219
+ return undefined;
220
+ if (typeof time === 'number')
221
+ return time;
222
+ return new Date(time).getTime();
223
+ }
@@ -0,0 +1,242 @@
1
+ /**
2
+ * 通知服务
3
+ * 负责发送任务通知
4
+ * 优先级:WebSocket > HMS Push (华为) / Expo Push (其他)
5
+ */
6
+ import { PushTokenStore } from './task/PushTokenStore';
7
+ import { webSocketService } from './WebSocketService';
8
+ import { hmsPushService } from './HMSPushService';
9
+ import { getLogger } from '@pocketclaw/shared';
10
+ const logger = getLogger('NotificationService');
11
+ const log = logger;
12
+ // 全局 PushTokenStore 实例
13
+ let pushTokenStore = null;
14
+ function getPushTokenStore() {
15
+ if (!pushTokenStore) {
16
+ pushTokenStore = new PushTokenStore();
17
+ }
18
+ return pushTokenStore;
19
+ }
20
+ class NotificationService {
21
+ expoPushUrl = 'https://exp.host/--/api/v2/push/send';
22
+ /**
23
+ * 发送任务通知给指定用户
24
+ * 优先使用 WebSocket(实时推送),失败则降级到 Expo Push
25
+ */
26
+ async sendTaskNotification(task) {
27
+ // 1. 优先尝试 WebSocket 发送(用户在线时)
28
+ if (webSocketService.isUserOnline(task.userId)) {
29
+ const sent = webSocketService.sendTaskNotification(task);
30
+ if (sent) {
31
+ return true;
32
+ }
33
+ }
34
+ // 2. 降级到 Expo Push Notifications
35
+ return this.sendExpoPushNotification(task);
36
+ }
37
+ /**
38
+ * 通过 Expo Push Notifications 发送任务通知
39
+ */
40
+ async sendExpoPushNotification(task) {
41
+ const pushStore = getPushTokenStore();
42
+ const pushTokens = pushStore.getExpoTokens(task.userId);
43
+ if (pushTokens.length === 0) {
44
+ log.warn(`用户 ${task.userId} 没有注册 Push Token`);
45
+ return false;
46
+ }
47
+ const messages = pushTokens.map(token => ({
48
+ to: token,
49
+ title: `${task.title}`,
50
+ body: task.description || "",
51
+ data: {
52
+ taskId: task.id,
53
+ type: 'task_reminder',
54
+ },
55
+ sound: 'default',
56
+ priority: 'high',
57
+ }));
58
+ try {
59
+ const response = await fetch(this.expoPushUrl, {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ 'Accept': 'application/json',
64
+ },
65
+ body: JSON.stringify(messages),
66
+ });
67
+ const result = await response.json();
68
+ // 检查发送结果
69
+ let successCount = 0;
70
+ for (const item of result.data) {
71
+ if (item.status === 'ok') {
72
+ successCount++;
73
+ }
74
+ else {
75
+ log.error(`推送失败: ${item.message}`, item.details);
76
+ // 如果 token 无效,从内存中移除
77
+ if (item.details?.error === 'DeviceNotRegistered') {
78
+ const invalidToken = messages.find(m => m.to)?.to;
79
+ if (invalidToken) {
80
+ pushStore.delete(invalidToken);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ return successCount > 0;
86
+ }
87
+ catch (error) {
88
+ log.error('推送异常:', error);
89
+ return false;
90
+ }
91
+ }
92
+ /**
93
+ * 发送测试通知
94
+ */
95
+ async sendTestNotification(pushToken, title, body) {
96
+ const message = {
97
+ to: pushToken,
98
+ title,
99
+ body,
100
+ data: { type: 'test' },
101
+ sound: 'default',
102
+ };
103
+ try {
104
+ const response = await fetch(this.expoPushUrl, {
105
+ method: 'POST',
106
+ headers: {
107
+ 'Content-Type': 'application/json',
108
+ 'Accept': 'application/json',
109
+ },
110
+ body: JSON.stringify([message]),
111
+ });
112
+ const result = await response.json();
113
+ return result.data[0]?.status === 'ok';
114
+ }
115
+ catch (error) {
116
+ log.error('测试推送异常:', error);
117
+ return false;
118
+ }
119
+ }
120
+ /**
121
+ * 批量发送通知给多个用户
122
+ */
123
+ async sendBatchNotifications(notifications) {
124
+ const allMessages = [];
125
+ for (const notification of notifications) {
126
+ const pushStore = getPushTokenStore();
127
+ const pushTokens = pushStore.getExpoTokens(String(notification.userId));
128
+ for (const token of pushTokens) {
129
+ allMessages.push({
130
+ to: token,
131
+ title: notification.title,
132
+ body: notification.body,
133
+ data: notification.data,
134
+ sound: 'default',
135
+ priority: 'high',
136
+ });
137
+ }
138
+ }
139
+ if (allMessages.length === 0) {
140
+ return;
141
+ }
142
+ try {
143
+ // Expo 限制每次最多发送 100 条消息
144
+ const batchSize = 100;
145
+ for (let i = 0; i < allMessages.length; i += batchSize) {
146
+ const batch = allMessages.slice(i, i + batchSize);
147
+ const response = await fetch(this.expoPushUrl, {
148
+ method: 'POST',
149
+ headers: {
150
+ 'Content-Type': 'application/json',
151
+ 'Accept': 'application/json',
152
+ },
153
+ body: JSON.stringify(batch),
154
+ });
155
+ const result = await response.json();
156
+ let successCount = 0;
157
+ for (const item of result.data) {
158
+ if (item.status === 'ok')
159
+ successCount++;
160
+ }
161
+ }
162
+ }
163
+ catch (error) {
164
+ log.error('批量推送异常:', error);
165
+ }
166
+ }
167
+ /**
168
+ * 发送 HMS Push 通知(华为设备专用)
169
+ */
170
+ async sendHMSPushNotification(hmsTokens, title, body, data) {
171
+ if (!hmsPushService.isAvailable()) {
172
+ log.warn('HMS Push 服务未配置');
173
+ return false;
174
+ }
175
+ const result = await hmsPushService.sendToMany(hmsTokens, {
176
+ title,
177
+ body,
178
+ data: {
179
+ ...data,
180
+ type: data?.type || 'task_reminder',
181
+ },
182
+ }, {
183
+ channelId: 'task_reminder',
184
+ });
185
+ return result.success;
186
+ }
187
+ /**
188
+ * 根据设备类型发送推送通知
189
+ * @param devices 设备列表
190
+ * @param title 标题
191
+ * @param body 正文
192
+ * @param data 自定义数据
193
+ */
194
+ async sendPushByDeviceType(devices, title, body, data) {
195
+ // 按 Push Provider 分组
196
+ const expoTokens = [];
197
+ const hmsTokens = [];
198
+ for (const device of devices) {
199
+ if (device.pushProvider === 'hms' && device.hmsPushToken) {
200
+ hmsTokens.push(device.hmsPushToken);
201
+ }
202
+ else if (device.pushToken) {
203
+ expoTokens.push(device.pushToken);
204
+ }
205
+ }
206
+ let expoCount = 0;
207
+ let hmsCount = 0;
208
+ // 发送 Expo Push
209
+ if (expoTokens.length > 0) {
210
+ const messages = expoTokens.map(token => ({
211
+ to: token,
212
+ title,
213
+ body,
214
+ data,
215
+ sound: 'default',
216
+ priority: 'high',
217
+ }));
218
+ try {
219
+ const response = await fetch(this.expoPushUrl, {
220
+ method: 'POST',
221
+ headers: {
222
+ 'Content-Type': 'application/json',
223
+ 'Accept': 'application/json',
224
+ },
225
+ body: JSON.stringify(messages),
226
+ });
227
+ const result = await response.json();
228
+ expoCount = result.data.filter(item => item.status === 'ok').length;
229
+ }
230
+ catch (error) {
231
+ log.error('Expo Push 发送失败:', error);
232
+ }
233
+ }
234
+ // 发送 HMS Push
235
+ if (hmsTokens.length > 0) {
236
+ const result = await this.sendHMSPushNotification(hmsTokens, title, body, data);
237
+ hmsCount = result ? hmsTokens.length : 0;
238
+ }
239
+ return { expoCount, hmsCount };
240
+ }
241
+ }
242
+ export const notificationService = new NotificationService();