companionbot 0.3.0 → 0.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/README.md +214 -60
- package/dist/agents/manager.js +60 -4
- package/dist/cron/store.js +350 -86
- package/dist/session/state.js +14 -5
- package/dist/telegram/bot.js +9 -0
- package/dist/telegram/handlers/commands.js +89 -30
- package/dist/telegram/handlers/messages.js +92 -91
- package/dist/tools/index.js +75 -4
- package/dist/tools/pathCheck.js +79 -0
- package/package.json +6 -2
- package/dist/cron/storage.js +0 -59
package/README.md
CHANGED
|
@@ -1,46 +1,47 @@
|
|
|
1
1
|
# CompanionBot
|
|
2
2
|
|
|
3
|
-
Claude 기반의 개인화된 페르소나를 가진 AI Companion Bot
|
|
3
|
+
> Claude 기반의 개인화된 페르소나를 가진 AI Companion Bot
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/companionbot)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
- 첫 실행 시 온보딩으로 페르소나 설정
|
|
9
|
-
- 이미지 분석 (사진 보내면 분석)
|
|
10
|
-
- 링크 요약 (URL 보내면 내용 요약)
|
|
11
|
-
- 날씨 조회 ("서울 날씨 어때?")
|
|
12
|
-
- 리마인더 ("10분 뒤에 알려줘")
|
|
13
|
-
- Google Calendar 연동
|
|
14
|
-
- 일일 브리핑 (매일 아침 날씨/일정)
|
|
15
|
-
- Heartbeat (주기적 체크 후 알림)
|
|
16
|
-
- 일일 메모리 자동 저장
|
|
9
|
+
## 주요 기능
|
|
17
10
|
|
|
18
|
-
|
|
11
|
+
### 💬 대화
|
|
12
|
+
- **자연스러운 대화** - Claude Sonnet/Opus/Haiku 모델 선택 가능
|
|
13
|
+
- **페르소나 커스터마이징** - 첫 실행 시 온보딩으로 봇의 성격, 말투, 이름 설정
|
|
14
|
+
- **이미지 분석** - 사진을 보내면 분석
|
|
15
|
+
- **링크 요약** - URL을 보내면 내용을 읽고 요약
|
|
19
16
|
|
|
20
|
-
###
|
|
17
|
+
### 🔍 정보 검색 (v0.3.0)
|
|
18
|
+
- **웹 검색** - "최신 React 뉴스 검색해줘" (Brave Search API)
|
|
19
|
+
- **웹 페이지 읽기** - URL 내용을 가져와서 분석/요약
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
### ⏰ 일정 관리
|
|
22
|
+
- **리마인더** - "10분 뒤에 알려줘"
|
|
23
|
+
- **Google Calendar 연동** - 일정 조회/추가/삭제
|
|
24
|
+
- **일일 브리핑** - 매일 아침 날씨와 일정 알림
|
|
25
|
+
- **Heartbeat** - 주기적으로 체크리스트 확인 후 알림
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
### 🕐 스케줄링 (v0.3.0)
|
|
28
|
+
- **Cron 작업** - "매일 아침 9시에 뉴스 알려줘", "평일 오후 6시에 퇴근 알림"
|
|
29
|
+
- **일회성 예약** - "내일 오전 9시에 알려줘"
|
|
30
|
+
- **반복 작업** - "30분마다 주식 가격 확인해줘"
|
|
28
31
|
|
|
29
|
-
###
|
|
32
|
+
### 🤖 고급 기능 (v0.3.0)
|
|
33
|
+
- **서브 에이전트** - 복잡한 작업을 백그라운드에서 처리
|
|
34
|
+
- **백그라운드 실행** - 긴 명령어를 백그라운드에서 실행하고 결과 확인
|
|
35
|
+
- **파일 시스템** - 워크스페이스 내 파일 읽기/쓰기/편집
|
|
36
|
+
- **일일 메모리** - 대화 내용 자동 저장
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
git clone https://github.com/DinN0000/CompanionBot.git
|
|
33
|
-
cd companionbot
|
|
34
|
-
npm install
|
|
35
|
-
npm run build
|
|
36
|
-
npm start
|
|
37
|
-
```
|
|
38
|
+
## 설치
|
|
38
39
|
|
|
39
40
|
### 사전 준비
|
|
40
41
|
|
|
41
|
-
- **Node.js 18+**
|
|
42
|
-
- **Telegram Bot Token** - @BotFather에서 발급
|
|
43
|
-
- **Anthropic API Key** - console.anthropic.com
|
|
42
|
+
- **Node.js 18+** ([다운로드](https://nodejs.org))
|
|
43
|
+
- **Telegram Bot Token** - [@BotFather](https://t.me/BotFather)에서 봇 생성 후 발급
|
|
44
|
+
- **Anthropic API Key** - [console.anthropic.com](https://console.anthropic.com)에서 발급
|
|
44
45
|
|
|
45
46
|
#### Linux 사용자 (keytar 의존성)
|
|
46
47
|
|
|
@@ -55,8 +56,27 @@ sudo dnf install libsecret-devel
|
|
|
55
56
|
sudo pacman -S libsecret
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
### 간편 설치
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install -g companionbot
|
|
63
|
+
companionbot
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 개발자 설치
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/DinN0000/CompanionBot.git
|
|
70
|
+
cd companionbot
|
|
71
|
+
npm install
|
|
72
|
+
npm run build
|
|
73
|
+
npm start
|
|
74
|
+
```
|
|
75
|
+
|
|
58
76
|
## 첫 실행
|
|
59
77
|
|
|
78
|
+
처음 실행하면 대화형 설정이 시작됩니다:
|
|
79
|
+
|
|
60
80
|
```
|
|
61
81
|
🤖 CompanionBot 첫 실행입니다!
|
|
62
82
|
|
|
@@ -74,39 +94,98 @@ sudo pacman -S libsecret
|
|
|
74
94
|
🚀 봇을 시작합니다!
|
|
75
95
|
```
|
|
76
96
|
|
|
97
|
+
설정 완료 후 **Telegram에서 봇에게 `/start`를 보내면** 온보딩이 시작됩니다:
|
|
98
|
+
- 봇 이름 짓기
|
|
99
|
+
- 성격과 말투 설정
|
|
100
|
+
- 사용자 정보 입력
|
|
101
|
+
|
|
77
102
|
## 명령어
|
|
78
103
|
|
|
104
|
+
### 기본 명령어
|
|
105
|
+
|
|
79
106
|
| 명령어 | 설명 |
|
|
80
107
|
|--------|------|
|
|
81
108
|
| `/start` | 봇 시작 (첫 실행 시 온보딩) |
|
|
82
|
-
| `/
|
|
109
|
+
| `/compact` | 대화 정리 (토큰 절약) |
|
|
110
|
+
| `/memory` | 최근 일주일 기억 보기 |
|
|
111
|
+
| `/model [id]` | AI 모델 변경 (sonnet/opus/haiku) |
|
|
112
|
+
| `/reset` | 페르소나 초기화 (온보딩 다시) |
|
|
113
|
+
|
|
114
|
+
### 기능 설정
|
|
115
|
+
|
|
116
|
+
| 명령어 | 설명 |
|
|
117
|
+
|--------|------|
|
|
118
|
+
| `/setup` | 전체 기능 설정 메뉴 |
|
|
119
|
+
| `/setup weather` | 날씨 API 설정 |
|
|
120
|
+
| `/setup calendar` | Google Calendar 설정 |
|
|
121
|
+
| `/setup briefing` | 일일 브리핑 설정 |
|
|
122
|
+
| `/setup heartbeat` | Heartbeat 설정 |
|
|
123
|
+
|
|
124
|
+
### 빠른 명령어
|
|
125
|
+
|
|
126
|
+
| 명령어 | 설명 |
|
|
127
|
+
|--------|------|
|
|
83
128
|
| `/briefing` | 일일 브리핑 토글 |
|
|
84
129
|
| `/heartbeat` | Heartbeat 토글 |
|
|
85
|
-
| `/reminders` | 알림 목록 |
|
|
86
|
-
| `/calendar` | 오늘 일정 |
|
|
87
|
-
| `/compact` | 대화 정리 |
|
|
88
|
-
| `/memory` | 최근 기억 |
|
|
89
|
-
| `/reset` | 페르소나 초기화 |
|
|
130
|
+
| `/reminders` | 알림 목록 보기 |
|
|
131
|
+
| `/calendar` | 오늘 일정 보기 |
|
|
90
132
|
|
|
91
133
|
### 자연어 명령
|
|
92
134
|
|
|
93
|
-
명령어 대신
|
|
135
|
+
명령어 대신 자연스럽게 말해도 됩니다:
|
|
94
136
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
137
|
+
```
|
|
138
|
+
모델 변경 "하이쿠로 바꿔줘" / "opus로 변경해줘"
|
|
139
|
+
리마인더 "10분 뒤에 알려줘" / "내일 9시에 회의 알림"
|
|
140
|
+
브리핑 "브리핑 켜줘" / "지금 브리핑 해줘" / "아침 9시에 브리핑"
|
|
141
|
+
Heartbeat "하트비트 켜줘" / "10분마다 체크해줘"
|
|
142
|
+
날씨 "서울 날씨 어때?" / "도쿄 날씨 알려줘"
|
|
143
|
+
메모리 "이거 기억해둬"
|
|
144
|
+
웹 검색 "React 19 검색해줘" / "최신 뉴스 찾아줘"
|
|
145
|
+
Cron "매일 아침 9시에 뉴스 알려줘" / "평일 오후 6시에 퇴근 알림"
|
|
146
|
+
서브에이전트 "이 코드 분석해줘" (복잡한 작업은 자동으로 서브에이전트 사용)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 선택적 기능 설정
|
|
150
|
+
|
|
151
|
+
### 🌤️ 날씨 (OpenWeatherMap)
|
|
152
|
+
|
|
153
|
+
1. [openweathermap.org](https://openweathermap.org) 가입
|
|
154
|
+
2. API Keys에서 무료 키 발급
|
|
155
|
+
3. 봇에게 DM으로: `/weather_setup YOUR_API_KEY`
|
|
156
|
+
|
|
157
|
+
### 📅 Google Calendar
|
|
158
|
+
|
|
159
|
+
1. [Google Cloud Console](https://console.cloud.google.com) 접속
|
|
160
|
+
2. 프로젝트 생성 → Calendar API 활성화
|
|
161
|
+
3. OAuth 동의 화면 설정 (앱 이름, 범위 추가)
|
|
162
|
+
4. 사용자 인증 정보 → OAuth 클라이언트 ID (데스크톱 앱)
|
|
163
|
+
5. 봇에게 DM으로: `/calendar_setup CLIENT_ID CLIENT_SECRET`
|
|
164
|
+
6. 인증 링크 클릭하여 Google 로그인
|
|
165
|
+
|
|
166
|
+
### 🔍 웹 검색 (Brave Search)
|
|
167
|
+
|
|
168
|
+
1. [Brave Search API](https://api.search.brave.com) 가입
|
|
169
|
+
2. API 키 발급
|
|
170
|
+
3. 터미널에서: `npm run setup brave YOUR_API_KEY`
|
|
103
171
|
|
|
104
172
|
## PM2로 상시 실행
|
|
105
173
|
|
|
106
174
|
```bash
|
|
175
|
+
# PM2 설치
|
|
107
176
|
npm install -g pm2
|
|
177
|
+
|
|
178
|
+
# 봇 시작
|
|
108
179
|
pm2 start npm --name companionbot -- start
|
|
180
|
+
|
|
181
|
+
# 부팅 시 자동 시작
|
|
109
182
|
pm2 startup && pm2 save
|
|
183
|
+
|
|
184
|
+
# 로그 확인
|
|
185
|
+
pm2 logs companionbot
|
|
186
|
+
|
|
187
|
+
# 재시작
|
|
188
|
+
pm2 restart companionbot
|
|
110
189
|
```
|
|
111
190
|
|
|
112
191
|
## 워크스페이스
|
|
@@ -114,36 +193,111 @@ pm2 startup && pm2 save
|
|
|
114
193
|
`~/.companionbot/` 구조:
|
|
115
194
|
|
|
116
195
|
```
|
|
117
|
-
├── AGENTS.md # 운영 지침
|
|
118
|
-
├── BOOTSTRAP.md # 온보딩 (완료 후
|
|
196
|
+
├── AGENTS.md # 운영 지침 (봇 행동 규칙)
|
|
197
|
+
├── BOOTSTRAP.md # 온보딩 프롬프트 (완료 후 삭제됨)
|
|
119
198
|
├── HEARTBEAT.md # 주기적 체크 항목
|
|
120
|
-
├── IDENTITY.md # 봇 정체성
|
|
199
|
+
├── IDENTITY.md # 봇 정체성 (이름, 이모지, 소개)
|
|
121
200
|
├── MEMORY.md # 장기 기억
|
|
122
|
-
├── SOUL.md # 봇
|
|
123
|
-
├── TOOLS.md # 도구 설정
|
|
201
|
+
├── SOUL.md # 봇 성격과 말투
|
|
202
|
+
├── TOOLS.md # 도구 설정 노트
|
|
124
203
|
├── USER.md # 사용자 정보
|
|
125
204
|
├── canvas/ # 봇 작업 디렉토리
|
|
205
|
+
├── cron.json # 크론 작업 저장
|
|
126
206
|
└── memory/ # 일일 로그
|
|
127
207
|
└── YYYY-MM-DD.md
|
|
128
208
|
```
|
|
129
209
|
|
|
210
|
+
### 파일 커스터마이징
|
|
211
|
+
|
|
212
|
+
- **SOUL.md** - 봇의 성격, 말투, 관심사 수정
|
|
213
|
+
- **HEARTBEAT.md** - 주기적으로 체크할 항목 설정
|
|
214
|
+
- **AGENTS.md** - 봇의 행동 지침 수정
|
|
215
|
+
|
|
130
216
|
## 시크릿 저장
|
|
131
217
|
|
|
132
|
-
OS 키체인에 안전하게 저장됩니다:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
218
|
+
API 키는 OS 키체인에 안전하게 저장됩니다:
|
|
219
|
+
|
|
220
|
+
| OS | 저장 위치 |
|
|
221
|
+
|----|-----------|
|
|
222
|
+
| macOS | Keychain Access |
|
|
223
|
+
| Windows | Credential Manager |
|
|
224
|
+
| Linux | libsecret (GNOME Keyring 등) |
|
|
225
|
+
|
|
226
|
+
완전 초기화: `~/.companionbot/` 폴더 삭제 후 다시 실행
|
|
227
|
+
|
|
228
|
+
## 트러블슈팅
|
|
229
|
+
|
|
230
|
+
### 봇이 응답하지 않아요
|
|
231
|
+
|
|
232
|
+
1. **API 키 확인**: Anthropic API 키가 유효한지 확인
|
|
233
|
+
2. **토큰 확인**: Telegram Bot Token이 정확한지 확인
|
|
234
|
+
3. **로그 확인**: `pm2 logs companionbot` 또는 터미널 출력 확인
|
|
136
235
|
|
|
137
|
-
|
|
236
|
+
### "rate limit" 오류가 나요
|
|
237
|
+
|
|
238
|
+
- Anthropic API 사용량 한도 초과
|
|
239
|
+
- 잠시 후 다시 시도하거나 API 플랜 업그레이드
|
|
240
|
+
|
|
241
|
+
### Linux에서 설치 오류
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
# keytar 의존성 설치 필요
|
|
245
|
+
sudo apt-get install libsecret-1-dev # Debian/Ubuntu
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Google Calendar 인증 실패
|
|
249
|
+
|
|
250
|
+
1. OAuth 클라이언트 ID가 "데스크톱 앱" 유형인지 확인
|
|
251
|
+
2. 리디렉션 URI에 `http://localhost:3847/oauth2callback` 추가
|
|
252
|
+
3. `/setup calendar off` 후 `/calendar_setup`으로 다시 시도
|
|
253
|
+
|
|
254
|
+
### 온보딩이 다시 안 나와요
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# 페르소나 리셋
|
|
258
|
+
# Telegram에서: /reset
|
|
259
|
+
|
|
260
|
+
# 또는 완전 초기화
|
|
261
|
+
rm -rf ~/.companionbot
|
|
262
|
+
companionbot
|
|
263
|
+
```
|
|
138
264
|
|
|
139
265
|
## 개발
|
|
140
266
|
|
|
141
267
|
```bash
|
|
142
|
-
npm run dev # 개발 모드
|
|
143
|
-
npm run build # 빌드
|
|
144
|
-
npm start # 실행
|
|
268
|
+
npm run dev # 개발 모드 (tsx)
|
|
269
|
+
npm run build # TypeScript 빌드
|
|
270
|
+
npm start # 빌드된 코드 실행
|
|
271
|
+
npm test # 테스트 실행
|
|
145
272
|
```
|
|
146
273
|
|
|
147
|
-
##
|
|
274
|
+
## 버전 히스토리
|
|
275
|
+
|
|
276
|
+
### v0.3.0 (현재)
|
|
277
|
+
- 🔍 웹 검색 (Brave Search API)
|
|
278
|
+
- 🕐 Cron 스케줄링 (한국어 지원)
|
|
279
|
+
- 🤖 서브 에이전트 (백그라운드 작업)
|
|
280
|
+
- 🖥️ 백그라운드 명령어 실행
|
|
281
|
+
- 📝 파일 편집 도구 추가
|
|
282
|
+
|
|
283
|
+
### v0.2.0
|
|
284
|
+
- 📅 Google Calendar 연동
|
|
285
|
+
- ☀️ 일일 브리핑
|
|
286
|
+
- 💓 Heartbeat 시스템
|
|
287
|
+
- 🌤️ 날씨 조회
|
|
288
|
+
|
|
289
|
+
### v0.1.0
|
|
290
|
+
- 🚀 초기 릴리스
|
|
291
|
+
- 💬 Claude 기반 대화
|
|
292
|
+
- 🎭 페르소나 온보딩
|
|
293
|
+
- ⏰ 리마인더
|
|
294
|
+
- 🖼️ 이미지 분석
|
|
295
|
+
- 🔗 링크 요약
|
|
296
|
+
|
|
297
|
+
## 라이선스
|
|
298
|
+
|
|
299
|
+
[MIT](LICENSE)
|
|
300
|
+
|
|
301
|
+
---
|
|
148
302
|
|
|
149
|
-
|
|
303
|
+
**문제가 있거나 기능 요청이 있으면** [Issues](https://github.com/DinN0000/CompanionBot/issues)에 등록해주세요!
|
package/dist/agents/manager.js
CHANGED
|
@@ -10,6 +10,8 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
10
10
|
import { randomUUID } from "crypto";
|
|
11
11
|
// Agent 저장소
|
|
12
12
|
const agents = new Map();
|
|
13
|
+
// AbortController 저장소 (실행 중인 API 호출 취소용)
|
|
14
|
+
const abortControllers = new Map();
|
|
13
15
|
// Bot 인스턴스 (결과 전송용)
|
|
14
16
|
let botInstance = null;
|
|
15
17
|
// Anthropic 클라이언트
|
|
@@ -50,6 +52,9 @@ export async function spawnAgent(task, chatId) {
|
|
|
50
52
|
*/
|
|
51
53
|
async function runAgent(agent) {
|
|
52
54
|
const client = getClient();
|
|
55
|
+
// AbortController 생성 및 저장
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
abortControllers.set(agent.id, controller);
|
|
53
58
|
const systemPrompt = `You are a sub-agent assistant. Your job is to complete a specific task and report the result concisely.
|
|
54
59
|
|
|
55
60
|
TASK: ${agent.task}
|
|
@@ -73,7 +78,14 @@ Complete the task and provide your final answer.`;
|
|
|
73
78
|
content: `Please complete this task: ${agent.task}`,
|
|
74
79
|
},
|
|
75
80
|
],
|
|
81
|
+
}, {
|
|
82
|
+
signal: controller.signal,
|
|
76
83
|
});
|
|
84
|
+
// 취소됐으면 결과 무시
|
|
85
|
+
if (agent.status === "cancelled") {
|
|
86
|
+
console.log(`[Agent ${agent.id}] Was cancelled, ignoring result`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
77
89
|
// 결과 추출
|
|
78
90
|
const textBlock = response.content.find((block) => block.type === "text");
|
|
79
91
|
const result = textBlock?.text ?? "No response generated.";
|
|
@@ -86,6 +98,11 @@ Complete the task and provide your final answer.`;
|
|
|
86
98
|
await sendAgentResult(agent);
|
|
87
99
|
}
|
|
88
100
|
catch (error) {
|
|
101
|
+
// 취소로 인한 abort는 무시
|
|
102
|
+
if (agent.status === "cancelled") {
|
|
103
|
+
console.log(`[Agent ${agent.id}] Aborted due to cancellation`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
89
106
|
agent.status = "failed";
|
|
90
107
|
agent.completedAt = new Date();
|
|
91
108
|
agent.error = error instanceof Error ? error.message : String(error);
|
|
@@ -93,6 +110,10 @@ Complete the task and provide your final answer.`;
|
|
|
93
110
|
// 실패도 알림
|
|
94
111
|
await sendAgentResult(agent);
|
|
95
112
|
}
|
|
113
|
+
finally {
|
|
114
|
+
// Controller 정리
|
|
115
|
+
abortControllers.delete(agent.id);
|
|
116
|
+
}
|
|
96
117
|
}
|
|
97
118
|
/**
|
|
98
119
|
* Agent 결과를 chat에 전송
|
|
@@ -143,10 +164,15 @@ export function cancelAgent(agentId) {
|
|
|
143
164
|
if (agent.status !== "running") {
|
|
144
165
|
return false; // 이미 완료된 agent는 취소 불가
|
|
145
166
|
}
|
|
146
|
-
//
|
|
147
|
-
// 상태를 cancelled로 표시하고 결과 전송 시 무시되도록 함
|
|
167
|
+
// 상태를 먼저 cancelled로 설정 (race condition 방지)
|
|
148
168
|
agent.status = "cancelled";
|
|
149
169
|
agent.completedAt = new Date();
|
|
170
|
+
// 실행 중인 API 호출 취소
|
|
171
|
+
const controller = abortControllers.get(agentId);
|
|
172
|
+
if (controller) {
|
|
173
|
+
controller.abort();
|
|
174
|
+
abortControllers.delete(agentId);
|
|
175
|
+
}
|
|
150
176
|
console.log(`[Agent ${agentId}] Cancelled`);
|
|
151
177
|
return true;
|
|
152
178
|
}
|
|
@@ -158,14 +184,44 @@ export function getAgent(agentId) {
|
|
|
158
184
|
}
|
|
159
185
|
/**
|
|
160
186
|
* 오래된 agent 정리 (1시간 이상)
|
|
187
|
+
* - 완료된 agent: completedAt 기준 1시간
|
|
188
|
+
* - running 상태도 createdAt 기준 1시간 지나면 정리 (stuck 방지)
|
|
161
189
|
*/
|
|
162
190
|
export function cleanupOldAgents() {
|
|
163
191
|
const oneHourAgo = Date.now() - 60 * 60 * 1000;
|
|
164
192
|
for (const [id, agent] of agents.entries()) {
|
|
193
|
+
// 완료된 agent: completedAt 기준
|
|
165
194
|
if (agent.completedAt && agent.completedAt.getTime() < oneHourAgo) {
|
|
166
195
|
agents.delete(id);
|
|
196
|
+
continue;
|
|
167
197
|
}
|
|
198
|
+
// running 상태도 1시간 지나면 정리 (stuck agent 방지)
|
|
199
|
+
if (agent.status === "running" && agent.createdAt.getTime() < oneHourAgo) {
|
|
200
|
+
console.log(`[Agent ${id}] Cleaning up stuck agent (running > 1h)`);
|
|
201
|
+
agents.delete(id);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Cleanup interval 참조 저장
|
|
206
|
+
let cleanupIntervalId = null;
|
|
207
|
+
/**
|
|
208
|
+
* 정기 cleanup 시작
|
|
209
|
+
*/
|
|
210
|
+
export function startCleanup() {
|
|
211
|
+
if (cleanupIntervalId)
|
|
212
|
+
return; // 이미 실행 중
|
|
213
|
+
cleanupIntervalId = setInterval(cleanupOldAgents, 10 * 60 * 1000);
|
|
214
|
+
console.log("[AgentManager] Cleanup interval started");
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* 정기 cleanup 중지
|
|
218
|
+
*/
|
|
219
|
+
export function stopCleanup() {
|
|
220
|
+
if (cleanupIntervalId) {
|
|
221
|
+
clearInterval(cleanupIntervalId);
|
|
222
|
+
cleanupIntervalId = null;
|
|
223
|
+
console.log("[AgentManager] Cleanup interval stopped");
|
|
168
224
|
}
|
|
169
225
|
}
|
|
170
|
-
//
|
|
171
|
-
|
|
226
|
+
// 자동 시작
|
|
227
|
+
startCleanup();
|