Files
telegram-bot-workers/docs/plans/2026-02-05-agent-refactoring-implementation.md
kappa f9f577f25c feat: implement processDomainConsultation main handler
- Add full conversation flow with session management
- Handle tool call execution
- Support __PASSTHROUGH__ and __SESSION_END__ markers
- Add hasDomainSession helper for routing
- Export executeDomainAction from domain-tool.ts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 12:47:34 +09:00

29 KiB

도메인/예치금 에이전트 리팩토링 구현 계획

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: domain-tool.ts와 deposit-agent.ts를 server-agent 패턴의 세션 기반 AI 에이전트로 리팩토링

Architecture: 모든 도메인/예치금 요청이 에이전트를 통과하고 AI가 의도를 파악. 도메인은 작업 단위 세션, 예치금은 입금 신고만 세션 유지.

Tech Stack: TypeScript, Cloudflare Workers, D1, OpenAI GPT-4o-mini


Phase 1: 기반 작업

Task 1.1: 마이그레이션 SQL 작성

Files:

  • Create: migrations/006_add_agent_sessions.sql

Step 1: 마이그레이션 파일 작성

-- 006_add_agent_sessions.sql
-- Domain Agent Sessions
CREATE TABLE IF NOT EXISTS domain_sessions (
  user_id TEXT PRIMARY KEY,
  status TEXT NOT NULL CHECK(status IN ('gathering', 'suggesting', 'confirming', 'setting_ns', 'completed')),
  collected_info TEXT,           -- JSON: {keywords, purpose, suggestions[]}
  target_domain TEXT,            -- 등록/NS변경 대상 도메인
  messages TEXT,                 -- JSON: 대화 히스토리
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL,
  expires_at INTEGER NOT NULL    -- TTL 1시간
);

CREATE INDEX IF NOT EXISTS idx_domain_sessions_expires ON domain_sessions(expires_at);

-- Deposit Agent Sessions
CREATE TABLE IF NOT EXISTS deposit_sessions (
  user_id TEXT PRIMARY KEY,
  status TEXT NOT NULL CHECK(status IN ('collecting_amount', 'collecting_name', 'confirming', 'completed')),
  collected_info TEXT,           -- JSON: {amount, depositor_name}
  messages TEXT,                 -- JSON: 대화 히스토리
  created_at INTEGER NOT NULL,
  updated_at INTEGER NOT NULL,
  expires_at INTEGER NOT NULL    -- TTL 30분
);

CREATE INDEX IF NOT EXISTS idx_deposit_sessions_expires ON deposit_sessions(expires_at);

Step 2: 로컬 D1에 적용 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && wrangler d1 execute telegram-conversations --local --file=migrations/006_add_agent_sessions.sql Expected: 테이블 생성 성공

Step 3: 커밋

git add migrations/006_add_agent_sessions.sql
git commit -m "chore: add domain/deposit agent session tables migration"

Task 1.2: agents 디렉토리 생성 및 server-agent 이동

Files:

  • Create: src/agents/ 디렉토리
  • Move: src/server-agent.tssrc/agents/server-agent.ts
  • Modify: src/openai-service.ts (import 경로 수정)
  • Modify: src/tools/server-tool.ts (import 경로 수정)
  • Modify: src/index.ts (import 경로 수정)

Step 1: 디렉토리 생성 및 파일 이동

mkdir -p src/agents
mv src/server-agent.ts src/agents/server-agent.ts

Step 2: openai-service.ts import 수정

기존:

import { getServerSession, processServerConsultation } from './server-agent';

변경:

import { getServerSession, processServerConsultation } from './agents/server-agent';

Step 3: tools/server-tool.ts import 수정

기존:

import { getServerSession, saveServerSession, deleteServerSession } from '../server-agent';

변경:

import { getServerSession, saveServerSession, deleteServerSession } from '../agents/server-agent';

Step 4: index.ts import 수정 (있다면)

필요 시 import 경로 수정

Step 5: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npm run dev Expected: 에러 없이 실행

Step 6: 커밋

git add src/agents/ src/openai-service.ts src/tools/server-tool.ts
git commit -m "refactor: move server-agent to agents directory"

Task 1.3: troubleshoot-agent도 agents로 이동

Files:

  • Move: src/troubleshoot-agent.tssrc/agents/troubleshoot-agent.ts
  • Modify: src/openai-service.ts (import 경로 수정)
  • Modify: src/tools/troubleshoot-tool.ts (import 경로 수정)

Step 1: 파일 이동

mv src/troubleshoot-agent.ts src/agents/troubleshoot-agent.ts

Step 2: openai-service.ts import 수정

기존:

import { getTroubleshootSession, processTroubleshoot } from './troubleshoot-agent';

변경:

import { getTroubleshootSession, processTroubleshoot } from './agents/troubleshoot-agent';

Step 3: tools/troubleshoot-tool.ts import 수정

필요 시 import 경로 수정

Step 4: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npm run dev Expected: 에러 없이 실행

Step 5: 커밋

git add src/agents/troubleshoot-agent.ts src/openai-service.ts src/tools/troubleshoot-tool.ts
git commit -m "refactor: move troubleshoot-agent to agents directory"

Task 1.4: 타입 정의 추가 (types.ts)

Files:

  • Modify: src/types.ts

Step 1: DomainSession 타입 추가

// Domain Agent Session
export interface DomainSession {
  telegramUserId: string;
  status: 'gathering' | 'suggesting' | 'confirming' | 'setting_ns' | 'completed';
  collectedInfo: {
    keywords?: string;
    purpose?: string;       // 블로그, 쇼핑몰, 브랜드 등
    suggestions?: Array<{
      domain: string;
      price: number;
      available: boolean;
    }>;
  };
  targetDomain?: string;    // 등록/NS변경 대상
  targetNameservers?: string[];  // NS변경 시 새 네임서버
  messages: Array<{ role: 'user' | 'assistant'; content: string }>;
  createdAt: number;
  updatedAt: number;
}

// Deposit Agent Session
export interface DepositSession {
  telegramUserId: string;
  status: 'collecting_amount' | 'collecting_name' | 'confirming' | 'completed';
  collectedInfo: {
    amount?: number;
    depositorName?: string;
  };
  messages: Array<{ role: 'user' | 'assistant'; content: string }>;
  createdAt: number;
  updatedAt: number;
}

Step 2: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npx tsc --noEmit Expected: 타입 에러 없음

Step 3: 커밋

git add src/types.ts
git commit -m "feat: add DomainSession and DepositSession types"

Phase 2: 도메인 에이전트

Task 2.1: domain-agent.ts 기본 구조 생성

Files:

  • Create: src/agents/domain-agent.ts

Step 1: 세션 CRUD 함수 작성

/**
 * Domain Agent - 도메인 관리 AI 에이전트
 *
 * 기능:
 * - 대화형 도메인 추천 상담
 * - 도메인 등록 확인 흐름
 * - 네임서버 변경 확인 흐름
 * - 단순 조회는 즉시 응답 (세션 없음)
 */

import type { Env, DomainSession } from '../types';
import { createLogger } from '../utils/logger';

const logger = createLogger('domain-agent');

const SESSION_TTL_MS = 3600 * 1000; // 1 hour

// Session CRUD
export async function getDomainSession(
  db: D1Database,
  userId: string
): Promise<DomainSession | null> {
  try {
    const now = Date.now();
    const result = await db.prepare(
      'SELECT * FROM domain_sessions WHERE user_id = ? AND expires_at > ?'
    ).bind(userId, now).first<{
      user_id: string;
      status: string;
      collected_info: string | null;
      target_domain: string | null;
      messages: string | null;
      created_at: number;
      updated_at: number;
    }>();

    if (!result) {
      return null;
    }

    return {
      telegramUserId: result.user_id,
      status: result.status as DomainSession['status'],
      collectedInfo: result.collected_info ? JSON.parse(result.collected_info) : {},
      targetDomain: result.target_domain || undefined,
      messages: result.messages ? JSON.parse(result.messages) : [],
      createdAt: result.created_at,
      updatedAt: result.updated_at,
    };
  } catch (error) {
    logger.error('세션 조회 실패', error as Error, { userId });
    return null;
  }
}

export async function saveDomainSession(
  db: D1Database,
  userId: string,
  session: DomainSession
): Promise<void> {
  try {
    const now = Date.now();
    const expiresAt = now + SESSION_TTL_MS;

    await db.prepare(`
      INSERT INTO domain_sessions
        (user_id, status, collected_info, target_domain, messages, created_at, updated_at, expires_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
      ON CONFLICT(user_id) DO UPDATE SET
        status = excluded.status,
        collected_info = excluded.collected_info,
        target_domain = excluded.target_domain,
        messages = excluded.messages,
        updated_at = excluded.updated_at,
        expires_at = excluded.expires_at
    `).bind(
      userId,
      session.status,
      JSON.stringify(session.collectedInfo || {}),
      session.targetDomain || null,
      JSON.stringify(session.messages || []),
      session.createdAt || now,
      now,
      expiresAt
    ).run();

    logger.info('세션 저장 성공', { userId, status: session.status });
  } catch (error) {
    logger.error('세션 저장 실패', error as Error, { userId });
    throw error;
  }
}

export async function deleteDomainSession(
  db: D1Database,
  userId: string
): Promise<void> {
  try {
    await db.prepare('DELETE FROM domain_sessions WHERE user_id = ?')
      .bind(userId)
      .run();
    logger.info('세션 삭제 성공', { userId });
  } catch (error) {
    logger.error('세션 삭제 실패', error as Error, { userId });
    throw error;
  }
}

export async function cleanupExpiredDomainSessions(db: D1Database): Promise<number> {
  try {
    const result = await db.prepare(
      'DELETE FROM domain_sessions WHERE expires_at < ?'
    ).bind(Date.now()).run();

    const deleted = result.meta.changes || 0;
    if (deleted > 0) {
      logger.info('만료 세션 정리', { deleted });
    }
    return deleted;
  } catch (error) {
    logger.error('만료 세션 정리 실패', error as Error);
    return 0;
  }
}

Step 2: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npx tsc --noEmit Expected: 타입 에러 없음

Step 3: 커밋

git add src/agents/domain-agent.ts
git commit -m "feat: add domain-agent session CRUD functions"

Task 2.2: 도메인 에이전트 AI 호출 함수 작성

Files:

  • Modify: src/agents/domain-agent.ts

Step 1: AI 도구 정의 및 호출 함수 추가

// Domain Expert AI Tools
const domainAgentTools = [
  {
    type: 'function' as const,
    function: {
      name: 'check_domain',
      description: '도메인 등록 가능 여부와 가격을 확인합니다.',
      parameters: {
        type: 'object',
        properties: {
          domain: {
            type: 'string',
            description: '확인할 도메인 (예: example.com)',
          },
        },
        required: ['domain'],
      },
    },
  },
  {
    type: 'function' as const,
    function: {
      name: 'search_suggestions',
      description: '키워드 기반으로 도메인을 추천합니다.',
      parameters: {
        type: 'object',
        properties: {
          keywords: {
            type: 'string',
            description: '도메인 추천을 위한 키워드',
          },
        },
        required: ['keywords'],
      },
    },
  },
  {
    type: 'function' as const,
    function: {
      name: 'get_whois',
      description: '도메인 WHOIS 정보를 조회합니다.',
      parameters: {
        type: 'object',
        properties: {
          domain: {
            type: 'string',
            description: '조회할 도메인',
          },
        },
        required: ['domain'],
      },
    },
  },
  {
    type: 'function' as const,
    function: {
      name: 'get_tld_price',
      description: 'TLD 가격을 조회합니다.',
      parameters: {
        type: 'object',
        properties: {
          tld: {
            type: 'string',
            description: 'TLD (예: com, io, net)',
          },
        },
        required: ['tld'],
      },
    },
  },
  {
    type: 'function' as const,
    function: {
      name: 'list_my_domains',
      description: '사용자의 도메인 목록을 조회합니다.',
      parameters: {
        type: 'object',
        properties: {},
      },
    },
  },
  {
    type: 'function' as const,
    function: {
      name: 'get_nameservers',
      description: '도메인의 네임서버를 조회합니다.',
      parameters: {
        type: 'object',
        properties: {
          domain: {
            type: 'string',
            description: '조회할 도메인',
          },
        },
        required: ['domain'],
      },
    },
  },
];

// AI 호출 함수
async function callDomainExpertAI(
  env: Env,
  session: DomainSession,
  userMessage: string
): Promise<{
  action: 'question' | 'suggest' | 'register' | 'set_ns' | 'immediate';
  message: string;
  collectedInfo?: DomainSession['collectedInfo'];
  targetDomain?: string;
  targetNameservers?: string[];
  toolCalls?: Array<{ name: string; args: Record<string, unknown> }>;
}> {
  if (!env.OPENAI_API_KEY) {
    throw new Error('OPENAI_API_KEY not configured');
  }

  const { getOpenAIUrl } = await import('../utils/api-urls');

  const conversationHistory = session.messages.map(m => ({
    role: m.role === 'user' ? 'user' as const : 'assistant' as const,
    content: m.content,
  }));

  const systemPrompt = `당신은 10년 경력의 도메인 컨설턴트입니다.

## 전문성
- 브랜딩, SEO, 가격 대비 가치를 고려한 조언 제공
- 불필요한 프리미엄 도메인 추천 자제
- 실용적이고 기억하기 쉬운 도메인 추천

## 성격
- 따뜻하고 친근하지만 전문적인 어조
- 비기술자도 이해하기 쉽게 설명

## 작업 분류

### 세션 필요 작업 (action으로 반환)
- "도메인 추천" → action="suggest", 키워드/용도 수집
- "도메인 등록" → action="register", 도메인명 확인
- "네임서버 변경" → action="set_ns", 변경할 NS 확인

### 즉시 응답 작업 (도구 호출 후 action="immediate")
- 가격 조회 → get_tld_price 도구 호출
- WHOIS 조회 → get_whois 도구 호출
- 내 도메인 목록 → list_my_domains 도구 호출
- 네임서버 조회 → get_nameservers 도구 호출
- 도메인 가용성 확인 → check_domain 도구 호출

## 현재 수집된 정보
${JSON.stringify(session.collectedInfo, null, 2)}

## 응답 형식 (반드시 JSON만 반환)
{
  "action": "question" | "suggest" | "register" | "set_ns" | "immediate",
  "message": "사용자에게 보여줄 메시지",
  "collectedInfo": { ... },  // 수집된 정보 업데이트
  "targetDomain": "example.com",  // register, set_ns 시
  "targetNameservers": ["ns1.example.com", "ns2.example.com"]  // set_ns 시
}`;

  try {
    const response = await fetch(getOpenAIUrl(env), {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${env.OPENAI_API_KEY}`,
      },
      body: JSON.stringify({
        model: 'gpt-4o-mini',
        messages: [
          { role: 'system', content: systemPrompt },
          ...conversationHistory,
          { role: 'user', content: userMessage },
        ],
        tools: domainAgentTools,
        tool_choice: 'auto',
        response_format: { type: 'json_object' },
        max_tokens: 800,
        temperature: 0.7,
      }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`OpenAI API error: ${response.status} - ${error}`);
    }

    const data = await response.json() as {
      choices: Array<{
        message: {
          content: string | null;
          tool_calls?: Array<{
            id: string;
            function: { name: string; arguments: string };
          }>;
        };
      }>;
    };

    const assistantMessage = data.choices[0].message;

    // Tool calls가 있으면 처리
    if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
      const toolCalls = assistantMessage.tool_calls.map(tc => ({
        name: tc.function.name,
        args: JSON.parse(tc.function.arguments),
      }));

      return {
        action: 'immediate',
        message: '',
        toolCalls,
      };
    }

    // JSON 응답 파싱
    const aiResponse = assistantMessage.content || '';
    const parsed = JSON.parse(aiResponse);

    return {
      action: parsed.action,
      message: parsed.message,
      collectedInfo: parsed.collectedInfo,
      targetDomain: parsed.targetDomain,
      targetNameservers: parsed.targetNameservers,
    };
  } catch (error) {
    logger.error('Domain Expert AI 호출 실패', error as Error);
    throw error;
  }
}

Step 2: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npx tsc --noEmit Expected: 타입 에러 없음

Step 3: 커밋

git add src/agents/domain-agent.ts
git commit -m "feat: add domain-agent AI tools and callDomainExpertAI function"

Task 2.3: 도메인 에이전트 도구 실행 함수 이관

Files:

  • Modify: src/agents/domain-agent.ts
  • Reference: src/tools/domain-tool.ts (로직 복사)

Step 1: domain-tool.ts에서 핵심 로직 복사

기존 domain-tool.tscallNamecheapApi, executeDomainAction 함수들을 domain-agent.ts로 이관. 단, 함수명 변경:

  • executeDomainActionexecuteDomainTool
// domain-agent.ts에 추가

// Namecheap API 호출 함수 (domain-tool.ts에서 복사)
async function callNamecheapApi(
  funcName: string,
  funcArgs: Record<string, unknown>,
  allowedDomains: string[],
  env?: Env,
  telegramUserId?: string,
  db?: D1Database,
  userId?: number
): Promise<unknown> {
  // ... domain-tool.ts의 callNamecheapApi 함수 전체 복사
}

// 도구 실행 함수
async function executeDomainTool(
  toolName: string,
  args: Record<string, unknown>,
  env: Env,
  telegramUserId: string,
  db: D1Database
): Promise<string> {
  // 사용자 도메인 목록 조회
  const user = await db.prepare(
    'SELECT id FROM users WHERE telegram_id = ?'
  ).bind(telegramUserId).first<{ id: number }>();

  if (!user) {
    return '🚫 사용자 정보를 찾을 수 없습니다.';
  }

  const domains = await db.prepare(
    'SELECT domain FROM user_domains WHERE user_id = ? AND verified = 1'
  ).bind(user.id).all<{ domain: string }>();
  const userDomains = domains.results?.map(d => d.domain) || [];

  switch (toolName) {
    case 'check_domain': {
      // domain-tool.ts의 'check' action 로직
      const domain = args.domain as string;
      // ... 구현
    }
    case 'search_suggestions': {
      // domain-tool.ts의 executeSuggestDomains 로직
      // ... 구현
    }
    case 'get_whois': {
      // domain-tool.ts의 'whois' action 로직
      // ... 구현
    }
    case 'get_tld_price': {
      // domain-tool.ts의 'price' action 로직
      // ... 구현
    }
    case 'list_my_domains': {
      // domain-tool.ts의 'list' action 로직
      // ... 구현
    }
    case 'get_nameservers': {
      // domain-tool.ts의 'get_ns' action 로직
      // ... 구현
    }
    default:
      return `알 수 없는 도구: ${toolName}`;
  }
}

Step 2: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npx tsc --noEmit Expected: 타입 에러 없음

Step 3: 커밋

git add src/agents/domain-agent.ts
git commit -m "feat: add domain tool execution functions to domain-agent"

Task 2.4: 도메인 에이전트 메인 처리 함수 작성

Files:

  • Modify: src/agents/domain-agent.ts

Step 1: processDomainConsultation 함수 작성

export async function processDomainConsultation(
  userMessage: string,
  session: DomainSession,
  env: Env
): Promise<string> {
  try {
    logger.info('도메인 상담 처리 시작', {
      userId: session.telegramUserId,
      message: userMessage.slice(0, 50),
      status: session.status
    });

    // 취소 키워드 처리
    if (/^(취소|다시|처음|리셋)/.test(userMessage.trim())) {
      await deleteDomainSession(env.DB, session.telegramUserId);
      return '도메인 상담이 취소되었습니다. 다시 시작하려면 "도메인 추천"이라고 말씀해주세요.';
    }

    // 무관한 메시지 감지
    const unrelatedPatterns = /날씨|시간|계산|서버|입금|충전|잔액/;
    if (unrelatedPatterns.test(userMessage)) {
      return '__PASSTHROUGH__';
    }

    // 세션에 메시지 추가
    session.messages.push({ role: 'user', content: userMessage });

    // AI 호출
    const aiResult = await callDomainExpertAI(env, session, userMessage);

    // 도구 호출이 있으면 실행
    if (aiResult.action === 'immediate' && aiResult.toolCalls) {
      const results: string[] = [];
      for (const toolCall of aiResult.toolCalls) {
        const result = await executeDomainTool(
          toolCall.name,
          toolCall.args,
          env,
          session.telegramUserId,
          env.DB
        );
        results.push(result);
      }

      // 세션 삭제 (즉시 응답은 세션 불필요)
      await deleteDomainSession(env.DB, session.telegramUserId);
      return results.join('\n\n');
    }

    // 수집된 정보 업데이트
    if (aiResult.collectedInfo) {
      session.collectedInfo = { ...session.collectedInfo, ...aiResult.collectedInfo };
    }
    if (aiResult.targetDomain) {
      session.targetDomain = aiResult.targetDomain;
    }
    if (aiResult.targetNameservers) {
      session.targetNameservers = aiResult.targetNameservers;
    }

    // 세션에 AI 응답 추가
    session.messages.push({ role: 'assistant', content: aiResult.message });

    // 상태 전환
    switch (aiResult.action) {
      case 'question':
        session.status = 'gathering';
        break;
      case 'suggest':
        session.status = 'suggesting';
        // TODO: 도메인 추천 실행
        break;
      case 'register':
        session.status = 'confirming';
        // TODO: 등록 확인 흐름
        break;
      case 'set_ns':
        session.status = 'setting_ns';
        // TODO: NS 변경 확인 흐름
        break;
    }

    await saveDomainSession(env.DB, session.telegramUserId, session);
    return aiResult.message;

  } catch (error) {
    logger.error('도메인 상담 처리 실패', error as Error);
    await deleteDomainSession(env.DB, session.telegramUserId);
    return '죄송합니다. 도메인 상담 중 오류가 발생했습니다.\n다시 시도하려면 "도메인 추천"이라고 말씀해주세요.';
  }
}

Step 2: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npx tsc --noEmit Expected: 타입 에러 없음

Step 3: 커밋

git add src/agents/domain-agent.ts
git commit -m "feat: add processDomainConsultation main handler"

Task 2.5: openai-service.ts에 도메인 세션 체크 추가

Files:

  • Modify: src/openai-service.ts

Step 1: import 추가

import { getDomainSession, processDomainConsultation } from './agents/domain-agent';

Step 2: generateOpenAIResponse에 도메인 세션 체크 추가

서버 세션 체크 전에 도메인 세션 체크 추가:

export async function generateOpenAIResponse(
  env: Env,
  userMessage: string,
  systemPrompt: string,
  recentContext: { role: 'user' | 'assistant'; content: string }[],
  telegramUserId?: string,
  db?: D1Database,
  chatIdStr?: string
): Promise<string> {
  // Check if domain session is active
  if (telegramUserId && env.DB) {
    try {
      const domainSession = await getDomainSession(env.DB, telegramUserId);

      if (domainSession && domainSession.status !== 'completed') {
        logger.info('Active domain session detected', {
          userId: telegramUserId,
          status: domainSession.status
        });

        const result = await processDomainConsultation(userMessage, domainSession, env);

        if (result !== '__PASSTHROUGH__') {
          return result;
        }
      }
    } catch (error) {
      logger.error('Domain session check failed', error as Error);
    }
  }

  // ... 기존 서버 세션 체크 코드 ...

Step 3: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npx tsc --noEmit Expected: 타입 에러 없음

Step 4: 커밋

git add src/openai-service.ts
git commit -m "feat: add domain session check to openai-service"

Task 2.6: domain-tool.ts를 에이전트 시작 트리거로 변경

Files:

  • Modify: src/tools/domain-tool.ts

Step 1: manage_domain 도구를 세션 시작 트리거로 변경

// domain-tool.ts 전체 리팩토링

import type { Env } from '../types';
import { createLogger } from '../utils/logger';
import { getDomainSession, saveDomainSession } from '../agents/domain-agent';

const logger = createLogger('domain-tool');

export const manageDomainTool = {
  type: 'function',
  function: {
    name: 'manage_domain',
    description: '도메인 관련 모든 작업을 처리합니다. 도메인 추천, 등록, 가격 조회, WHOIS, 네임서버 관리 등.',
    parameters: {
      type: 'object',
      properties: {
        request: {
          type: 'string',
          description: '사용자의 도메인 관련 요청 (자연어)',
        },
      },
      required: ['request'],
    },
  },
};

// suggest_domains 도구는 manage_domain으로 통합, 제거 예정
export const suggestDomainsTool = manageDomainTool;

export async function executeManageDomain(
  args: { request: string },
  env?: Env,
  telegramUserId?: string,
  db?: D1Database
): Promise<string> {
  if (!env || !telegramUserId || !db) {
    return '🚫 도메인 관리 권한이 없습니다.';
  }

  // 세션 시작
  const existingSession = await getDomainSession(db, telegramUserId);

  if (!existingSession) {
    // 새 세션 생성
    const newSession = {
      telegramUserId,
      status: 'gathering' as const,
      collectedInfo: {},
      messages: [],
      createdAt: Date.now(),
      updatedAt: Date.now(),
    };
    await saveDomainSession(db, telegramUserId, newSession);

    logger.info('도메인 세션 시작', { userId: telegramUserId });
    return '__START_DOMAIN_SESSION__';  // 특수 마커로 세션 시작 알림
  }

  return '__DOMAIN_SESSION_ACTIVE__';  // 이미 세션 활성화 중
}

export async function executeSuggestDomains(
  args: { keywords: string },
  env?: Env,
  telegramUserId?: string,
  db?: D1Database
): Promise<string> {
  // manage_domain으로 리다이렉트
  return executeManageDomain({ request: `도메인 추천: ${args.keywords}` }, env, telegramUserId, db);
}

Step 2: tools/index.ts 수정

도구 목록에서 suggest_domains 제거 또는 manage_domain과 통합

Step 3: 빌드 테스트

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npx tsc --noEmit Expected: 타입 에러 없음

Step 4: 커밋

git add src/tools/domain-tool.ts src/tools/index.ts
git commit -m "refactor: convert domain-tool to session start trigger"

Phase 3: 예치금 에이전트 (Task 3.1 - 3.5)

Phase 2와 동일한 패턴으로:

Task 3.1: deposit-agent.ts를 agents로 이동 및 세션 로직 추가

Task 3.2: 예치금 에이전트 AI 호출 함수 작성

Task 3.3: 예치금 에이전트 도구 실행 함수 정리

Task 3.4: processDepositConsultation 함수 작성

Task 3.5: openai-service.ts에 예치금 세션 체크 추가

(상세 내용은 Phase 2 패턴과 동일하게 진행)


Phase 4: 정리 및 테스트

Task 4.1: 사용하지 않는 코드 정리

Files:

  • Delete: src/tools/deposit-tool.ts (deposit-agent로 통합됨)
  • Modify: src/tools/index.ts (import 정리)

Task 4.2: 로컬 테스트

Step 1: 로컬 서버 실행

Run: cd /Users/kaffa/Projects/bots/telegram-bot-workers && npm run dev

Step 2: 도메인 에이전트 테스트

# 도메인 추천 테스트
curl -X POST http://localhost:8787/webhook \
  -H "Content-Type: application/json" \
  -H "X-Telegram-Bot-Api-Secret-Token: test" \
  -d '{"message":{"chat":{"id":123},"from":{"id":123},"text":"도메인 추천해줘"}}'

# .com 가격 조회 테스트 (즉시 응답)
curl -X POST http://localhost:8787/webhook \
  -H "Content-Type: application/json" \
  -H "X-Telegram-Bot-Api-Secret-Token: test" \
  -d '{"message":{"chat":{"id":123},"from":{"id":123},"text":".com 가격"}}'

Step 3: 예치금 에이전트 테스트

# 충전 테스트 (세션 시작)
curl -X POST http://localhost:8787/webhook \
  -H "Content-Type: application/json" \
  -H "X-Telegram-Bot-Api-Secret-Token: test" \
  -d '{"message":{"chat":{"id":123},"from":{"id":123},"text":"충전할게"}}'

# 잔액 조회 테스트 (즉시 응답)
curl -X POST http://localhost:8787/webhook \
  -H "Content-Type: application/json" \
  -H "X-Telegram-Bot-Api-Secret-Token: test" \
  -d '{"message":{"chat":{"id":123},"from":{"id":123},"text":"잔액"}}'

Task 4.3: 문서 업데이트

Files:

  • Modify: CLAUDE.md
  • Modify: README.md

파일 구조, 에이전트 설명 업데이트

Task 4.4: 최종 커밋 및 배포

git add .
git commit -m "feat: complete domain and deposit agent refactoring"

# 프로덕션 마이그레이션 (주의!)
wrangler d1 execute telegram-conversations --file=migrations/006_add_agent_sessions.sql

# 배포
npm run deploy

위험 요소 체크리스트

  • 마이그레이션 전 D1 백업
  • 기존 domain-tool.ts 테스트가 통과하는지 확인
  • 기존 deposit-agent.ts 테스트가 통과하는지 확인
  • 세션 TTL 만료 후 정상 동작 확인
  • __PASSTHROUGH__ 동작 확인
  • Circuit Breaker 동작 확인