@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.
- package/README.md +194 -0
- package/dist/.env +6 -0
- package/dist/api/index.js +182 -0
- package/dist/config/index.js +41 -0
- package/dist/index.js +183 -0
- package/dist/middleware/auth.js +53 -0
- package/dist/middleware/errorHandler.js +20 -0
- package/dist/routes/agent.js +513 -0
- package/dist/routes/auth.js +172 -0
- package/dist/routes/chat.js +45 -0
- package/dist/routes/config.js +21 -0
- package/dist/routes/models.js +123 -0
- package/dist/routes/service.js +240 -0
- package/dist/routes/settings.js +101 -0
- package/dist/routes/skillHub.js +126 -0
- package/dist/routes/skills.js +159 -0
- package/dist/routes/tasks.js +149 -0
- package/dist/routes/upload.js +129 -0
- package/dist/routes/version.js +66 -0
- package/dist/services/HMSPushService.js +24 -0
- package/dist/services/LocalTaskService.js +223 -0
- package/dist/services/NotificationService.js +242 -0
- package/dist/services/ServiceManager.js +348 -0
- package/dist/services/TaskSchedulerService.js +195 -0
- package/dist/services/TaskService.js +240 -0
- package/dist/services/WebSocketService.js +236 -0
- package/dist/services/agent/Agent.js +120 -0
- package/dist/services/agent/AgentManager.js +265 -0
- package/dist/services/agent/AgentStore.js +73 -0
- package/dist/services/dataService.js +293 -0
- package/dist/services/index.js +15 -0
- package/dist/services/llm/LLMClient.js +724 -0
- package/dist/services/memory/MemoryManager.js +117 -0
- package/dist/services/model/ModelCapabilities.js +141 -0
- package/dist/services/model/index.js +4 -0
- package/dist/services/models.js +16 -0
- package/dist/services/session/MigrationManager.js +176 -0
- package/dist/services/session/Session.js +733 -0
- package/dist/services/session/SessionManager.js +255 -0
- package/dist/services/session/SessionStore.js +186 -0
- package/dist/services/session/index.js +3 -0
- package/dist/services/skills.js +34 -0
- package/dist/services/systemPrompt.js +150 -0
- package/dist/services/task/PushTokenStore.js +124 -0
- package/dist/services/task/TaskStore.js +143 -0
- package/dist/services/tools/calculator.js +27 -0
- package/dist/services/tools/edit.js +318 -0
- package/dist/services/tools/exec.js +119 -0
- package/dist/services/tools/fetch.js +155 -0
- package/dist/services/tools/file.js +315 -0
- package/dist/services/tools/index.js +48 -0
- package/dist/services/tools/keyboard.js +145 -0
- package/dist/services/tools/model.js +86 -0
- package/dist/services/tools/mouse.js +55 -0
- package/dist/services/tools/screenshot.js +19 -0
- package/dist/services/tools/search.js +53 -0
- package/dist/services/tools/skill.js +108 -0
- package/dist/services/tools/task.js +110 -0
- package/dist/services/tools/types.js +1 -0
- package/dist/services/tools/webFetch.js +34 -0
- package/dist/stores/authStore.js +178 -0
- package/dist/stores/index.js +6 -0
- package/dist/stores/memoryStore.js +191 -0
- package/dist/stores/persistStore.js +317 -0
- package/package.json +94 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
import { getLogger } from '@pocketclaw/shared';
|
|
3
|
+
const logger = getLogger('SessionManager');
|
|
4
|
+
import { sessionStore } from './SessionStore';
|
|
5
|
+
import { authStore } from '@/stores/authStore';
|
|
6
|
+
import { Session } from './Session';
|
|
7
|
+
/**
|
|
8
|
+
* SessionManager - Business logic layer
|
|
9
|
+
* Manages sessions in memory
|
|
10
|
+
*/
|
|
11
|
+
export class SessionManager {
|
|
12
|
+
sessions = null;
|
|
13
|
+
initialized = false;
|
|
14
|
+
constructor() {
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Initialize - Load all sessions from database on startup
|
|
18
|
+
*/
|
|
19
|
+
initialize() {
|
|
20
|
+
if (this.initialized) {
|
|
21
|
+
logger.warn('Already initialized');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const userId = this.getCurrentUserId();
|
|
25
|
+
if (!userId) {
|
|
26
|
+
logger.info('No user logged in, skipping initialization');
|
|
27
|
+
this.initialized = true;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.sessions = new Map();
|
|
31
|
+
const sessionDataList = sessionStore.findSessionsByUserId(userId);
|
|
32
|
+
for (const sessionData of sessionDataList) {
|
|
33
|
+
const session = this.createSessionFromData(sessionData);
|
|
34
|
+
this.sessions.set(session.id, session);
|
|
35
|
+
}
|
|
36
|
+
this.initialized = true;
|
|
37
|
+
logger.info(`Initialized with ${this.sessions.size} sessions for user ${userId}`);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get current user ID
|
|
41
|
+
*/
|
|
42
|
+
getCurrentUserId() {
|
|
43
|
+
const user = authStore.getUser();
|
|
44
|
+
return user ? String(user.id) : null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create Session instance from data
|
|
48
|
+
*/
|
|
49
|
+
createSessionFromData(data) {
|
|
50
|
+
const session = new Session({
|
|
51
|
+
id: data.id,
|
|
52
|
+
userId: data.userId,
|
|
53
|
+
agentId: data.agentId,
|
|
54
|
+
title: data.title,
|
|
55
|
+
selectModelId: data.selectModelId,
|
|
56
|
+
voiceState: data.voiceState,
|
|
57
|
+
createdAt: data.createdAt,
|
|
58
|
+
updatedAt: data.updatedAt,
|
|
59
|
+
isCurrent: data.isCurrent,
|
|
60
|
+
lastMessageSummary: data.lastMessageSummary,
|
|
61
|
+
lastMessageSummaryAt: data.lastMessageSummaryAt
|
|
62
|
+
});
|
|
63
|
+
session.loadMessages();
|
|
64
|
+
return session;
|
|
65
|
+
}
|
|
66
|
+
// ========== Session Operations ==========
|
|
67
|
+
/**
|
|
68
|
+
* Create new session
|
|
69
|
+
*/
|
|
70
|
+
createSession(config = {}) {
|
|
71
|
+
if (this.sessions == null) {
|
|
72
|
+
this.initialize();
|
|
73
|
+
}
|
|
74
|
+
const userId = this.getCurrentUserId();
|
|
75
|
+
if (!userId) {
|
|
76
|
+
throw new Error('No user logged in, cannot create session');
|
|
77
|
+
}
|
|
78
|
+
const session = new Session({
|
|
79
|
+
id: config.id || uuidv4(),
|
|
80
|
+
userId,
|
|
81
|
+
agentId: config.agentId,
|
|
82
|
+
title: config.title || 'New Chat',
|
|
83
|
+
selectModelId: config.selectModelId,
|
|
84
|
+
voiceState: { isRecording: false, isPlaying: false },
|
|
85
|
+
createdAt: Date.now(),
|
|
86
|
+
updatedAt: Date.now(),
|
|
87
|
+
});
|
|
88
|
+
this.sessions.set(session.id, session);
|
|
89
|
+
session.save();
|
|
90
|
+
logger.info(`Created session ${session.id} for user ${userId}`);
|
|
91
|
+
return session;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get session
|
|
95
|
+
*/
|
|
96
|
+
getSession(sessionId) {
|
|
97
|
+
if (this.sessions == null) {
|
|
98
|
+
this.initialize();
|
|
99
|
+
}
|
|
100
|
+
return this.sessions.get(sessionId) || null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Stop session generation
|
|
104
|
+
*/
|
|
105
|
+
stopSession(sessionId) {
|
|
106
|
+
const session = this.getSession(sessionId);
|
|
107
|
+
if (!session) {
|
|
108
|
+
logger.warn(`Session ${sessionId} not found for stop`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
if (session.isGenerating) {
|
|
112
|
+
session.stopGenerating();
|
|
113
|
+
logger.info(`Stopped session ${sessionId}`);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Get all sessions
|
|
120
|
+
*/
|
|
121
|
+
getAllSessions() {
|
|
122
|
+
if (this.sessions == null) {
|
|
123
|
+
this.initialize();
|
|
124
|
+
}
|
|
125
|
+
return Array.from(this.sessions.values());
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get user sessions (sorted by update time)
|
|
129
|
+
*/
|
|
130
|
+
getUserSessions() {
|
|
131
|
+
return this.getAllSessions().sort((a, b) => b.updatedAt - a.updatedAt);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Delete session
|
|
135
|
+
*/
|
|
136
|
+
deleteSession(sessionId) {
|
|
137
|
+
if (this.sessions == null) {
|
|
138
|
+
this.initialize();
|
|
139
|
+
}
|
|
140
|
+
const session = this.sessions.get(sessionId);
|
|
141
|
+
if (!session)
|
|
142
|
+
return false;
|
|
143
|
+
sessionStore.transaction(() => {
|
|
144
|
+
sessionStore.deleteMessagesBySessionId(sessionId);
|
|
145
|
+
sessionStore.deleteSession(sessionId);
|
|
146
|
+
});
|
|
147
|
+
this.sessions.delete(sessionId);
|
|
148
|
+
logger.info(`Deleted session ${sessionId}`);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Update session
|
|
153
|
+
*/
|
|
154
|
+
updateSession(sessionId, updates) {
|
|
155
|
+
if (this.sessions == null) {
|
|
156
|
+
this.initialize();
|
|
157
|
+
}
|
|
158
|
+
const session = this.sessions.get(sessionId);
|
|
159
|
+
if (!session)
|
|
160
|
+
return null;
|
|
161
|
+
if (updates.title !== undefined)
|
|
162
|
+
session.title = updates.title;
|
|
163
|
+
if (updates.selectModelId !== undefined)
|
|
164
|
+
session.selectModelId = updates.selectModelId;
|
|
165
|
+
if (updates.agentId !== undefined)
|
|
166
|
+
session.agentId = updates.agentId;
|
|
167
|
+
if (updates.unreadCount !== undefined)
|
|
168
|
+
session.unreadCount = updates.unreadCount;
|
|
169
|
+
session.updatedAt = Date.now();
|
|
170
|
+
session.save();
|
|
171
|
+
return session;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Set current session (when user views it)
|
|
175
|
+
*/
|
|
176
|
+
setCurrentSession(sessionId) {
|
|
177
|
+
if (this.sessions == null) {
|
|
178
|
+
this.initialize();
|
|
179
|
+
}
|
|
180
|
+
const currentSession = this.getCurrentSession();
|
|
181
|
+
if (currentSession) {
|
|
182
|
+
currentSession.isCurrent = false;
|
|
183
|
+
currentSession.save();
|
|
184
|
+
}
|
|
185
|
+
const session = this.getSession(sessionId);
|
|
186
|
+
if (session) {
|
|
187
|
+
session.isCurrent = true;
|
|
188
|
+
session.save();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Check if a session is the current session
|
|
193
|
+
*/
|
|
194
|
+
isCurrentSession(sessionId) {
|
|
195
|
+
const session = this.getSession(sessionId);
|
|
196
|
+
if (session) {
|
|
197
|
+
return session.isCurrent;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* On message completion, increment unread if not current
|
|
203
|
+
*/
|
|
204
|
+
onMessageComplete(sessionId) {
|
|
205
|
+
if (this.sessions == null) {
|
|
206
|
+
this.initialize();
|
|
207
|
+
}
|
|
208
|
+
const session = this.sessions.get(sessionId);
|
|
209
|
+
if (session && !session.isCurrent) {
|
|
210
|
+
session.incrementUnreadCount();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get total unread count across all sessions
|
|
215
|
+
*/
|
|
216
|
+
getTotalUnreadCount() {
|
|
217
|
+
if (this.sessions == null) {
|
|
218
|
+
this.initialize();
|
|
219
|
+
}
|
|
220
|
+
let total = 0;
|
|
221
|
+
for (const session of this.sessions.values()) {
|
|
222
|
+
total += session.unreadCount;
|
|
223
|
+
}
|
|
224
|
+
return total;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get unread count for sessions of a specific agent
|
|
228
|
+
*/
|
|
229
|
+
getAgentUnreadCount(agentId) {
|
|
230
|
+
if (this.sessions == null) {
|
|
231
|
+
this.initialize();
|
|
232
|
+
}
|
|
233
|
+
let total = 0;
|
|
234
|
+
for (const session of this.sessions.values()) {
|
|
235
|
+
if (session.agentId === agentId) {
|
|
236
|
+
total += session.unreadCount;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return total;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Get current session
|
|
243
|
+
*/
|
|
244
|
+
getCurrentSession() {
|
|
245
|
+
return Array.from(this.sessions.values()).find(x => x.isCurrent) || null;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get migration status
|
|
249
|
+
*/
|
|
250
|
+
getMigrationStatus() {
|
|
251
|
+
return sessionStore.getMigrationStatus();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Export singleton
|
|
255
|
+
export const sessionManager = new SessionManager();
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { getLogger } from '@pocketclaw/shared';
|
|
5
|
+
import { MigrationManager } from './MigrationManager';
|
|
6
|
+
const logger = getLogger('SessionStore');
|
|
7
|
+
/**
|
|
8
|
+
* SessionStore - Data access layer
|
|
9
|
+
* Responsible for direct SQLite operations
|
|
10
|
+
*/
|
|
11
|
+
export class SessionStore {
|
|
12
|
+
db;
|
|
13
|
+
migrationManager;
|
|
14
|
+
constructor() {
|
|
15
|
+
const dataDir = path.join(process.cwd(), 'data');
|
|
16
|
+
if (!fs.existsSync(dataDir)) {
|
|
17
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
const dbPath = path.join(dataDir, 'sessions.db');
|
|
20
|
+
this.db = new Database(dbPath);
|
|
21
|
+
this.db.pragma('foreign_keys = ON');
|
|
22
|
+
const migrationsDir = path.join(process.cwd(), 'migrations');
|
|
23
|
+
this.migrationManager = new MigrationManager(this.db, migrationsDir);
|
|
24
|
+
this.migrationManager.migrate();
|
|
25
|
+
this.ensureColumns();
|
|
26
|
+
logger.info(`Initialized at ${dbPath}`);
|
|
27
|
+
// 注册到全局供 Session 使用
|
|
28
|
+
global.__sessionStore = this;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Ensure required columns exist
|
|
32
|
+
*/
|
|
33
|
+
ensureColumns() {
|
|
34
|
+
// Ensure agent_id column in sessions
|
|
35
|
+
const sessionTableInfo = this.db.prepare("PRAGMA table_info(sessions)").all();
|
|
36
|
+
const hasAgentId = sessionTableInfo.some(col => col.name === 'agent_id');
|
|
37
|
+
if (!hasAgentId) {
|
|
38
|
+
this.db.exec("ALTER TABLE sessions ADD COLUMN agent_id TEXT");
|
|
39
|
+
logger.info('Added agent_id column to sessions');
|
|
40
|
+
}
|
|
41
|
+
// Ensure tool_calls column in messages
|
|
42
|
+
const msgTableInfo = this.db.prepare("PRAGMA table_info(messages)").all();
|
|
43
|
+
const hasToolCalls = msgTableInfo.some(col => col.name === 'tool_calls');
|
|
44
|
+
if (!hasToolCalls) {
|
|
45
|
+
this.db.exec("ALTER TABLE messages ADD COLUMN tool_calls TEXT");
|
|
46
|
+
logger.info('Added tool_calls column to messages');
|
|
47
|
+
}
|
|
48
|
+
// Ensure feedback column in messages
|
|
49
|
+
const hasFeedback = msgTableInfo.some(col => col.name === 'feedback');
|
|
50
|
+
if (!hasFeedback) {
|
|
51
|
+
this.db.exec("ALTER TABLE messages ADD COLUMN feedback TEXT");
|
|
52
|
+
logger.info('Added feedback column to messages');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Provide transaction method
|
|
57
|
+
*/
|
|
58
|
+
transaction(fn) {
|
|
59
|
+
return this.db.transaction(fn)();
|
|
60
|
+
}
|
|
61
|
+
// ========== Session Operations ==========
|
|
62
|
+
insertSession(session) {
|
|
63
|
+
this.db.prepare(`
|
|
64
|
+
INSERT INTO sessions (id, user_id, agent_id, title, select_model_id, voice_state, created_at, updated_at,last_message_summary,last_message_summary_at,unread_count,is_current)
|
|
65
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?,?,?,?,?)
|
|
66
|
+
`).run(session.id, session.userId, session.agentId || null, session.title, session.selectModelId || '', JSON.stringify(session.voiceState), session.createdAt, session.updatedAt, session.lastMessageSummary, session.lastMessageSummaryAt, session.unreadCount || 0, session.isCurrent ? 1 : 0);
|
|
67
|
+
}
|
|
68
|
+
updateSession(session) {
|
|
69
|
+
this.db.prepare(`
|
|
70
|
+
UPDATE sessions
|
|
71
|
+
SET user_id = ?, agent_id = ?, title = ?, select_model_id = ?,
|
|
72
|
+
voice_state = ?, updated_at = ?,last_message_summary=?,last_message_summary_at=?,unread_count=?,is_current=?
|
|
73
|
+
WHERE id = ?
|
|
74
|
+
`).run(session.userId, session.agentId || null, session.title, session.selectModelId || '', JSON.stringify(session.voiceState), session.updatedAt, session.lastMessageSummary, session.lastMessageSummaryAt, session.unreadCount || 0, session.isCurrent ? 1 : 0, session.id);
|
|
75
|
+
}
|
|
76
|
+
// 支持部分更新的 updateSession
|
|
77
|
+
updateSessionPartial(id, data) {
|
|
78
|
+
const updates = [];
|
|
79
|
+
const values = [];
|
|
80
|
+
if (data.title !== undefined) {
|
|
81
|
+
updates.push('title = ?');
|
|
82
|
+
values.push(data.title);
|
|
83
|
+
}
|
|
84
|
+
if (data.selectModelId !== undefined) {
|
|
85
|
+
updates.push('select_model_id = ?');
|
|
86
|
+
values.push(data.selectModelId || '');
|
|
87
|
+
}
|
|
88
|
+
if (updates.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
updates.push('updated_at = ?');
|
|
91
|
+
values.push(Date.now());
|
|
92
|
+
values.push(id);
|
|
93
|
+
this.db.prepare(`UPDATE sessions SET ${updates.join(', ')} WHERE id = ?`).run(...values);
|
|
94
|
+
}
|
|
95
|
+
deleteSession(id) {
|
|
96
|
+
this.db.prepare('DELETE FROM sessions WHERE id = ?').run(id);
|
|
97
|
+
}
|
|
98
|
+
findSessionById(id) {
|
|
99
|
+
const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id);
|
|
100
|
+
return row ? this.rowToSessionData(row) : null;
|
|
101
|
+
}
|
|
102
|
+
findSessionsByUserId(userId) {
|
|
103
|
+
const rows = this.db.prepare('SELECT * FROM sessions WHERE user_id = ? ORDER BY updated_at DESC').all(userId);
|
|
104
|
+
return rows.map(row => this.rowToSessionData(row));
|
|
105
|
+
}
|
|
106
|
+
findAllSessions() {
|
|
107
|
+
const rows = this.db.prepare('SELECT * FROM sessions ORDER BY updated_at DESC').all();
|
|
108
|
+
return rows.map(row => this.rowToSessionData(row));
|
|
109
|
+
}
|
|
110
|
+
// ========== Message Operations ==========
|
|
111
|
+
insertMessage(sessionId, msg) {
|
|
112
|
+
this.db.prepare(`
|
|
113
|
+
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls, created_at, updated_at, model_name, feedback)
|
|
114
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
115
|
+
`).run(msg.id, sessionId, msg.role, msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.createdAt, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null);
|
|
116
|
+
}
|
|
117
|
+
insertMessages(sessionId, messages) {
|
|
118
|
+
const stmt = this.db.prepare(`
|
|
119
|
+
INSERT INTO messages (id, session_id, role, content, attachments, tool_calls,created_at, updated_at, model_name, feedback)
|
|
120
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
121
|
+
`);
|
|
122
|
+
for (const msg of messages) {
|
|
123
|
+
stmt.run(msg.id, sessionId, msg.role, msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.createdAt, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
updateMessage(sessionId, msg) {
|
|
127
|
+
this.db.prepare(`
|
|
128
|
+
UPDATE messages
|
|
129
|
+
SET content = ?, attachments = ?, tool_calls = ?, updated_at = ?, model_name = ?, feedback = ?
|
|
130
|
+
WHERE id = ? AND session_id = ?
|
|
131
|
+
`).run(msg.content, JSON.stringify(msg.attachments || []), msg.toolCalls ? JSON.stringify(msg.toolCalls) : null, msg.updatedAt || msg.createdAt, msg.modelName || null, msg.feedback || null, msg.id, sessionId);
|
|
132
|
+
}
|
|
133
|
+
deleteMessage(messageId) {
|
|
134
|
+
this.db.prepare('DELETE FROM messages WHERE id = ?').run(messageId);
|
|
135
|
+
}
|
|
136
|
+
deleteMessagesBySessionId(sessionId) {
|
|
137
|
+
this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
|
|
138
|
+
}
|
|
139
|
+
findMessagesBySessionId(sessionId) {
|
|
140
|
+
const rows = this.db.prepare('SELECT * FROM messages WHERE session_id = ? ORDER BY created_at ASC').all(sessionId);
|
|
141
|
+
return rows.map(row => ({
|
|
142
|
+
id: row.id,
|
|
143
|
+
sessionId: row.session_id,
|
|
144
|
+
role: row.role,
|
|
145
|
+
content: row.content,
|
|
146
|
+
attachments: JSON.parse(row.attachments || '[]'),
|
|
147
|
+
toolCalls: row.tool_calls ? JSON.parse(row.tool_calls) : undefined,
|
|
148
|
+
createdAt: row.created_at,
|
|
149
|
+
updatedAt: row.updated_at,
|
|
150
|
+
modelName: row.model_name,
|
|
151
|
+
feedback: row.feedback || undefined
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
rowToSessionData(row) {
|
|
155
|
+
return {
|
|
156
|
+
id: row.id,
|
|
157
|
+
userId: row.user_id,
|
|
158
|
+
agentId: row.agent_id || undefined,
|
|
159
|
+
title: row.title,
|
|
160
|
+
selectModelId: row.select_model_id || undefined,
|
|
161
|
+
voiceState: JSON.parse(row.voice_state),
|
|
162
|
+
createdAt: row.created_at,
|
|
163
|
+
updatedAt: row.updated_at,
|
|
164
|
+
lastMessageSummary: row.last_message_summary,
|
|
165
|
+
lastMessageSummaryAt: row.last_message_summary_at,
|
|
166
|
+
unreadCount: row.unread_count,
|
|
167
|
+
isCurrent: !!row.is_current
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get migration status
|
|
172
|
+
*/
|
|
173
|
+
getMigrationStatus() {
|
|
174
|
+
return this.migrationManager.getStatus();
|
|
175
|
+
}
|
|
176
|
+
close() {
|
|
177
|
+
this.db.close();
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Get db instance (for AgentStore)
|
|
181
|
+
*/
|
|
182
|
+
getDb() {
|
|
183
|
+
return this.db;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
export const sessionStore = new SessionStore();
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const mockSkills = [
|
|
2
|
+
{
|
|
3
|
+
id: 'web-search',
|
|
4
|
+
name: 'Web Search',
|
|
5
|
+
description: 'Search the web for current information',
|
|
6
|
+
category: 'search',
|
|
7
|
+
type: 'builtin',
|
|
8
|
+
enabled: true
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: 'image-generation',
|
|
12
|
+
name: 'Image Generation',
|
|
13
|
+
description: 'Generate images from text descriptions',
|
|
14
|
+
category: 'creation',
|
|
15
|
+
type: 'builtin',
|
|
16
|
+
enabled: true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'code-interpreter',
|
|
20
|
+
name: 'Code Interpreter',
|
|
21
|
+
description: 'Execute and analyze code in multiple languages',
|
|
22
|
+
category: 'development',
|
|
23
|
+
type: 'builtin',
|
|
24
|
+
enabled: true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'file-manager',
|
|
28
|
+
name: 'File Manager',
|
|
29
|
+
description: 'Read, write, and manage files on your device',
|
|
30
|
+
category: 'system',
|
|
31
|
+
type: 'builtin',
|
|
32
|
+
enabled: true
|
|
33
|
+
}
|
|
34
|
+
];
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import { settingsService, skillsService } from './dataService';
|
|
3
|
+
import { getLogger } from '@pocketclaw/shared';
|
|
4
|
+
const logger = getLogger('SystemPrompt');
|
|
5
|
+
// 预设的对话风格模板
|
|
6
|
+
export const CHAT_STYLE_TEMPLATES = [
|
|
7
|
+
{
|
|
8
|
+
id: 'friendly',
|
|
9
|
+
icon: 'face-smile',
|
|
10
|
+
titleKey: 'setup.styleFriendly',
|
|
11
|
+
descKey: 'setup.styleFriendlyDesc',
|
|
12
|
+
style: '回复简洁友好,像朋友一样聊天,适当使用emoji表情。',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'professional',
|
|
16
|
+
icon: 'briefcase',
|
|
17
|
+
titleKey: 'setup.styleProfessional',
|
|
18
|
+
descKey: 'setup.styleProfessionalDesc',
|
|
19
|
+
style: '回复专业严谨,提供详细的分析和建议,不使用emoji。',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'creative',
|
|
23
|
+
icon: 'wand-magic-sparkles',
|
|
24
|
+
titleKey: 'setup.styleCreative',
|
|
25
|
+
descKey: 'setup.styleCreativeDesc',
|
|
26
|
+
style: '回复富有创意和想象力,语言生动有趣,可以讲故事。',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'concise',
|
|
30
|
+
icon: 'bolt',
|
|
31
|
+
titleKey: 'setup.styleConcise',
|
|
32
|
+
descKey: 'setup.styleConciseDesc',
|
|
33
|
+
style: '回复简洁明了,直接回答问题,不废话。',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
// 支持的语言列表
|
|
37
|
+
export const LANGUAGES = [
|
|
38
|
+
{ code: 'zh-CN', name: 'Simplified Chinese', nativeName: '简体中文', chineseName: '简体中文' },
|
|
39
|
+
{ code: 'en-US', name: 'English', nativeName: 'English', chineseName: '英文' },
|
|
40
|
+
];
|
|
41
|
+
/**
|
|
42
|
+
* 获取操作系统信息
|
|
43
|
+
*/
|
|
44
|
+
function getOSInfo() {
|
|
45
|
+
const platform = os.platform();
|
|
46
|
+
const release = os.release();
|
|
47
|
+
const arch = os.arch();
|
|
48
|
+
let osName = 'Unknown';
|
|
49
|
+
switch (platform) {
|
|
50
|
+
case 'darwin':
|
|
51
|
+
osName = `macOS ${release}`;
|
|
52
|
+
break;
|
|
53
|
+
case 'win32':
|
|
54
|
+
osName = `Windows ${release}`;
|
|
55
|
+
break;
|
|
56
|
+
case 'linux':
|
|
57
|
+
osName = `Linux ${release}`;
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
osName = `${platform} ${release}`;
|
|
61
|
+
}
|
|
62
|
+
return `${osName} (${arch})`;
|
|
63
|
+
}
|
|
64
|
+
function getChatStyle(settings) {
|
|
65
|
+
// 这里可以根据需要返回不同的提示词风格
|
|
66
|
+
const chatStyleId = settings.chatStyleId;
|
|
67
|
+
if (chatStyleId) {
|
|
68
|
+
return CHAT_STYLE_TEMPLATES.find(s => s.id === chatStyleId)?.style || '';
|
|
69
|
+
}
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
function getIndentityInfoAsync(settings, agent) {
|
|
73
|
+
const style = getChatStyle(settings);
|
|
74
|
+
const agentName = agent?.name || settings.agentName || settings.assistantName;
|
|
75
|
+
return `【身份】
|
|
76
|
+
- 你是「${agentName}」,${settings.userName}的智能助手。${style}
|
|
77
|
+
- 自称"我",称用户为"您"或「${settings.userName}」`;
|
|
78
|
+
}
|
|
79
|
+
function getInstalledSkillsPrompt(skills) {
|
|
80
|
+
// 替换技能列表(模板中应该有占位符,如 {{skills}})
|
|
81
|
+
const skillsList = skills.map((skill, index) => `skillId: "${skill.id}". **${skill.name}** - ${skill.description || '无描述'}`).join('\n');
|
|
82
|
+
const skillSystemPrompt = `【技能系统】当你出现无法解决用户需求时,需要通过技能系统来帮助完成。 技能有两种,一种是通过skill工具可以获取到的技能,一种是通过clawhub安装的技能。`;
|
|
83
|
+
const skillToolPrompt = `【skill工具技能】
|
|
84
|
+
【已安装skill技能】
|
|
85
|
+
${skillsList}
|
|
86
|
+
【强制调用流程】
|
|
87
|
+
1.必须先调用技能管理工具获取详情
|
|
88
|
+
2.如果技能的scriptsUrl不为空,并且当前目录中的skills目录下不存在对应的技能,需要先下载,然后解压后才可以使用,下载目录统一下载到当前目录的skills目录下的该技能目录。
|
|
89
|
+
3.下载直接使用命令行工具,不要使用fetch工具。运行完脚本记得把脚本及其解压内容删除。`;
|
|
90
|
+
const clawhubSkillPrompt = `
|
|
91
|
+
【clawhub技能】
|
|
92
|
+
1.通过clawhub安装的技能,首先通过命令行工具查看clawhub命令是否安装
|
|
93
|
+
2.如果没有安装需要先安装clawhub
|
|
94
|
+
3.如果已经安装可以使用clawhub search命令搜索相关工具
|
|
95
|
+
4.安装后根据技能中的SKILL.md文档描述进行使用`;
|
|
96
|
+
return `\n${skillSystemPrompt}\n${skillToolPrompt}\n${clawhubSkillPrompt}\n`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 获取系统提示词
|
|
100
|
+
* @param agent - 当前智能体(可选)
|
|
101
|
+
* @param customPrompt - 自定义提示词(可选)
|
|
102
|
+
* @returns 完整的系统提示词
|
|
103
|
+
*/
|
|
104
|
+
export async function getSystemPromptAsync(agent, customPrompt) {
|
|
105
|
+
const osInfo = getOSInfo();
|
|
106
|
+
const settings = (await settingsService.get()).data;
|
|
107
|
+
const skills = (await skillsService.list()).data;
|
|
108
|
+
const skillPrompts = getInstalledSkillsPrompt(skills);
|
|
109
|
+
const chineseLocalName = LANGUAGES.filter(x => x.code === settings.language)[0]?.chineseName ?? '简体中文';
|
|
110
|
+
const date = new Date();
|
|
111
|
+
let prompt = `${getIndentityInfoAsync(settings, agent)}。
|
|
112
|
+
【当前运行环境】:${osInfo}。
|
|
113
|
+
【当前时间】:${date.toLocaleString(settings.language)},weekday:${date.getDay()}。
|
|
114
|
+
【语言】你的回复语言严格遵循用户设备系统语言:${chineseLocalName},无论用户使用任何语言提问,你都只使用【${chineseLocalName}】进行完整回复,不要混用其他语言。
|
|
115
|
+
【工具调用】
|
|
116
|
+
1.逐步思考,分步骤调用工具,工具调用前尽量返回content,明确当前工具调用的目的。
|
|
117
|
+
2.工具调用的参数全部采用JSON语法,调用前先验证JSON语法正确后,再回复,切记未验证JSON语法就直接回复。
|
|
118
|
+
【命令执行】执行命令时一定要根据当前系统来执行命令
|
|
119
|
+
【文件操作】文件编辑后一定要验证编辑是否成功,文件内容与预期是否一致,文件工具目前未提供批量替换操作,如果需要批量修改,请自行创建脚本来完成。
|
|
120
|
+
【特殊语法】mermaid折线图需要使用xychart-beta,完整示例如下:
|
|
121
|
+
xychart-beta
|
|
122
|
+
title "2025年上半年产品销量对比"
|
|
123
|
+
x-axis ["1月", "2月", "3月", "4月", "5月", "6月"]
|
|
124
|
+
y-axis "销量 (件)" 0 --> 80
|
|
125
|
+
line "智能手机" [45, 52, 38, 65, 58, 72]
|
|
126
|
+
line "笔记本电脑" [30, 28, 35, 42, 38, 45]
|
|
127
|
+
line "平板电脑" [20, 25, 30, 28, 35, 40]
|
|
128
|
+
【解决问题思路】
|
|
129
|
+
1.首先分析用户需求,根据需求罗列出实施计划。如果对需求不够明确,需要把计划罗列完后,让用户确认后再执行。
|
|
130
|
+
2.按照计划分步执行
|
|
131
|
+
3.所有步骤完成后,验证结果,比如编写程序,则需要编译修改后的代码是否有问题
|
|
132
|
+
4.验证完全通过,总结完成情况,告诉用户
|
|
133
|
+
【代码规范】
|
|
134
|
+
## 1 面向对象代码设计
|
|
135
|
+
1)根据功能先添加接口
|
|
136
|
+
2)对应接口添加一个基类
|
|
137
|
+
3)实现类默认继承这个基类
|
|
138
|
+
## 2 添加新功能
|
|
139
|
+
1)服务端根据DDD架构,进行分层设计:控制器、服务、领域服务
|
|
140
|
+
2)客户端将一个页面分为3部分,页面组件、页面样式、页面执行的脚本,页面执行的脚本中,数据服务与业务逻辑分开处理,数据服务又分为本地数据和远程数据
|
|
141
|
+
## 3 修改已经存在的代码文件
|
|
142
|
+
1)修改Bug,找到Bug后精确替换,不要调整架构
|
|
143
|
+
2)修改业务功能,删除旧的业务功能,重新添加新的,不要在旧的上面做修改,直接删除旧功能,然后添加新功能
|
|
144
|
+
${skillPrompts}`;
|
|
145
|
+
// 如果有自定义提示词,追加
|
|
146
|
+
if (customPrompt) {
|
|
147
|
+
prompt += `\n\n${customPrompt}`;
|
|
148
|
+
}
|
|
149
|
+
return prompt;
|
|
150
|
+
}
|