clawmate 1.2.0 → 1.4.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/electron-builder.yml +1 -1
- package/index.js +945 -17
- package/main/ai-bridge.js +76 -16
- package/main/ai-connector.js +94 -11
- package/main/autostart.js +3 -3
- package/main/file-command-parser.js +360 -0
- package/main/index.js +19 -5
- package/main/ipc-handlers.js +107 -2
- package/main/platform.js +48 -1
- package/main/smart-file-ops.js +373 -0
- package/main/telegram.js +593 -0
- package/main/tray.js +307 -39
- package/openclaw.plugin.json +1 -1
- package/package.json +4 -4
- package/preload/preload.js +19 -3
- package/renderer/first-run.html +2 -2
- package/renderer/index.html +2 -0
- package/renderer/js/ai-controller.js +312 -7
- package/renderer/js/app.js +19 -6
- package/renderer/js/browser-watcher.js +172 -0
- package/renderer/js/character.js +119 -22
- package/renderer/js/interactions.js +45 -2
- package/renderer/js/memory.js +108 -1
- package/renderer/js/metrics.js +607 -0
- package/renderer/js/mode-manager.js +53 -9
- package/renderer/js/pet-engine.js +372 -30
- package/renderer/js/state-machine.js +7 -0
- package/renderer/launcher.html +3 -3
- package/shared/messages.js +110 -0
- package/shared/personalities.js +37 -2
- package/skills/launch-pet/index.js +1 -1
- package/skills/launch-pet/skill.json +1 -1
package/main/ai-bridge.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* AI ↔ ClawMate 브릿지
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* - ClawMate →
|
|
4
|
+
* AI 에이전트가 ClawMate의 뇌 역할을 한다.
|
|
5
|
+
* - AI → ClawMate: 행동 명령, 말풍선, 감정, 이동
|
|
6
|
+
* - ClawMate → AI: 사용자 이벤트 (클릭, 드래그, 커서, 파일 변화)
|
|
7
7
|
*
|
|
8
8
|
* 통신: WebSocket (로컬 ws://localhost:9320)
|
|
9
9
|
* 프로토콜: JSON 메시지
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* AI 연결 안 됐을 때 → 자율 모드 (기존 FSM) 로 폴백
|
|
12
12
|
*/
|
|
13
13
|
const WebSocket = require('ws');
|
|
14
14
|
const EventEmitter = require('events');
|
|
@@ -17,7 +17,7 @@ class AIBridge extends EventEmitter {
|
|
|
17
17
|
constructor() {
|
|
18
18
|
super();
|
|
19
19
|
this.wss = null;
|
|
20
|
-
this.client = null; // 연결된
|
|
20
|
+
this.client = null; // 연결된 AI 에이전트
|
|
21
21
|
this.connected = false;
|
|
22
22
|
this.port = 9320;
|
|
23
23
|
this.heartbeatInterval = null;
|
|
@@ -33,19 +33,19 @@ class AIBridge extends EventEmitter {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* WebSocket 서버 시작 —
|
|
36
|
+
* WebSocket 서버 시작 — AI 에이전트가 여기에 접속
|
|
37
37
|
*/
|
|
38
38
|
start() {
|
|
39
39
|
this.wss = new WebSocket.Server({ port: this.port, host: '127.0.0.1' });
|
|
40
40
|
|
|
41
41
|
this.wss.on('connection', (ws) => {
|
|
42
|
-
console.log('[AI Bridge]
|
|
42
|
+
console.log('[AI Bridge] AI 연결됨');
|
|
43
43
|
this.client = ws;
|
|
44
44
|
this.connected = true;
|
|
45
45
|
this.reconnectAttempts = 0;
|
|
46
46
|
this.emit('connected');
|
|
47
47
|
|
|
48
|
-
//
|
|
48
|
+
// AI에 현재 상태 전송
|
|
49
49
|
this.send('sync', this.petState);
|
|
50
50
|
|
|
51
51
|
ws.on('message', (data) => {
|
|
@@ -58,7 +58,7 @@ class AIBridge extends EventEmitter {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
ws.on('close', () => {
|
|
61
|
-
console.log('[AI Bridge]
|
|
61
|
+
console.log('[AI Bridge] AI 연결 해제');
|
|
62
62
|
this.client = null;
|
|
63
63
|
this.connected = false;
|
|
64
64
|
this.emit('disconnected');
|
|
@@ -84,7 +84,7 @@ class AIBridge extends EventEmitter {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* AI에서 온 명령 처리
|
|
88
88
|
*/
|
|
89
89
|
_handleCommand(msg) {
|
|
90
90
|
const { type, payload } = msg;
|
|
@@ -92,7 +92,7 @@ class AIBridge extends EventEmitter {
|
|
|
92
92
|
switch (type) {
|
|
93
93
|
// === 행동 제어 ===
|
|
94
94
|
case 'action':
|
|
95
|
-
//
|
|
95
|
+
// AI가 펫의 행동을 직접 지시
|
|
96
96
|
// payload: { state: 'walking'|'excited'|..., duration?: ms }
|
|
97
97
|
this.emit('action', payload);
|
|
98
98
|
break;
|
|
@@ -111,7 +111,7 @@ class AIBridge extends EventEmitter {
|
|
|
111
111
|
|
|
112
112
|
// === 말하기 ===
|
|
113
113
|
case 'speak':
|
|
114
|
-
//
|
|
114
|
+
// AI가 펫을 통해 사용자에게 말함
|
|
115
115
|
// payload: { text: string, style?: 'normal'|'thought'|'shout' }
|
|
116
116
|
this.emit('speak', payload);
|
|
117
117
|
break;
|
|
@@ -133,6 +133,12 @@ class AIBridge extends EventEmitter {
|
|
|
133
133
|
this.emit('drop_file', payload);
|
|
134
134
|
break;
|
|
135
135
|
|
|
136
|
+
case 'smart_file_op':
|
|
137
|
+
// 스마트 파일 조작 (텔레그램 또는 AI에서 트리거)
|
|
138
|
+
// payload: { phase: 'pick_up'|'drop'|'complete', fileName?, targetName?, ... }
|
|
139
|
+
this.emit('smart_file_op', payload);
|
|
140
|
+
break;
|
|
141
|
+
|
|
136
142
|
// === 외형 변화 ===
|
|
137
143
|
case 'evolve':
|
|
138
144
|
// 진화 트리거
|
|
@@ -188,6 +194,49 @@ class AIBridge extends EventEmitter {
|
|
|
188
194
|
this.emit('query_windows', payload);
|
|
189
195
|
break;
|
|
190
196
|
|
|
197
|
+
// === 커스텀 이동 패턴 ===
|
|
198
|
+
case 'register_movement':
|
|
199
|
+
// AI가 커스텀 이동 패턴 등록
|
|
200
|
+
// payload: { name: string, definition: { type: 'waypoints'|'formula'|'sequence', ... } }
|
|
201
|
+
this.emit('register_movement', payload);
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
case 'custom_move':
|
|
205
|
+
// 등록된 커스텀 이동 패턴 실행
|
|
206
|
+
// payload: { name: string, params?: object }
|
|
207
|
+
this.emit('custom_move', payload);
|
|
208
|
+
break;
|
|
209
|
+
|
|
210
|
+
case 'stop_custom_move':
|
|
211
|
+
// 현재 커스텀 이동 강제 중지
|
|
212
|
+
// payload: {}
|
|
213
|
+
this.emit('stop_custom_move', payload);
|
|
214
|
+
break;
|
|
215
|
+
|
|
216
|
+
case 'list_movements':
|
|
217
|
+
// 등록된 이동 패턴 목록 요청 → 응답은 renderer에서 reportToAI로 전송
|
|
218
|
+
// payload: {}
|
|
219
|
+
this.emit('list_movements', payload);
|
|
220
|
+
break;
|
|
221
|
+
|
|
222
|
+
// === 캐릭터 커스터마이징 ===
|
|
223
|
+
case 'set_character':
|
|
224
|
+
// AI가 생성한 캐릭터 데이터 적용
|
|
225
|
+
// payload: { colorMap?: {...}, frames?: {...} }
|
|
226
|
+
this.emit('set_character', payload);
|
|
227
|
+
break;
|
|
228
|
+
|
|
229
|
+
case 'reset_character':
|
|
230
|
+
// 원래 캐릭터로 리셋
|
|
231
|
+
this.emit('reset_character', payload);
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case 'set_persona':
|
|
235
|
+
// 봇 인격체 전환 (Incarnation 모드)
|
|
236
|
+
// payload: { name, personality, speakingStyle, color?, ... }
|
|
237
|
+
this.emit('set_persona', payload);
|
|
238
|
+
break;
|
|
239
|
+
|
|
191
240
|
// === 컨텍스트 질의 ===
|
|
192
241
|
case 'query_state':
|
|
193
242
|
// 현재 펫 상태 요청
|
|
@@ -201,7 +250,7 @@ class AIBridge extends EventEmitter {
|
|
|
201
250
|
|
|
202
251
|
// === AI 의사결정 결과 ===
|
|
203
252
|
case 'ai_decision':
|
|
204
|
-
//
|
|
253
|
+
// AI의 종합적 의사결정
|
|
205
254
|
// payload: { action, speech?, emotion?, reasoning? }
|
|
206
255
|
this.emit('ai_decision', payload);
|
|
207
256
|
break;
|
|
@@ -212,7 +261,7 @@ class AIBridge extends EventEmitter {
|
|
|
212
261
|
}
|
|
213
262
|
|
|
214
263
|
/**
|
|
215
|
-
*
|
|
264
|
+
* AI에 이벤트 전송
|
|
216
265
|
*/
|
|
217
266
|
send(type, payload) {
|
|
218
267
|
if (!this.client || this.client.readyState !== WebSocket.OPEN) return false;
|
|
@@ -224,7 +273,7 @@ class AIBridge extends EventEmitter {
|
|
|
224
273
|
}
|
|
225
274
|
}
|
|
226
275
|
|
|
227
|
-
// === 사용자 이벤트 리포트 (ClawMate →
|
|
276
|
+
// === 사용자 이벤트 리포트 (ClawMate → AI) ===
|
|
228
277
|
|
|
229
278
|
reportUserClick(position) {
|
|
230
279
|
this.send('user_event', {
|
|
@@ -285,6 +334,17 @@ class AIBridge extends EventEmitter {
|
|
|
285
334
|
});
|
|
286
335
|
}
|
|
287
336
|
|
|
337
|
+
/**
|
|
338
|
+
* 메트릭 데이터를 AI에 전송
|
|
339
|
+
* 렌더러에서 수집한 펫 동작 품질 메트릭을 AI에 전달
|
|
340
|
+
*/
|
|
341
|
+
reportMetrics(summary) {
|
|
342
|
+
this.send('metrics_report', {
|
|
343
|
+
metrics: summary,
|
|
344
|
+
timestamp: Date.now(),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
|
|
288
348
|
// === 상태 업데이트 ===
|
|
289
349
|
|
|
290
350
|
updatePetState(updates) {
|
package/main/ai-connector.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* AI 에이전트 측 커넥터
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* AI가 ClawMate에 접속해서 펫을 조종하는 클라이언트.
|
|
5
|
+
* ClawMate 플러그인(index.js)에서 사용됨.
|
|
6
6
|
*
|
|
7
7
|
* 사용 예:
|
|
8
|
-
* const connector = new
|
|
8
|
+
* const connector = new ClawMateConnector();
|
|
9
9
|
* await connector.connect();
|
|
10
10
|
* connector.speak('안녕! 오늘 뭐 할 거야?');
|
|
11
11
|
* connector.action('excited');
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
const WebSocket = require('ws');
|
|
15
15
|
const EventEmitter = require('events');
|
|
16
16
|
|
|
17
|
-
class
|
|
17
|
+
class ClawMateConnector extends EventEmitter {
|
|
18
18
|
constructor(port = 9320) {
|
|
19
19
|
super();
|
|
20
20
|
this.port = port;
|
|
@@ -81,12 +81,12 @@ class OpenClawConnector extends EventEmitter {
|
|
|
81
81
|
break;
|
|
82
82
|
|
|
83
83
|
case 'user_event':
|
|
84
|
-
// 사용자 이벤트 →
|
|
84
|
+
// 사용자 이벤트 → AI가 반응 결정
|
|
85
85
|
this.emit('user_event', payload);
|
|
86
86
|
break;
|
|
87
87
|
|
|
88
88
|
case 'screen_capture':
|
|
89
|
-
// 화면 캡처 응답 →
|
|
89
|
+
// 화면 캡처 응답 → AI가 분석
|
|
90
90
|
this.emit('screen_capture', payload);
|
|
91
91
|
break;
|
|
92
92
|
|
|
@@ -95,6 +95,11 @@ class OpenClawConnector extends EventEmitter {
|
|
|
95
95
|
this.emit('window_positions', payload);
|
|
96
96
|
break;
|
|
97
97
|
|
|
98
|
+
case 'metrics_report':
|
|
99
|
+
// 메트릭 데이터 수신 → AI가 분석
|
|
100
|
+
this.emit('metrics_report', payload);
|
|
101
|
+
break;
|
|
102
|
+
|
|
98
103
|
case 'heartbeat':
|
|
99
104
|
break;
|
|
100
105
|
}
|
|
@@ -110,7 +115,7 @@ class OpenClawConnector extends EventEmitter {
|
|
|
110
115
|
}
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
// ===
|
|
118
|
+
// === AI → ClawMate 명령 API ===
|
|
114
119
|
|
|
115
120
|
/** 펫이 말하게 함 */
|
|
116
121
|
speak(text, style = 'normal') {
|
|
@@ -159,13 +164,13 @@ class OpenClawConnector extends EventEmitter {
|
|
|
159
164
|
|
|
160
165
|
/**
|
|
161
166
|
* AI 종합 의사결정 전송
|
|
162
|
-
*
|
|
167
|
+
* AI가 상황을 분석하고 내린 결정을 한번에 전송
|
|
163
168
|
*/
|
|
164
169
|
decide(decision) {
|
|
165
170
|
return this._send('ai_decision', decision);
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
// === 공간 이동 API (
|
|
173
|
+
// === 공간 이동 API (펫이 컴퓨터를 "집"처럼 돌아다님) ===
|
|
169
174
|
|
|
170
175
|
/** 특정 위치로 점프 */
|
|
171
176
|
jumpTo(x, y) {
|
|
@@ -197,6 +202,79 @@ class OpenClawConnector extends EventEmitter {
|
|
|
197
202
|
return this._send('query_windows', {});
|
|
198
203
|
}
|
|
199
204
|
|
|
205
|
+
// === 커스텀 이동 패턴 API ===
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 커스텀 이동 패턴 등록
|
|
209
|
+
* ClawMate에 새로운 이동 패턴을 동적으로 추가
|
|
210
|
+
*
|
|
211
|
+
* @param {string} name - 패턴 이름 (예: 'figure8', 'spiral')
|
|
212
|
+
* @param {object} definition - 패턴 정의
|
|
213
|
+
* type: 'waypoints' | 'formula' | 'sequence'
|
|
214
|
+
* waypoints?: [{x, y, pause?}] — 웨이포인트 순차 이동
|
|
215
|
+
* formula?: { xAmp, yAmp, xFreq, yFreq, xPhase, yPhase } — 수학 궤도
|
|
216
|
+
* sequence?: ['zigzag', 'shake'] — 기존 패턴 순차 실행
|
|
217
|
+
* duration?: number — 지속 시간 (ms, formula 타입)
|
|
218
|
+
* speed?: number — 이동 속도
|
|
219
|
+
*
|
|
220
|
+
* 사용 예:
|
|
221
|
+
* connector.registerMovement('figure8', {
|
|
222
|
+
* type: 'formula',
|
|
223
|
+
* formula: { xAmp: 80, yAmp: 40, xFreq: 1, yFreq: 2 },
|
|
224
|
+
* duration: 4000,
|
|
225
|
+
* });
|
|
226
|
+
*/
|
|
227
|
+
registerMovement(name, definition) {
|
|
228
|
+
return this._send('register_movement', { name, definition });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* 등록된 커스텀 이동 패턴 실행
|
|
233
|
+
*
|
|
234
|
+
* @param {string} name - 실행할 패턴 이름
|
|
235
|
+
* 사전 등록 패턴: 'zigzag', 'patrol', 'circle', 'shake', 'dance'
|
|
236
|
+
* 또는 registerMovement()로 등록한 커스텀 패턴
|
|
237
|
+
* @param {object} params - 실행 파라미터 (패턴별로 다름)
|
|
238
|
+
*
|
|
239
|
+
* 사용 예:
|
|
240
|
+
* connector.customMove('zigzag', { distance: 200, amplitude: 30 });
|
|
241
|
+
* connector.customMove('patrol', { pointAX: 100, pointBX: 500, laps: 5 });
|
|
242
|
+
* connector.customMove('shake', { intensity: 6, duration: 1000 });
|
|
243
|
+
*/
|
|
244
|
+
customMove(name, params = {}) {
|
|
245
|
+
return this._send('custom_move', { name, params });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** 현재 실행 중인 커스텀 이동 강제 중지 */
|
|
249
|
+
stopCustomMove() {
|
|
250
|
+
return this._send('stop_custom_move', {});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** 등록된 이동 패턴 목록 요청 (응답은 user_event로 수신) */
|
|
254
|
+
listMovements() {
|
|
255
|
+
return this._send('list_movements', {});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** 스마트 파일 조작 명령 전송 */
|
|
259
|
+
smartFileOp(payload) {
|
|
260
|
+
return this._send('smart_file_op', payload);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** 캐릭터 데이터 전송 (AI 생성 캐릭터 적용) */
|
|
264
|
+
setCharacter(data) {
|
|
265
|
+
return this._send('set_character', data);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** 원래 캐릭터로 리셋 */
|
|
269
|
+
resetCharacter() {
|
|
270
|
+
return this._send('reset_character', {});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** 인격체 전환 (Incarnation 모드에서 봇 인격 반영) */
|
|
274
|
+
setPersona(data) {
|
|
275
|
+
return this._send('set_persona', data);
|
|
276
|
+
}
|
|
277
|
+
|
|
200
278
|
/**
|
|
201
279
|
* 현재 펫 상태 요청 (Promise 반환)
|
|
202
280
|
* 서버에서 state_response가 오면 resolve, 타임아웃 시 캐시된 상태 반환
|
|
@@ -234,6 +312,11 @@ class OpenClawConnector extends EventEmitter {
|
|
|
234
312
|
this.on('user_event', callback);
|
|
235
313
|
}
|
|
236
314
|
|
|
315
|
+
/** 메트릭 리포트 리스너 등록 */
|
|
316
|
+
onMetrics(callback) {
|
|
317
|
+
this.on('metrics_report', callback);
|
|
318
|
+
}
|
|
319
|
+
|
|
237
320
|
disconnect() {
|
|
238
321
|
this._autoReconnect = false;
|
|
239
322
|
if (this._reconnectTimer) clearTimeout(this._reconnectTimer);
|
|
@@ -241,4 +324,4 @@ class OpenClawConnector extends EventEmitter {
|
|
|
241
324
|
}
|
|
242
325
|
}
|
|
243
326
|
|
|
244
|
-
module.exports = {
|
|
327
|
+
module.exports = { ClawMateConnector };
|
package/main/autostart.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* macOS: LaunchAgent plist
|
|
6
6
|
* Linux: ~/.config/autostart/ 디렉토리에 .desktop 파일 생성
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* ClawMate가 먼저 혼자 자율 모드로 돌다가 →
|
|
8
|
+
* AI가 나중에 켜져도, ClawMate는 이미 돌고 있어서 바로 연결됨.
|
|
9
|
+
* ClawMate가 먼저 혼자 자율 모드로 돌다가 → AI 연결되면 AI 모드 전환.
|
|
10
10
|
*/
|
|
11
11
|
const { app } = require('electron');
|
|
12
12
|
const fs = require('fs');
|
|
@@ -22,7 +22,7 @@ function getDesktopFileContent() {
|
|
|
22
22
|
'[Desktop Entry]',
|
|
23
23
|
'Type=Application',
|
|
24
24
|
'Name=ClawMate',
|
|
25
|
-
'Comment=
|
|
25
|
+
'Comment=ClawMate Desktop Pet',
|
|
26
26
|
`Exec=${process.execPath} ${path.resolve(__dirname, '..')}`,
|
|
27
27
|
'X-GNOME-Autostart-enabled=true',
|
|
28
28
|
'Hidden=false',
|