choavis-agent 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.
Files changed (107) hide show
  1. package/.env.example +37 -0
  2. package/README.md +258 -0
  3. package/dist/agent/memory.d.ts +13 -0
  4. package/dist/agent/memory.d.ts.map +1 -0
  5. package/dist/agent/memory.js +70 -0
  6. package/dist/agent/memory.js.map +1 -0
  7. package/dist/agent/queue.d.ts +14 -0
  8. package/dist/agent/queue.d.ts.map +1 -0
  9. package/dist/agent/queue.js +91 -0
  10. package/dist/agent/queue.js.map +1 -0
  11. package/dist/agent/runner.d.ts +40 -0
  12. package/dist/agent/runner.d.ts.map +1 -0
  13. package/dist/agent/runner.js +179 -0
  14. package/dist/agent/runner.js.map +1 -0
  15. package/dist/agent/session.d.ts +8 -0
  16. package/dist/agent/session.d.ts.map +1 -0
  17. package/dist/agent/session.js +127 -0
  18. package/dist/agent/session.js.map +1 -0
  19. package/dist/agent/usage.d.ts +27 -0
  20. package/dist/agent/usage.d.ts.map +1 -0
  21. package/dist/agent/usage.js +67 -0
  22. package/dist/agent/usage.js.map +1 -0
  23. package/dist/cli/config.d.ts +2 -0
  24. package/dist/cli/config.d.ts.map +1 -0
  25. package/dist/cli/config.js +84 -0
  26. package/dist/cli/config.js.map +1 -0
  27. package/dist/cli/env.d.ts +3 -0
  28. package/dist/cli/env.d.ts.map +1 -0
  29. package/dist/cli/env.js +27 -0
  30. package/dist/cli/env.js.map +1 -0
  31. package/dist/cli/init.d.ts +2 -0
  32. package/dist/cli/init.d.ts.map +1 -0
  33. package/dist/cli/init.js +56 -0
  34. package/dist/cli/init.js.map +1 -0
  35. package/dist/cli/prompts.d.ts +9 -0
  36. package/dist/cli/prompts.d.ts.map +1 -0
  37. package/dist/cli/prompts.js +36 -0
  38. package/dist/cli/prompts.js.map +1 -0
  39. package/dist/cli.d.ts +3 -0
  40. package/dist/cli.d.ts.map +1 -0
  41. package/dist/cli.js +39 -0
  42. package/dist/cli.js.map +1 -0
  43. package/dist/config.d.ts +26 -0
  44. package/dist/config.d.ts.map +1 -0
  45. package/dist/config.js +50 -0
  46. package/dist/config.js.map +1 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +41 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/slack/app.d.ts +3 -0
  52. package/dist/slack/app.d.ts.map +1 -0
  53. package/dist/slack/app.js +11 -0
  54. package/dist/slack/app.js.map +1 -0
  55. package/dist/slack/commands/compact.d.ts +3 -0
  56. package/dist/slack/commands/compact.d.ts.map +1 -0
  57. package/dist/slack/commands/compact.js +53 -0
  58. package/dist/slack/commands/compact.js.map +1 -0
  59. package/dist/slack/commands/help.d.ts +3 -0
  60. package/dist/slack/commands/help.d.ts.map +1 -0
  61. package/dist/slack/commands/help.js +52 -0
  62. package/dist/slack/commands/help.js.map +1 -0
  63. package/dist/slack/commands/index.d.ts +7 -0
  64. package/dist/slack/commands/index.d.ts.map +1 -0
  65. package/dist/slack/commands/index.js +40 -0
  66. package/dist/slack/commands/index.js.map +1 -0
  67. package/dist/slack/commands/memory.d.ts +3 -0
  68. package/dist/slack/commands/memory.d.ts.map +1 -0
  69. package/dist/slack/commands/memory.js +82 -0
  70. package/dist/slack/commands/memory.js.map +1 -0
  71. package/dist/slack/commands/new.d.ts +3 -0
  72. package/dist/slack/commands/new.d.ts.map +1 -0
  73. package/dist/slack/commands/new.js +24 -0
  74. package/dist/slack/commands/new.js.map +1 -0
  75. package/dist/slack/commands/project.d.ts +3 -0
  76. package/dist/slack/commands/project.d.ts.map +1 -0
  77. package/dist/slack/commands/project.js +41 -0
  78. package/dist/slack/commands/project.js.map +1 -0
  79. package/dist/slack/commands/status.d.ts +3 -0
  80. package/dist/slack/commands/status.d.ts.map +1 -0
  81. package/dist/slack/commands/status.js +59 -0
  82. package/dist/slack/commands/status.js.map +1 -0
  83. package/dist/slack/commands/stop.d.ts +3 -0
  84. package/dist/slack/commands/stop.d.ts.map +1 -0
  85. package/dist/slack/commands/stop.js +31 -0
  86. package/dist/slack/commands/stop.js.map +1 -0
  87. package/dist/slack/commands/types.d.ts +22 -0
  88. package/dist/slack/commands/types.d.ts.map +1 -0
  89. package/dist/slack/commands/types.js +2 -0
  90. package/dist/slack/commands/types.js.map +1 -0
  91. package/dist/slack/commands/usage.d.ts +3 -0
  92. package/dist/slack/commands/usage.d.ts.map +1 -0
  93. package/dist/slack/commands/usage.js +62 -0
  94. package/dist/slack/commands/usage.js.map +1 -0
  95. package/dist/slack/formatter.d.ts +40 -0
  96. package/dist/slack/formatter.d.ts.map +1 -0
  97. package/dist/slack/formatter.js +255 -0
  98. package/dist/slack/formatter.js.map +1 -0
  99. package/dist/slack/handlers.d.ts +6 -0
  100. package/dist/slack/handlers.d.ts.map +1 -0
  101. package/dist/slack/handlers.js +386 -0
  102. package/dist/slack/handlers.js.map +1 -0
  103. package/dist/utils/logger.d.ts +9 -0
  104. package/dist/utils/logger.d.ts.map +1 -0
  105. package/dist/utils/logger.js +48 -0
  106. package/dist/utils/logger.js.map +1 -0
  107. package/package.json +34 -0
package/.env.example ADDED
@@ -0,0 +1,37 @@
1
+ # Slack Bot Token (xoxb-...)
2
+ SLACK_BOT_TOKEN=xoxb-your-bot-token
3
+
4
+ # Slack App-Level Token (xapp-...) - Socket Mode에 필요
5
+ SLACK_APP_TOKEN=xapp-your-app-token
6
+
7
+ # Claude 모델 (비워두면 Claude Code 사용자 설정을 따라감)
8
+ # opus, sonnet, haiku 또는 전체 모델명(claude-opus-4-1-20250414 등) 입력 가능
9
+ # CLAUDE_MODEL=
10
+
11
+ # Claude Code 권한 모드 (기본값: plan)
12
+ # plan: 작업 전 사용자에게 확인 요청
13
+ # default: 기본 권한
14
+ # bypassPermissions: 모든 도구 자동 승인
15
+ # AGENT_PERMISSION_MODE=plan
16
+
17
+ # Claude Code가 실행될 작업 디렉토리 (기본값: 현재 디렉토리)
18
+ # AGENT_WORK_DIR=/path/to/workspace
19
+
20
+ # Claude CLI 경로 (기본값: claude)
21
+ # CLAUDE_PATH=claude
22
+
23
+ # 프로젝트 레지스트리 (JSON) - Slack에서 "프로젝트: 이름"으로 전환 가능
24
+ # AGENT_PROJECTS={"my-project":"/path/to/project"}
25
+
26
+ # 허용된 Slack 사용자 ID (쉼표 구분, 미설정 시 모든 사용자 허용)
27
+ # ALLOWED_SLACK_USERS=U12345678,U87654321
28
+
29
+ # 동시 Claude 프로세스 제한 (기본값: 5)
30
+ # MAX_CONCURRENT_AGENTS=5
31
+
32
+ # 세션 자동 리셋
33
+ # SESSION_DAILY_RESET_HOUR=4
34
+ # SESSION_IDLE_RESET_MINUTES=120
35
+
36
+ # 사용량 데이터 경로
37
+ # USAGE_DATA_DIR=./data/usage
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # Choavis Agent
2
+
3
+ **언제 어디서든, 내 코딩 에이전트를 호출한다.**
4
+
5
+ Choavis Agent는 로컬 PC에 설치된 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)를 Slack을 통해 제어하는 개인 AI 코딩 에이전트입니다.
6
+
7
+ 카페에서 커피를 마시며 스마트폰으로 Slack 메시지 하나를 보내면, 집에 있는 내 개발 머신의 Claude Code가 코드를 작성하고, 테스트를 실행하고, Git에 커밋합니다. 출퇴근 지하철에서 버그 리포트를 보고, Slack에서 바로 수정을 지시할 수 있습니다.
8
+
9
+ > [OpenClaw](https://github.com/anthropics/claude-code)의 개념에서 영감을 받았습니다.
10
+ > 외부 서버에 코드를 올리는 보안 리스크 없이, **내 로컬 머신에서 직접 실행**되는 구조로 설계했습니다.
11
+
12
+ ---
13
+
14
+ ## 왜 Choavis Agent인가?
15
+
16
+ ### 문제
17
+ - Claude Code는 강력하지만, **터미널 앞에 앉아있어야** 사용할 수 있다
18
+ - 클라우드 기반 코딩 에이전트는 편리하지만, **내 코드가 외부 서버를 거친다**
19
+ - 팀원들과 AI 에이전트를 공유하려면 별도 인프라가 필요하다
20
+
21
+ ### 해결
22
+ - Slack이 리모컨, 내 PC가 실행 머신. **코드는 내 로컬에서만 돌아간다**
23
+ - `npm install` 한 번으로 설치, 대화형 셋업으로 3분 만에 시작
24
+ - Slack만 있으면 스마트폰, 태블릿, 어디서든 접근 가능
25
+
26
+ ---
27
+
28
+ ## 이런 것들이 가능합니다
29
+
30
+ ### 코드 작성 & 수정
31
+ > "UserService에 이메일 중복 체크 로직 추가해줘"
32
+
33
+ 슬랙 메시지 하나로 코드 수정, 파일 생성, 리팩토링이 실행됩니다.
34
+
35
+ ### 버그 분석 & 수정
36
+ > "로그인 API에서 500 에러 나는데, AuthController 확인해봐"
37
+
38
+ 에이전트가 코드를 읽고, 원인을 분석하고, 수정안을 제시하거나 직접 고칩니다.
39
+
40
+ ### Git & 배포
41
+ > "지금까지 변경사항 커밋하고 PR 만들어줘"
42
+
43
+ Git add, commit, push, PR 생성까지 한 번에 처리합니다.
44
+
45
+ ### 코드 리뷰 & 분석
46
+ > "src/services/ 디렉토리 구조 분석해줘"
47
+
48
+ 프로젝트 구조를 파악하고, 코드 품질을 분석하고, 개선안을 제안합니다.
49
+
50
+ ### 업무 자동화
51
+ > "테스트 돌려서 결과 알려줘"
52
+
53
+ 반복 작업을 에이전트에게 위임하여 개발 워크플로우를 자동화합니다.
54
+
55
+ ### 멀티 프로젝트
56
+ > "프로젝트: mss-backend" → "회원 API 엔드포인트 목록 보여줘"
57
+
58
+ 여러 프로젝트를 등록하고 Slack에서 자유롭게 전환하며 작업합니다.
59
+
60
+ ---
61
+
62
+ ## 활용 시나리오
63
+
64
+ **출퇴근 중**
65
+ 지하철에서 Jira 티켓을 보고, Slack으로 "JIRA-123 이슈 수정해줘"라고 보내면 집에 있는 PC가 코드를 수정하고 PR을 올립니다.
66
+
67
+ **카페에서**
68
+ 노트북 없이 스마트폰만으로 "어제 작업한 feature 브랜치 테스트 돌려봐" → 테스트 결과를 Slack으로 받습니다.
69
+
70
+ **재택 중**
71
+ 업무용 PC가 회사에 있어도, Slack으로 회사 PC의 Claude Code를 제어하여 개발합니다.
72
+
73
+ **팀 공유**
74
+ 팀원들의 Slack ID를 등록하면, 하나의 에이전트를 팀이 함께 사용할 수 있습니다. 각자의 스레드에서 독립적인 세션으로 작업합니다.
75
+
76
+ ---
77
+
78
+ ## 주요 기능
79
+
80
+ | 기능 | 설명 |
81
+ |------|------|
82
+ | 대화형 코딩 | 자연어로 코드 작성, 수정, 리팩토링 지시 |
83
+ | 스레드 세션 | Slack 스레드별 독립 세션, 컨텍스트 유지 |
84
+ | 스트리밍 응답 | 실시간 진행 상황 표시 |
85
+ | 명령어 시스템 | `/help`, `/status`, `/new`, `/compact`, `/stop`, `/usage` |
86
+ | 기억 시스템 | `기억해: ...`로 에이전트에게 영구 정보 저장 |
87
+ | 멀티 프로젝트 | `프로젝트: 이름`으로 작업 디렉토리 전환 |
88
+ | 에러 자동 복구 | 컨텍스트 오버플로우 자동 압축, 세션 오류 자동 리셋 |
89
+ | 사용량 추적 | 토큰 사용량, 비용 추정, 사용자별 통계 |
90
+ | 퍼미션 모드 | plan / default / bypassPermissions 선택 가능 |
91
+ | 모델 선택 | opus, sonnet, haiku 또는 직접 모델명 입력 |
92
+
93
+ ---
94
+
95
+ ## 사전 준비
96
+
97
+ 1. **Node.js 20 이상** - [다운로드](https://nodejs.org/)
98
+ 2. **Claude Code CLI** - [설치 가이드](https://docs.anthropic.com/en/docs/claude-code)
99
+ ```bash
100
+ npm install -g @anthropic-ai/claude-code
101
+ ```
102
+ 3. **Slack 앱** - 아래 가이드를 따라 생성
103
+
104
+ ---
105
+
106
+ ## 빠른 시작
107
+
108
+ ```bash
109
+ # 1. 설치
110
+ npm install -g choavis-agent
111
+
112
+ # 2. 설정 (대화형 온보딩)
113
+ choavis-agent init
114
+
115
+ # 3. 실행
116
+ choavis-agent start
117
+ ```
118
+
119
+ 설정을 변경하고 싶다면:
120
+ ```bash
121
+ choavis-agent config
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Slack 앱 설정 가이드
127
+
128
+ ### 1. 앱 생성
129
+ 1. [Slack API](https://api.slack.com/apps)에서 **Create New App** → **From scratch**
130
+ 2. 앱 이름 입력 (예: `Choavis Agent`), 워크스페이스 선택
131
+
132
+ ### 2. Socket Mode 활성화
133
+ 1. **Settings > Socket Mode** → Enable Socket Mode
134
+ 2. App-Level Token 생성 (이름: `socket`, Scope: `connections:write`)
135
+ 3. 생성된 토큰 복사 (`xapp-...`) → 온보딩에서 입력
136
+
137
+ ### 3. 봇 권한 설정
138
+ **Features > OAuth & Permissions > Bot Token Scopes**에 추가:
139
+
140
+ | Scope | 용도 |
141
+ |-------|------|
142
+ | `app_mentions:read` | @멘션 감지 |
143
+ | `channels:history` | 채널 메시지 읽기 |
144
+ | `chat:write` | 메시지 전송 |
145
+ | `files:write` | 코드 스니펫 업로드 |
146
+ | `groups:history` | 비공개 채널 메시지 읽기 |
147
+ | `im:history` | DM 메시지 읽기 |
148
+ | `im:write` | DM 전송 |
149
+ | `reactions:read` | 리액션 읽기 |
150
+ | `reactions:write` | 리액션 추가/제거 |
151
+
152
+ ### 4. 이벤트 구독
153
+ **Features > Event Subscriptions** → Enable Events
154
+
155
+ **Subscribe to bot events:**
156
+ - `app_mention` - 채널에서 @멘션 시
157
+ - `message.channels` - 채널 메시지
158
+ - `message.groups` - 비공개 채널 메시지
159
+ - `message.im` - DM 메시지
160
+
161
+ ### 5. 앱 설치
162
+ **Settings > Install App** → Install to Workspace → 권한 승인
163
+
164
+ Bot User OAuth Token 복사 (`xoxb-...`) → 온보딩에서 입력
165
+
166
+ ---
167
+
168
+ ## 명령어 레퍼런스
169
+
170
+ Slack에서 에이전트에게 다음 명령어를 보낼 수 있습니다:
171
+
172
+ | 명령어 | 별칭 | 설명 |
173
+ |--------|------|------|
174
+ | `/help` | `도움말` | 사용 가능한 명령어 목록 |
175
+ | `/status` | `상태` | 현재 설정, 세션, 사용량 요약 |
176
+ | `/usage` | - | 오늘의 사용량 통계 |
177
+ | `/usage me` | - | 내 사용량만 조회 |
178
+ | `/usage week` | - | 최근 7일 사용량 |
179
+ | `/new` | `/reset` | 현재 스레드 세션 초기화 |
180
+ | `/compact` | - | 컨텍스트 압축 (긴 대화 시) |
181
+ | `/stop` | `중지` | 현재 처리 중인 작업 중단 |
182
+ | `프로젝트: 이름` | - | 작업 디렉토리 전환 |
183
+ | `기억해: 내용` | - | 에이전트에 영구 정보 저장 |
184
+ | `기억 목록` | - | 저장된 기억 조회 |
185
+ | `기억 삭제: ID` | - | 저장된 기억 삭제 |
186
+
187
+ ---
188
+
189
+ ## 설정 옵션
190
+
191
+ `choavis-agent config`로 변경하거나, `.env` 파일을 직접 수정할 수 있습니다.
192
+
193
+ | 환경변수 | 필수 | 기본값 | 설명 |
194
+ |----------|------|--------|------|
195
+ | `SLACK_BOT_TOKEN` | O | - | Slack Bot OAuth Token (`xoxb-...`) |
196
+ | `SLACK_APP_TOKEN` | O | - | Slack App-Level Token (`xapp-...`) |
197
+ | `CLAUDE_MODEL` | - | *(Claude Code 기본값)* | Claude 모델 (opus, sonnet, haiku 또는 전체 모델명) |
198
+ | `AGENT_PERMISSION_MODE` | - | `plan` | Claude Code 권한 모드 |
199
+ | `ALLOWED_SLACK_USERS` | - | *(전체 허용)* | 허용 사용자 ID (쉼표 구분) |
200
+ | `AGENT_WORK_DIR` | - | 현재 디렉토리 | Claude Code 작업 디렉토리 |
201
+ | `CLAUDE_PATH` | - | `claude` | Claude CLI 경로 |
202
+ | `AGENT_PROJECTS` | - | `{}` | 프로젝트 레지스트리 (JSON) |
203
+ | `MAX_CONCURRENT_AGENTS` | - | `5` | 동시 실행 가능한 Claude 프로세스 수 |
204
+ | `SESSION_DAILY_RESET_HOUR` | - | `4` | 매일 세션 자동 리셋 시각 (0-23) |
205
+ | `SESSION_IDLE_RESET_MINUTES` | - | `120` | 유휴 세션 자동 리셋 시간 (분) |
206
+
207
+ ---
208
+
209
+ ## 아키텍처
210
+
211
+ ```
212
+ ┌─────────────┐ Socket Mode ┌──────────────────┐
213
+ │ Slack App │◄───────────────────►│ Choavis Agent │
214
+ │ (UI 역할) │ │ (Node.js) │
215
+ └─────────────┘ ├──────────────────┤
216
+ │ Command Router │
217
+ 사용자가 │ Session Manager │
218
+ Slack에서 │ Queue System │
219
+ 메시지 전송 │ Memory System │
220
+ │ Usage Tracker │
221
+ └────────┬─────────┘
222
+ │ subprocess
223
+ ┌────────▼─────────┐
224
+ │ Claude Code │
225
+ │ CLI (로컬) │
226
+ │ │
227
+ │ 코드 읽기/쓰기 │
228
+ │ 터미널 명령 실행 │
229
+ │ Git 작업 │
230
+ │ ... 무엇이든 │
231
+ └──────────────────┘
232
+ ```
233
+
234
+ **핵심 설계 원칙:**
235
+ - **코드는 내 로컬에서만** - 외부 서버 없음, Claude Code CLI가 로컬에서 직접 실행
236
+ - **Slack은 리모컨** - 메시지를 전달하고 결과를 보여주는 UI 역할
237
+ - **스레드 = 세션** - 각 Slack 스레드가 독립적인 Claude Code 세션
238
+
239
+ ---
240
+
241
+ ## 개발
242
+
243
+ ```bash
244
+ # 개발 모드 (tsx로 즉시 실행)
245
+ npm run dev
246
+
247
+ # 빌드
248
+ npm run build
249
+
250
+ # 테스트
251
+ npm test
252
+ ```
253
+
254
+ ---
255
+
256
+ ## 라이선스
257
+
258
+ MIT
@@ -0,0 +1,13 @@
1
+ export interface MemoryEntry {
2
+ id: string;
3
+ content: string;
4
+ createdAt: string;
5
+ }
6
+ export interface MemoryStore {
7
+ entries: MemoryEntry[];
8
+ }
9
+ export declare function addMemory(baseDir: string, content: string): Promise<MemoryEntry>;
10
+ export declare function listMemories(baseDir: string): Promise<MemoryEntry[]>;
11
+ export declare function deleteMemory(baseDir: string, memoryId: string): Promise<boolean>;
12
+ export declare function buildMemoryContext(baseDir: string): Promise<string>;
13
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/agent/memory.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AA+BD,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAYtF;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAI1E;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQtF;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASzE"}
@@ -0,0 +1,70 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { createLogger } from '../utils/logger.js';
5
+ const log = createLogger('memory');
6
+ function getFilePath(baseDir) {
7
+ return join(baseDir, 'memories.json');
8
+ }
9
+ async function ensureDir(baseDir) {
10
+ if (!existsSync(baseDir)) {
11
+ await mkdir(baseDir, { recursive: true });
12
+ }
13
+ }
14
+ async function loadStore(filePath) {
15
+ if (!existsSync(filePath))
16
+ return { entries: [] };
17
+ try {
18
+ const data = await readFile(filePath, 'utf-8');
19
+ return JSON.parse(data);
20
+ }
21
+ catch {
22
+ log.warn('메모리 파일 파싱 실패', { filePath });
23
+ return { entries: [] };
24
+ }
25
+ }
26
+ async function saveStore(filePath, store) {
27
+ await writeFile(filePath, JSON.stringify(store, null, 2));
28
+ }
29
+ function generateId() {
30
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
31
+ }
32
+ export async function addMemory(baseDir, content) {
33
+ await ensureDir(baseDir);
34
+ const filePath = getFilePath(baseDir);
35
+ const store = await loadStore(filePath);
36
+ const entry = {
37
+ id: generateId(),
38
+ content,
39
+ createdAt: new Date().toISOString(),
40
+ };
41
+ store.entries.push(entry);
42
+ await saveStore(filePath, store);
43
+ return entry;
44
+ }
45
+ export async function listMemories(baseDir) {
46
+ const filePath = getFilePath(baseDir);
47
+ const store = await loadStore(filePath);
48
+ return store.entries;
49
+ }
50
+ export async function deleteMemory(baseDir, memoryId) {
51
+ const filePath = getFilePath(baseDir);
52
+ const store = await loadStore(filePath);
53
+ const idx = store.entries.findIndex((e) => e.id === memoryId);
54
+ if (idx === -1)
55
+ return false;
56
+ store.entries.splice(idx, 1);
57
+ await saveStore(filePath, store);
58
+ return true;
59
+ }
60
+ export async function buildMemoryContext(baseDir) {
61
+ const entries = await listMemories(baseDir);
62
+ if (entries.length === 0)
63
+ return '';
64
+ const lines = ['[기억 컨텍스트]'];
65
+ for (const e of entries) {
66
+ lines.push(`- ${e.content}`);
67
+ }
68
+ return lines.join('\n');
69
+ }
70
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/agent/memory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 주어진 스레드의 큐에 작업을 추가하고, 실행이 완료되면 resolve되는 Promise를 반환합니다.
3
+ * 같은 스레드의 작업은 순차적으로 실행되며, 다른 스레드 간에는 병렬로 실행됩니다.
4
+ */
5
+ export declare function enqueue(threadTs: string, fn: () => Promise<void>): Promise<void>;
6
+ /**
7
+ * 해당 스레드의 대기 중인 작업을 모두 제거하고 제거된 개수를 반환합니다.
8
+ */
9
+ export declare function clearQueue(threadTs: string): number;
10
+ /**
11
+ * 해당 스레드의 대기 중인 작업 수를 반환합니다.
12
+ */
13
+ export declare function getQueueSize(threadTs: string): number;
14
+ //# sourceMappingURL=queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../../src/agent/queue.ts"],"names":[],"mappings":"AA6DA;;;GAGG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhF;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMnD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIrD"}
@@ -0,0 +1,91 @@
1
+ const CLEANUP_DELAY_MS = 60_000;
2
+ const queues = new Map();
3
+ function getOrCreateQueue(threadTs) {
4
+ let queue = queues.get(threadTs);
5
+ if (!queue) {
6
+ queue = { items: [], processing: false, cleanup: null };
7
+ queues.set(threadTs, queue);
8
+ }
9
+ return queue;
10
+ }
11
+ function scheduleCleanup(threadTs, queue) {
12
+ if (queue.cleanup) {
13
+ clearTimeout(queue.cleanup);
14
+ queue.cleanup = null;
15
+ }
16
+ queue.cleanup = setTimeout(() => {
17
+ if (queue.items.length === 0 && !queue.processing) {
18
+ queues.delete(threadTs);
19
+ console.log(`[queue] 스레드 ${threadTs} 큐 정리됨`);
20
+ }
21
+ }, CLEANUP_DELAY_MS);
22
+ }
23
+ async function processQueue(threadTs, queue) {
24
+ if (queue.processing)
25
+ return;
26
+ queue.processing = true;
27
+ // cleanup 타이머가 있으면 취소 (처리 중이므로)
28
+ if (queue.cleanup) {
29
+ clearTimeout(queue.cleanup);
30
+ queue.cleanup = null;
31
+ }
32
+ while (queue.items.length > 0) {
33
+ const item = queue.items[0];
34
+ try {
35
+ await item.execute();
36
+ }
37
+ catch (err) {
38
+ console.error(`[queue] 스레드 ${threadTs} 작업 실행 중 오류:`, err);
39
+ }
40
+ queue.items.shift();
41
+ }
42
+ queue.processing = false;
43
+ scheduleCleanup(threadTs, queue);
44
+ }
45
+ /**
46
+ * 주어진 스레드의 큐에 작업을 추가하고, 실행이 완료되면 resolve되는 Promise를 반환합니다.
47
+ * 같은 스레드의 작업은 순차적으로 실행되며, 다른 스레드 간에는 병렬로 실행됩니다.
48
+ */
49
+ export function enqueue(threadTs, fn) {
50
+ const queue = getOrCreateQueue(threadTs);
51
+ return new Promise((resolve, reject) => {
52
+ const item = {
53
+ execute: async () => {
54
+ try {
55
+ await fn();
56
+ resolve();
57
+ }
58
+ catch (err) {
59
+ reject(err);
60
+ throw err;
61
+ }
62
+ },
63
+ };
64
+ queue.items.push(item);
65
+ // 현재 처리 중이 아니면 큐 처리 시작
66
+ if (!queue.processing) {
67
+ void processQueue(threadTs, queue);
68
+ }
69
+ });
70
+ }
71
+ /**
72
+ * 해당 스레드의 대기 중인 작업을 모두 제거하고 제거된 개수를 반환합니다.
73
+ */
74
+ export function clearQueue(threadTs) {
75
+ const queue = queues.get(threadTs);
76
+ if (!queue)
77
+ return 0;
78
+ const cleared = queue.items.length;
79
+ queue.items = [];
80
+ return cleared;
81
+ }
82
+ /**
83
+ * 해당 스레드의 대기 중인 작업 수를 반환합니다.
84
+ */
85
+ export function getQueueSize(threadTs) {
86
+ const queue = queues.get(threadTs);
87
+ if (!queue)
88
+ return 0;
89
+ return queue.items.length;
90
+ }
91
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../../src/agent/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ export type PermissionMode = 'default' | 'plan' | 'acceptEdits' | 'bypassPermissions';
2
+ export interface RunOptions {
3
+ prompt: string;
4
+ sessionId?: string;
5
+ workDir?: string;
6
+ abortSignal?: AbortSignal;
7
+ permissionMode?: PermissionMode;
8
+ allowedTools?: string[];
9
+ }
10
+ export type AgentErrorType = 'transient' | 'session_corrupt' | 'context_overflow' | 'fatal';
11
+ export declare function classifyError(exitCode: number | null, stderr: string): AgentErrorType;
12
+ export interface StreamEvent {
13
+ type: string;
14
+ subtype?: string;
15
+ session_id?: string;
16
+ errorType?: AgentErrorType;
17
+ message?: {
18
+ content: Array<{
19
+ type: string;
20
+ text?: string;
21
+ }>;
22
+ };
23
+ result?: string;
24
+ tool_name?: string;
25
+ content_block?: {
26
+ type: string;
27
+ text?: string;
28
+ };
29
+ delta?: {
30
+ type: string;
31
+ text?: string;
32
+ };
33
+ [key: string]: any;
34
+ }
35
+ export interface RunResult {
36
+ stream: AsyncGenerator<StreamEvent>;
37
+ kill: () => void;
38
+ }
39
+ export declare function runAgent(options: RunOptions): RunResult;
40
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,aAAa,GAAG,mBAAmB,CAAC;AAEtF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,OAAO,CAAC;AAE5F,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CAWrF;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,OAAO,CAAC,EAAE;QAAE,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAExC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC;IACpC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAID,wBAAgB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,SAAS,CAuFvD"}