clawmate 1.4.0 → 1.4.2

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 (42) hide show
  1. package/index.js +441 -442
  2. package/main/ai-bridge.js +59 -59
  3. package/main/ai-connector.js +60 -60
  4. package/main/autostart.js +6 -6
  5. package/main/desktop-path.js +4 -4
  6. package/main/file-command-parser.js +46 -46
  7. package/main/file-ops.js +27 -27
  8. package/main/index.js +17 -17
  9. package/main/ipc-handlers.js +24 -24
  10. package/main/manifest.js +2 -2
  11. package/main/platform.js +16 -16
  12. package/main/smart-file-ops.js +64 -64
  13. package/main/store.js +1 -1
  14. package/main/telegram.js +137 -137
  15. package/main/tray.js +61 -61
  16. package/main/updater.js +13 -13
  17. package/openclaw.plugin.json +1 -1
  18. package/package.json +2 -2
  19. package/preload/preload.js +18 -18
  20. package/renderer/css/effects.css +6 -6
  21. package/renderer/css/pet.css +8 -8
  22. package/renderer/css/speech.css +5 -5
  23. package/renderer/first-run.html +14 -14
  24. package/renderer/index.html +4 -4
  25. package/renderer/js/ai-controller.js +91 -91
  26. package/renderer/js/app.js +24 -24
  27. package/renderer/js/browser-watcher.js +32 -32
  28. package/renderer/js/character.js +33 -33
  29. package/renderer/js/interactions.js +21 -21
  30. package/renderer/js/memory.js +60 -60
  31. package/renderer/js/metrics.js +141 -141
  32. package/renderer/js/mode-manager.js +13 -13
  33. package/renderer/js/pet-engine.js +236 -236
  34. package/renderer/js/speech.js +19 -19
  35. package/renderer/js/state-machine.js +23 -23
  36. package/renderer/js/time-aware.js +15 -15
  37. package/renderer/launcher.html +8 -8
  38. package/shared/constants.js +11 -11
  39. package/shared/messages.js +130 -130
  40. package/shared/personalities.js +44 -44
  41. package/skills/launch-pet/index.js +57 -47
  42. package/skills/launch-pet/skill.json +12 -23
@@ -1,8 +1,8 @@
1
1
  <!DOCTYPE html>
2
- <html lang="ko">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <title>ClawMate - 만남</title>
5
+ <title>ClawMate - First Meeting</title>
6
6
  <link rel="stylesheet" href="css/launcher.css">
7
7
  <style>
8
8
  .intro-text {
@@ -28,38 +28,38 @@
28
28
  <div class="launcher">
29
29
  <div class="launcher-header">
30
30
  <div class="welcome-anim">\u{1F99E}</div>
31
- <h1>환영합니다!</h1>
32
- <p class="subtitle">ClawMate에 오신 걸 환영해요</p>
31
+ <h1>Welcome!</h1>
32
+ <p class="subtitle">Welcome to ClawMate</p>
33
33
  </div>
34
34
 
35
35
  <div class="intro-text">
36
- <p>바탕화면 위를 돌아다니며 함께하는 작은 친구를 소개합니다.</p>
37
- <p>오래 함께할수록 친구의 모습이 점점 변해갈 거예요!</p>
38
- <p><strong>어떤 모드로 시작할까요?</strong></p>
36
+ <p>Meet your little friend who roams around your desktop with you.</p>
37
+ <p>The longer you stay together, the more your friend will evolve!</p>
38
+ <p><strong>Which mode would you like to start with?</strong></p>
39
39
  </div>
40
40
 
41
41
  <div class="launcher-body">
42
42
  <div class="mode-card" id="mode-pet" onclick="selectMode('pet')">
43
43
  <div class="mode-icon">\u{1F990}</div>
44
44
  <h2>Clawby</h2>
45
- <p>귀여운 동반자</p>
46
- <span class="mode-desc">장난기 많고 다정한 꼬마 랍스터!</span>
45
+ <p>Cute companion</p>
46
+ <span class="mode-desc">A playful and affectionate little lobster!</span>
47
47
  </div>
48
48
 
49
49
  <div class="mode-card" id="mode-incarnation" onclick="selectMode('incarnation')">
50
50
  <div class="mode-icon">\u{1F980}</div>
51
51
  <h2>Claw</h2>
52
- <p>육체를 얻은 존재</p>
53
- <span class="mode-desc">침착하고 카리스마 있는 Claw!</span>
52
+ <p>An entity given physical form</p>
53
+ <span class="mode-desc">The calm and charismatic Claw!</span>
54
54
  </div>
55
55
  </div>
56
56
 
57
57
  <button class="start-btn" id="start-btn" disabled onclick="startFirstRun()">
58
- 모드를 선택해주세요
58
+ Select a mode
59
59
  </button>
60
60
 
61
61
  <p class="tip" style="text-align:center;color:#999;font-size:11px;margin-top:12px;">
62
- 나중에 3번 연속 클릭이나 트레이 메뉴에서 모드를 바꿀 있어요
62
+ You can change modes later with a triple-click or from the tray menu
63
63
  </p>
64
64
  </div>
65
65
 
@@ -72,7 +72,7 @@
72
72
  document.getElementById('mode-' + mode).classList.add('selected');
73
73
  const btn = document.getElementById('start-btn');
74
74
  btn.disabled = false;
75
- btn.textContent = mode === 'pet' ? 'Clawby와 시작!' : 'Claw과 시작!';
75
+ btn.textContent = mode === 'pet' ? 'Start with Clawby!' : 'Start with Claw!';
76
76
  }
77
77
 
78
78
  async function startFirstRun() {
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html lang="ko">
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -39,7 +39,7 @@
39
39
  </head>
40
40
  <body>
41
41
  <div id="world">
42
- <!-- 레펠 실(thread) 시각화용 SVG 레이어 -->
42
+ <!-- SVG layer for rappel thread visualization -->
43
43
  <svg id="thread-svg" style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:999;">
44
44
  <line id="thread-line" x1="0" y1="0" x2="0" y2="0" stroke="#888" stroke-width="1" style="display:none;" />
45
45
  </svg>
@@ -47,11 +47,11 @@
47
47
  <div id="speech-container"></div>
48
48
  </div>
49
49
 
50
- <!-- Shared 데이터 -->
50
+ <!-- Shared data -->
51
51
  <script src="../shared/messages.js"></script>
52
52
  <script src="../shared/personalities.js"></script>
53
53
 
54
- <!-- 모듈 -->
54
+ <!-- Modules -->
55
55
  <script src="js/character.js"></script>
56
56
  <script src="js/state-machine.js"></script>
57
57
  <script src="js/pet-engine.js"></script>
@@ -1,27 +1,27 @@
1
1
  /**
2
- * AI 행동 컨트롤러
2
+ * AI Behavior Controller
3
3
  *
4
- * AI 연결되면 AI 모든 행동을 결정
5
- * AI 끊기면 → 자율 모드 (기존 FSM) 로 폴백
4
+ * When AI is connected -> AI decides all behaviors
5
+ * When AI disconnects -> Falls back to autonomous mode (existing FSM)
6
6
  *
7
- * AI 결정하는 것:
8
- * - 언제 뭐라고 말할지
9
- * - 어디로 움직일지
10
- * - 어떤 감정을 표현할지
11
- * - 파일을 집을지 말지
12
- * - 사용자 행동에 어떻게 반응할지
7
+ * AI decides:
8
+ * - When and what to say
9
+ * - Where to move
10
+ * - What emotions to express
11
+ * - Whether to pick up files
12
+ * - How to react to user actions
13
13
  */
14
14
  const AIController = (() => {
15
15
  let connected = false;
16
- let autonomousMode = true; // AI 미연결 자율 모드
16
+ let autonomousMode = true; // Autonomous mode when AI is not connected
17
17
  let pendingDecision = null;
18
18
  let lastAIAction = 0;
19
19
 
20
- // AI 연결 상태에 따라 preload 통해 IPC로 통신
21
- // (main 프로세스의 AIBridge가 WebSocket 관리)
20
+ // Communicates via IPC through preload based on AI connection status
21
+ // (AIBridge in main process manages WebSocket)
22
22
 
23
23
  function init() {
24
- // main 프로세스에서 AI 명령이 오면 실행
24
+ // Execute when AI commands arrive from the main process
25
25
  if (window.clawmate.onAICommand) {
26
26
  window.clawmate.onAICommand((command) => {
27
27
  handleAICommand(command);
@@ -32,7 +32,7 @@ const AIController = (() => {
32
32
  window.clawmate.onAIConnected(() => {
33
33
  connected = true;
34
34
  autonomousMode = false;
35
- Speech.show('AI 연결됨... 의식이 깨어난다.');
35
+ Speech.show('AI connected... consciousness awakens.');
36
36
  StateMachine.forceState('excited');
37
37
  });
38
38
  }
@@ -41,13 +41,13 @@ const AIController = (() => {
41
41
  window.clawmate.onAIDisconnected(() => {
42
42
  connected = false;
43
43
  autonomousMode = true;
44
- Speech.show('...혼자가 됐다. 알아서 놀아야지!');
44
+ Speech.show('...left alone. Gotta keep myself entertained!');
45
45
  });
46
46
  }
47
47
  }
48
48
 
49
49
  /**
50
- * AI로부터 명령 실행
50
+ * Execute commands received from AI
51
51
  */
52
52
  function handleAICommand(command) {
53
53
  const { type, payload } = command;
@@ -84,12 +84,12 @@ const AIController = (() => {
84
84
 
85
85
  case 'carry_file':
86
86
  StateMachine.forceState('carrying');
87
- Speech.show(`${payload.fileName} 집었다!`);
87
+ Speech.show(`Grabbed ${payload.fileName}!`);
88
88
  break;
89
89
 
90
90
  case 'drop_file':
91
91
  StateMachine.forceState('idle');
92
- Speech.show('내려놨다!');
92
+ Speech.show('Dropped it!');
93
93
  break;
94
94
 
95
95
  case 'set_mode':
@@ -97,64 +97,64 @@ const AIController = (() => {
97
97
  break;
98
98
 
99
99
  case 'evolve':
100
- // AI 직접 진화 결정
100
+ // AI directly decides evolution
101
101
  if (typeof Memory !== 'undefined') {
102
- Speech.show(window._messages?.evolution?.[`stage_${payload.stage}`] || '변하고 있어...!');
102
+ Speech.show(window._messages?.evolution?.[`stage_${payload.stage}`] || 'I\'m changing...!');
103
103
  }
104
104
  break;
105
105
 
106
106
  case 'accessorize':
107
- // 임시 악세사리
107
+ // Temporary accessory
108
108
  break;
109
109
 
110
110
  case 'ai_decision':
111
- // 종합 의사결정 여러 행동을 순서대로 실행
111
+ // Comprehensive decision -- execute multiple actions in sequence
112
112
  executeDecision(payload);
113
113
  break;
114
114
 
115
- // === 공간 이동 명령 ===
115
+ // === Spatial movement commands ===
116
116
 
117
117
  case 'jump_to':
118
- // 특정 위치로 점프
118
+ // Jump to a specific position
119
119
  // payload: { x, y }
120
120
  PetEngine.jumpTo(payload.x, payload.y);
121
121
  break;
122
122
 
123
123
  case 'rappel':
124
- // 레펠 시작 (천장/벽에서 타고 내려가기)
124
+ // Start rappelling (descend on a thread from ceiling/wall)
125
125
  PetEngine.startRappel();
126
126
  break;
127
127
 
128
128
  case 'release_thread':
129
- // 레펠 해제 (낙하)
129
+ // Release rappel thread (free fall)
130
130
  PetEngine.releaseThread();
131
131
  break;
132
132
 
133
133
  case 'move_to_center':
134
- // 화면 중앙으로 이동 (물리적 방법으로)
134
+ // Move to screen center (using physics-based methods)
135
135
  PetEngine.moveToCenter();
136
136
  break;
137
137
 
138
138
  case 'walk_on_window':
139
- // 특정 윈도우 타이틀바 위로 이동
139
+ // Move onto a specific window's title bar
140
140
  // payload: { windowId, x, y }
141
141
  PetEngine.jumpTo(payload.x, payload.y);
142
142
  break;
143
143
 
144
- // === 커스텀 이동 패턴 ===
144
+ // === Custom movement patterns ===
145
145
 
146
146
  case 'register_movement':
147
- // AI가 JSON으로 이동 패턴 정의를 보내면 등록
147
+ // Register movement pattern definition sent by AI as JSON
148
148
  // payload: { name, definition }
149
- // definition: { type, params } 타입별 파라미터
149
+ // definition: { type, params } -- parameters per type
150
150
  _registerAIMovement(payload.name, payload.definition);
151
151
  break;
152
152
 
153
153
  case 'custom_move':
154
- // 등록된 커스텀 이동 패턴 실행
154
+ // Execute a registered custom movement pattern
155
155
  // payload: { name, params? }
156
156
  if (!PetEngine.executeCustomMovement(payload.name, payload.params || {})) {
157
- // 실행 실패 AI에 알림
157
+ // Notify AI on execution failure
158
158
  if (window.clawmate.reportToAI) {
159
159
  window.clawmate.reportToAI('custom_move_failed', {
160
160
  name: payload.name,
@@ -165,12 +165,12 @@ const AIController = (() => {
165
165
  break;
166
166
 
167
167
  case 'stop_custom_move':
168
- // 현재 커스텀 이동 강제 중지
168
+ // Force stop current custom movement
169
169
  PetEngine.stopCustomMovement();
170
170
  break;
171
171
 
172
172
  case 'list_movements':
173
- // 등록된 이동 패턴 목록 요청
173
+ // Request list of registered movement patterns
174
174
  if (window.clawmate.reportToAI) {
175
175
  window.clawmate.reportToAI('movement_list', {
176
176
  movements: PetEngine.getRegisteredMovements(),
@@ -178,14 +178,14 @@ const AIController = (() => {
178
178
  }
179
179
  break;
180
180
 
181
- // === 캐릭터 커스터마이징 ===
181
+ // === Character customization ===
182
182
  case 'set_character':
183
- // AI가 생성한 캐릭터 데이터 적용
183
+ // Apply new character data generated by AI
184
184
  Character.setCharacterData(payload);
185
185
  if (payload.speech) {
186
186
  Speech.show(payload.speech);
187
187
  } else {
188
- Speech.show('변신 완료!');
188
+ Speech.show('Transformation complete!');
189
189
  }
190
190
  StateMachine.forceState('excited');
191
191
  setTimeout(() => {
@@ -194,24 +194,24 @@ const AIController = (() => {
194
194
  break;
195
195
 
196
196
  case 'reset_character':
197
- // 원래 캐릭터로 복원
197
+ // Restore to original character
198
198
  Character.resetCharacter();
199
- Speech.show('원래 모습으로 돌아왔어!');
199
+ Speech.show('Back to my original form!');
200
200
  StateMachine.forceState('excited');
201
201
  break;
202
202
 
203
- // === 인격체 전환 (Incarnation 모드) ===
203
+ // === Persona switching (Incarnation mode) ===
204
204
  case 'set_persona':
205
- // 인격체 데이터 적용
205
+ // Apply bot persona data
206
206
  if (typeof ModeManager !== 'undefined') {
207
207
  ModeManager.setPersona(payload);
208
208
  const name = payload.name || 'Claw';
209
- Speech.show(`${name} 인격이 깨어났다.`);
209
+ Speech.show(`${name}'s persona has awakened.`);
210
210
  StateMachine.forceState('excited');
211
211
  }
212
212
  break;
213
213
 
214
- // === 스마트 파일 조작 애니메이션 ===
214
+ // === Smart file operation animations ===
215
215
  case 'smart_file_op':
216
216
  handleSmartFileOp(payload);
217
217
  break;
@@ -219,111 +219,111 @@ const AIController = (() => {
219
219
  }
220
220
 
221
221
  /**
222
- * 스마트 파일 조작 애니메이션 처리
222
+ * Handle smart file operation animations
223
223
  *
224
- * 텔레그램이나 AI에서 트리거된 파일 이동 작업의
225
- * 단계(phase)에 따라 애니메이션을 순차 실행.
224
+ * Sequentially execute pet animations for each phase
225
+ * of file move operations triggered by Telegram or AI.
226
226
  *
227
227
  * phase:
228
- * - start: 작업 시작, 파일 표시
229
- * - pick_up: 파일 집어들기 (carrying 상태 + 말풍선)
230
- * - drop: 파일 내려놓기 (걷기 상태 + 말풍선)
231
- * - complete: 완료 (excited 상태 + 결과 말풍선)
232
- * - error: 오류 (scared 상태 + 에러 말풍선)
228
+ * - start: Operation begins, show total file count
229
+ * - pick_up: Pick up file (carrying state + speech bubble)
230
+ * - drop: Drop file (walking state + speech bubble)
231
+ * - complete: Done (excited state + result speech bubble)
232
+ * - error: Error (scared state + error speech bubble)
233
233
  */
234
234
  function handleSmartFileOp(payload) {
235
235
  switch (payload.phase) {
236
236
  case 'start':
237
237
  StateMachine.forceState('excited');
238
- Speech.show(`${payload.totalFiles} 파일 정리 시작!`);
238
+ Speech.show(`Starting to organize ${payload.totalFiles} files!`);
239
239
  break;
240
240
 
241
241
  case 'pick_up':
242
- // 펫이 파일 위치로 이동 (화면 랜덤 위치)
242
+ // Move pet to file location (random position on screen)
243
243
  _smartFileJumpToSource(payload.index);
244
- // 집어들기 애니메이션
244
+ // Pick up animation
245
245
  setTimeout(() => {
246
246
  StateMachine.forceState('carrying');
247
- Speech.show(`${payload.fileName} 집었다!`);
247
+ Speech.show(`Grabbed ${payload.fileName}!`);
248
248
  }, 400);
249
249
  break;
250
250
 
251
251
  case 'drop':
252
- // 대상 폴더 위치로 이동
252
+ // Move to target folder location
253
253
  _smartFileJumpToTarget(payload.index);
254
- // 내려놓기 애니메이션
254
+ // Drop animation
255
255
  setTimeout(() => {
256
256
  StateMachine.forceState('walking');
257
- Speech.show(`여기! (${payload.targetName})`);
257
+ Speech.show(`Here! (${payload.targetName})`);
258
258
  }, 400);
259
259
  break;
260
260
 
261
261
  case 'complete':
262
262
  StateMachine.forceState('excited');
263
263
  if (payload.movedCount > 0) {
264
- Speech.show(`${payload.movedCount} 파일 옮겼어!`);
264
+ Speech.show(`Moved ${payload.movedCount} files!`);
265
265
  } else {
266
- Speech.show('옮길 파일이 없었어!');
266
+ Speech.show('No files to move!');
267
267
  }
268
268
  break;
269
269
 
270
270
  case 'error':
271
271
  StateMachine.forceState('scared');
272
- Speech.show('앗, 뭔가 잘못됐어...');
272
+ Speech.show('Oops, something went wrong...');
273
273
  break;
274
274
  }
275
275
  }
276
276
 
277
277
  /**
278
- * 파일 집어들기 위치로 점프
279
- * 파일 인덱스에 따라 화면 좌측 영역의 다른 위치로 이동
278
+ * Jump to file pickup location
279
+ * Move to different positions in the left area of the screen based on file index
280
280
  */
281
281
  function _smartFileJumpToSource(index) {
282
282
  const screenW = window.innerWidth;
283
283
  const screenH = window.innerHeight;
284
- // 화면 왼쪽 1/3 영역에서 세로 위치를 파일 인덱스에 따라 분산
284
+ // Distribute vertical positions within the left 1/3 of the screen based on file index
285
285
  const targetX = screenW * 0.1 + (index % 3) * 50;
286
286
  const targetY = screenH * 0.3 + ((index * 80) % (screenH * 0.5));
287
287
  PetEngine.jumpTo(targetX, targetY);
288
288
  }
289
289
 
290
290
  /**
291
- * 파일 내려놓기 위치로 점프
292
- * 화면 오른쪽 영역으로 이동
291
+ * Jump to file drop location
292
+ * Move to the right area of the screen
293
293
  */
294
294
  function _smartFileJumpToTarget(index) {
295
295
  const screenW = window.innerWidth;
296
296
  const screenH = window.innerHeight;
297
- // 화면 오른쪽 1/3 영역
297
+ // Right 1/3 area of the screen
298
298
  const targetX = screenW * 0.7 + (index % 3) * 50;
299
299
  const targetY = screenH * 0.4 + ((index * 60) % (screenH * 0.4));
300
300
  PetEngine.jumpTo(targetX, targetY);
301
301
  }
302
302
 
303
303
  /**
304
- * AI가 JSON으로 정의한 이동 패턴을 동적으로 등록
305
- * 안전한 실행을 위해 Function 생성자 대신 사전정의된 행동 유형 조합 사용
304
+ * Dynamically register movement patterns defined by AI as JSON
305
+ * Uses predefined behavior type combinations instead of Function constructor for safe execution
306
306
  *
307
- * definition 형식:
307
+ * definition format:
308
308
  * {
309
309
  * type: 'waypoints' | 'formula' | 'sequence',
310
- * waypoints?: [{x, y, pause?}], // waypoints 타입
311
- * formula?: { xExpr, yExpr }, // formula 타입 (sin, cos 기반)
312
- * sequence?: ['zigzag', 'shake', ...], // sequence 타입 (기존 패턴 순차 실행)
310
+ * waypoints?: [{x, y, pause?}], // waypoints type
311
+ * formula?: { xExpr, yExpr }, // formula type (sin, cos based)
312
+ * sequence?: ['zigzag', 'shake', ...], // sequence type (execute existing patterns sequentially)
313
313
  * duration?: number,
314
314
  * speed?: number,
315
315
  * }
316
316
  */
317
317
  function _registerAIMovement(name, definition) {
318
318
  if (!name || !definition || !definition.type) {
319
- console.warn('[AIController] 이동 패턴 등록 실패: name, definition.type 필수');
319
+ console.warn('[AIController] Movement pattern registration failed: name and definition.type required');
320
320
  return;
321
321
  }
322
322
 
323
323
  let handler;
324
324
 
325
325
  switch (definition.type) {
326
- // 웨이포인트 타입: 지정된 좌표들을 순서대로 이동
326
+ // Waypoints type: move through specified coordinates in order
327
327
  case 'waypoints':
328
328
  handler = {
329
329
  init(params) {
@@ -340,7 +340,7 @@ const AIController = (() => {
340
340
 
341
341
  const wp = state.waypoints[state.currentIdx];
342
342
 
343
- // 웨이포인트에서 멈춤
343
+ // Pausing at waypoint
344
344
  if (state.pausing) {
345
345
  state.pauseTime -= dt;
346
346
  if (state.pauseTime <= 0) {
@@ -355,7 +355,7 @@ const AIController = (() => {
355
355
  const dist = Math.hypot(dx, dy);
356
356
 
357
357
  if (dist < 5) {
358
- // 웨이포인트 도달
358
+ // Waypoint reached
359
359
  if (wp.pause && wp.pause > 0) {
360
360
  state.pausing = true;
361
361
  state.pauseTime = wp.pause;
@@ -377,7 +377,7 @@ const AIController = (() => {
377
377
  };
378
378
  break;
379
379
 
380
- // 수식 타입: sin/cos 기반 수학적 궤도
380
+ // Formula type: mathematical trajectory based on sin/cos
381
381
  case 'formula':
382
382
  handler = {
383
383
  init(params) {
@@ -409,7 +409,7 @@ const AIController = (() => {
409
409
  };
410
410
  break;
411
411
 
412
- // 시퀀스 타입: 기존 등록된 패턴들을 순차 실행
412
+ // Sequence type: execute existing registered patterns sequentially
413
413
  case 'sequence':
414
414
  handler = {
415
415
  init(params) {
@@ -424,7 +424,7 @@ const AIController = (() => {
424
424
 
425
425
  if (!state.subStarted) {
426
426
  const subName = state.sequence[state.currentIdx];
427
- // 서브 패턴을 직접 실행하지 않고 상태만 추적
427
+ // Track state only without directly executing sub-patterns
428
428
  PetEngine.executeCustomMovement(subName, {
429
429
  x: ctx.x, y: ctx.y,
430
430
  screenW: ctx.screenW, screenH: ctx.screenH,
@@ -440,24 +440,24 @@ const AIController = (() => {
440
440
  break;
441
441
 
442
442
  default:
443
- console.warn(`[AIController] 없는 이동 패턴 타입: ${definition.type}`);
443
+ console.warn(`[AIController] Unknown movement pattern type: ${definition.type}`);
444
444
  return;
445
445
  }
446
446
 
447
447
  PetEngine.registerMovement(name, handler);
448
- console.log(`[AIController] AI 이동 패턴 등록됨: ${name} (${definition.type})`);
448
+ console.log(`[AIController] AI movement pattern registered: ${name} (${definition.type})`);
449
449
  }
450
450
 
451
451
  /**
452
- * AI 종합 의사결정 실행
453
- * AI 상황을 분석하고 내린 복합적 결정
452
+ * Execute AI comprehensive decision
453
+ * A complex decision made by AI after analyzing the situation
454
454
  *
455
- * 예시:
455
+ * Example:
456
456
  * {
457
457
  * action: 'walking',
458
- * speech: '오늘 바탕화면이 어지럽네...',
458
+ * speech: 'The desktop looks a bit messy today...',
459
459
  * emotion: 'curious',
460
- * reasoning: '바탕화면 파일이 15 이상 감지됨'
460
+ * reasoning: 'Detected 15+ files on desktop'
461
461
  * }
462
462
  */
463
463
  function executeDecision(decision) {
@@ -474,7 +474,7 @@ const AIController = (() => {
474
474
  }
475
475
 
476
476
  if (decision.moveTo) {
477
- // 이동 방법에 따라 다른 물리 동작 사용
477
+ // Use different physics-based movement depending on method
478
478
  if (decision.moveTo.method === 'jump') {
479
479
  PetEngine.jumpTo(decision.moveTo.x, decision.moveTo.y);
480
480
  } else if (decision.moveTo.method === 'rappel') {
@@ -488,7 +488,7 @@ const AIController = (() => {
488
488
  }
489
489
 
490
490
  /**
491
- * 감정 행동 매핑
491
+ * Emotion -> behavior mapping
492
492
  */
493
493
  function applyEmotion(emotion) {
494
494
  const emotionMap = {
@@ -507,7 +507,7 @@ const AIController = (() => {
507
507
  StateMachine.forceState(state);
508
508
  }
509
509
 
510
- // === 사용자 이벤트 AI 리포트 ===
510
+ // === User events -> Report to AI ===
511
511
 
512
512
  function reportClick(position) {
513
513
  if (window.clawmate.reportToAI) {