1.0.1 변경 이력 1.0.0 기준

변경 이력

RAWP 1.0.1

1.0.0 대비 변경 사항을 정리한 문서입니다.

수정됨

Document Overview

추가된 줄 6 삭제된 줄 5
1 1 # RAWP 1.0: Remote Agent Wire Protocol
2 2
3 | 항목 | 값 |
4 | ---------------- | ---------------- |
5 | 상태 | Stable |
6 | 버전 | 1.0 |
7 | 데이터 평면 규격 | **RAWP-DPS 1.0** |
3 | 항목 | 값 |
4 | ---------------------- | ---------------- |
5 | 상태 | Stable |
6 | 버전 | 1.0 |
7 | 데이터 평면 규격 | **RAWP-DPS 1.0** |
8 | 클라이언트 렌더링 규격 | **RAWP-CRS 1.0** |
8 9
9 10 ---
수정됨

1. 개요 (Introduction)

추가된 줄 8 삭제된 줄 6
1 1 ## 1. 개요 (Introduction)
2 2
3 3 RAWP 1.0은 중앙 제어 서버(Master Server)가 원격지의 게이트웨이(Local Client)를 안전하게 제어하기 위한 Push 기반의 분산 제어 프로토콜이다. 제어 및 상태 모니터링을 위한 Stateless HTTP 통신과, 실시간 데이터 입출력을 위한 Stateful WebSocket 통신으로 제어 평면(Control Plane)과 데이터 평면(Data Plane)을 분리한다.
4 4
5 5 ### 1.1. 요구사항 표기 규약 (Requirements Notation)
6 6
7 7 본 문서의 "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "OPTIONAL"은 RFC 2119를 따른다.
8 8
9 9 ### 1.2. 용어 정의 (Terminology)
10 10
11 | 용어 | 정의 |
12 | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
13 | Master Server | 클라이언트 노드를 관리하고 제어 명령을 하달하는 중앙 서버 (Control Plane). |
14 | Local Client | 마스터 서버의 명령을 수신하는 HTTP 엔드포인트를 소유하며, 로컬 프로세스(Agent)의 실행 및 I/O를 담당하는 에지 게이트웨이 (Execution Plane). |
15 | Agent | 클라이언트 환경에서 실행되는 실제 단위 작업 프로세스 (예: LLM, 스크립트 등). |
16 | Session | 특정 에이전트의 실행부터 종료까지의 라이프사이클 및 I/O 스트림의 논리적 단위. |
11 | 용어 | 정의 |
12 | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
13 | Master Server | 클라이언트 노드를 관리하고 제어 명령을 하달하는 중앙 서버 (Control Plane). |
14 | Local Client | 마스터 서버의 명령을 수신하는 HTTP 엔드포인트를 소유하며, 로컬 프로세스(Agent)의 실행 및 I/O를 담당하는 에지 게이트웨이 (Execution Plane). |
15 | Agent | 클라이언트 환경에서 실행되는 실제 단위 작업 프로세스 (예: LLM, 스크립트 등). |
16 | Session | 특정 에이전트의 실행부터 종료까지의 라이프사이클 및 I/O 스트림의 논리적 단위. |
17 | Config Scope | 클라이언트 설정의 논리적 분류 단위. 각 스코프는 독립된 엔드포인트, 독립된 `config_version`, 독립된 갱신 주기를 갖는다. |
18 | Session-Pinned Config | 세션 초기화 시점에 바인딩된 설정 스냅샷. 해당 세션의 전체 라이프사이클 동안 불변이다. |
17 19
18 20 ### 1.3. 호환성 및 파싱 규약 (Forward Compatibility)
19 21
20 22 - **필드 무시 원칙 (Postel's Law)**: 클라이언트와 서버의 JSON 파서는 스펙에 정의되지 않은 알 수 없는 키(Unknown Key)를 수신하더라도 파싱 에러를 발생시키지 않고 조용히 무시해야 한다 (MUST).
21 23 - **API 단종 예고**: 서버는 향후 지원이 중단될 API 응답에 `Warning: 299 - "Deprecated API"` HTTP 헤더를 포함할 수 있으며, 수신자는 이를 감지 시 시스템 로그에 기록해야 한다 (MUST).
22 24
23 25 ---
수정됨

3. Phase 1: 등록, 페어링 및 키 교환 (Registration & Lifecycle)

추가된 줄 2 삭제된 줄 2
1 1 ## 3. Phase 1: 등록, 페어링 및 키 교환 (Registration & Lifecycle)
2 2
3 3 클라이언트와 마스터 서버가 상호 통신을 위한 토큰 페어(Access/Refresh)를 교환하고 검증하는 단계다.
4 4
5 5 ### 3.1. 페어링 URL 발급 (Master Server)
6 6
7 7 - **형식**: `https://{master_host}/v1/auth/pair?token={pairing_token}`
8 8 - **제약 조건**: `pairing_token`은 발급 후 15분 이내에 사용되지 않으면 반드시 폐기되어야 한다 (MUST).
9 9
10 10 ### 3.2. 등록 주소 검증 규약 (Address Validation)
11 11
12 12 클라이언트가 제출하는 `client_address`에 대한 마스터 서버의 검증 규칙:
13 13
14 14 - **Allowed Schemes**: `https://` 사용 강제 (단, 명시적인 개발 플래그 활성화 시 `http://` 허용).
15 15 - **Loopback Forbidden**: `127.0.0.1`, `localhost` 등 루프백 및 링크 로컬 주소는 등록을 거부해야 한다 (MUST).
16 16 - **Private Network**: `192.168.x.x` 등 사설 IP는 허용된다.
17 17
18 18 ### 3.3. 등록 요청 및 상호 키 교환 (Mutual Registration)
19 19
20 20 클라이언트는 등록 시 자신이 마스터 서버를 호출할 때 쓸 토큰을 발급받음과 동시에, 마스터 서버가 자신을 호출할 때 사용할 초기 토큰 페어를 생성하여 전달해야 한다 (MUST).
21 21
22 22 **Endpoint**: `POST /v1/auth/pair` (Master Server 제공)
23 23
24 24 **Request** (Client → Master):
25 25
26 26 ```json
27 27 {
28 28 "pairing_token": "String (필수, 최소 32자 이상)",
29 29 "client_address": "String (필수, 향후 마스터가 호출할 URI)",
30 30 "device_name": "String (필수, 최대 100자)",
31 31 "client_credentials": {
32 32 "access_token": "String (필수, 마스터가 클라이언트를 호출할 때 사용할 토큰)",
33 33 "refresh_token": "String (필수, 클라이언트용 갱신 토큰)",
34 34 "expires_in": "Number (필수, 초 단위)"
35 35 }
36 36 }
37 37 ```
38 38
39 39 **Response** (Master → Client) (200 OK):
40 40
41 41 ```json
42 42 {
43 43 "access_token": "String (필수, 클라이언트가 마스터를 호출할 때 사용할 Bearer 토큰)",
44 44 "refresh_token": "String (필수, 마스터용 장기 갱신 토큰)",
45 45 "expires_in": "Number (필수)",
46 46 "server_credentials": "Object (필수, 서버 서명 및 자격 증명용 JWK 공개키)"
47 47 }
48 48 ```
49 49
50 50 ### 3.4. 토큰 갱신 엔드포인트 대칭 구현 (Token Refresh)
51 51
52 52 양측은 타 API 호출 시 `401 Unauthorized`를 받으면, 발급 주체의 갱신 엔드포인트를 호출하여 토큰을 갱신하고 원래 요청을 재시도해야 한다 (MUST).
53 53
54 #### 3.4.1. 클라이언트의 마스터 토큰 갱신 (Client → Master)
54 ### 3.4.1. 클라이언트의 마스터 토큰 갱신 (Client → Master)
55 55
56 56 - **Endpoint**: `POST /v1/auth/refresh` (Master Server 제공)
57 57 - **Request**: `{"refresh_token": "String (필수)"}`
58 58 - **Response**: §3.3의 Master → Client 응답 포맷과 동일.
59 59
60 #### 3.4.2. 마스터의 클라이언트 토큰 갱신 (Master → Client)
60 ### 3.4.2. 마스터의 클라이언트 토큰 갱신 (Master → Client)
61 61
62 62 - **Endpoint**: `POST /v1/auth/refresh` (Local Client 제공)
63 63 - **Request**: `{"refresh_token": "String (필수)"}`
64 64 - **Response**:
65 65
66 66 ```json
67 67 {
68 68 "access_token": "String (필수)",
69 69 "refresh_token": "String (필수)",
70 70 "expires_in": "Number (필수)"
71 71 }
72 72 ```
73 73
74 74 ### 3.5. 등록 해제 (Unregister)
75 75
76 76 클라이언트가 자발적으로 네트워크에서 이탈할 때 잔여 리소스를 정리한다.
77 77
78 78 - **Endpoint**: `DELETE /v1/nodes/self` (Master Server 제공)
79 79 - **Headers**: `Authorization: Bearer {client_access_token}`
80 80 - **제약 조건**: 마스터 서버는 해당 클라이언트와 연결된 모든 WSS 세션을 즉시 강제 종료하고 상태를 정리해야 한다 (MUST). 관련된 모든 토큰은 즉시 무효화된다.
81 81 - **Response**: `204 No Content`
82 82
83 83 ---
수정됨

4. Phase 2: 제어 및 모니터링 인터페이스 (Master → Client HTTP)

추가된 줄 178 삭제된 줄 1
1 1 ## 4. Phase 2: 제어 및 모니터링 인터페이스 (Master → Client HTTP)
2 2
3 3 마스터 서버가 클라이언트를 호출할 때는 반드시 발급받은 `access_token`을 사용하며, 클라이언트는 서명을 검증해야 한다 (MUST).
4 4
5 5 ### 4.1. 클라이언트 설정 동기화 (Client Configuration)
6 6
7 클라이언트는 구동 직후 시스템 한계치와 지원 능력을 서버에 동기화한다. 마스터는 이 값으로 요청을 스로틀링하며, `capabilities`에 없는 기능을 요구해서는 안 된다 (MUST).
7 클라이언트는 시스템 한계치와 지원 능력을 서버에 동기화한다. 설정은 의미적 성격에 따라 독립된 Config Scope로 분리되며, 각 스코프는 전용 엔드포인트를 통해 전체 교체(Full Replacement) 방식으로 동기화된다. 마스터 서버는 수신한 설정 값으로 요청을 스로틀링하며, 보고되지 않은 기능을 요구해서는 안 된다 (MUST).
8 8
9 ### 4.1.1. Config Scope 분리 원칙
10
11 클라이언트 설정은 다음 두 개의 Config Scope로 분리된다:
12
13 | Config Scope | 엔드포인트 | 성격 | 변경 트리거 |
14 | ------------------- | ----------------------------------- | -------------------------------------------------- | --------------------------------------------------------- |
15 | **Resource Limits** | `PUT /v1/nodes/config/limits` | 수량적 운영 제약 — 처리량, 전송 크기, 메모리, 정책 | 시스템 부하 변동, 메모리 압박, 운영 정책 조정 |
16 | **Capabilities** | `PUT /v1/nodes/config/capabilities` | 질적 기능 선언 — 클라이언트가 지원하는 기능 목록 | 플러그인/도구 로드·언로드, 모델 교체, 소프트웨어 업데이트 |
17
18 각 스코프는 독립된 `config_version`을 가지며, 한 스코프의 갱신이 다른 스코프의 버전에 영향을 주지 않는다 (MUST). 클라이언트는 변경이 발생한 스코프의 엔드포인트만 호출하면 되고, 변경되지 않은 스코프를 함께 재전송할 필요가 없다.
19
20 ### 4.1.2. 초기 동기화 (Initial Synchronization)
21
22 클라이언트는 페어링 완료(§3.3) 직후 그리고 재기동 직후, **양쪽 Config Scope를 모두** 동기화해야 한다 (MUST). 초기 동기화 시 `config_version`은 `1`로 시작한다. 서버는 양쪽 스코프의 초기 동기화가 완료되기 전까지 해당 클라이언트에 대한 세션 초기화(§5.1) 요청을 보류해야 한다 (MUST). 초기 동기화 미완료 상태에서 세션 초기화가 시도되면 서버는 `503 Service Unavailable`을 반환해야 한다 (MUST).
23
24 마스터 서버가 특정 스코프에 대한 설정을 보유하지 않은 상태에서 해당 클라이언트의 헬스 체크(§4.2) 또는 에이전트 탐색(§4.3)을 수행하는 것은 허용된다. 설정 동기화 보류 규칙은 세션 초기화에만 적용된다.
25
26 ### 4.1.3. 런타임 설정 갱신 (Runtime Configuration Update)
27
28 클라이언트는 런타임 중 설정 변경이 발생할 때마다 해당 스코프의 엔드포인트를 호출하여 서버에 변경 사항을 동기화해야 한다 (MUST). 각 호출은 해당 스코프의 **전체 필드를 포함하는 완전한 스냅샷**이어야 하며, 부분 필드만 포함하는 것은 허용되지 않는다 (MUST NOT). 서버는 수신한 스냅샷으로 해당 스코프의 기존 상태를 전체 교체한다.
29
30 ### 4.1.4. 설정 버전 관리 및 순서 보장 (Config Versioning)
31
32 모든 Config Scope 요청에는 `config_version` 필드가 포함되어야 한다 (MUST). 이 값은 각 스코프별로 독립적이며, 클라이언트가 해당 스코프의 설정을 변경할 때마다 반드시 1씩 단조 증가시켜야 한다 (MUST).
33
34 **순서 보장 규칙**: 마스터 서버는 수신한 `config_version`이 현재 보유한 해당 스코프의 버전보다 큰 경우에만 설정을 갱신해야 한다 (MUST). 현재 보유 버전 이하의 값이 수신되면 네트워크 지연에 의한 역전(stale update)으로 간주하고, `409 Conflict`를 반환하여 거부해야 한다 (MUST). 이때 응답 바디에는 서버가 현재 보유 중인 `config_version`을 포함하여 클라이언트가 재동기화할 수 있도록 해야 한다 (MUST).
35
36 **클라이언트 재기동 시 버전 처리**: 클라이언트가 재기동될 때 이전 `config_version`을 영속적으로 보관하지 못하는 경우를 위해, 다음의 특수 규칙을 적용한다: `config_version`이 `1`인 요청은 서버가 보유한 버전과 무관하게 항상 수락해야 한다 (MUST). 이는 재기동 후 초기 동기화를 보장하기 위한 것이며, 서버는 `config_version: 1` 수신 시 내부 버전 카운터를 `1`로 리셋해야 한다. 단, 활성 세션이 존재하는 상태에서 `config_version: 1`이 수신되면, 서버는 이를 클라이언트 재기동 시그널로 해석하여 시스템 로그에 기록해야 한다 (MUST).
37
38 ### 4.1.5. Resource Limits 동기화
39
40 클라이언트의 수량적 운영 제약을 서버에 동기화한다.
41
42 **Endpoint**: `PUT /v1/nodes/config/limits` (Master Server 제공)
43
44 **Request** (Client → Master):
45
46 ```json
47 {
48 "config_version": "Number (필수, 이 스코프의 단조 증가 정수, 초기값 1)",
49 "effective_at": "String (필수, ISO 8601, 클라이언트가 이 설정을 적용한 시각)",
50 "reason": "String (선택, 변경 사유. 예: 'memory_pressure', 'scheduled_maintenance')",
51 "limits": {
52 "rate_limit": "Number (필수, 초당 처리 가능한 최대 HTTP 요청 수. 양의 정수)",
53 "max_frame_size": "Number (필수, WSS 단일 프레임 최대 바이트. 최소 1024)",
54 "reattach_window": "Number (필수, WSS 단절 시 DETACHED 상태 유지 시간 초. 0 이상 정수. 0이면 DETACHED 미지원으로, 단절 시 즉시 TERMINATED 전이)",
55 "max_buffer_size": "Number (필수, 최대 메모리 버퍼 바이트. 양의 정수. max_frame_size 이상이어야 함)",
56 "buffer_overflow_policy": "String (필수, 'RING' 또는 'DROP')"
57 }
58 }
59 ```
60
61 **서버 검증 규칙 (MUST)**:
62
63 | 필드 | 검증 조건 | 위반 시 에러 |
64 | ------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- |
65 | `config_version` | 양의 정수. 현재 보유 버전보다 큰 값이거나, 정확히 `1` (재기동) | `409 Conflict` (버전 역전) |
66 | `effective_at` | 유효한 ISO 8601 형식. 미래 시각 불허 (서버 현재 시각 + 30초 허용 오차 초과 시 거부) | `422` `EFFECTIVE_AT_IN_FUTURE` |
67 | `limits.rate_limit` | 양의 정수 (> 0). 소수점 불허 | `422` `INVALID_RATE_LIMIT` |
68 | `limits.max_frame_size` | 양의 정수. 최소 `1024` | `422` `FRAME_SIZE_TOO_SMALL` |
69 | `limits.reattach_window` | 0 이상 정수 | `422` `INVALID_REATTACH_WINDOW` |
70 | `limits.max_buffer_size` | 양의 정수. `max_frame_size` 이상이어야 함 | `422` `BUFFER_SMALLER_THAN_FRAME` |
71 | `limits.buffer_overflow_policy` | `"RING"` 또는 `"DROP"` 중 하나 | `422` `INVALID_OVERFLOW_POLICY` |
72 | 모든 필드 존재 여부 | `limits` 객체 내 모든 필드가 존재해야 함 (전체 교체) | `400` `CONFIG_FIELD_MISSING` |
73
74 **Response (200 OK):**
75
76 ```json
77 {
78 "config_version": "Number (수신한 버전 에코백)",
79 "accepted_at": "String (ISO 8601, 서버가 설정을 수락하고 저장한 시각)",
80 "active_sessions_affected": "Number (현재 RUNNING 또는 DETACHED 상태의 세션 수. 이 세션들은 이전 설정으로 계속 동작함. §4.1.8 참조)"
81 }
82 ```
83
84 ### 4.1.6. Capabilities 동기화
85
86 클라이언트가 지원하는 기능 목록을 서버에 동기화한다.
87
88 **Endpoint**: `PUT /v1/nodes/config/capabilities` (Master Server 제공)
89
90 **Request** (Client → Master):
91
92 ```json
93 {
94 "config_version": "Number (필수, 이 스코프의 단조 증가 정수, 초기값 1)",
95 "effective_at": "String (필수, ISO 8601, 클라이언트가 이 설정을 적용한 시각)",
96 "reason": "String (선택, 변경 사유. 예: 'plugin_loaded: vision_v2', 'model_replaced: gpt4o')",
97 "capabilities": [
98 "String (지원 기능 식별자 배열. 예: 'vision_v1', 'tool_call_v2'. 빈 배열 허용)"
99 ]
100 }
101 ```
102
103 **서버 검증 규칙 (MUST)**:
104
105 | 필드 | 검증 조건 | 위반 시 에러 |
106 | ----------------- | ------------------------------------------------------------------------------------------ | ------------------------------------- |
107 | `config_version` | §4.1.5의 `config_version` 검증과 동일 | `409 Conflict` (버전 역전) |
108 | `effective_at` | §4.1.5의 `effective_at` 검증과 동일 | `422` `EFFECTIVE_AT_IN_FUTURE` |
109 | `capabilities` | JSON 배열이어야 함. 빈 배열 허용 | `422` `INVALID_CAPABILITIES_FORMAT` |
110 | `capabilities[*]` | 각 항목은 비어 있지 않은 문자열. 최대 128자. 허용 문자: `a-z`, `A-Z`, `0-9`, `_`, `.`, `-` | `422` `INVALID_CAPABILITY_IDENTIFIER` |
111 | 중복 검사 | 배열 내 동일한 값이 2회 이상 출현하면 거부 | `422` `DUPLICATE_CAPABILITY` |
112
113 **서버의 미인식 Capability 처리**: 서버는 자신이 인식하지 못하는 capability 식별자를 수신하더라도, 형식 검증을 통과하면 거부 없이 저장해야 한다 (MUST). 이는 §1.3의 필드 무시 원칙(Postel's Law)과 일관되며, 클라이언트가 서버보다 먼저 새로운 기능을 탑재하는 시나리오를 허용하기 위함이다.
114
115 **Response (200 OK):**
116
117 ```json
118 {
119 "config_version": "Number (수신한 버전 에코백)",
120 "accepted_at": "String (ISO 8601, 서버가 설정을 수락하고 저장한 시각)",
121 "active_sessions_affected": "Number (현재 RUNNING 또는 DETACHED 상태의 세션 수. 이 세션들은 이전 capabilities로 계속 동작함. §4.1.8 참조)"
122 }
123 ```
124
125 ### 4.1.7. 설정 갱신 에러 응답 (Config Update Errors)
126
127 설정 갱신 요청이 검증에 실패한 경우, 서버는 §7.1의 에러 응답 표준 규격을 확장한 다음 포맷으로 응답해야 한다 (MUST).
128
129 **검증 실패 (422 Unprocessable Entity):**
130
131 ```json
132 {
133 "error_code": "String (에러 코드. 예: 'CONFIG_VALIDATION_FAILED')",
134 "message": "String (휴먼 리더블 디버깅 메시지)",
135 "field_errors": [
136 {
137 "field": "String (문제가 된 필드의 JSON Path. 예: 'limits.rate_limit', 'capabilities[2]')",
138 "constraint": "String (위반된 제약 조건의 식별자. 예: 'POSITIVE_INTEGER', 'MIN_VALUE:1024', 'ENUM:RING|DROP')",
139 "reason": "String (휴먼 리더블 설명. 예: 'MUST be a positive integer')"
140 }
141 ]
142 }
143 ```
144
145 `field_errors` 배열에는 검증에 실패한 **모든** 필드가 포함되어야 한다 (MUST). 첫 번째 위반에서 중단하고 나머지를 생략해서는 안 된다. 이는 클라이언트가 한 번의 응답으로 모든 위반 사항을 파악하고 수정할 수 있도록 하기 위함이다.
146
147 **버전 충돌 (409 Conflict):**
148
149 ```json
150 {
151 "error_code": "CONFIG_VERSION_CONFLICT",
152 "message": "String (휴먼 리더블 디버깅 메시지)",
153 "current_config_version": "Number (서버가 현재 보유 중인 해당 스코프의 config_version)"
154 }
155 ```
156
157 `current_config_version`을 통해 클라이언트는 자신의 `config_version`을 서버 값 이상으로 재조정한 후 재시도할 수 있다.
158
159 ### 4.1.8. 설정 적용 범위: Session-Pinned Config
160
161 런타임 설정 갱신은 **새로 초기화되는 세션에만 적용**되며, 이미 `RUNNING` 또는 `DETACHED` 상태인 세션에는 소급 적용되지 않는다 (MUST NOT). 이를 Session-Pinned Config라 한다.
162
163 **바인딩 규칙:**
164
165 - **신규 세션** (`reattach: false`): 마스터 서버가 `POST /v1/session/init` (§5.1)을 호출하는 시점에 보유한 각 스코프의 최신 설정 스냅샷을 해당 세션에 바인딩한다. 바인딩된 설정은 세션이 `TERMINATED` 상태로 전이될 때까지 불변이다 (MUST).
166 - **세션 재연결** (`reattach: true`): 원래 세션에 바인딩된 설정을 그대로 유지한다. 재연결 시점의 최신 설정으로 교체하지 않는다 (MUST NOT).
167
168 **서버 추적 의무**: 마스터 서버는 각 세션에 바인딩된 `config_version` 쌍(limits 스코프 버전 + capabilities 스코프 버전)을 세션 메타데이터로 보관해야 한다 (MUST). 이 정보는 세션의 행동을 예측하고 디버깅하는 데 필요하다.
169
170 **활성 세션에 대한 서버의 행동 원칙**: 설정 갱신 후에도 활성 세션은 바인딩된 이전 설정에 따라 동작하므로, 서버는 다음을 준수해야 한다 (MUST):
171
172 - 활성 세션에 대한 스로틀링은 해당 세션에 바인딩된 `rate_limit` 값을 기준으로 적용한다. 갱신된 값이 아니다.
173 - 활성 세션에 대한 기능 요구는 해당 세션에 바인딩된 `capabilities` 목록 내에서만 허용한다. 갱신 후 추가된 capability를 기존 세션에 요구해서는 안 된다.
174 - 갱신 후 제거된 capability가 활성 세션에 바인딩되어 있는 경우, 해당 세션은 정상적으로 계속 동작한다. 서버가 해당 세션을 강제 종료하거나 기능을 제한해서는 안 된다 (MUST NOT).
175
176 ### 4.1.9. 레거시 호환: POST /v1/nodes/config (Deprecated)
177
178 > **본 엔드포인트는 단종 예고(Deprecated) 상태이다.** 새로운 클라이언트 구현은 §4.1.5 및 §4.1.6의 분리된 PUT 엔드포인트를 사용해야 한다 (MUST).
179
9 180 **Endpoint**: `POST /v1/nodes/config` (Master Server 제공)
10 181
182 기존 클라이언트와의 하위 호환을 위해 서버는 이 엔드포인트를 계속 수락할 수 있다 (MAY). 수신된 요청은 서버 내부에서 limits와 capabilities 스코프로 분리 저장한다. 이 경우 `config_version`은 양쪽 스코프 모두 `1`로 초기화한다.
183
184 서버는 이 엔드포인트로의 응답에 반드시 `Warning: 299 - "Deprecated: Use PUT /v1/nodes/config/limits and PUT /v1/nodes/config/capabilities"` HTTP 헤더를 포함해야 한다 (MUST).
185
11 186 **Request** (Client → Master):
12 187
13 188 ```json
14 189 {
15 190 "rate_limit": "Number (필수, 초당 처리 가능한 최대 HTTP 요청 수)",
16 191 "max_frame_size": "Number (필수, WSS 단일 프레임 최대 바이트)",
17 192 "reattach_window": "Number (필수, WSS 단절 시 대기 시간 초)",
18 193 "max_buffer_size": "Number (필수, 최대 메모리 버퍼 바이트)",
19 194 "buffer_overflow_policy": "String (필수, 'RING' 또는 'DROP')",
20 195 "capabilities": ["String (선택, e.g., 'vision_v1', 'tool_call_v2')"]
21 196 }
22 197 ```
23 198
24 199 ### 4.2. 헬스 체크 (Health Check)
25 200
26 201 **Endpoint**: `GET /v1/health` (Local Client 제공)
27 202
28 203 **Response**:
29 204
30 205 ```json
31 206 {
32 207 "status": "String (필수, 'online', 'busy', 'error')",
33 208 "uptime": "Number (필수, 클라이언트 가동 초)",
34 209 "active_sessions": "Number (선택, 현재 유지 중인 세션 수)"
35 210 }
36 211 ```
37 212
38 213 **상태 전이 규칙**: 활성 세션이 0개면 즉시 `online`. 1개 이상 존재 시 `busy`. 세션이 0이 되면 즉시 `online`으로 복귀해야 한다 (MUST).
39 214
40 215 ### 4.3. 에이전트 탐색 (Agent Discovery)
41 216
42 217 클라이언트 내 실행 가능한 에이전트 목록을 조회한다.
43 218
44 219 **Endpoint**: `GET /v1/agents` (Local Client 제공)
45 220
46 221 **Response**:
47 222
48 223 ```json
49 224 {
50 225 "agents": [
51 226 {
52 227 "id": "String (고유 식별자)",
53 228 "name": "String (세션 식별자로 사용될 이름)",
54 229 "is_active": "Boolean (에이전트 활성화 상태)"
55 230 }
56 231 ]
57 232 }
58 233 ```
59 234
60 235 ### 4.4. 파일 탐색 (Directory Browsing)
61 236
62 237 인가된 작업 경로 내의 디렉토리를 조회한다.
63 238
64 239 **Endpoint**: `POST /v1/fs/list` (Local Client 제공)
65 240
66 241 **Request**: `{"target_path": "String (필수, 조회할 절대 경로)"}`
67 242
243 > **변경 사항**: 실시간 퍼지 파일 검색은 RAWP-DPS 1.0 §16의 WSS 이벤트(`control.file.search` / `session.file.candidates`)를 통해 제공된다. 본 엔드포인트는 디렉토리 단위 일괄 조회용으로 유지한다.
244
68 245 ---
수정됨

5. Phase 3: 세션 라이프사이클 및 연결 (Session & I/O)

추가된 줄 2 삭제된 줄 0
1 1 ## 5. Phase 3: 세션 라이프사이클 및 연결 (Session & I/O)
2 2
3 3 **세션 생명주기**: `INIT` → `RUNNING` → `DETACHED`(소켓 단절 시) → `TERMINATED`
4 4
5 5 ### 5.1. 세션 초기화 및 재연결 (Session Initialization)
6 6
7 7 신규 에이전트 실행 또는 DETACHED 상태 프로세스에 WSS 바인딩을 위한 티켓을 발급한다.
8 8
9 9 **Endpoint**: `POST /v1/session/init` (Local Client 제공)
10 10
11 11 **Request**:
12 12
13 13 ```json
14 14 {
15 15 "session_id": "String (필수, UUID v4)",
16 16 "agent_name": "String (reattach: false 시 필수)",
17 17 "workspace_path": "String (reattach: false 시 필수)",
18 18 "ticket": "String (필수, WSS 연결 인증용 난수 티켓)",
19 19 "reattach": "Boolean (선택, 기본값 false. true 시 기존 구동 중 프로세스 바인딩)",
20 20 "last_sync_timestamp": "String (선택, ISO 8601)",
21 21 "last_message_id": "String (선택, UUID)"
22 22 }
23 23 ```
24 24
25 25 **제약 조건**: `reattach: true`이나 해당 `session_id`가 존재하지 않거나 종료된 경우 `404 Not Found` 반환 (MUST).
26 26
27 **Config 바인딩 제약 조건**: 마스터 서버는 `reattach: false`로 세션을 초기화할 때, §4.1.8에 따라 양쪽 Config Scope(limits, capabilities)의 최신 스냅샷을 해당 세션에 바인딩해야 한다 (MUST). 양쪽 스코프의 초기 동기화(§4.1.2)가 완료되지 않은 클라이언트에 대해서는 세션 초기화를 시도해서는 안 된다 (MUST NOT).
28
27 29 ### 5.2. 세션 명시적 종료 (Session Termination)
28 30
29 31 **Endpoint**: `DELETE /v1/session/{session_id}` (Local Client 제공)
30 32
31 33 **제약 조건**: 클라이언트는 자식 프로세스에 `SIGTERM`을 전송하고, 5초 내 종료되지 않으면 `SIGKILL`로 강제 종료 후 메모리 버퍼 및 소켓을 파기해야 한다 (MUST).
32 34
33 35 ### 5.3. WebSocket 연결 수립 (WebSocket Upgrade)
34 36
35 37 **Connection URI**: `wss://{client_address}/v1/ws/stream?ticket={ticket}&session_id={session_id}`
36 38
37 39 **인가 절차** (MUST):
38 40
39 41 1. 클라이언트는 HTTP Upgrade 요청 수신 시 HTTP 헤더의 `Sec-WebSocket-Protocol`을 확인해야 한다. RAWP-DPS 1.0 적용 시 `rawp-dps-1.0`을 우선 수락하고, 미지원 시 `rawp-1.0`으로 폴백한다. 양측 모두 미지원 시 `426` 또는 `400`을 반환해야 한다.
40 42 2. Query Parameter의 `ticket`과 `session_id`를 검증하고, 만료되거나 불일치 시 `401 Unauthorized`를 반환 후 연결을 거절한다.
41 43 3. 사용된 티켓은 1회 사용 시 즉시 무효화(Burn)해야 한다.
42 44
43 45 > **변경 사항**: RAWP-DPS 1.0 적용에 따라 `Sec-WebSocket-Protocol` 협상 절차가 확장되었다. `rawp-dps-1.0`이 우선이며, 레거시 호환을 위해 `rawp-1.0`도 수락 가능하다. 자세한 프로토콜 협상 규칙은 RAWP-DPS 1.0 §1.3을 참조한다.
44 46
45 47 ---
수정됨

6. 데이터 평면 스트리밍 규격 (Data Plane WSS Protocol)

추가된 줄 1 삭제된 줄 0
1 1 ## 6. 데이터 평면 스트리밍 규격 (Data Plane WSS Protocol)
2 2
3 3 > **본 절은 별도 규격으로 분리되었다.**
4 4 >
5 5 > WSS 연결 수립(§5.3) 이후 교환되는 데이터 프레임의 Envelope 구조, 이벤트 타입 및 페이로드 정의는 **RAWP-DPS 1.0** 규격을 따른다.
6 6 >
7 7 > RAWP-DPS 1.0은 다음을 정의한다:
8 8 >
9 9 > - **Envelope 구조**: 버전 필드(`v`), 네임스페이스 기반 이벤트 타입, Turn 추적(`turn_id`), 요청-응답 상관(`parent_id`)을 포함하는 확장된 프레임 구조 (RAWP-DPS §2)
10 10 > - **에이전트 출력** (`agent.*`): 텍스트 스트리밍, 사고(Thinking) 스트리밍, 상호작용 요청, 에러 보고, 파일 전송, 상태 변경 (RAWP-DPS §4)
11 11 > - **도구 호출** (`tool.*`): 도구 호출 요청/결과/실시간 스트리밍, 도구 카탈로그 고지 (RAWP-DPS §5)
12 12 > - **제어 명령** (`control.*`): 프롬프트 전달, 프롬프트 취소, 상호작용 응답, 컨텍스트 압축 요청, 동작 모드 전환 (RAWP-DPS §6)
13 13 > - **세션 이벤트** (`session.*`): 이력 복구, 컨텍스트 압축 보고, 사용량 지표, Turn 라이프사이클, 능력 협상 (RAWP-DPS §7, §12)
14 14 > - **태스크 관리** (RAWP-DPS §8), **계획 문서** (RAWP-DPS §9), **서브에이전트 위임** (RAWP-DPS §10), **구조화 출력** (RAWP-DPS §11)
15 > - **파일 참조** (RAWP-DPS §16): 인라인 파일 참조 토큰 포맷, 실시간 퍼지 파일 검색, 토큰 이스케이프 규칙, 어댑터별 변환 규칙
15 16 >
16 17 > 구현자는 RAWP-DPS 1.0 규격 문서를 참조하여 데이터 평면을 구현해야 한다 (MUST).
17 18 >
18 19 > **레거시 호환**: RAWP-DPS-0.1-Legacy만 지원하는 피어와의 통신이 필요한 경우, RAWP-DPS 1.0 §14의 하위 호환성 가이드라인 및 브릿지 변환 규칙을 따른다.
19 20
20 21 ---
수정됨

7. 보안 및 예외 처리 (Security & Errors)

추가된 줄 11 삭제된 줄 454
1 1 ## 7. 보안 및 예외 처리 (Security & Errors)
2 2
3 3 ### 7.1. HTTP 에러 응답 표준 규격
4 4
5 5 클라이언트나 서버가 HTTP 오류를 반환할 때, 다음 JSON 포맷을 Body에 포함해야 한다 (MUST).
6 6
7 7 ```json
8 8 {
9 9 "error_code": "String (e.g., TOKEN_EXPIRED, SESSION_NOT_FOUND, INVALID_PATH)",
10 10 "message": "String (휴먼 리더블 디버깅 메시지)"
11 11 }
12 12 ```
13 13
14 14 **상태 코드 매핑 규칙**:
15 15
16 | HTTP 상태 코드 | 사유 |
17 | --------------------------- | ----------------------------------------------------------- |
18 | `400 Bad Request` | 필수 파라미터 누락, 잘못된 UUID 포맷. |
19 | `401 Unauthorized` | 토큰 만료, 서명 불일치, WSS Upgrade 티켓 무효. |
20 | `403 Forbidden` | White-list 없는 명령어, 인가되지 않은 디렉토리 접근. |
21 | `404 Not Found` | 존재하지 않는 `session_id` 제어 시도. |
22 | `409 Conflict` | 이미 `RUNNING` 중인 세션에 `reattach: false`로 초기화 시도. |
23 | `429 Too Many Requests` | `rate_limit` 초과. |
24 | `500 Internal Server Error` | 내부 파일 시스템 오류, 바인딩 충돌 등. |
16 | HTTP 상태 코드 | 사유 |
17 | --------------------------- | --------------------------------------------------------------------------------------------------------------- |
18 | `400 Bad Request` | 필수 파라미터 누락, 잘못된 UUID 포맷. |
19 | `401 Unauthorized` | 토큰 만료, 서명 불일치, WSS Upgrade 티켓 무효. |
20 | `403 Forbidden` | White-list 없는 명령어, 인가되지 않은 디렉토리 접근. |
21 | `404 Not Found` | 존재하지 않는 `session_id` 제어 시도. |
22 | `409 Conflict` | 이미 `RUNNING` 중인 세션에 `reattach: false`로 초기화 시도. Config Scope의 `config_version` 역전 감지 (§4.1.4). |
23 | `422 Unprocessable Entity` | 설정 갱신 시 필드값 형식, 범위, 상호 정합성 위반 (§4.1.7). |
24 | `429 Too Many Requests` | `rate_limit` 초과. |
25 | `500 Internal Server Error` | 내부 파일 시스템 오류, 바인딩 충돌 등. |
26 | `503 Service Unavailable` | 초기 설정 동기화 미완료 상태에서 세션 초기화 시도 (§4.1.2). |
25 27
26 28 ### 7.2. 보안 제약 및 필수 보호 장치 (MUST)
27 29
28 30 - **경로 검증 (Path Normalization)**: 클라이언트는 모든 파일/디렉토리 조회 요청 시 경로 내 `../` 등을 정규화하고 인가된 `workspace_path` 범위 내인지 반드시 검증해야 한다 (Directory Traversal 방어).
29 31 - **명령어 및 에이전트 보호 (Whitelisting)**: 활성화된 `agent_name`에 매핑된 명령어만 실행 가능해야 하며, 임의의 쉘 스크립트나 바이너리를 원격으로 주입받아 실행하는 시도는 차단해야 한다 (RCE 방어).
30 32 - **버퍼 관리 및 OOM 방어**: WSS 단절 중 자식 프로세스를 유지할 때 발생하는 출력은 버퍼에 저장하되, `max_buffer_size`에 도달하면 반드시 사전에 합의된 `buffer_overflow_policy`(RING/DROP)에 따라 강제로 메모리를 관리하여 게이트웨이 다운을 방지해야 한다.
31 33 - **도구 호출 보안**: RAWP-DPS 1.0 적용 시, `tool.catalog.publish`에서 고지된 도구만 `tool.invoke.request`에 사용 가능하며, 고지되지 않은 도구의 호출 요청은 클라이언트가 거부해야 한다 (MUST). 자세한 도구 보안 규칙은 RAWP-DPS 1.0 §15를 참조한다.
32 34
33 35 ---
34
35 ### 8. 클라이언트 UI/UX 구현 지침 (Client UI Implementer Notes)
36
37 프론트엔드 또는 시각적 피드백(터미널, 대시보드 등)을 구현하는 시스템은 본 프로토콜의 상호작용 이벤트 처리 시 다음의 UI 제약 사항을 엄격히 준수해야 한다.
38
39 #### 8.1. 상호작용 및 애니메이션 기본 원칙 (필수 준수)
40
41 **불필요한 애니메이션 제한**: 실제 클릭 등 인터랙션이 없는 정적 요소(단순 텍스트 스트림 출력부 등)에는 불필요하게 호버(Hover) 애니메이션을 적용하지 않아야 한다 (MUST NOT).
42
43 **방해 없는 애니메이션 효과**: 버튼(상호작용 요청의 옵션 등)과 같이 실제 인터랙션이 존재하는 곳은, 반드시 사용자의 작업 흐름이나 시선을 방해하지 않는 선에서 부드러운 애니메이션 효과를 제공해야 한다 (MUST).
44
45 #### 8.2. 실시간 스트리밍 환경 (WebSocket 지원 클라이언트)
46
47 전용 웹 클라이언트나 데스크톱 앱과 같이 `agent.text.delta` 및 `agent.thinking.delta`를 실시간으로 수신할 수 있는 환경에 대한 지침이다.
48
49 ##### 8.2.1. 점진적 텍스트 렌더링 (Progressive Rendering)
50
51 **버퍼 병합**: 클라이언트는 `content_index` 단위로 수신되는 텍스트 페이로드를 지연 없이 화면에 점진적으로 렌더링해야 한다 (MUST).
52
53 **오토 스크롤 제어**: 텍스트 생성 중에는 뷰포트 하단을 자동으로 추적하되, 사용자가 과거 이력을 보기 위해 스크롤을 위로 올린 상태(Scroll Up)에서는 읽기 방해를 막기 위해 오토 스크롤을 일시 중단해야 한다 (SHOULD).
54
55 **스크롤 안정성 (Scroll Stability)**: 텍스트가 점진적으로 생성되면 콘텐츠 높이가 지속적으로 증가하며, 이로 인해 뷰포트가 위아래로 떨리거나(jitter) 현재 읽고 있는 텍스트의 위치가 밀려나는 현상이 발생할 수 있다. 클라이언트는 사용자가 현재 읽고 있는 텍스트가 시각적으로 흔들리지 않도록 스크롤 위치를 능동적으로 제어해야 한다 (MUST). 구체적으로 다음을 준수한다:
56
57 - **앵커 포인트 고정**: 오토 스크롤이 활성화된 상태에서는, 새로운 텍스트가 삽입될 때 뷰포트의 스크롤 위치를 **현재 생성 중인 마지막 라인이 뷰포트 하단의 일정 영역(예: 하단 20~30% 지점)에 고정**되도록 조정해야 한다 (MUST). 콘텐츠 높이 변화량만큼 단순히 `scrollTop`을 증가시키는 방식은 프레임 간 미세한 위치 차이로 떨림을 유발하므로 지양한다.
58 - **상방 삽입 보상 (Upward Insertion Compensation)**: 현재 읽고 있는 영역 **위쪽**에 콘텐츠가 삽입되거나 높이가 변하는 경우(예: 사고 블록 접힘/펼침, 도구 결과 렌더링, 이미지 로딩 완료 등), 삽입된 높이만큼 `scrollTop`을 보상하여 현재 보고 있는 텍스트의 화면상 절대 위치가 변하지 않도록 해야 한다 (MUST). 이 보상이 없으면 사용자가 읽고 있던 텍스트가 갑자기 아래로 밀려나는 현상이 발생한다.
59 - **부드러운 추종**: 오토 스크롤 시 `scrollTo`에 `behavior: 'instant'`를 사용하여 프레임 단위로 즉각 추종해야 한다 (SHOULD). `behavior: 'smooth'`는 빠른 텍스트 생성 속도에 스크롤 애니메이션이 뒤처져 누적 지연(scroll lag)을 유발할 수 있으므로, 스트리밍 중에는 사용하지 않아야 한다 (SHOULD NOT). 부드러운 시각 효과가 필요하면 CSS `scroll-behavior` 대신 `requestAnimationFrame` 기반으로 프레임 동기화된 위치 조정을 구현할 것을 권장한다.
60 - **높이 변동 최소화**: 마크다운 렌더링 시 코드 블록, 테이블, 이미지 등 높이가 확정되지 않은 요소는 예상 높이를 미리 확보(placeholder/skeleton)하여, 렌더링 완료 시 높이 변동폭을 최소화해야 한다 (SHOULD). 특히 코드 블록은 열림 태그(` ``` `) 수신 시점에 최소 높이를 할당하고, 내용이 채워지면서 점진적으로 확장하는 방식을 적용한다.
61
62 **미완성 마크다운 방어**: 렌더러는 스트리밍 중 마크다운 태그(예: 코드 블록 ` ``` `, 볼드체 `**`)가 닫히지 않은 상태로 전달되더라도 UI가 깨지지 않도록 방어적으로 처리해야 한다 (MUST).
63
64 ##### 8.2.2. 사고 과정 (Thinking Process) 시각화
65
66 **시각적 분리**: `agent.thinking.delta`의 내용은 일반 응답 텍스트와 명확히 구분되는 별도의 컨테이너(예: 인용구, 배경색이 다른 블록)에 렌더링해야 한다 (MUST).
67
68 **진행 상태 표현 (Thinking Indicator)**: 사고 과정이 진행 중일 때, 사용자에게 에이전트가 능동적으로 작업 중임을 시각적으로 전달해야 한다 (MUST). 구체적으로 다음을 권장한다:
69
70 - **그라데이션 애니메이션**: 사고 컨테이너의 좌측 보더 또는 배경에 부드러운 그라데이션 펄스(gradient pulse) 또는 시머(shimmer) 효과를 적용하여 활성 상태를 표현해야 한다 (SHOULD). 색상은 일반 응답 영역과 구별되는 톤(예: 보라/남색 계열의 저채도 그라데이션)을 사용하되, 본문 가독성을 해치지 않아야 한다 (MUST NOT).
71 - **애니메이션 템포**: 그라데이션 또는 펄스의 주기는 1.5~3초 사이의 느린 호흡 리듬을 유지하여 사용자에게 안정감을 주어야 한다 (SHOULD). 빠르게 깜빡이거나 점멸하는 효과는 시각적 피로를 유발하므로 사용해서는 안 된다 (MUST NOT).
72 - **레이블 표시**: 컨테이너 상단에 "Thinking…" 또는 동등한 상태 레이블을 표시하여 해당 영역의 성격을 명시해야 한다 (MUST). 레이블에는 경과 시간 카운터(예: "Thinking… 3s")를 선택적으로 병행 표시할 수 있다 (MAY).
73
74 **실시간 텍스트 노출 정책**: 사고 스트림의 텍스트 본문을 사용자에게 실시간으로 노출할지 여부는 클라이언트 설정에 따른다. 두 가지 모드를 지원할 것을 권장한다 (SHOULD):
75
76 - **요약 모드 (기본값 권장)**: 사고 텍스트 본문을 숨기고, Thinking Indicator와 레이블만 표시한다. 사용자가 클릭 또는 토글하면 본문을 펼칠 수 있다.
77 - **실시간 모드**: `agent.thinking.delta`의 텍스트를 점진적으로 렌더링한다. 이 경우에도 일반 응답 텍스트와의 시각적 분리(배경색, 폰트 스타일, 불투명도 차등 등)를 유지해야 한다 (MUST).
78
79 **상태 전이 및 제거**: `agent.thinking.done` 수신 시, 사고 영역은 다음 절차를 따라 전이되어야 한다 (MUST):
80
81 1. **즉시 비활성화**: 그라데이션 애니메이션과 펄스 효과를 즉시 중단한다 (MUST). 사고가 끝난 후에도 애니메이션이 잔존하면 사용자에게 혼란을 줄 수 있으므로, 전이 지연 없이 정지해야 한다.
82 2. **축소 전환**: 비활성화 후, 컨테이너를 접힌 상태(Collapsed)로 전환하여 시각적 비중을 최소화해야 한다 (SHOULD). 접힌 상태에서는 "Thought for Ns" 형태의 요약 한 줄만 표시하고, 클릭 시 전체 내용을 펼칠 수 있어야 한다 (SHOULD).
83 3. **전환 애니메이션**: 활성 → 접힘 전환 시, 높이 축소(collapse)와 불투명도 감소(fade)를 150~300ms 이내의 부드러운 트랜지션으로 처리하여 시각적 점프를 방지해야 한다 (SHOULD).
84 4. **포커스 이동**: 사고 영역이 접힌 직후, 뷰포트 스크롤을 후속 `agent.text.delta` 영역으로 자연스럽게 이동시켜 사용자의 시선이 최종 응답에 집중되도록 유도해야 한다 (SHOULD).
85
86 **마스킹 처리**: 페이로드에 `redacted: true`가 포함된 경우, 텍스트 원문 대신 `[사고 내용 생략]` 등의 플레이스홀더로 마스킹하여 표시해야 한다 (MUST). 마스킹된 사고 블록은 펼침(Expand) 동작 자체를 비활성화하여 원문 접근을 차단해야 한다 (MUST).
87
88 **다중 사고 블록**: 단일 Turn 내에서 여러 번의 사고-응답 사이클이 발생할 수 있다 (예: 도구 호출 사이의 중간 추론). 각 `content_index`별로 독립된 사고 컨테이너를 생성하되, 동시에 두 개 이상의 Thinking Indicator가 활성 상태로 표시되어서는 안 된다 (MUST NOT).
89
90 ##### 8.2.3. 에이전트 상태 표시 (Agent State Indicator)
91
92 `agent.state.changed`(RAWP-DPS §4.6.1) 이벤트는 에이전트의 내부 상태 전이를 실시간으로 통보한다. 단일 Turn 내에서 `idle → thinking → tool_calling → thinking → idle` 등 여러 차례의 상태 전이가 빠르게 연속 발생할 수 있으며, 이를 각각 독립된 채팅 메시지(버블)로 렌더링해서는 안 된다 (MUST NOT). 상태 전이를 개별 라인으로 나열하면 대화 이력이 시스템 로그처럼 오염되어 사용자의 실제 대화 맥락 파악을 방해한다.
93
94 **단일 인라인 상태 표시줄 (Single Status Line)**: 클라이언트는 에이전트의 현재 상태를 대화 영역의 고정된 단일 위치에 표시하고, `agent.state.changed` 수신 시마다 해당 위치의 내용을 **제자리 갱신(in-place update)** 해야 한다 (MUST). 새로운 채팅 버블을 추가하는 것이 아니라, 기존 상태 표시줄의 텍스트와 아이콘을 교체하는 방식이어야 한다.
95
96 **표시 위치**: 상태 표시줄은 다음 위치 중 하나에 배치해야 한다 (MUST):
97
98 - **응답 영역 직상단**: 현재 에이전트 응답이 스트리밍되는 영역 바로 위에 인라인으로 표시. 응답 텍스트가 시작되면 자연스럽게 밀려 올라간다.
99 - **입력창 직상단**: 사용자 입력창과 대화 이력 사이의 경계 영역에 고정 표시.
100
101 두 방식 모두 대화 이력 내에 별도의 메시지 버블을 삽입하지 않는다는 점에서 동일하다.
102
103 **상태별 렌더링**: 각 `current_state` 값에 따라 다음과 같이 표시할 것을 권장한다 (SHOULD):
104
105 | `current_state` | 표시 예시 | 시각 효과 |
106 | ---------------- | --------------------- | ------------------------------------ |
107 | `thinking` | `🧠 Thinking…` | 그라데이션 펄스 또는 도트 애니메이션 |
108 | `tool_calling` | `🔧 Running tool…` | 스피너 회전 |
109 | `awaiting_input` | `⏸ Waiting for input` | 정적 표시, 애니메이션 없음 |
110 | `error` | `⚠ Error occurred` | 붉은 계열 강조 |
111 | `idle` | (표시줄 제거) | 아래 "유휴 상태 처리" 참조 |
112
113 `tool_calling` 상태에서 `tool.invoke.request`가 동반 수신되면, 도구명을 상태 텍스트에 포함하여 구체화할 수 있다 (MAY). 예: `🔧 Running: Edit (src/auth.ts)…`
114
115 **전환 애니메이션**: 상태 텍스트가 교체될 때, 크로스페이드(cross-fade) 또는 슬라이드 전환을 100~200ms 이내로 적용하여 텍스트가 갑작스럽게 교체되는 시각적 불쾌감을 방지해야 한다 (SHOULD). 단, 전환 속도가 상태 변경 빈도보다 느려서 전환 애니메이션이 누적 지연되는 상황이 발생하면, 애니메이션을 생략하고 즉시 교체해야 한다 (MUST).
116
117 **유휴 상태 처리**: `current_state`가 `idle`로 전이되면(Turn 종료), 상태 표시줄을 즉시 제거하거나 페이드아웃(200~300ms)으로 사라지게 해야 한다 (MUST). 에이전트가 작업 중이 아닌데도 상태 표시줄이 잔존하면 사용자에게 혼란을 준다. Turn이 완전히 종료된 후 대화 이력에는 상태 전이의 흔적이 남아서는 안 된다 (MUST NOT).
118
119 **대화 이력 비오염 원칙**: `agent.state.changed` 이벤트는 대화 이력(채팅 로그)에 영구적 항목으로 기록해서는 안 된다 (MUST NOT). 상태 표시는 순수하게 일시적(ephemeral)이며, 세션 재연결이나 이력 조회(`session.history`) 시 과거 상태 전이 메시지가 대화 이력에 표시되어서는 안 된다. 디버깅 목적의 상태 전이 로그가 필요한 경우, 별도의 개발자 콘솔이나 로그 패널에 기록할 수 있다 (MAY).
120
121 ##### 8.2.4. 사용자 중지 피드백 (Cancellation Feedback)
122
123 사용자가 중지 버튼을 눌러 진행 중인 Turn을 취소하면(`control.prompt.cancel` 발송), 클라이언트는 대화 이력에 **시스템 채팅 버블**을 삽입하여 취소가 실행되었음을 명시적으로 기록해야 한다 (MUST). 이는 §8.2.3의 일시적(ephemeral) 상태 표시와 달리, 대화 이력에 영구적으로 남는 항목이다.
124
125 **시스템 버블의 필요성**: 중지 후 아무런 시각적 피드백 없이 에이전트 응답이 끊기면, 사용자는 에이전트가 에러로 멈춘 것인지, 응답이 완료된 것인지, 자신이 취소한 것인지 구분할 수 없다. 특히 대화 이력을 나중에 다시 열었을 때, 중간에 잘린 응답만 보이면 맥락 파악이 불가능하다. 시스템 버블은 이 단절의 원인을 명확히 기록한다.
126
127 **렌더링 규격**: 시스템 중지 버블은 다음을 준수해야 한다 (MUST):
128
129 - **사용자/에이전트 버블과 구별**: 시스템 버블은 사용자 메시지 버블이나 에이전트 응답 버블과 시각적으로 다른 스타일을 적용해야 한다 (MUST). 좌우 정렬이 아닌 **중앙 정렬**로 표시하거나, 배경색 없이 텍스트만 연한 색상으로 표시하는 등 "시스템 안내 메시지"임을 즉각적으로 인지할 수 있어야 한다.
130 - **간결한 메시지**: 버블 내용은 `⏹ 응답이 중지되었습니다` 또는 동등한 한 줄 문구로, 중지가 사용자의 의도적 행동임을 전달한다. 장황한 설명은 불필요하다.
131 - **시각적 비중 최소화**: 시스템 버블은 대화 흐름에서 보조 정보이므로, 사용자 메시지나 에이전트 응답보다 시각적 비중이 낮아야 한다 (SHOULD). 작은 폰트 크기, 연한 텍스트 색상, 아이콘 단독 표시 등으로 존재감을 줄이되, 완전히 보이지 않을 정도로 숨겨서는 안 된다.
132
133 **삽입 위치 및 타이밍**: 시스템 버블은 에이전트의 마지막 출력(잘린 텍스트, 도구 결과 등) 바로 아래에 삽입해야 한다 (MUST). `control.prompt.cancel` 발송 직후, `session.turn.end`(stop_reason: `cancelled`) 수신을 기다리지 않고 즉시 표시하여 사용자에게 중지 요청이 접수되었음을 지체 없이 피드백해야 한다 (SHOULD).
134
135 **부분 응답 보존**: 중지 이전까지 수신된 `agent.text.delta` 텍스트는 삭제하지 않고 그대로 유지해야 한다 (MUST). 시스템 버블은 기존 부분 응답을 대체하는 것이 아니라, 그 하단에 추가되어 "여기까지가 중지 이전에 생성된 내용"임을 표시하는 경계 역할을 한다.
136
137 **에이전트 상태 정리와의 관계**: 시스템 버블이 삽입되면, §8.2.3의 에이전트 상태 표시줄(Thinking Indicator, Tool Indicator 등)은 즉시 제거되어야 한다 (MUST). 중지 후에도 "🧠 Thinking…" 이 잔존하면 에이전트가 여전히 작업 중인 것으로 오인된다.
138
139 #### 8.3. 비스트리밍/완성형 환경 (Discord, Slack, Webhook 등)
140
141 WebSocket을 지원하지 않아 단일 메시지로만 응답을 렌더링해야 하는 서드파티 플랫폼 연동 게이트웨이에 대한 지침이다.
142
143 ##### 8.3.1. 플랫폼 네이티브 상태 지시기 (Typing Indicator) 적극 활용
144
145 슬랙(Slack), 디스코드(Discord) 등 대부분의 메신저 플랫폼은 자체적으로 '입력 중(Typing...)'과 같은 네이티브 UX 상태 지시기를 제공한다. 점진적 텍스트 렌더링이 불가능한 환경에서는 이러한 플랫폼 네이티브 기능을 최대한 활용하여 사용자에게 지연에 대한 시각적 피드백을 제공해야 한다 (MUST).
146
147 **이벤트-지시기 매핑**: 게이트웨이는 RAWP-DPS 이벤트를 수신하여 플랫폼 상태 API를 다음과 같이 연동해야 한다 (MUST):
148
149 | 수신 이벤트 | 지시기 동작 | 비고 |
150 | ----------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ |
151 | `session.turn.start` | '입력 중' 활성화 시작 | 주기적 갱신 개시 (아래 참조) |
152 | `agent.thinking.delta` | '입력 중' 유지 | 사고 중에도 활성 유지 |
153 | `tool.invoke.request` | '입력 중' 유지 또는 커스텀 상태 메시지 발송 | 플랫폼이 상태 메시지를 지원하면 "🔧 도구 실행 중…" 등의 임시 메시지를 발송할 수 있다 (MAY) |
154 | `agent.text.done` 또는 `session.turn.end` | '입력 중' 해제 | 최종 메시지 발송과 동시에 해제 |
155 | `agent.error` (severity: `fatal`) | '입력 중' 즉시 해제 | 에러 메시지로 대체 |
156
157 **주기적 갱신(Heartbeat)**: 대부분의 플랫폼은 '입력 중' 상태가 일정 시간(일반적으로 5~10초) 후 자동 만료된다. 게이트웨이는 Turn이 진행 중인 동안 플랫폼의 만료 주기보다 짧은 간격(예: 3~5초)으로 '입력 중' API를 반복 호출하여 상태를 유지해야 한다 (MUST).
158
159 **장시간 작업 대응**: 도구 호출이나 서브에이전트 위임으로 인해 에이전트 응답이 30초 이상 지연될 경우, 단순 '입력 중' 상태만으로는 사용자가 작업 중단으로 오인할 수 있다. 이 경우 게이트웨이는 다음 중 하나의 방법으로 진행 상황을 보강해야 한다 (SHOULD):
160
161 - **중간 상태 메시지**: "⏳ 코드를 분석하고 있습니다…", "🔍 파일을 검색하고 있습니다…" 등, `tool.invoke.request`의 `tool_name`을 기반으로 한 임시 메시지를 발송한 뒤, 최종 응답 시 해당 메시지를 삭제 또는 교체한다.
162 - **리액션/이모지 업데이트**: 플랫폼이 메시지 리액션을 지원하면, 사용자의 원본 메시지에 ⏳ → ✅ 등의 리액션을 단계별로 추가하여 진행 상태를 전달한다.
163
164 ##### 8.3.2. 버퍼링 및 일괄 발송 (Buffered Rendering)
165
166 게이트웨이는 `agent.text.delta`를 메모리에 버퍼링하고, 조립된 전체 텍스트를 단일 메시지로 플랫폼에 발송해야 한다 (MUST).
167
168 **플랫폼 제한 대응**: 대상 플랫폼의 단일 메시지 길이 제한(예: Discord의 경우 2,000자)을 초과할 경우, 코드 블록이나 문단이 중간에 훼손되지 않도록 논리적 단위로 분할(Chunking)하여 연속 발송해야 한다 (MUST).
169
170 ##### 8.3.3. 사고 과정 텍스트 우회 처리
171
172 비스트리밍 환경에서는 긴 사고 과정 텍스트가 화면을 과도하게 점유할 수 있으므로, 플랫폼에서 지원하는 스포일러 태그(예: `||내용||`)를 활용하여 기본적으로 가려진 상태로 전송하거나, 최종 응답과 별개의 스레드/메시지로 분리하여 발송해야 한다 (SHOULD).
173
174 #### 8.4. 도구 호출 및 태스크 시각화 (RAWP-DPS 1.0 적용 시)
175
176 ##### 8.4.1. 도구 실행 상태 표시 (Tool Execution Indicator)
177
178 `tool.invoke.request` 수신 시, 클라이언트는 사용자에게 도구 실행이 진행 중임을 명확히 전달해야 한다 (MUST). 구체적으로 다음을 준수한다:
179
180 **진행 중 표시**: 도구명(`tool_name`)과 함께 스피너 또는 진행 애니메이션을 인라인으로 표시해야 한다 (MUST). 표시 형식은 `⏳ Running: Edit (src/auth.ts)` 또는 도구 카테고리에 따른 아이콘(§5.4.1 `category` 참조)을 활용할 수 있다. `tool.invoke.stream` 수신 시에는 스피너 하단에 실시간 출력(예: Bash stdout)을 점진적으로 렌더링해야 한다 (SHOULD).
181
182 **병렬 도구 그룹화**: 동일한 `parallel_group_id`를 가진 복수의 `tool.invoke.request`가 수신되면, 개별 스피너를 나열하지 않고 하나의 그룹 컨테이너로 묶어 표시해야 한다 (SHOULD). 예: `⏳ Running 3 tools in parallel: git status, git diff, git log`.
183
184 **완료 전환**: `tool.invoke.result` 수신 시, 스피너를 즉시 중단하고 `output_type`에 적합한 결과 뷰어로 전환해야 한다 (MUST). 뷰어 매핑은 다음을 따른다:
185
186 | `output_type` | 권장 뷰어 | 비고 |
187 | -------------- | ---------------------------------------- | ---------------------------------- |
188 | `text` | 모노스페이스 텍스트 블록 | Bash stdout 등 |
189 | `diff` | Unified Diff 뷰어 또는 Side-by-side Diff | 변경 라인 하이라이팅 포함 (SHOULD) |
190 | `file_content` | 라인 번호 포함 코드 뷰어 | 구문 하이라이팅 적용 (SHOULD) |
191 | `file_list` | 파일 트리 또는 경로 목록 | 클릭 시 해당 파일 열기 지원 (MAY) |
192 | `web_content` | 링크 포함 요약 카드 | 출처 URL 표시 (MUST) |
193 | `structured` | JSON 트리 뷰어 또는 전용 위젯 | TodoRead 등은 §8.4.2 참조 |
194 | `empty` | 성공 체크마크 한 줄 | "✅ Write completed" 등 |
195 | `agent_result` | 서브에이전트 결과 접이식 블록 | 클릭 시 전체 결과 펼침 (SHOULD) |
196
197 **에러 결과 표시**: `status`가 `error`, `timeout`, `cancelled`인 경우, 결과 뷰어 대신 에러 상태를 시각적으로 구분하여 표시해야 한다 (MUST). 예: 붉은 계열 배경, ❌ 아이콘, 에러 메시지 인라인 표시.
198
199 **접이식 기본 동작**: 도구 결과는 기본적으로 접힌 상태(Collapsed)로 표시하되, 결과 요약 한 줄(도구명 + 상태 + 소요 시간)을 노출하고 클릭 시 전체 결과를 펼칠 수 있어야 한다 (SHOULD). 단, `output_type: diff`인 경우에는 코드 리뷰 편의를 위해 기본 펼침 상태를 허용한다 (MAY).
200
201 ##### 8.4.2. 태스크 목록 실시간 갱신 (Task List Rendering)
202
203 `output_type: structured` 형태의 TodoRead/TodoWrite 결과(RAWP-DPS §8.2)를 수신하면, 클라이언트는 전용 태스크 관리 UI를 실시간으로 갱신해야 한다 (SHOULD). 구체적으로 다음을 권장한다:
204
205 **태스크 상태 시각화**: 각 태스크의 `status` 필드를 다음과 같이 시각적으로 구분한다 (SHOULD):
206
207 | `status` | 시각 표현 | 비고 |
208 | ------------- | -------------------------------- | -------------------------- |
209 | `pending` | ○ 빈 원 또는 미체크 체크박스 | 회색 텍스트 |
210 | `in_progress` | ◐ 반원 또는 스피너 부착 체크박스 | 볼드 텍스트, 강조 배경 |
211 | `completed` | ● 채워진 원 또는 체크된 체크박스 | 취소선 텍스트 (SHOULD) |
212 | `cancelled` | ✕ 취소 표시 | 흐린 텍스트, 불투명도 감소 |
213
214 **계층 구조 지원**: `parent_id`가 존재하는 태스크는 부모 태스크 하위에 들여쓰기하여 트리 구조로 렌더링해야 한다 (SHOULD). 부모 태스크의 진행률은 자식 태스크의 완료 비율을 기반으로 프로그레스 바 형태로 표시할 수 있다 (MAY).
215
216 **전환 애니메이션**: 태스크 상태가 변경될 때(예: `pending` → `in_progress` → `completed`), 부드러운 전환 애니메이션(배경색 페이드, 체크마크 드로잉 등)을 적용하여 변경 사항이 사용자에게 자연스럽게 인지되도록 해야 한다 (SHOULD). 단, 한 번에 다수의 태스크가 동시에 갱신되는 경우(TodoWrite의 batch 갱신 등)에는 개별 애니메이션을 생략하고 전체 목록을 일괄 교체할 수 있다 (MAY).
217
218 ##### 8.4.3. Diff 출력 렌더링 (Diff Viewer)
219
220 `output_type: diff` 또는 `output_type: text`이면서 내용이 Unified Diff 형식(행 접두사 `+`, `-`, `@@` 등 포함)인 도구 결과는, 단순 모노스페이스 텍스트 블록이 아닌 **구문 인지 Diff 뷰어**로 렌더링해야 한다 (MUST). 평문 diff를 색상 구분 없이 그대로 출력하면 수십~수백 줄의 변경 사항에서 삭제/추가 라인을 시각적으로 식별하기 극히 어렵다.
221
222 **Diff 형식 자동 감지**: 도구 결과의 `output_type`이 `diff`이면 무조건 Diff 뷰어를 적용한다. `output_type`이 `text`인 경우에도, 출력 내용의 첫 10라인 이내에 `--- a/`, `+++ b/`, `@@ ` 등 Unified Diff 시그니처가 감지되면 Diff 뷰어로 자동 전환해야 한다 (SHOULD). 이는 Bash 도구에서 `git diff`, `git show`, `diff -u` 등을 실행한 결과가 `output_type: text`로 보고되는 경우를 포괄하기 위함이다.
223
224 **라인 수준 색상 구분**: Diff 뷰어는 각 라인의 접두 문자를 파싱하여 다음과 같이 색상을 적용해야 한다 (MUST):
225
226 | 접두 문자 | 의미 | 배경색 | 텍스트 색상 |
227 | ------------ | ----------------------- | ---------------------- | ------------------------ |
228 | `-` | 삭제된 라인 | 연한 빨강 (예: `#fdd`) | 어두운 빨강 (예: `#a00`) |
229 | `+` | 추가된 라인 | 연한 녹색 (예: `#dfd`) | 어두운 녹색 (예: `#080`) |
230 | `@@` | 청크 헤더 (Hunk Header) | 연한 파랑 (예: `#def`) | 파랑 (예: `#36c`) |
231 | ` ` (공백) | 변경 없는 컨텍스트 라인 | 투명 (기본 배경) | 기본 텍스트 색상 |
232 | `diff --git` | 파일 경계 헤더 | 없음 또는 연한 회색 | 볼드 처리 |
233
234 위 색상값은 참고 예시이며, 클라이언트의 테마(라이트/다크)에 따라 적절히 조정한다. 핵심 원칙은 **삭제=빨강 계열, 추가=녹색 계열의 대비**가 즉각적으로 인지되어야 한다는 것이다 (MUST).
235
236 **다크 테마 대응**: 다크 모드에서는 배경색의 명도를 낮추고 채도를 조정하여 가독성을 유지해야 한다 (MUST). 예: 삭제 라인 배경 `#3d1114`, 추가 라인 배경 `#1a361a`. 라이트 모드와 동일한 색상을 다크 배경에 그대로 사용하면 눈부심이나 대비 부족이 발생한다.
237
238 **인라인 단어 수준 하이라이팅 (Word-level Diff)**: 동일 위치의 삭제 라인(`-`)과 추가 라인(`+`) 쌍에 대해, 라인 내에서 실제로 변경된 단어 또는 문자 범위를 추가로 강조(진한 배경색)할 것을 권장한다 (SHOULD). 이를 통해 라인 전체가 아닌 구체적으로 어떤 부분이 변경되었는지를 한눈에 파악할 수 있다. 예:
239
240 - 삭제 라인: `"version": "1.0.0"` — `1.0.0` 부분에 진한 빨강 배경
241 - 추가 라인: `"version": "0.1.0"` — `0.1.0` 부분에 진한 녹색 배경
242
243 **라인 번호 표시**: 각 라인 좌측에 원본 파일(old)과 변경 파일(new)의 라인 번호를 이중 컬럼으로 표시해야 한다 (SHOULD). 삭제 라인은 원본 번호만, 추가 라인은 변경 번호만, 컨텍스트 라인은 양쪽 모두 표시한다. 라인 번호는 diff 내용과 시각적으로 분리(연한 색상, 좁은 폰트)하여 본문 가독성을 해치지 않아야 한다.
244
245 **파일 헤더 구분**: 단일 diff 출력에 여러 파일의 변경 사항이 포함된 경우(`diff --git a/file1 b/file1` ... `diff --git a/file2 b/file2`), 각 파일 경계를 명확한 헤더 바로 구분해야 한다 (MUST). 파일 헤더에는 파일 경로를 볼드 또는 큰 폰트로 표시하고, 각 파일 섹션 사이에 시각적 구분선(divider)을 삽입한다. 파일 단위로 접기/펼치기(collapse/expand)를 지원할 것을 권장한다 (SHOULD).
246
247 **뷰 모드 전환**: 클라이언트는 다음 두 가지 Diff 뷰 모드를 제공할 것을 권장한다 (SHOULD):
248
249 - **Unified View (기본값)**: 삭제와 추가 라인이 상하로 인접 배치되는 단일 컬럼 뷰. 공간 효율이 높아 채팅 인터페이스의 제한된 폭에 적합하다.
250 - **Side-by-side View**: 원본(좌)과 변경(우)을 좌우 분할로 비교하는 이중 컬럼 뷰. 넓은 화면에서 대규모 변경 사항을 비교할 때 유용하다.
251
252 모드 전환은 뷰어 상단의 토글 버튼으로 제공하며, 사용자의 마지막 선택을 로컬에 기억하여 다음 diff 표시 시 동일 모드를 적용할 수 있다 (MAY).
253
254 **접근성 고려**: 색상만으로 삭제/추가를 구분하면 색각 이상(color blindness) 사용자가 식별하기 어렵다. 색상과 함께 접두 기호(`-`, `+`)를 항상 가시적으로 유지하고, 선택적으로 좌측 거터(gutter)에 삭제(▬) / 추가(▊) 형태의 아이콘 또는 패턴을 병행 표시할 것을 권장한다 (SHOULD).
255
256 #### 8.5. 슬래시 명령어 자동 완성 (Slash Command Autocomplete)
257
258 사용자가 입력창에 `/` 문자를 타이핑하면, 클라이언트는 `agent.commands.publish`(RAWP-DPS §12.1.1)를 통해 수신한 명령어 목록을 기반으로 자동 완성 UI를 즉시 표시해야 한다 (MUST).
259
260 ##### 8.5.1. 자동 완성 팝업 트리거 및 노출
261
262 **트리거 조건**: 입력창에서 커서가 행 시작 위치이거나 공백 직후에 `/` 문자가 입력되면, 자동 완성 팝업을 활성화해야 한다 (MUST). 문장 중간의 슬래시(예: `https://...`, `path/to/file`)에서는 활성화해서는 안 된다 (MUST NOT).
263
264 **팝업 위치**: 자동 완성 팝업은 입력창 바로 위(또는 아래, 뷰포트 공간에 따라 동적 결정)에 오버레이로 표시해야 한다 (MUST). 기존 대화 이력이나 응답 영역을 가려서는 안 되며 (SHOULD NOT), 입력 커서와의 시각적 연결성을 유지해야 한다.
265
266 **즉시 노출**: `/` 입력 후 팝업 표시까지의 지연은 100ms를 초과해서는 안 된다 (SHOULD NOT). `agent.commands.publish`로 수신한 명령어 목록은 세션 시작 시 로컬에 캐싱하여 네트워크 지연 없이 즉시 표시할 수 있어야 한다 (MUST).
267
268 ##### 8.5.2. 명령어 목록 표시 및 필터링
269
270 **목록 구성**: 각 명령어 항목은 다음 요소를 포함하여 표시해야 한다 (MUST):
271
272 - **명령어 이름**: `/{name}` 형식으로 좌측에 고정 표시. 모노스페이스 폰트 적용 (SHOULD).
273 - **설명(description)**: 명령어 이름 우측 또는 하단에 간략한 설명 텍스트를 표시.
274 - **카테고리 표지**: `category` 필드가 존재하면, 색상 뱃지 또는 아이콘으로 시각적 그룹핑을 제공할 수 있다 (MAY). 예: `session` 카테고리는 🔄, `display`는 👁, `config`는 ⚙ 등.
275
276 **항목 간 시각적 분리 (Item Separation)**: 명령어 목록의 각 항목은 인접 항목과 명확히 구분되어야 한다 (MUST). 항목이 빽빽하게 나열되면 어떤 명령어에 포커스가 있는지, 각 설명이 어떤 명령어에 속하는지 구분이 어려워진다. 다음 방법 중 하나 이상을 적용한다:
277
278 - **충분한 내부 패딩**: 각 항목에 상하 패딩(예: 8~12px)을 부여하여 텍스트가 경계에 밀착되지 않도록 한다 (MUST). 항목 간 여백 없이 패딩만으로 구분이 충분하면 구분선은 생략할 수 있다.
279 - **구분선 또는 배경 교차**: 항목 사이에 얇은 구분선(1px, 저대비 색상)을 삽입하거나, 짝수/홀수 항목에 미세하게 다른 배경색(zebra striping)을 적용할 수 있다 (MAY).
280 - **명령어명-설명 레이아웃**: 명령어 이름과 설명 텍스트는 시각적 위계가 명확해야 한다 (MUST). 명령어명은 볼드 또는 기본 텍스트 색상, 설명은 연한 색상(muted color)과 작은 폰트 크기로 차등을 둔다. 두 줄 레이아웃(명령어명 위, 설명 아래) 또는 한 줄 레이아웃(명령어명 좌, 설명 우) 모두 허용하되, 항목 내의 명령어명과 설명이 하나의 시각적 단위로 묶여 있음이 명확해야 한다.
281
282 **실시간 필터링**: 사용자가 `/` 이후 추가 문자를 타이핑하면(예: `/com`), 입력된 접두사와 일치하는 명령어만 팝업에 필터링하여 표시해야 한다 (MUST). 필터링은 명령어 이름(`name`)에 대한 접두사 매칭을 기본으로 하되, 퍼지 매칭(fuzzy match)을 추가 지원할 수 있다 (MAY). 필터링 결과가 갱신될 때, 포커스는 목록의 첫 번째 항목으로 리셋되어야 한다 (SHOULD).
283
284 **정렬 순서**: 명령어 목록은 다음 우선순위로 정렬할 것을 권장한다 (SHOULD):
285
286 1. 사용 빈도 높은 순 (클라이언트 로컬에 사용 이력이 있는 경우)
287 2. `category`별 그룹핑 (같은 카테고리의 명령어를 인접 배치)
288 3. 알파벳순 (동일 조건 내)
289
290 **빈 결과 처리**: 입력된 접두사에 매칭되는 명령어가 없으면, 팝업에 "일치하는 명령어가 없습니다" 등의 안내 메시지를 표시해야 한다 (SHOULD). 팝업을 즉시 닫아서는 안 된다 (MUST NOT) — 사용자가 타이핑을 수정할 기회를 보장해야 한다.
291
292 ##### 8.5.3. 선택 및 실행
293
294 **포커스 하이라이트 (Focus Highlight)**: 키보드 또는 마우스로 현재 선택 대상이 되는 항목은, 다른 항목과 즉각적으로 구별되는 하이라이트를 적용해야 한다 (MUST). 하이라이트는 해당 항목의 **전체 행 영역**(좌측 끝~우측 끝)에 배경색을 적용하는 방식이어야 하며, 텍스트 일부만 강조하거나 밑줄만 추가하는 방식은 불충분하다. 구체적으로 다음을 준수한다:
295
296 - **배경색 대비**: 하이라이트 배경색은 비선택 항목의 배경과 충분한 명도 차이를 가져야 한다 (MUST). 미세한 차이로는 어떤 항목이 선택 상태인지 인지하기 어렵다. 예: 다크 테마에서 비선택 `#1e1e1e` → 선택 `#2d3748`, 라이트 테마에서 비선택 `#fff` → 선택 `#e8f0fe`.
297 - **단일 포커스**: 동시에 두 개 이상의 항목이 하이라이트되어서는 안 된다 (MUST NOT). 키보드 포커스와 마우스 호버가 서로 다른 항목을 가리킬 경우, 마우스 호버가 우선하고 키보드 포커스 하이라이트는 해제한다 (SHOULD). 마우스가 팝업 영역을 벗어나면 키보드 포커스 위치로 하이라이트가 복원된다.
298
299 **키보드 내비게이션**: 팝업이 활성화된 상태에서 다음 키보드 조작을 지원해야 한다 (MUST):
300
301 | 키 | 동작 |
302 | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
303 | `↑` / `↓` | 목록 항목 간 포커스 이동. 하이라이트가 즉시 이동하여 현재 선택 대상이 시각적으로 반영되어야 한다. 목록 끝에서 `↓`는 첫 항목으로, 첫 항목에서 `↑`는 마지막 항목으로 순환한다 (SHOULD). |
304 | `Enter` 또는 `Tab` | 포커스된 항목 선택 및 입력창에 반영 |
305 | `Esc` | 팝업 닫기, 입력 텍스트는 유지 |
306 | 계속 타이핑 | 실시간 필터링 갱신. 포커스는 필터링된 목록의 첫 항목으로 리셋 |
307
308 **키보드 포커스 자동 스크롤**: 팝업 내 목록이 스크롤 가능한 길이일 때, `↑`/`↓` 키로 포커스가 현재 보이는 영역 밖으로 이동하면, 포커스된 항목이 뷰포트 내에 보이도록 팝업 내부를 자동 스크롤해야 한다 (MUST).
309
310 **마우스/터치 인터랙션**: 팝업 항목을 마우스 클릭 또는 터치 탭으로 선택할 수 있어야 한다 (MUST). 마우스 호버 시 해당 항목에 포커스 하이라이트와 동일한 배경색을 적용하여 클릭 시 어떤 항목이 선택될지를 사전에 명확히 보여주어야 한다 (MUST). 마우스 커서가 항목 위를 이동할 때 하이라이트는 커서를 따라 즉시(지연 없이) 전환되어야 한다.
311
312 **선택 후 동작**: 명령어가 선택되면, 입력창의 텍스트를 `/{선택된_name} `(후행 공백 포함)으로 교체하고 팝업을 닫아야 한다 (MUST). 명령어에 `parameters`가 정의되어 있는 경우, 후행 공백 뒤에 플레이스홀더 힌트(예: `<filename>`)를 연하게 표시하여 필수 파라미터를 안내할 수 있다 (MAY).
313
314 **파라미터 인라인 힌트**: `parameters` 배열에 `required: true`인 항목이 있으면, 선택 후 입력창에 해당 파라미터의 힌트를 회색 플레이스홀더로 표시하여 사용자가 추가 입력이 필요함을 인지하도록 해야 한다 (SHOULD). 예: `/compact` 선택 시 파라미터가 없으므로 즉시 실행 가능하지만, `/review <branch>` 같은 명령어는 분기명 입력을 유도한다.
315
316 ##### 8.5.4. 명령어 실행 채팅 버블 렌더링 (Command Bubble)
317
318 사용자가 슬래시 명령어를 실행하면, 대화 이력에 표시되는 채팅 버블은 일반 텍스트 메시지와 명확히 구별되는 전용 렌더링을 적용해야 한다 (MUST). 이를 통해 사용자는 대화 이력을 스크롤할 때 어떤 메시지가 자연어 입력이고 어떤 메시지가 시스템 명령어 실행인지를 즉각적으로 식별할 수 있어야 한다.
319
320 **슬래시 접두사 제거**: 채팅 버블에 명령어를 표시할 때 선행 `/` 문자는 제거해야 한다 (MUST). `/` 는 입력 트리거이자 자동 완성 활성화 기호일 뿐, 실행 이력에서의 정보 가치가 없다. 예: 사용자가 `/compact focus on auth module`을 입력했다면, 버블에는 `compact focus on auth module`로 표시한다.
321
322 **중첩 컨테이너 금지 (No Nested Containers)**: 명령어 버블은 **버블 자체가 뱃지(badge) 역할**을 겸해야 하며, 버블 내부에 별도의 뱃지, 칩(chip), 또는 박스 요소를 중첩해서는 안 된다 (MUST NOT). 상자 안에 상자가 들어가는 구조는 시각적 노이즈를 유발하고 일반 메시지 버블과의 차별화 효과를 오히려 저해한다.
323
324 **명령어명 강조**: 버블 내에서 명령어명(첫 번째 토큰)은 중첩 컨테이너 없이 **인라인 텍스트 스타일**로 구분해야 한다 (MUST). 구체적으로 다음을 따른다:
325
326 - 명령어명은 **볼드(font-weight: bold)** 와 일반 텍스트 대비 약간 다른 색상(예: 강조 색상 또는 밝은 화이트)을 적용하여 시각적으로 구분한다. 별도의 배경색 박스나 보더로 감싸지 않는다.
327 - 파라미터가 존재하면, 명령어명 뒤에 일반 두께(regular weight)의 텍스트로 이어서 표시한다. 예: **compact** focus on auth module — 여기서 "compact"만 볼드이다.
328 - 파라미터가 없는 명령어(예: `compact`, `clear`, `cost`)는 볼드 명령어명만 단독으로 표시한다.
329
330 **버블 스타일 차별화**: 명령어 버블은 그 자체로 일반 메시지와 즉시 구분되어야 한다 (MUST). 다음 조합을 적용한다:
331
332 - **인라인 크기**: 명령어 버블은 내용 길이에 맞춰 최소 폭으로 렌더링해야 한다 (SHOULD). 일반 메시지처럼 넓은 영역을 차지하지 않고, 내용에 꼭 맞는 컴팩트한 인라인 요소로 표시하여 "실행 태그" 느낌을 준다.
333 - **배경색 차별화**: 일반 메시지 버블과 명확히 다른 배경색(예: 다크 테마에서 일반 버블보다 어두운 톤, 또는 테두리 강조)을 적용한다.
334 - **모노스페이스 폰트**: 버블 전체에 모노스페이스 폰트를 적용하여 "코드/명령어 실행"이라는 맥락을 시각적으로 전달해야 한다 (SHOULD).
335 - **아이콘 접두**: 명령어명 좌측에 소형 아이콘(예: `⚡`, `▶`, 터미널 커서 아이콘 등)을 표시할 수 있다 (MAY). 아이콘은 명령어의 `category`에 따라 차별화할 수 있다.
336
337 **실행 결과 연결**: 명령어 버블 직후에 에이전트의 응답(명령어 실행 결과)이 이어지는 경우, 명령어 버블과 결과 영역 사이의 시각적 연결성을 표현하여 인과 관계를 명확히 해야 한다 (SHOULD). 예: 간격 축소, 연결선, 또는 동일한 카드/컨테이너 내 그룹화 등.
338
339 **프로토콜 연계**: 클라이언트는 `control.prompt.request` 발송 시 `input_type: "slash_command"`(RAWP-DPS §6.1.1)로 설정된 프레임을 대화 이력에 렌더링할 때 본 섹션의 Command Bubble 스타일을 적용해야 한다 (MUST). `input_type: "text"` 또는 기타 값인 경우에는 일반 메시지 버블 스타일을 유지한다.
340
341 ##### 8.5.5. 명령어 목록 갱신
342
343 `agent.commands.publish` 이벤트가 세션 중 재수신되면(예: 플러그인 로드, 도구 구성 변경 등), 클라이언트는 로컬 캐시를 즉시 갱신해야 한다 (MUST). 팝업이 열려 있는 상태에서 갱신이 발생하면, 현재 필터 조건을 유지한 채 목록을 교체해야 한다 (SHOULD). 갱신 과정에서 사용자의 포커스 위치나 입력 상태가 초기화되어서는 안 된다 (MUST NOT).
344
345 ---
346
347 ### 9. Edge Node API (사용자 접점 통신 규약)
348
349 본 장은 사용자가 원격지에서 UI(웹 대시보드, 모바일 앱, 챗봇 게이트웨이 등)를 통해 마스터 서버(Master Server)에 접속하여 로컬 클라이언트(Local Client)를 제어하기 위한 Northbound API를 정의한다.
350
351 **아키텍처 정의 (3-Tier):**
352
353 ```
354 [Edge Node (User UI)] ↔ (Edge API) ↔ [Master Server] ↔ (RAWP/Southbound) ↔ [Local Client]
355 ```
356
357 #### 9.1. Edge Node 인증 및 권한 (Authentication)
358
359 엣지 노드는 마스터 서버의 로컬 클라이언트 인증 방식(Pairing Token)과 분리된 사용자 단위의 인증 체계(예: OIDC 기반 JWT)를 사용해야 한다. 모든 Edge API 요청은 `Authorization: Bearer {user_access_token}` 헤더를 포함해야 한다 (MUST).
360
361 #### 9.2. 로컬 머신(Node) 풀 관리 및 상태 모니터링
362
363 사용자 계정에 바인딩된 로컬 클라이언트 목록과 현재 상태를 조회한다.
364
365 ##### 9.2.1. 연결된 노드 목록 조회
366
367 **Endpoint:** `GET /v1/edge/nodes`
368
369 **Response (200 OK):**
370
371 ```json
372 {
373 "nodes": [
374 {
375 "node_id": "String (필수, 클라이언트 식별자)",
376 "device_name": "String (필수, 등록 시 제공된 이름)",
377 "status": "String (필수, 'online' | 'busy' | 'offline')",
378 "last_seen": "String (필수, ISO 8601, 마스터 서버와 마지막 통신 시각)",
379 "capabilities": ["String (선택, 로컬 머신이 지원하는 기능 목록)"],
380 "active_sessions_count": "Number (필수)"
381 }
382 ]
383 }
384 ```
385
386 ##### 9.2.2. 로컬 머신별 사용량 및 지표 (Metrics)
387
388 해당 노드에서 발생한 세션들의 누적/실시간 사용량을 조회한다.
389
390 **Endpoint:** `GET /v1/edge/nodes/{node_id}/metrics`
391
392 **Query Parameters:** `?period=today` (선택, `'today'`, `'week'`, `'month'`)
393
394 **Response (200 OK):**
395
396 ```json
397 {
398 "node_id": "String (필수)",
399 "period": "String (필수)",
400 "aggregated_usage": {
401 "total_input_tokens": "Number",
402 "total_output_tokens": "Number",
403 "estimated_cost": "Number (USD 기준)",
404 "total_tool_invocations": "Number"
405 },
406 "health": {
407 "uptime_seconds": "Number",
408 "last_error": "String (선택, 최근 발생한 치명적 에러)"
409 }
410 }
411 ```
412
413 #### 9.3. 엣지 세션(Edge Session) 라이프사이클 관리
414
415 엣지 노드는 마스터 서버를 통해 특정 로컬 머신에 새로운 세션을 생성하거나 기존 세션을 조회할 수 있다.
416
417 ##### 9.3.1. 원격 세션 생성 (Edge → Master)
418
419 사용자가 특정 로컬 머신을 선택하여 대화를 시작할 때 호출한다.
420
421 **Endpoint:** `POST /v1/edge/nodes/{node_id}/sessions`
422
423 **Request:**
424
425 ```json
426 {
427 "agent_name": "String (필수, 실행할 에이전트)",
428 "workspace_path": "String (선택, 작업 디렉토리)"
429 }
430 ```
431
432 **Response (201 Created):**
433
434 ```json
435 {
436 "session_id": "String (필수, UUID v4)",
437 "edge_ws_ticket": "String (필수, 엣지용 WSS 연결 티켓. 1회용)",
438 "status": "String (필수, 'INIT' | 'RUNNING')"
439 }
440 ```
441
442 **동작 원리:** 이 요청을 받은 마스터 서버는 즉시 대상 로컬 머신의 `POST /v1/session/init` (본 스펙 §5.1)을 호출하여 로컬 세션을 확보한 뒤 응답해야 한다.
443
444 ##### 9.3.2. 활성 세션 목록 조회
445
446 **Endpoint:** `GET /v1/edge/sessions`
447
448 **Query Parameters:** `?status=RUNNING` (선택)
449
450 #### 9.4. 엣지 데이터 평면 (Edge WebSocket 통신)
451
452 엣지 노드는 발급받은 티켓을 사용하여 마스터 서버와 WSS를 연결하며, 마스터 서버는 이 연결을 대상 로컬 머신과 투명하게 중계(Transparent Proxy)한다.
453
454 **Connection URI:** `wss://{master_host}/v1/edge/ws/stream?ticket={edge_ws_ticket}&session_id={session_id}`
455
456 ##### 9.4.1. 프로토콜 투명성 (Protocol Transparency)
457
458 엣지 노드와 마스터 서버 간의 WSS 통신은 RAWP-DPS 1.0 규격을 완전히 동일하게 사용한다 (MUST).
459
460 엣지 노드는 본 스펙 §6의 `control.*` 이벤트(사용자 프롬프트, 인터랙션 응답 등)를 마스터 서버로 발송하며, 마스터는 이를 로컬 머신으로 포워딩한다.
461
462 엣지 노드는 로컬 머신이 발송한 `agent.*`, `tool.*`, `session.*` 이벤트를 마스터 서버를 통해 수신하여 사용자 UI에 렌더링한다.
463
464 ##### 9.4.2. 마스터 서버의 중계 및 개입 규칙
465
466 마스터 서버는 단순한 파이프 역할을 넘어 다음의 제어 책임을 가진다:
467
468 **라우팅**: 엣지 노드의 발신 프레임을 올바른 로컬 머신의 소켓으로 전달.
469
470 **사용량 집계**: 중계되는 `session.usage` 이벤트를 인터셉트하여 데이터베이스에 누적 저장 (§9.2.2 지표 제공용).
471
472 **보안 필터링**: 엣지 노드가 허가되지 않은 `control.prompt.request` 내장 도구(e.g., 로컬 시스템 파괴성 명령어)를 전송하려 할 경우, 마스터 서버 단에서 프레임을 드롭하고 엣지 노드에 `session.error`를 반환해야 한다 (MUST).
473
474 ##### 9.4.3. 다중 접점 동기화 (Multi-device Synchronization)
475
476 동일한 `session_id`에 대해 여러 엣지 노드(예: 모바일 앱과 데스크톱 브라우저 동시 접속)가 연결을 요청할 경우, 마스터 서버는 수신되는 RAWP-DPS 1.0 프레임을 모든 연결된 엣지 소켓으로 브로드캐스트(Fan-out)하여 UI 상태를 동기화해야 한다 (SHOULD). 단, `control.*` 이벤트 발송 시 동시성 충돌을 막기 위해 `message_id` 기반 멱등성 처리를 수행해야 한다.
477
478 ---
수정됨

부록: 관련 규격 문서

추가된 줄 1 삭제된 줄 0
1 1 ## 부록: 관련 규격 문서
2 2
3 3 | 문서 | 설명 |
4 4 | ----------------------- | ----------------------------------------------------- |
5 5 | **RAWP-DPS 1.0** | 데이터 평면 스트리밍 규격 (현행). 본 문서 §6이 참조. |
6 | **RAWP-CRS 1.0** | 클라이언트 렌더링 규격 (현행). 본 문서 §8이 참조. |
6 7 | **RAWP-DPS-0.1-Legacy** | 데이터 평면 스트리밍 규격 (단종 예고). 레거시 호환용. |
추가됨

8. 클라이언트 UI/UX 구현 지침 (Client UI Implementer Notes)

추가된 줄 17 삭제된 줄 0
1 ## 8. 클라이언트 UI/UX 구현 지침 (Client UI Implementer Notes)
2
3 > **본 절은 별도 규격으로 분리되었다.**
4 >
5 > RAWP 프로토콜의 제어 평면 이벤트와 RAWP-DPS 1.0의 데이터 평면 이벤트를 사용자에게 시각적으로 전달하는 클라이언트 렌더링 규격은 **RAWP-CRS 1.0** 규격을 따른다.
6 >
7 > RAWP-CRS 1.0은 다음을 정의한다:
8 >
9 > - **메시지 아키텍처**: User(우측 버블), Agent(좌측 버블리스), System(중앙 정렬) 세 행위자의 시각적 정체성 및 레이아웃 규칙 (RAWP-CRS §2)
10 > - **유저 메시지**: 채팅 버블 스타일, 슬래시 명령어 자동 완성 UI, 명령어 실행 버블 렌더링, **파일 참조 입력** (RAWP-CRS §3)
11 > - **에이전트 메시지**: 버블리스 디자인 원칙, 마크다운 렌더링 컴포넌트 명세, 점진적 텍스트 스트리밍, 사고 과정 시각화, 도구 호출 결과, Code Diff 뷰어, 에이전트 상태 표시, 태스크 목록 (RAWP-CRS §4)
12 > - **시스템 메시지**: 생략 금지 원칙, 드롭다운 패턴, 중지 피드백, 에러/경고 심각도별 시각 차등 (RAWP-CRS §5)
13 > - **비스트리밍 환경 적응**: Discord, Slack 등 완성형 플랫폼의 Typing Indicator 활용, 버퍼링, 사고 과정 우회 처리 (RAWP-CRS §6)
14 >
15 > 구현자는 RAWP-CRS 1.0 규격 문서를 참조하여 클라이언트 UI를 구현해야 한다 (MUST).
16
17 ---
추가됨

9. Edge Node API (사용자 접점 통신 규약)

추가된 줄 242 삭제된 줄 0
1 ## 9. Edge Node API (사용자 접점 통신 규약)
2
3 본 장은 사용자가 원격지에서 UI(웹 대시보드, 모바일 앱, 챗봇 게이트웨이 등)를 통해 마스터 서버(Master Server)에 접속하여 로컬 클라이언트(Local Client)를 제어하기 위한 Northbound API를 정의한다.
4
5 **아키텍처 정의 (3-Tier):**
6
7 ```
8 [Edge Node (User UI)] ↔ (Edge API) ↔ [Master Server] ↔ (RAWP/Southbound) ↔ [Local Client]
9 ```
10
11 ### 9.1. Edge Node 인증 및 권한 (Authentication)
12
13 엣지 노드는 마스터 서버의 로컬 클라이언트 인증 방식(Pairing Token)과 분리된 사용자 단위의 인증 체계(예: OIDC 기반 JWT)를 사용해야 한다. 모든 Edge API 요청은 `Authorization: Bearer {user_access_token}` 헤더를 포함해야 한다 (MUST).
14
15 ### 9.2. 로컬 머신(Node) 풀 관리 및 상태 모니터링
16
17 사용자 계정에 바인딩된 로컬 클라이언트 목록과 현재 상태를 조회한다.
18
19 ### 9.2.1. 연결된 노드 목록 조회
20
21 **Endpoint:** `GET /v1/edge/nodes`
22
23 **Response (200 OK):**
24
25 ```json
26 {
27 "nodes": [
28 {
29 "node_id": "String (필수, 클라이언트 식별자)",
30 "device_name": "String (필수, 등록 시 제공된 이름)",
31 "status": "String (필수, 'online' | 'busy' | 'offline')",
32 "last_seen": "String (필수, ISO 8601, 마스터 서버와 마지막 통신 시각)",
33 "capabilities": [
34 "String (선택, 로컬 머신이 지원하는 기능 목록. §4.1.6에서 동기화된 최신 값 반영)"
35 ],
36 "active_sessions_count": "Number (필수)",
37 "config_versions": {
38 "limits": "Number (선택, 현재 보유 중인 Resource Limits config_version)",
39 "capabilities": "Number (선택, 현재 보유 중인 Capabilities config_version)"
40 }
41 }
42 ]
43 }
44 ```
45
46 > **변경 사항**: `capabilities` 필드는 §4.1.6을 통해 동기화된 최신 값을 반영한다. `config_versions` 객체가 추가되어 각 Config Scope의 현재 버전을 엣지 노드에서 조회할 수 있다.
47
48 ### 9.2.2. 로컬 머신별 사용량 및 지표 (Metrics)
49
50 해당 노드에서 발생한 세션들의 누적/실시간 사용량을 조회한다.
51
52 **Endpoint:** `GET /v1/edge/nodes/{node_id}/metrics`
53
54 **Query Parameters:** `?period=today` (선택, `'today'`, `'week'`, `'month'`)
55
56 **Response (200 OK):**
57
58 ```json
59 {
60 "node_id": "String (필수)",
61 "period": "String (필수)",
62 "aggregated_usage": {
63 "total_input_tokens": "Number",
64 "total_output_tokens": "Number",
65 "estimated_cost": "Number (USD 기준)",
66 "total_tool_invocations": "Number"
67 },
68 "health": {
69 "uptime_seconds": "Number",
70 "last_error": "String (선택, 최근 발생한 치명적 에러)"
71 }
72 }
73 ```
74
75 ### 9.2.3. 노드 설정 조회 (Node Configuration Lookup)
76
77 엣지 노드가 특정 로컬 머신의 현재 설정 상태를 조회한다.
78
79 **Endpoint:** `GET /v1/edge/nodes/{node_id}/config`
80
81 **Response (200 OK):**
82
83 ```json
84 {
85 "node_id": "String (필수)",
86 "limits": {
87 "config_version": "Number (필수)",
88 "effective_at": "String (필수, ISO 8601)",
89 "limits": {
90 "rate_limit": "Number",
91 "max_frame_size": "Number",
92 "reattach_window": "Number",
93 "max_buffer_size": "Number",
94 "buffer_overflow_policy": "String"
95 }
96 },
97 "capabilities": {
98 "config_version": "Number (필수)",
99 "effective_at": "String (필수, ISO 8601)",
100 "capabilities": ["String"]
101 }
102 }
103 ```
104
105 이 엔드포인트는 Pull 방식으로 최신 설정을 확인하기 위한 것이다. 실시간 Push 알림은 §9.4.4를 참조한다.
106
107 ### 9.3. 엣지 세션(Edge Session) 라이프사이클 관리
108
109 엣지 노드는 마스터 서버를 통해 특정 로컬 머신에 새로운 세션을 생성하거나 기존 세션을 조회할 수 있다.
110
111 ### 9.3.1. 원격 세션 생성 (Edge → Master)
112
113 사용자가 특정 로컬 머신을 선택하여 대화를 시작할 때 호출한다.
114
115 **Endpoint:** `POST /v1/edge/nodes/{node_id}/sessions`
116
117 **Request:**
118
119 ```json
120 {
121 "agent_name": "String (필수, 실행할 에이전트)",
122 "workspace_path": "String (선택, 작업 디렉토리)"
123 }
124 ```
125
126 **Response (201 Created):**
127
128 ```json
129 {
130 "session_id": "String (필수, UUID v4)",
131 "edge_ws_ticket": "String (필수, 엣지용 WSS 연결 티켓. 1회용)",
132 "status": "String (필수, 'INIT' | 'RUNNING')",
133 "pinned_config_versions": {
134 "limits": "Number (필수, 이 세션에 바인딩된 Resource Limits config_version)",
135 "capabilities": "Number (필수, 이 세션에 바인딩된 Capabilities config_version)"
136 }
137 }
138 ```
139
140 > **변경 사항**: `pinned_config_versions` 객체가 추가되어, 이 세션에 바인딩된 설정 버전을 엣지 노드가 확인할 수 있다. §4.1.8의 Session-Pinned Config 참조.
141
142 **동작 원리:** 이 요청을 받은 마스터 서버는 즉시 대상 로컬 머신의 `POST /v1/session/init` (본 스펙 §5.1)을 호출하여 로컬 세션을 확보한 뒤 응답해야 한다.
143
144 ### 9.3.2. 활성 세션 목록 조회
145
146 사용자 계정에 귀속된 모든 세션의 현재 상태를 조회한다.
147
148 **Endpoint:** `GET /v1/edge/sessions`
149
150 **Query Parameters:**
151
152 | 파라미터 | 타입 | 필수 | 설명 |
153 | --------- | ------ | ---- | ----------------------------------------------------------------------------------------------- |
154 | `status` | String | 선택 | 세션 상태 필터. `'INIT'`, `'RUNNING'`, `'DETACHED'` 중 하나. 미지정 시 모든 상태의 세션을 반환. |
155 | `node_id` | String | 선택 | 특정 노드에 귀속된 세션만 필터링. |
156
157 **Response (200 OK):**
158
159 ```json
160 {
161 "sessions": [
162 {
163 "session_id": "String (필수, UUID v4)",
164 "node_id": "String (필수, 세션이 실행 중인 로컬 클라이언트 식별자)",
165 "agent_name": "String (필수, 실행 중인 에이전트 이름)",
166 "status": "String (필수, 'INIT' | 'RUNNING' | 'DETACHED')",
167 "created_at": "String (필수, ISO 8601, 세션 생성 시각)",
168 "last_activity_at": "String (필수, ISO 8601, 마지막 이벤트 수신 시각)",
169 "pinned_config_versions": {
170 "limits": "Number (필수, 이 세션에 바인딩된 Resource Limits config_version)",
171 "capabilities": "Number (필수, 이 세션에 바인딩된 Capabilities config_version)"
172 }
173 }
174 ]
175 }
176 ```
177
178 `pinned_config_versions`를 통해 엣지 노드는 각 세션이 어떤 시점의 설정으로 동작 중인지를 목록 수준에서 확인할 수 있다. 노드의 현재 `config_versions`(§9.2.1)와 비교하면, 세션이 최신 설정인지 이전 설정인지를 즉시 판별할 수 있다. `TERMINATED` 상태의 세션은 이미 종료되었으므로 목록에 포함하지 않는다 (MUST NOT). 종료된 세션의 이력 조회가 필요한 경우 별도의 세션 이력 API를 통해 제공한다.
179
180 ### 9.4. 엣지 데이터 평면 (Edge WebSocket 통신)
181
182 엣지 노드는 발급받은 티켓을 사용하여 마스터 서버와 WSS를 연결하며, 마스터 서버는 이 연결을 대상 로컬 머신과 투명하게 중계(Transparent Proxy)한다.
183
184 **Connection URI:** `wss://{master_host}/v1/edge/ws/stream?ticket={edge_ws_ticket}&session_id={session_id}`
185
186 ### 9.4.1. 프로토콜 투명성 (Protocol Transparency)
187
188 엣지 노드와 마스터 서버 간의 WSS 통신은 RAWP-DPS 1.0 규격을 완전히 동일하게 사용한다 (MUST).
189
190 엣지 노드는 본 스펙 §6의 `control.*` 이벤트(사용자 프롬프트, 인터랙션 응답 등)를 마스터 서버로 발송하며, 마스터는 이를 로컬 머신으로 포워딩한다.
191
192 엣지 노드는 로컬 머신이 발송한 `agent.*`, `tool.*`, `session.*` 이벤트를 마스터 서버를 통해 수신하여 사용자 UI에 렌더링한다.
193
194 ### 9.4.2. 마스터 서버의 중계 및 개입 규칙
195
196 마스터 서버는 단순한 파이프 역할을 넘어 다음의 제어 책임을 가진다:
197
198 **라우팅**: 엣지 노드의 발신 프레임을 올바른 로컬 머신의 소켓으로 전달.
199
200 **사용량 집계**: 중계되는 `session.usage` 이벤트를 인터셉트하여 데이터베이스에 누적 저장 (§9.2.2 지표 제공용).
201
202 **파일 검색 중계**: `control.file.search`, `control.file.search.cancel`, `session.file.candidates` 이벤트는 중계 대상에 포함된다. `session.file.candidates`는 `session.usage`와 달리 인터셉트 없이 단순 포워딩 대상이다.
203
204 **보안 필터링**: 엣지 노드가 허가되지 않은 `control.prompt.request` 내장 도구(e.g., 로컬 시스템 파괴성 명령어)를 전송하려 할 경우, 마스터 서버 단에서 프레임을 드롭하고 엣지 노드에 `session.error`를 반환해야 한다 (MUST).
205
206 ### 9.4.3. 다중 접점 동기화 (Multi-device Synchronization)
207
208 동일한 `session_id`에 대해 여러 엣지 노드(예: 모바일 앱과 데스크톱 브라우저 동시 접속)가 연결을 요청할 경우, 마스터 서버는 수신되는 RAWP-DPS 1.0 프레임을 모든 연결된 엣지 소켓으로 브로드캐스트(Fan-out)하여 UI 상태를 동기화해야 한다 (SHOULD). 단, `control.*` 이벤트 발송 시 동시성 충돌을 막기 위해 `message_id` 기반 멱등성 처리를 수행해야 한다.
209
210 ### 9.4.4. 노드 설정 변경 Push 알림 (Node Config Change Notification)
211
212 로컬 클라이언트가 §4.1.5 또는 §4.1.6을 통해 설정을 갱신하면, 마스터 서버는 해당 노드에 연결된 모든 활성 엣지 WSS 소켓에 설정 변경 알림 프레임을 전송해야 한다 (MUST). 이 알림을 통해 엣지 노드는 UI에 반영할 설정 변경(예: capabilities 변경으로 인한 기능 목록 갱신)을 실시간으로 인지할 수 있다.
213
214 **알림 프레임 구조**:
215
216 이 알림은 RAWP-DPS 1.0의 Envelope 구조(RAWP-DPS §2)를 따르되, 세션 단위가 아닌 노드 단위의 이벤트이므로 `session_id`와 `turn_id`를 포함하지 않는다. 마스터 서버가 자체적으로 생성하여 발송하는 프레임이다.
217
218 ```json
219 {
220 "v": "rawp-dps-1.0",
221 "type": "node.config.changed",
222 "message_id": "String (필수, UUID v4)",
223 "timestamp": "String (필수, ISO 8601)",
224 "payload": {
225 "node_id": "String (필수, 설정이 변경된 노드의 식별자)",
226 "changed_scope": "String (필수, 'limits' | 'capabilities')",
227 "config_version": "Number (필수, 갱신된 스코프의 새 config_version)",
228 "reason": "String (선택, 클라이언트가 갱신 요청 시 전달한 reason 값 전파)"
229 }
230 }
231 ```
232
233 **엣지 노드의 처리 규칙**:
234
235 - `node.config.changed` 수신 시, 엣지 노드는 `changed_scope`에 따라 관련된 로컬 캐시를 무효화해야 한다 (MUST).
236 - 상세 설정 값이 필요하면 §9.2.3의 `GET /v1/edge/nodes/{node_id}/config`을 Pull 호출하여 최신 값을 획득한다. 알림 프레임 자체에는 변경된 설정의 전체 값을 포함하지 않는다. 이는 알림 프레임의 크기를 최소화하고, 엣지 노드가 필요한 시점에만 상세 데이터를 가져오도록 하기 위함이다.
237 - `changed_scope: "capabilities"` 수신 시, 엣지 UI가 기능 목록이나 에이전트 지원 기능 표시를 동적으로 갱신하는 경우, Pull 호출 후 UI를 갱신해야 한다 (SHOULD).
238 - 활성 세션에는 §4.1.8에 따라 이전 설정이 유지되므로, 현재 진행 중인 세션의 동작을 변경할 필요는 없다.
239
240 **전송 범위**: 마스터 서버는 해당 노드(`node_id`)에 연결된 모든 엣지 WSS 소켓에 알림을 브로드캐스트한다. 특정 세션의 WSS가 아닌, 해당 노드와 관련된 모든 엣지 연결이 대상이다. 세션 WSS가 아직 연결되지 않았더라도, 해당 노드에 대해 다른 세션의 WSS가 활성 상태이면 해당 소켓을 통해 알림이 전달된다.
241
242 ---