companionbot 0.11.0 → 0.11.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.
@@ -1,105 +1,88 @@
1
1
  /**
2
2
  * 전역 상수 설정
3
- *
4
3
  * 하드코딩된 매직 넘버들을 한 곳에서 관리
5
- * 환경변수로 오버라이드 가능
6
4
  */
7
- // 환경변수에서 숫자 읽기 (기본값 사용)
8
- function envInt(key, defaultValue) {
9
- const val = process.env[key];
10
- if (!val)
11
- return defaultValue;
12
- const parsed = parseInt(val, 10);
13
- return isNaN(parsed) ? defaultValue : parsed;
14
- }
15
- function envFloat(key, defaultValue) {
16
- const val = process.env[key];
17
- if (!val)
18
- return defaultValue;
19
- const parsed = parseFloat(val);
20
- return isNaN(parsed) ? defaultValue : parsed;
21
- }
22
5
  // ============================================
23
6
  // 세션 관련 설정
24
7
  // ============================================
25
8
  export const SESSION = {
26
9
  /** 최대 동시 세션 수 (LRU 정리) */
27
- MAX_SESSIONS: envInt("CB_MAX_SESSIONS", 100),
28
- /** 세션 TTL (밀리초) - 기본 24시간 */
29
- TTL_MS: envInt("CB_SESSION_TTL_MS", 24 * 60 * 60 * 1000),
10
+ MAX_SESSIONS: 100,
11
+ /** 세션 TTL (밀리초) - 24시간 */
12
+ TTL_MS: 24 * 60 * 60 * 1000,
30
13
  /** 메모리에 로드할 최대 히스토리 메시지 수 */
31
- MAX_HISTORY_LOAD: envInt("CB_MAX_HISTORY_LOAD", 50),
14
+ MAX_HISTORY_LOAD: 50,
32
15
  };
33
16
  // ============================================
34
17
  // 토큰/컨텍스트 관련 설정
35
18
  // ============================================
36
19
  export const TOKENS = {
37
20
  /** Claude 최대 컨텍스트 토큰 */
38
- MAX_CONTEXT: envInt("CB_MAX_CONTEXT_TOKENS", 100000),
21
+ MAX_CONTEXT: 100000,
39
22
  /** 히스토리 토큰 한도 */
40
- MAX_HISTORY: envInt("CB_MAX_HISTORY_TOKENS", 40000),
23
+ MAX_HISTORY: 40000,
41
24
  /** 이 이상이면 자동 요약 시작 */
42
- SUMMARY_THRESHOLD: envInt("CB_SUMMARY_THRESHOLD_TOKENS", 25000),
25
+ SUMMARY_THRESHOLD: 25000,
43
26
  /** 핀 맥락 최대 토큰 */
44
- MAX_PINNED: envInt("CB_MAX_PINNED_TOKENS", 5000),
27
+ MAX_PINNED: 5000,
45
28
  /** 자동 압축 시작 비율 (0.35 = 35%) */
46
- COMPACTION_THRESHOLD: envFloat("CB_COMPACTION_THRESHOLD", 0.35),
29
+ COMPACTION_THRESHOLD: 0.35,
47
30
  /** compact 스킵 기준 토큰 */
48
- COMPACT_MIN_TOKENS: envInt("CB_COMPACT_MIN_TOKENS", 5000),
31
+ COMPACT_MIN_TOKENS: 5000,
49
32
  };
50
33
  // ============================================
51
34
  // 메시지 관련 설정
52
35
  // ============================================
53
36
  export const MESSAGES = {
54
37
  /** 트리밍 시 최소 유지할 최근 메시지 수 */
55
- MIN_RECENT: envInt("CB_MIN_RECENT_MESSAGES", 6),
38
+ MIN_RECENT: 6,
56
39
  /** compact 시 유지할 최근 메시지 수 */
57
- KEEP_ON_COMPACT: envInt("CB_KEEP_ON_COMPACT", 4),
40
+ KEEP_ON_COMPACT: 4,
58
41
  /** 최대 요약 청크 수 */
59
- MAX_SUMMARY_CHUNKS: envInt("CB_MAX_SUMMARY_CHUNKS", 3),
42
+ MAX_SUMMARY_CHUNKS: 3,
60
43
  /** 검색 기본 결과 수 */
61
- SEARCH_LIMIT: envInt("CB_SEARCH_LIMIT", 10),
44
+ SEARCH_LIMIT: 10,
62
45
  /** 히스토리 로드 기본 limit */
63
- HISTORY_LOAD_LIMIT: envInt("CB_HISTORY_LOAD_LIMIT", 100),
46
+ HISTORY_LOAD_LIMIT: 100,
64
47
  };
65
48
  // ============================================
66
49
  // 메모리/벡터 저장소 설정
67
50
  // ============================================
68
51
  export const MEMORY = {
69
- /** 벡터 캐시 TTL (밀리초) - 기본 5분 */
70
- CACHE_TTL_MS: envInt("CB_MEMORY_CACHE_TTL_MS", 5 * 60 * 1000),
52
+ /** 벡터 캐시 TTL (밀리초) - 5분 */
53
+ CACHE_TTL_MS: 5 * 60 * 1000,
71
54
  /** 최소 청크 길이 (이하는 무시) */
72
- MIN_CHUNK_LENGTH: envInt("CB_MIN_CHUNK_LENGTH", 20),
55
+ MIN_CHUNK_LENGTH: 20,
73
56
  /** 최대 청크 길이 (초과 시 분할) */
74
- MAX_CHUNK_LENGTH: envInt("CB_MAX_CHUNK_LENGTH", 500),
57
+ MAX_CHUNK_LENGTH: 500,
75
58
  /** 로드할 최근 메모리 파일 일수 */
76
- RECENT_DAYS: envInt("CB_MEMORY_RECENT_DAYS", 30),
59
+ RECENT_DAYS: 30,
77
60
  /** 벡터 검색 기본 topK */
78
- SEARCH_TOP_K: envInt("CB_VECTOR_SEARCH_TOP_K", 5),
61
+ SEARCH_TOP_K: 5,
79
62
  /** 벡터 검색 최소 유사도 점수 */
80
- MIN_SIMILARITY: envFloat("CB_MIN_SIMILARITY", 0.3),
63
+ MIN_SIMILARITY: 0.3,
81
64
  /** /memory 명령어 표시 일수 */
82
- DISPLAY_DAYS: envInt("CB_MEMORY_DISPLAY_DAYS", 7),
65
+ DISPLAY_DAYS: 7,
83
66
  /** /memory 최대 표시 길이 */
84
- MAX_DISPLAY_LENGTH: envInt("CB_MEMORY_MAX_DISPLAY_LENGTH", 2000),
67
+ MAX_DISPLAY_LENGTH: 2000,
85
68
  };
86
69
  // ============================================
87
70
  // 텔레그램/UI 관련 설정
88
71
  // ============================================
89
72
  export const TELEGRAM = {
90
73
  /** 스트리밍 업데이트 간격 (밀리초) */
91
- STREAM_UPDATE_INTERVAL_MS: envInt("CB_STREAM_UPDATE_INTERVAL_MS", 500),
92
- /** 최대 이미지 크기 (바이트) - 기본 10MB */
93
- MAX_IMAGE_SIZE: envInt("CB_MAX_IMAGE_SIZE", 10 * 1024 * 1024),
74
+ STREAM_UPDATE_INTERVAL_MS: 500,
75
+ /** 최대 이미지 크기 (바이트) - 10MB */
76
+ MAX_IMAGE_SIZE: 10 * 1024 * 1024,
94
77
  /** URL 처리 최대 개수 */
95
- MAX_URL_FETCH: envInt("CB_MAX_URL_FETCH", 3),
78
+ MAX_URL_FETCH: 3,
96
79
  /** 캘린더 미리보기 이벤트 수 */
97
- CALENDAR_PREVIEW_COUNT: envInt("CB_CALENDAR_PREVIEW_COUNT", 3),
80
+ CALENDAR_PREVIEW_COUNT: 3,
98
81
  };
99
82
  // ============================================
100
83
  // 보안/토큰 관련 설정
101
84
  // ============================================
102
85
  export const SECURITY = {
103
- /** 리셋 토큰 만료 시간 (밀리초) - 기본 1분 */
104
- RESET_TOKEN_TTL_MS: envInt("CB_RESET_TOKEN_TTL_MS", 60000),
86
+ /** 리셋 토큰 만료 시간 (밀리초) - 1분 */
87
+ RESET_TOKEN_TTL_MS: 60000,
105
88
  };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 설정 파일 로더
3
+ * config.json에서 사용자 설정을 읽음
4
+ */
5
+ import { readFileSync, existsSync } from "fs";
6
+ import { resolve, dirname } from "path";
7
+ import { fileURLToPath } from "url";
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const CONFIG_PATH = resolve(__dirname, "../../config.json");
10
+ const DEFAULT_CONFIG = {
11
+ thinking: "medium",
12
+ };
13
+ let cachedConfig = null;
14
+ export function loadConfig() {
15
+ if (cachedConfig)
16
+ return cachedConfig;
17
+ if (!existsSync(CONFIG_PATH)) {
18
+ console.log("[Config] config.json not found, using defaults");
19
+ cachedConfig = DEFAULT_CONFIG;
20
+ return cachedConfig;
21
+ }
22
+ try {
23
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
24
+ const parsed = JSON.parse(raw);
25
+ cachedConfig = {
26
+ thinking: parsed.thinking ?? DEFAULT_CONFIG.thinking,
27
+ };
28
+ console.log(`[Config] Loaded: thinking=${cachedConfig.thinking}`);
29
+ return cachedConfig;
30
+ }
31
+ catch (error) {
32
+ console.error("[Config] Failed to load config.json:", error);
33
+ cachedConfig = DEFAULT_CONFIG;
34
+ return cachedConfig;
35
+ }
36
+ }
37
+ export function getConfig() {
38
+ return cachedConfig ?? loadConfig();
39
+ }
@@ -2,18 +2,20 @@ import { AsyncLocalStorage } from "async_hooks";
2
2
  import { estimateMessagesTokens, estimateTokens } from "../utils/tokens.js";
3
3
  import * as persistence from "./persistence.js";
4
4
  import { SESSION, TOKENS, MESSAGES } from "../config/constants.js";
5
+ import { getConfig } from "../config/index.js";
5
6
  // 세션별 상태 저장
6
7
  const sessions = new Map();
7
8
  // AsyncLocalStorage for chatId context
8
9
  const chatIdStorage = new AsyncLocalStorage();
9
10
  function getSession(chatId) {
11
+ const config = getConfig();
10
12
  // chatId 유효성 검사
11
13
  if (chatId == null || isNaN(chatId)) {
12
14
  console.error(`[Session] BUG: Invalid chatId: ${chatId} - history will NOT persist!`);
13
15
  return {
14
16
  history: [],
15
17
  model: "sonnet",
16
- thinkingLevel: "medium",
18
+ thinkingLevel: config.thinking,
17
19
  lastAccessedAt: Date.now(),
18
20
  pinnedContexts: [],
19
21
  summaryChunks: [],
@@ -29,7 +31,7 @@ function getSession(chatId) {
29
31
  if (!existing.summaryChunks)
30
32
  existing.summaryChunks = [];
31
33
  if (!existing.thinkingLevel)
32
- existing.thinkingLevel = "medium";
34
+ existing.thinkingLevel = config.thinking;
33
35
  return existing;
34
36
  }
35
37
  // 새 세션 생성 전 정리
@@ -47,7 +49,7 @@ function getSession(chatId) {
47
49
  const session = {
48
50
  history,
49
51
  model: "sonnet",
50
- thinkingLevel: "medium",
52
+ thinkingLevel: config.thinking,
51
53
  lastAccessedAt: now,
52
54
  pinnedContexts: [],
53
55
  summaryChunks: [],
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from "crypto";
2
2
  import { getHealthStatus, formatUptime } from "../../health/index.js";
3
- import { chat, MODELS, THINKING_CONFIGS } from "../../ai/claude.js";
3
+ import { chat, MODELS } from "../../ai/claude.js";
4
4
  import { estimateMessagesTokens } from "../../utils/tokens.js";
5
5
  import { TOKENS, MESSAGES, MEMORY, SECURITY, TELEGRAM } from "../../config/constants.js";
6
6
  // 대화 요약 생성 함수
@@ -50,7 +50,7 @@ function validateResetToken(chatId, token) {
50
50
  resetTokens.delete(chatId); // 사용 후 삭제
51
51
  return true;
52
52
  }
53
- import { getHistory, clearHistory, getModel, setModel, getThinkingLevel, setThinkingLevel, runWithChatId, getPinnedContexts, pinContext, unpinContext, clearPins, getSessionStats, addMessage, } from "../../session/state.js";
53
+ import { getHistory, clearHistory, getModel, setModel, runWithChatId, getPinnedContexts, pinContext, unpinContext, clearPins, getSessionStats, addMessage, } from "../../session/state.js";
54
54
  import { hasBootstrap, loadRecentMemories, getWorkspacePath, } from "../../workspace/index.js";
55
55
  import { getSecret, setSecret, deleteSecret } from "../../config/secrets.js";
56
56
  import { getReminders } from "../../reminders/index.js";
@@ -225,48 +225,6 @@ export function registerCommands(bot) {
225
225
  `Available: sonnet, opus, haiku`);
226
226
  }
227
227
  });
228
- // /thinking 명령어 - thinking 레벨 변경
229
- bot.command("thinking", async (ctx) => {
230
- const chatId = ctx.chat.id;
231
- const arg = ctx.message?.text?.split(" ")[1]?.toLowerCase();
232
- const currentLevel = getThinkingLevel(chatId);
233
- const currentModel = getModel(chatId);
234
- const modelSupportsThinking = MODELS[currentModel].supportsThinking;
235
- if (!arg) {
236
- const levelList = Object.entries(THINKING_CONFIGS)
237
- .map(([level, config]) => {
238
- const marker = level === currentLevel ? "→" : " ";
239
- const desc = level === "off"
240
- ? "비활성화"
241
- : `최대 ${config.maxBudget} 토큰 (${Math.round(config.ratio * 100)}%)`;
242
- return `${marker} /thinking ${level} - ${desc}`;
243
- })
244
- .join("\n");
245
- const warning = !modelSupportsThinking
246
- ? `\n\n⚠️ 현재 모델(${MODELS[currentModel].name})은 thinking을 지원하지 않습니다.`
247
- : "";
248
- await ctx.reply(`🧠 Thinking 레벨: ${currentLevel}${warning}\n\n` +
249
- `사용 가능한 레벨:\n${levelList}\n\n` +
250
- `Thinking이 높을수록 복잡한 문제를 더 잘 해결하지만 응답이 느려집니다.`);
251
- return;
252
- }
253
- if (arg in THINKING_CONFIGS) {
254
- const level = arg;
255
- setThinkingLevel(chatId, level);
256
- const config = THINKING_CONFIGS[level];
257
- const desc = level === "off"
258
- ? "Thinking이 비활성화되었습니다."
259
- : `최대 ${config.maxBudget} 토큰 (출력의 ${Math.round(config.ratio * 100)}%)`;
260
- const warning = !modelSupportsThinking && level !== "off"
261
- ? `\n\n⚠️ 현재 모델(${MODELS[currentModel].name})은 thinking을 지원하지 않습니다. 모델을 변경해주세요.`
262
- : "";
263
- await ctx.reply(`🧠 Thinking 레벨: ${level}\n${desc}${warning}`);
264
- }
265
- else {
266
- await ctx.reply(`Unknown level: ${arg}\n\n` +
267
- `Available: off, low, medium, high`);
268
- }
269
- });
270
228
  // /setup 명령어 - 추가 기능 설정 및 관리
271
229
  bot.command("setup", async (ctx) => {
272
230
  const chatId = ctx.chat.id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "companionbot",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "AI 친구 텔레그램 봇 - Claude API 기반 개인화된 대화 상대",
5
5
  "keywords": [
6
6
  "telegram",