- 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
4.5 KiB
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)) — 단위 테스트 용이.