Files
joungmin eca097aa2c [02-Architect] #260 design spec + ADR-0005
- design/260-gemma-tool-calling/README.md — overall (12 AC + 7 OQ + 모듈 구조)
- fn-tool_dispatcher.md — multi-tool router (validate → confirm gate → handler → envelope)
- fn-add_habit_handler.md — destructive 대표 (R3/R7/R8 enforce)
- fn-confirm_gate.md — 모달 AlertDialog 흐름 (OQ-3 = 모달 확정)
- fn-chat_session_controller.md — multi-turn loop 상태 머신 (MAX_TURNS=4)
- ADR-0005 — in-app tool runtime + R 규칙 = 핸들러 책임 + schema SoT=Dart + 모달

OQ-1/2/4 = README §11 결정. OQ-3 = 모달 (사용자 결정).
신규 OQ-5/6/7 = Developer 가 구현 중 검증.

Refs #260
2026-06-15 10:15:44 +09:00

4.5 KiB

함수 설계서: ConfirmGate.show (#260)

부모 설계서: ./README.md · 상태: Draft 작성: [AI] Architect · 구현: lib/ai/tools/confirm_gate.dart:show · 테스트: test/ui/confirm_gate_test.dart

1. 시그니처

class ConfirmGate {
  static Future<bool> show(
    BuildContext context,
    ToolDefinition tool,
    Map<String, dynamic> args,
  );
}

2. 책임 (단일 책임, 1줄)

destructive tool 실행 직전 모달 AlertDialog 을 띄워 사용자 confirm 여부를 Future<bool> 로 반환한다.

3. 입력

파라미터 타입 제약 설명
context BuildContext context.mounted == true chat screen 의 context
tool ToolDefinition isDestructive=true 어떤 도구인가
args Map<String, dynamic> 이미 validate 통과 사용자에게 보여줄 인자

4. 출력

  • 반환: Future<bool>
    • true = 사용자가 "수행" 탭
    • false = 사용자가 "취소" 또는 outside-tap dismiss 또는 context.mounted == false
  • 부수효과: 모달 표시 (UI). DB 변경 없음.

5. 동작 / 알고리즘

1. if !context.mounted:
     return false

2. summary = _summarize(tool.name, args)
   // tool 별 사람 친화 요약 함수 (per-tool overridable)

3. result = await showDialog<bool>(
     context: context,
     barrierDismissible: true,    // outside-tap = 취소
     builder: (ctx) => AlertDialog(
       title: Text('이 작업을 수행할까요?'),
       content: Column(mainAxisSize: min, crossAxisAlignment: start, children: [
         Text(tool.description, style: bodyMedium),
         SizedBox(height: 12),
         Container(
           padding: EdgeInsets.all(12),
           decoration: BoxDecoration(
             color: Theme.surfaceVariant,
             borderRadius: BorderRadius.circular(8),
           ),
           child: Text(summary),
         ),
       ]),
       actions: [
         TextButton(onPressed: () => Navigator.pop(ctx, false), child: Text('취소')),
         FilledButton(
           autofocus: true,
           onPressed: () => Navigator.pop(ctx, true),
           child: Text('수행'),
         ),
       ],
     ),
   )

4. return result ?? false  // dismiss 시 null → false

_summarize 규칙 (tool 별)

  • add_habit → "프로토콜 '$title'을 ${frame_level} 프레임으로 새 습관으로 추가합니다.\n • 문장: "$framed_text"\n • 앵커: ${anchor_when ?? '-'} / ${anchor_after_what ?? '-'}"
  • log_tracker_entry → "$habit_title 의 ${date ?? '오늘'} 기록을 '${value == 'done' ? '완료' : '공란'}' 으로 저장합니다."
  • 기타 → JSON pretty (fallback)

title 은 핸들러가 호출 직전 lookup 해서 args 에 채워줄 수 있지만, 단순화를 위해 ConfirmGate 가 직접 catalog/habit 조회는 안 함 — args 에 이미 있는 값만 사용. (안 채워졌으면 protocol_id 그대로 노출 — 트레이드오프 수용.)

6. 에러 & 실패 모드

조건 처리 반환
context dispose 후 호출 guard 즉시 false
showDialog 자체 예외 (이론상 없음) rethrow X — catch 후 false 반환 false
args 가 _summarize 가 기대 안 한 형태 toString fallback 정상 동작 (dialog 노출)

7. 엣지케이스

  • chat 모달 위에 chat 모달: 동시에 호출되지 않도록 ChatSessionController 가 직렬화 (한 turn = 한 tool call 만). 다중 destructive tool 병렬 호출 시 첫 confirm 만 처리, 나머지는 ToolCancelled 자동 반환 (OQ-1 영향).
  • 시스템 back press: Android 뒤로가기 → dialog dismiss → false. 의도된 cancel.
  • autofocus + 키보드 enter: 수행 버튼 기본 포커스. 의도치 않은 enter 누름 위험 — 사용자 결정으로 수용 (단축키 활용성 ↑).

8. 복잡도 / 성능

  • O(1). 사용자 대기 시간 = 무한 (사용자 입력 대기).
  • 호출 빈도: 사용자 대화 turn 당 0 또는 1.

9. 테스트 케이스 (필수)

케이스 셋업 입력 기대
confirm MaterialApp + 모달 진입 → "수행" tap add_habit args true
cancel "취소" tap 동일 false
outside dismiss barrier tap 동일 false
unmounted context context dispose 후 호출 false 즉시

10. 의존

  • Flutter showDialog, AlertDialog
  • ToolDefinition (description 출처)
  • per-tool summary 규칙은 별도 함수로 분리 (_summarize(toolName, args)) — 단위 테스트 용이.