companionbot 0.14.0 → 0.15.1
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 +93 -264
- package/dist/agents/manager.js +69 -11
- package/dist/telegram/bot.js +3 -1
- package/dist/telegram/handlers/commands.js +32 -6
- package/dist/telegram/utils/prompt.js +3 -43
- package/dist/tools/agent.js +53 -0
- package/dist/tools/file.js +63 -0
- package/dist/tools/index.js +99 -961
- package/dist/tools/memory.js +54 -0
- package/dist/tools/model.js +21 -0
- package/dist/tools/pathCheck.js +3 -4
- package/dist/tools/schedule.js +343 -0
- package/dist/tools/session.js +227 -0
- package/dist/tools/utils.js +39 -0
- package/dist/tools/weather.js +39 -0
- package/dist/tools/web.js +103 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,327 +1,156 @@
|
|
|
1
1
|
# CompanionBot
|
|
2
2
|
|
|
3
|
-
> Claude
|
|
3
|
+
> Claude 기반 개인 AI 친구 - 텔레그램 봇
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/companionbot)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## ✨ 특징
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
11
|
+
- 🧠 **Extended Thinking** - Claude의 사고 과정 활용
|
|
12
|
+
- 🔍 **시맨틱 메모리** - 로컬 임베딩으로 관련 기억 검색
|
|
13
|
+
- ⚡ **빠른 응답** - 사전 로딩, 병렬 처리, 스마트 캐싱
|
|
14
|
+
- 🎭 **페르소나** - 이름, 성격, 말투 커스터마이징
|
|
15
|
+
- 🔧 **20+ 도구** - 파일, 웹, 일정, 메모리 등
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- **웹 검색** - "최신 React 뉴스 검색해줘" (Brave Search API)
|
|
19
|
-
- **웹 페이지 읽기** - URL 내용을 가져와서 분석/요약
|
|
20
|
-
|
|
21
|
-
### ⏰ 일정 관리
|
|
22
|
-
- **리마인더** - "10분 뒤에 알려줘"
|
|
23
|
-
- **Google Calendar 연동** - 일정 조회/추가/삭제
|
|
24
|
-
- **일일 브리핑** - 매일 아침 날씨와 일정 알림
|
|
25
|
-
- **Heartbeat** - 주기적으로 체크리스트 확인 후 알림
|
|
26
|
-
|
|
27
|
-
### 🕐 스케줄링 (v0.3.0)
|
|
28
|
-
- **Cron 작업** - "매일 아침 9시에 뉴스 알려줘", "평일 오후 6시에 퇴근 알림"
|
|
29
|
-
- **일회성 예약** - "내일 오전 9시에 알려줘"
|
|
30
|
-
- **반복 작업** - "30분마다 주식 가격 확인해줘"
|
|
31
|
-
|
|
32
|
-
### 🤖 고급 기능 (v0.3.0+)
|
|
33
|
-
- **서브 에이전트** - 복잡한 작업을 백그라운드에서 처리
|
|
34
|
-
- **백그라운드 실행** - 긴 명령어를 백그라운드에서 실행하고 결과 확인
|
|
35
|
-
- **파일 시스템** - 워크스페이스 내 파일 읽기/쓰기/편집
|
|
36
|
-
- **일일 메모리** - 대화 내용 자동 저장
|
|
37
|
-
|
|
38
|
-
### 🧠 시맨틱 메모리 (v0.6.0)
|
|
39
|
-
- **벡터 검색** - 임베딩 기반 관련 메모리 검색
|
|
40
|
-
- **자동 인덱싱** - 메모리 파일 자동 청크 분할 및 임베딩
|
|
41
|
-
- **유사도 매칭** - 의미 기반으로 관련 대화 기록 찾기
|
|
42
|
-
|
|
43
|
-
### 🔧 시스템 (v0.6.0)
|
|
44
|
-
- **헬스 체크** - 봇 상태 모니터링 (uptime, 메시지 수, 오류 수)
|
|
45
|
-
- **업데이트 알림** - 새 버전 출시 시 자동 알림
|
|
46
|
-
|
|
47
|
-
## 설치
|
|
48
|
-
|
|
49
|
-
### 사전 준비
|
|
50
|
-
|
|
51
|
-
- **Node.js 18+** ([다운로드](https://nodejs.org))
|
|
52
|
-
- **Telegram Bot Token** - [@BotFather](https://t.me/BotFather)에서 봇 생성 후 발급
|
|
53
|
-
- **Anthropic API Key** - [console.anthropic.com](https://console.anthropic.com)에서 발급
|
|
54
|
-
|
|
55
|
-
#### Linux 사용자 (keytar 의존성)
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
# Debian/Ubuntu
|
|
59
|
-
sudo apt-get install libsecret-1-dev
|
|
60
|
-
|
|
61
|
-
# Fedora
|
|
62
|
-
sudo dnf install libsecret-devel
|
|
63
|
-
|
|
64
|
-
# Arch
|
|
65
|
-
sudo pacman -S libsecret
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### 간편 설치
|
|
17
|
+
## 🚀 빠른 시작
|
|
69
18
|
|
|
70
19
|
```bash
|
|
71
20
|
npm install -g companionbot
|
|
72
21
|
companionbot
|
|
73
22
|
```
|
|
74
23
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
cd companionbot
|
|
80
|
-
npm install
|
|
81
|
-
npm run build
|
|
82
|
-
npm start
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## 첫 실행
|
|
24
|
+
첫 실행 시 안내에 따라:
|
|
25
|
+
1. Telegram Bot Token 입력 ([@BotFather](https://t.me/BotFather))
|
|
26
|
+
2. Anthropic API Key 입력 ([console.anthropic.com](https://console.anthropic.com))
|
|
27
|
+
3. Telegram에서 봇에게 `/start` 보내기
|
|
86
28
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```
|
|
90
|
-
🤖 CompanionBot 첫 실행입니다!
|
|
91
|
-
|
|
92
|
-
[1/2] Telegram Bot Token
|
|
93
|
-
@BotFather에서 봇 생성 후 토큰을 붙여넣으세요.
|
|
94
|
-
Token: _
|
|
95
|
-
|
|
96
|
-
[2/2] Anthropic API Key
|
|
97
|
-
console.anthropic.com에서 발급받으세요.
|
|
98
|
-
API Key: _
|
|
99
|
-
|
|
100
|
-
📁 워크스페이스 생성 중...
|
|
101
|
-
→ ~/.companionbot/ 생성 완료
|
|
102
|
-
|
|
103
|
-
🚀 봇을 시작합니다!
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
설정 완료 후 **Telegram에서 봇에게 `/start`를 보내면** 온보딩이 시작됩니다:
|
|
107
|
-
- 봇 이름 짓기
|
|
108
|
-
- 성격과 말투 설정
|
|
109
|
-
- 사용자 정보 입력
|
|
110
|
-
|
|
111
|
-
## 명령어
|
|
112
|
-
|
|
113
|
-
### 기본 명령어
|
|
29
|
+
## 📱 명령어
|
|
114
30
|
|
|
115
31
|
| 명령어 | 설명 |
|
|
116
32
|
|--------|------|
|
|
117
|
-
| `/
|
|
33
|
+
| `/help` | 도움말 보기 |
|
|
34
|
+
| `/model` | AI 모델 변경 (haiku/sonnet/opus) |
|
|
118
35
|
| `/compact` | 대화 정리 (토큰 절약) |
|
|
119
|
-
| `/memory` | 최근
|
|
120
|
-
| `/
|
|
121
|
-
| `/
|
|
122
|
-
|
|
123
|
-
### 기능 설정
|
|
124
|
-
|
|
125
|
-
| 명령어 | 설명 |
|
|
126
|
-
|--------|------|
|
|
127
|
-
| `/setup` | 전체 기능 설정 메뉴 |
|
|
128
|
-
| `/setup weather` | 날씨 API 설정 |
|
|
129
|
-
| `/setup calendar` | Google Calendar 설정 |
|
|
130
|
-
| `/setup briefing` | 일일 브리핑 설정 |
|
|
131
|
-
| `/setup heartbeat` | Heartbeat 설정 |
|
|
132
|
-
|
|
133
|
-
### 빠른 명령어
|
|
134
|
-
|
|
135
|
-
| 명령어 | 설명 |
|
|
136
|
-
|--------|------|
|
|
137
|
-
| `/briefing` | 일일 브리핑 토글 |
|
|
138
|
-
| `/heartbeat` | Heartbeat 토글 |
|
|
139
|
-
| `/reminders` | 알림 목록 보기 |
|
|
140
|
-
| `/calendar` | 오늘 일정 보기 |
|
|
141
|
-
|
|
142
|
-
### 자연어 명령
|
|
36
|
+
| `/memory` | 최근 기억 보기 |
|
|
37
|
+
| `/health` | 봇 상태 확인 |
|
|
38
|
+
| `/setup` | 전체 기능 설정 |
|
|
143
39
|
|
|
144
|
-
|
|
40
|
+
### 자연어로도 가능
|
|
145
41
|
|
|
146
42
|
```
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
메모리 "이거 기억해둬"
|
|
153
|
-
웹 검색 "React 19 검색해줘" / "최신 뉴스 찾아줘"
|
|
154
|
-
Cron "매일 아침 9시에 뉴스 알려줘" / "평일 오후 6시에 퇴근 알림"
|
|
155
|
-
서브에이전트 "이 코드 분석해줘" (복잡한 작업은 자동으로 서브에이전트 사용)
|
|
43
|
+
"하이쿠로 바꿔줘"
|
|
44
|
+
"10분 뒤에 알려줘"
|
|
45
|
+
"서울 날씨 어때?"
|
|
46
|
+
"React 19 검색해줘"
|
|
47
|
+
"매일 아침 9시에 뉴스 알려줘"
|
|
156
48
|
```
|
|
157
49
|
|
|
158
|
-
##
|
|
50
|
+
## 🔧 주요 기능
|
|
159
51
|
|
|
160
|
-
###
|
|
52
|
+
### AI 엔진
|
|
53
|
+
- **Claude 모델** - Sonnet 4 / Opus 4 / Haiku 3.5
|
|
54
|
+
- **Extended Thinking** - 내부 스트리밍으로 thinking 지원
|
|
55
|
+
- **도구 사용** - 20+ 도구, 병렬 실행
|
|
161
56
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
57
|
+
### 메모리 시스템
|
|
58
|
+
- **로컬 임베딩** - @xenova/transformers
|
|
59
|
+
- **하이브리드 검색** - 벡터 + 키워드 (FTS5)
|
|
60
|
+
- **한국어 최적화** - trigram tokenizer
|
|
165
61
|
|
|
166
|
-
###
|
|
62
|
+
### 일정/알림
|
|
63
|
+
- **리마인더** - 자연어로 알림 설정
|
|
64
|
+
- **Google Calendar** - 일정 조회/추가
|
|
65
|
+
- **Cron** - 반복 작업 스케줄링
|
|
66
|
+
- **브리핑/하트비트** - 주기적 알림
|
|
167
67
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
5. 봇에게 DM으로: `/calendar_setup CLIENT_ID CLIENT_SECRET`
|
|
173
|
-
6. 인증 링크 클릭하여 Google 로그인
|
|
68
|
+
### 성능
|
|
69
|
+
- **Warmup** - 시작 시 사전 로딩
|
|
70
|
+
- **병렬 처리** - 워크스페이스, 도구 실행
|
|
71
|
+
- **LRU 캐시** - 임베딩, 워크스페이스
|
|
174
72
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
1. [Brave Search API](https://api.search.brave.com) 가입
|
|
178
|
-
2. API 키 발급
|
|
179
|
-
3. 터미널에서: `npm run setup brave YOUR_API_KEY`
|
|
180
|
-
|
|
181
|
-
## PM2로 상시 실행
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
# PM2 설치
|
|
185
|
-
npm install -g pm2
|
|
186
|
-
|
|
187
|
-
# 봇 시작
|
|
188
|
-
pm2 start npm --name companionbot -- start
|
|
189
|
-
|
|
190
|
-
# 부팅 시 자동 시작
|
|
191
|
-
pm2 startup && pm2 save
|
|
192
|
-
|
|
193
|
-
# 로그 확인
|
|
194
|
-
pm2 logs companionbot
|
|
195
|
-
|
|
196
|
-
# 재시작
|
|
197
|
-
pm2 restart companionbot
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
## 워크스페이스
|
|
73
|
+
## 📁 워크스페이스
|
|
201
74
|
|
|
202
75
|
`~/.companionbot/` 구조:
|
|
203
76
|
|
|
204
77
|
```
|
|
205
|
-
├──
|
|
206
|
-
├──
|
|
207
|
-
├── HEARTBEAT.md # 주기적 체크 항목
|
|
208
|
-
├── IDENTITY.md # 봇 정체성 (이름, 이모지, 소개)
|
|
209
|
-
├── MEMORY.md # 장기 기억
|
|
210
|
-
├── SOUL.md # 봇 성격과 말투
|
|
211
|
-
├── TOOLS.md # 도구 설정 노트
|
|
78
|
+
├── SOUL.md # 봇 성격/말투
|
|
79
|
+
├── IDENTITY.md # 이름, 이모지
|
|
212
80
|
├── USER.md # 사용자 정보
|
|
213
|
-
├──
|
|
214
|
-
├──
|
|
81
|
+
├── MEMORY.md # 장기 기억
|
|
82
|
+
├── AGENTS.md # 행동 지침
|
|
83
|
+
├── HEARTBEAT.md # 주기적 체크 항목
|
|
84
|
+
├── TOOLS.md # 도구 설정
|
|
85
|
+
├── canvas/ # 작업 디렉토리
|
|
215
86
|
└── memory/ # 일일 로그
|
|
216
87
|
└── YYYY-MM-DD.md
|
|
217
88
|
```
|
|
218
89
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
- **SOUL.md** - 봇의 성격, 말투, 관심사 수정
|
|
222
|
-
- **HEARTBEAT.md** - 주기적으로 체크할 항목 설정
|
|
223
|
-
- **AGENTS.md** - 봇의 행동 지침 수정
|
|
90
|
+
## ⚙️ 선택 기능
|
|
224
91
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
92
|
+
### 날씨 (OpenWeatherMap)
|
|
93
|
+
```bash
|
|
94
|
+
companionbot setup weather
|
|
95
|
+
```
|
|
228
96
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
| Linux | libsecret (GNOME Keyring 등) |
|
|
97
|
+
### Google Calendar
|
|
98
|
+
```bash
|
|
99
|
+
companionbot setup calendar
|
|
100
|
+
```
|
|
234
101
|
|
|
235
|
-
|
|
102
|
+
### 웹 검색 (Brave Search)
|
|
103
|
+
```bash
|
|
104
|
+
companionbot setup brave
|
|
105
|
+
```
|
|
236
106
|
|
|
237
|
-
##
|
|
107
|
+
## 🖥️ PM2로 상시 실행
|
|
238
108
|
|
|
239
|
-
|
|
109
|
+
```bash
|
|
110
|
+
npm install -g pm2
|
|
111
|
+
pm2 start companionbot --name bot
|
|
112
|
+
pm2 startup && pm2 save
|
|
113
|
+
```
|
|
240
114
|
|
|
241
|
-
|
|
242
|
-
2. **토큰 확인**: Telegram Bot Token이 정확한지 확인
|
|
243
|
-
3. **로그 확인**: `pm2 logs companionbot` 또는 터미널 출력 확인
|
|
115
|
+
## 🔒 보안
|
|
244
116
|
|
|
245
|
-
|
|
117
|
+
- API 키는 OS 키체인에 저장 (macOS Keychain, Windows Credential Manager, Linux libsecret)
|
|
118
|
+
- 파일 접근 경로 검증 (TOCTOU 방지)
|
|
119
|
+
- 명령어 화이트리스트
|
|
120
|
+
- SSRF 방지
|
|
246
121
|
|
|
247
|
-
|
|
248
|
-
- 잠시 후 다시 시도하거나 API 플랜 업그레이드
|
|
122
|
+
## 🐛 트러블슈팅
|
|
249
123
|
|
|
250
|
-
###
|
|
124
|
+
### 봇이 응답 안 해요
|
|
125
|
+
1. API 키 확인
|
|
126
|
+
2. `tail -f /tmp/companionbot.log` 로그 확인
|
|
251
127
|
|
|
128
|
+
### Linux 설치 오류
|
|
252
129
|
```bash
|
|
253
|
-
# keytar 의존성 설치 필요
|
|
254
130
|
sudo apt-get install libsecret-1-dev # Debian/Ubuntu
|
|
255
131
|
```
|
|
256
132
|
|
|
257
|
-
###
|
|
258
|
-
|
|
259
|
-
1. OAuth 클라이언트 ID가 "데스크톱 앱" 유형인지 확인
|
|
260
|
-
2. 리디렉션 URI에 `http://localhost:3847/oauth2callback` 추가
|
|
261
|
-
3. `/setup calendar off` 후 `/calendar_setup`으로 다시 시도
|
|
262
|
-
|
|
263
|
-
### 온보딩이 다시 안 나와요
|
|
264
|
-
|
|
133
|
+
### 초기화하고 싶어요
|
|
265
134
|
```bash
|
|
266
|
-
|
|
267
|
-
# Telegram에서: /reset
|
|
268
|
-
|
|
269
|
-
# 또는 완전 초기화
|
|
270
|
-
rm -rf ~/.companionbot
|
|
271
|
-
companionbot
|
|
135
|
+
rm -rf ~/.companionbot && companionbot
|
|
272
136
|
```
|
|
273
137
|
|
|
274
|
-
##
|
|
138
|
+
## 📜 버전
|
|
275
139
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
140
|
+
현재: **v0.15.0**
|
|
141
|
+
|
|
142
|
+
주요 변경:
|
|
143
|
+
- 메모리 검색 → 도구 방식 (성능 개선)
|
|
144
|
+
- tools 모듈 분할 (15개 파일)
|
|
145
|
+
- Agent 메모리 누수 방지
|
|
146
|
+
- /help 명령어, 한국어 메시지 통일
|
|
147
|
+
|
|
148
|
+
전체 변경 이력: [CHANGELOG.md](CHANGELOG.md)
|
|
282
149
|
|
|
283
|
-
##
|
|
284
|
-
|
|
285
|
-
### v0.6.0 (현재)
|
|
286
|
-
- 🧠 시맨틱 메모리 검색 (임베딩 기반)
|
|
287
|
-
- 💚 헬스 체크 모니터링
|
|
288
|
-
- 🔄 자동 업데이트 알림
|
|
289
|
-
|
|
290
|
-
### v0.5.0
|
|
291
|
-
- 🔒 보안 강화 (SSRF 방지, 경로 검증)
|
|
292
|
-
- ✅ 테스트 추가 (vitest)
|
|
293
|
-
- 📖 문서 개선
|
|
294
|
-
|
|
295
|
-
### v0.4.0
|
|
296
|
-
- 🛡️ 보안 강화 (TOCTOU, 심볼릭 링크 검증)
|
|
297
|
-
- 🧹 세션 자동 정리
|
|
298
|
-
- 🔧 환경변수 기반 경로 설정
|
|
299
|
-
|
|
300
|
-
### v0.3.0
|
|
301
|
-
- 🔍 웹 검색 (Brave Search API)
|
|
302
|
-
- 🕐 Cron 스케줄링 (한국어 지원)
|
|
303
|
-
- 🤖 서브 에이전트 (백그라운드 작업)
|
|
304
|
-
- 🖥️ 백그라운드 명령어 실행
|
|
305
|
-
- 📝 파일 편집 도구 추가
|
|
306
|
-
|
|
307
|
-
### v0.2.0
|
|
308
|
-
- 📅 Google Calendar 연동
|
|
309
|
-
- ☀️ 일일 브리핑
|
|
310
|
-
- 💓 Heartbeat 시스템
|
|
311
|
-
- 🌤️ 날씨 조회
|
|
312
|
-
|
|
313
|
-
### v0.1.0
|
|
314
|
-
- 🚀 초기 릴리스
|
|
315
|
-
- 💬 Claude 기반 대화
|
|
316
|
-
- 🎭 페르소나 온보딩
|
|
317
|
-
- ⏰ 리마인더
|
|
318
|
-
- 🖼️ 이미지 분석
|
|
319
|
-
- 🔗 링크 요약
|
|
320
|
-
|
|
321
|
-
## 라이선스
|
|
150
|
+
## 📄 라이선스
|
|
322
151
|
|
|
323
152
|
[MIT](LICENSE)
|
|
324
153
|
|
|
325
154
|
---
|
|
326
155
|
|
|
327
|
-
|
|
156
|
+
**Issues**: [github.com/DinN0000/CompanionBot/issues](https://github.com/DinN0000/CompanionBot/issues)
|
package/dist/agents/manager.js
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import Anthropic from "@anthropic-ai/sdk";
|
|
10
10
|
import { randomUUID } from "crypto";
|
|
11
|
+
// ===== 제한 상수 =====
|
|
12
|
+
const MAX_CONCURRENT_AGENTS = 10; // 전체 동시 Agent 최대 개수
|
|
13
|
+
const MAX_AGENTS_PER_CHAT = 3; // chatId당 최대 동시 Agent 개수
|
|
14
|
+
const CLEANUP_INTERVAL_MS = 30 * 60 * 1000; // 30분마다 cleanup
|
|
15
|
+
const AGENT_TTL_MS = 30 * 60 * 1000; // Agent 보관 시간 (30분)
|
|
11
16
|
// Agent 저장소
|
|
12
17
|
const agents = new Map();
|
|
13
18
|
// AbortController 저장소 (실행 중인 API 호출 취소용)
|
|
@@ -28,10 +33,50 @@ function getClient() {
|
|
|
28
33
|
export function setAgentBot(bot) {
|
|
29
34
|
botInstance = bot;
|
|
30
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* 가장 오래된 Agent 정리 (한도 초과 시)
|
|
38
|
+
*/
|
|
39
|
+
function evictOldestAgent() {
|
|
40
|
+
let oldest = null;
|
|
41
|
+
for (const agent of agents.values()) {
|
|
42
|
+
if (!oldest || agent.createdAt < oldest.createdAt) {
|
|
43
|
+
oldest = agent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (oldest) {
|
|
47
|
+
console.log(`[AgentManager] Evicting oldest agent: ${oldest.id}`);
|
|
48
|
+
// running이면 취소
|
|
49
|
+
if (oldest.status === "running") {
|
|
50
|
+
cancelAgent(oldest.id);
|
|
51
|
+
}
|
|
52
|
+
agents.delete(oldest.id);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* chatId당 Agent 개수 확인
|
|
57
|
+
*/
|
|
58
|
+
function countAgentsForChat(chatId) {
|
|
59
|
+
let count = 0;
|
|
60
|
+
for (const agent of agents.values()) {
|
|
61
|
+
if (agent.chatId === chatId && agent.status === "running") {
|
|
62
|
+
count++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return count;
|
|
66
|
+
}
|
|
31
67
|
/**
|
|
32
68
|
* Sub-agent 생성 및 실행
|
|
33
69
|
*/
|
|
34
70
|
export async function spawnAgent(task, chatId) {
|
|
71
|
+
// chatId당 제한 확인
|
|
72
|
+
const chatAgentCount = countAgentsForChat(chatId);
|
|
73
|
+
if (chatAgentCount >= MAX_AGENTS_PER_CHAT) {
|
|
74
|
+
throw new Error(`이 채팅에서 동시에 실행 가능한 Agent 수(${MAX_AGENTS_PER_CHAT}개)를 초과했습니다. 기존 Agent 완료를 기다려주세요.`);
|
|
75
|
+
}
|
|
76
|
+
// 전체 한도 확인 및 정리
|
|
77
|
+
while (agents.size >= MAX_CONCURRENT_AGENTS) {
|
|
78
|
+
evictOldestAgent();
|
|
79
|
+
}
|
|
35
80
|
const id = randomUUID().slice(0, 8);
|
|
36
81
|
const agent = {
|
|
37
82
|
id,
|
|
@@ -41,6 +86,7 @@ export async function spawnAgent(task, chatId) {
|
|
|
41
86
|
createdAt: new Date(),
|
|
42
87
|
};
|
|
43
88
|
agents.set(id, agent);
|
|
89
|
+
console.log(`[AgentManager] Agent created: ${id} (total: ${agents.size}/${MAX_CONCURRENT_AGENTS})`);
|
|
44
90
|
// 비동기로 agent 실행 (await 하지 않음)
|
|
45
91
|
runAgent(agent).catch((err) => {
|
|
46
92
|
console.error(`[Agent ${id}] Error:`, err);
|
|
@@ -183,35 +229,47 @@ export function getAgent(agentId) {
|
|
|
183
229
|
return agents.get(agentId);
|
|
184
230
|
}
|
|
185
231
|
/**
|
|
186
|
-
* 오래된 agent 정리 (
|
|
187
|
-
* - 완료된 agent: completedAt 기준
|
|
188
|
-
* - running 상태도 createdAt 기준
|
|
232
|
+
* 오래된 agent 정리 (30분 이상)
|
|
233
|
+
* - 완료된 agent: completedAt 기준 30분
|
|
234
|
+
* - running 상태도 createdAt 기준 30분 지나면 정리 (stuck 방지)
|
|
189
235
|
*/
|
|
190
236
|
export function cleanupOldAgents() {
|
|
191
|
-
const
|
|
237
|
+
const cutoff = Date.now() - AGENT_TTL_MS;
|
|
238
|
+
let cleaned = 0;
|
|
192
239
|
for (const [id, agent] of agents.entries()) {
|
|
193
240
|
// 완료된 agent: completedAt 기준
|
|
194
|
-
if (agent.completedAt && agent.completedAt.getTime() <
|
|
241
|
+
if (agent.completedAt && agent.completedAt.getTime() < cutoff) {
|
|
195
242
|
agents.delete(id);
|
|
243
|
+
cleaned++;
|
|
196
244
|
continue;
|
|
197
245
|
}
|
|
198
|
-
// running 상태도
|
|
199
|
-
if (agent.status === "running" && agent.createdAt.getTime() <
|
|
200
|
-
console.log(`[Agent ${id}] Cleaning up stuck agent (running >
|
|
246
|
+
// running 상태도 TTL 지나면 정리 (stuck agent 방지)
|
|
247
|
+
if (agent.status === "running" && agent.createdAt.getTime() < cutoff) {
|
|
248
|
+
console.log(`[Agent ${id}] Cleaning up stuck agent (running > 30min)`);
|
|
249
|
+
// 실행 중인 API 호출 취소
|
|
250
|
+
const controller = abortControllers.get(id);
|
|
251
|
+
if (controller) {
|
|
252
|
+
controller.abort();
|
|
253
|
+
abortControllers.delete(id);
|
|
254
|
+
}
|
|
201
255
|
agents.delete(id);
|
|
256
|
+
cleaned++;
|
|
202
257
|
}
|
|
203
258
|
}
|
|
259
|
+
if (cleaned > 0) {
|
|
260
|
+
console.log(`[AgentManager] Cleanup: removed ${cleaned} agents (remaining: ${agents.size})`);
|
|
261
|
+
}
|
|
204
262
|
}
|
|
205
263
|
// Cleanup interval 참조 저장
|
|
206
264
|
let cleanupIntervalId = null;
|
|
207
265
|
/**
|
|
208
|
-
* 정기 cleanup 시작
|
|
266
|
+
* 정기 cleanup 시작 (30분 주기)
|
|
209
267
|
*/
|
|
210
268
|
export function startCleanup() {
|
|
211
269
|
if (cleanupIntervalId)
|
|
212
270
|
return; // 이미 실행 중
|
|
213
|
-
cleanupIntervalId = setInterval(cleanupOldAgents,
|
|
214
|
-
console.log(
|
|
271
|
+
cleanupIntervalId = setInterval(cleanupOldAgents, CLEANUP_INTERVAL_MS);
|
|
272
|
+
console.log(`[AgentManager] Cleanup interval started (every ${CLEANUP_INTERVAL_MS / 60000}min)`);
|
|
215
273
|
}
|
|
216
274
|
/**
|
|
217
275
|
* 정기 cleanup 중지
|
package/dist/telegram/bot.js
CHANGED
|
@@ -72,9 +72,11 @@ export function createBot(token) {
|
|
|
72
72
|
// 명령어 목록 등록
|
|
73
73
|
bot.api
|
|
74
74
|
.setMyCommands([
|
|
75
|
+
{ command: "help", description: "도움말 보기" },
|
|
76
|
+
{ command: "model", description: "AI 모델 변경" },
|
|
75
77
|
{ command: "compact", description: "대화 정리하기" },
|
|
76
78
|
{ command: "memory", description: "최근 기억 보기" },
|
|
77
|
-
{ command: "
|
|
79
|
+
{ command: "health", description: "봇 상태 확인" },
|
|
78
80
|
])
|
|
79
81
|
.catch((err) => console.error("Failed to set commands:", err));
|
|
80
82
|
// 핸들러 등록
|
|
@@ -60,6 +60,32 @@ import { setHeartbeatConfig, getHeartbeatConfig, disableHeartbeat, } from "../..
|
|
|
60
60
|
import { getWorkspace, invalidateWorkspaceCache, buildSystemPrompt, extractName, } from "../utils/index.js";
|
|
61
61
|
import { ensureDefaultCronJobs } from "../../cron/scheduler.js";
|
|
62
62
|
export function registerCommands(bot) {
|
|
63
|
+
// /help 명령어 - 전체 기능 안내
|
|
64
|
+
bot.command("help", async (ctx) => {
|
|
65
|
+
await ctx.reply(`📖 도움말\n\n` +
|
|
66
|
+
`🎯 기본 기능\n` +
|
|
67
|
+
`/model - AI 모델 변경 (sonnet/opus/haiku)\n` +
|
|
68
|
+
`/compact - 대화 압축해서 토큰 절약\n` +
|
|
69
|
+
`/clear - 대화 초기화\n\n` +
|
|
70
|
+
`📌 기억/핀\n` +
|
|
71
|
+
`/memory - 최근 기억 보기\n` +
|
|
72
|
+
`/pin [내용] - 중요한 정보 핀하기\n` +
|
|
73
|
+
`/pins - 핀 목록 보기\n` +
|
|
74
|
+
`/context - 현재 맥락 상태\n\n` +
|
|
75
|
+
`⏰ 알림/일정\n` +
|
|
76
|
+
`/reminders - 알림 목록\n` +
|
|
77
|
+
`/briefing - 일일 브리핑 켜기/상태\n` +
|
|
78
|
+
`/calendar - 오늘 일정 보기\n\n` +
|
|
79
|
+
`⚙️ 설정\n` +
|
|
80
|
+
`/setup - 기능별 설정 관리\n` +
|
|
81
|
+
`/health - 봇 상태 확인\n` +
|
|
82
|
+
`/reset - 페르소나 초기화\n\n` +
|
|
83
|
+
`💡 자연어로도 말할 수 있어요:\n` +
|
|
84
|
+
`• "opus로 바꿔줘"\n` +
|
|
85
|
+
`• "10분 뒤에 알려줘"\n` +
|
|
86
|
+
`• "기억해: 나는 채식주의자야"\n` +
|
|
87
|
+
`• "내일 일정 뭐야?"`);
|
|
88
|
+
});
|
|
63
89
|
// /start 명령어
|
|
64
90
|
bot.command("start", async (ctx) => {
|
|
65
91
|
const chatId = ctx.chat.id;
|
|
@@ -210,19 +236,19 @@ export function registerCommands(bot) {
|
|
|
210
236
|
const modelList = Object.entries(MODELS)
|
|
211
237
|
.map(([id, m]) => `${id === currentModel ? "→" : " "} /model ${id} - ${m.name}`)
|
|
212
238
|
.join("\n");
|
|
213
|
-
await ctx.reply(
|
|
214
|
-
|
|
215
|
-
|
|
239
|
+
await ctx.reply(`현재 모델: ${MODELS[currentModel].name}\n\n` +
|
|
240
|
+
`사용 가능한 모델:\n${modelList}\n\n` +
|
|
241
|
+
`팁: "모델 바꿔줘"처럼 자연어로도 바꿀 수 있어!`);
|
|
216
242
|
return;
|
|
217
243
|
}
|
|
218
244
|
if (arg in MODELS) {
|
|
219
245
|
const modelId = arg;
|
|
220
246
|
setModel(chatId, modelId);
|
|
221
|
-
await ctx.reply(
|
|
247
|
+
await ctx.reply(`모델 변경됨: ${MODELS[modelId].name}`);
|
|
222
248
|
}
|
|
223
249
|
else {
|
|
224
|
-
await ctx.reply(
|
|
225
|
-
|
|
250
|
+
await ctx.reply(`모르는 모델이야: ${arg}\n\n` +
|
|
251
|
+
`사용 가능: sonnet, opus, haiku`);
|
|
226
252
|
}
|
|
227
253
|
});
|
|
228
254
|
// /setup 명령어 - 추가 기능 설정 및 관리
|