iop/agent-task/cli_usage_status_command/plan_0.log

414 lines
23 KiB
Text

<!-- task=cli_usage_status_command plan=0 tag=API -->
# CLI Usage Status Command Plan
## 이 파일을 읽는 구현 에이전트에게
아래 체크리스트를 순서대로 완료하고, 각 항목의 중간 검증과 최종 검증을 실제로 실행하세요. 구현이 끝나면 `agent-task/cli_usage_status_command/CODE_REVIEW.md`의 모든 섹션을 실제 구현 내용과 명령 출력으로 채우세요. `CODE_REVIEW.md`의 `이 파일을 읽는 리뷰 에이전트에게` 섹션에 있는 아카이브 지시(`*.log`로 이름 변경, `complete.log` 작성)는 구현 에이전트가 수행하면 안 되며, 리뷰 스킬 전용입니다.
## 배경
마지막 커밋은 Codex 사용량을 PTY로 조회하는 `status` 패키지를 추가했지만, edge console 또는 node runtime 경로와 연결되지 않았습니다. 사용자는 먼저 `bin/edge.sh`로 실행하는 콘솔 테스트에서 프롬프트 명령 `/status`를 추가하고, 현재 선택된 `adapter/agent/session`에 대한 사용량을 표시하는 방향을 제안했습니다. 기존 구조상 edge console의 slash command는 edge-local 명령이고, node가 처리할 명령은 protobuf request/response로 명시해야 합니다. 이번 작업은 `RunRequest` 실행 경로와 분리된 node command 인터페이스를 만들고, 첫 command로 `usage_status`를 연결합니다.
## 의존 관계 및 구현 순서
1. `API-1`에서 protobuf 계약과 transport request/response 경계를 먼저 만든다.
2. `API-2`에서 node runtime command handler를 추가해 adapter에 위임한다.
3. `API-3`에서 `cli` adapter가 현재 선택된 agent/profile 기준으로 usage status를 제공한다.
4. `API-4`에서 edge console `/status` 명령과 출력 포맷을 연결한다.
### [API-1] node command protobuf 계약과 transport parser 추가
#### 문제
현재 wire 계약은 실행용 `RunRequest`와 취소용 `CancelRequest`만 가지고 있습니다. `proto/iop/runtime.proto:60-67`의 `CancelRequest` 이후에 node command를 표현하는 request/response가 없고, node 쪽 transport handler도 `apps/node/internal/transport/session.go:14-18`에서 `OnRunRequest`, `OnCancel`만 받습니다. edge 쪽 parser도 `apps/edge/internal/transport/server.go:24-35`에서 `RunEvent`, `RegisterRequest`만 파싱하므로, edge console이 node에 `/status` 명령을 request/response 방식으로 보낼 수 없습니다.
Before (`proto/iop/runtime.proto:60`):
```proto
// CancelRequest asks the node to cancel a running execution.
message CancelRequest {
string run_id = 1;
string adapter = 2;
string model = 3;
string session_id = 4;
CancelAction action = 5;
}
```
Before (`apps/node/internal/transport/session.go:14`):
```go
// Handler processes IOP messages received from edge.
type Handler interface {
OnRunRequest(ctx context.Context, sess *Session, req *iop.RunRequest) error
OnCancel(ctx context.Context, sess *Session, req *iop.CancelRequest) error
}
```
#### 해결 방법
`proto/iop/runtime.proto`에 generic node command envelope를 추가하되, 첫 command payload는 usage status로 제한합니다. command type은 enum으로 두어 `/status` 이후의 node command를 같은 request/response 경계에 얹을 수 있게 하고, response에는 `error` 문자열을 둬 node command 실패를 console에서 표시할 수 있게 합니다.
After:
```proto
enum NodeCommandType {
NODE_COMMAND_TYPE_UNSPECIFIED = 0;
NODE_COMMAND_TYPE_USAGE_STATUS = 1;
}
message NodeCommandRequest {
string request_id = 1;
NodeCommandType type = 2;
string adapter = 3;
string model = 4;
string session_id = 5;
int32 timeout_sec = 6;
map<string, string> metadata = 7;
}
message NodeCommandResponse {
string request_id = 1;
NodeCommandType type = 2;
string adapter = 3;
string model = 4;
string session_id = 5;
AgentUsageStatus usage_status = 6;
string error = 7;
}
message AgentUsageStatus {
string raw_output = 1;
string daily_limit = 2;
string daily_reset_time = 3;
string weekly_limit = 4;
string weekly_reset_time = 5;
map<string, string> metadata = 6;
}
```
Transport에는 `AddRequestListenerTyped[*iop.NodeCommandRequest, *iop.NodeCommandResponse]`를 추가합니다. node parser map에는 `NodeCommandRequest`를, edge parser map에는 `NodeCommandResponse`를 등록합니다. protobuf 변경 후 `make proto` 또는 Makefile의 `protoc` 명령으로 `proto/gen/iop/*.pb.go`를 갱신합니다.
#### 수정 파일 및 체크리스트
- [ ] `proto/iop/runtime.proto` - `NodeCommandType`, `NodeCommandRequest`, `NodeCommandResponse`, `AgentUsageStatus` 추가
- [ ] `proto/gen/iop/runtime.pb.go` - `make proto` 또는 동일 `protoc` 명령으로 생성 갱신
- [ ] `apps/node/internal/transport/session.go` - `Handler`에 command request handler 추가, request listener 등록
- [ ] `apps/node/internal/transport/parser.go` - `NodeCommandRequest` parser 등록
- [ ] `apps/node/internal/transport/parser_test.go` - `TestNodeParserMap_NodeCommandRequest` 추가
- [ ] `apps/edge/internal/transport/server.go` - `NodeCommandResponse` parser 등록
- [ ] `apps/edge/internal/transport/integration_test.go`와 `apps/node/internal/transport/integration_test.go` - mock parser/handler에 command request-response가 깨지지 않도록 필요한 stub 추가
- [ ] renamed/removed symbol call site 점검: 기존 symbol 삭제 없이 추가만 하므로 `rg -n "Handler interface|nodeParserMap|edgeParserMap|CancelRequest|RunRequest" apps proto`로 새 handler 구현 누락만 확인
#### 테스트 작성
테스트를 작성합니다.
- `apps/node/internal/transport/parser_test.go` - `TestNodeParserMap_NodeCommandRequest`: marshal한 `NodeCommandRequest{Type: USAGE_STATUS, Adapter: "cli", Model: "codex", SessionId: "default"}`가 parser를 통해 동일 필드로 복원되는지 검증
- `apps/edge/internal/transport/server_test.go` 또는 별도 parser 테스트 - `TestEdgeParserMap_NodeCommandResponse`: `NodeCommandResponse` parser 등록 여부와 `UsageStatus` 필드 복원 검증
#### 중간 검증
```bash
make proto
go test ./apps/node/internal/transport/... ./apps/edge/internal/transport/...
```
기대 결과: proto 생성이 성공하고 transport/parser 관련 테스트가 통과합니다. 실행 환경에 `make`가 없으면 Makefile의 `proto` 타깃과 같은 `protoc --go_out=. --go_opt=module=iop --proto_path=. proto/iop/runtime.proto proto/iop/node.proto proto/iop/control.proto proto/iop/job.proto`를 사용하고, 그 사실을 `CODE_REVIEW.md`에 기록합니다.
### [API-2] node runtime command handler를 adapter optional interface로 연결
#### 문제
node runtime에는 실행(`Adapter.Execute`)과 세션 종료(`SessionTerminator`)만 있고, 실행이 아닌 조회성 명령을 adapter에 위임할 계약이 없습니다. `apps/node/internal/runtime/types.go:98-114`에는 optional interface가 `SessionTerminator`뿐이라, 사용량 조회를 `RunRequest`의 prompt로 우회하면 실행 이력과 모델 응답 스트림에 섞이게 됩니다. `apps/node/internal/node/node.go:155-172`도 cancel 외의 command를 처리하지 않습니다.
Before (`apps/node/internal/runtime/types.go:98`):
```go
// SessionTerminator is an optional interface Adapters may implement to support
// explicit session lifecycle management separate from run cancellation.
type SessionTerminator interface {
TerminateSession(ctx context.Context, model, sessionID string) error
}
```
Before (`apps/node/internal/node/node.go:155`):
```go
// OnCancel cancels a running execution or terminates an adapter session.
func (n *Node) OnCancel(_ context.Context, _ *transport.Session, req *iop.CancelRequest) error {
n.logger.Info("cancel request", zap.String("run_id", req.GetRunId()), zap.String("action", req.GetAction().String()))
```
#### 해결 방법
`runtime`에 command domain type과 optional interface를 추가합니다. node는 proto command를 domain command로 변환하고, 선택 adapter가 `runtime.CommandHandler`를 구현하면 위임합니다. adapter가 command를 지원하지 않거나 command type이 unknown이면 `NodeCommandResponse.Error`에 명확한 메시지를 담아 반환합니다.
After:
```go
type CommandType string
const (
CommandTypeUsageStatus CommandType = "usage_status"
)
type AgentUsageStatus struct {
RawOutput string
DailyLimit string
DailyResetTime string
WeeklyLimit string
WeeklyResetTime string
Metadata map[string]string
}
type CommandRequest struct {
RequestID string
Type CommandType
Adapter string
Model string
SessionID string
TimeoutSec int
Metadata map[string]string
}
type CommandResponse struct {
RequestID string
Type CommandType
Adapter string
Model string
SessionID string
UsageStatus *AgentUsageStatus
}
type CommandHandler interface {
HandleCommand(ctx context.Context, req CommandRequest) (CommandResponse, error)
}
```
`node.Node`에는 `OnCommandRequest(ctx, sess, req) (*iop.NodeCommandResponse, error)`를 추가합니다. handler 내부에서는 adapter lookup, optional interface assertion, timeout 적용, domain/proto 변환을 담당합니다. 이 명령은 실행 이력이 아니므로 `store.InsertRun`을 호출하지 않습니다.
#### 수정 파일 및 체크리스트
- [ ] `apps/node/internal/runtime/types.go` - `CommandType`, `AgentUsageStatus`, `CommandRequest`, `CommandResponse`, `CommandHandler` 추가
- [ ] `apps/node/internal/node/node.go` - `OnCommandRequest` 추가, proto/domain 변환 helper 추가
- [ ] `apps/node/internal/node/node_test.go` - command 성공, adapter 미존재, command 미지원, adapter error 응답 테스트 추가
- [ ] `apps/node/internal/transport/session.go` - `Handler`의 새 메서드 시그니처가 `node.Node`와 테스트 mock에 모두 구현되었는지 확인
- [ ] renamed/removed symbol call site 점검: `rg -n "type Handler interface|OnCommandRequest|CommandHandler|SessionTerminator" apps/node`로 인터페이스 구현 누락 확인
#### 테스트 작성
테스트를 작성합니다.
- `apps/node/internal/node/node_test.go` - `commandAdapter` test double이 `runtime.CommandHandler`를 구현하고, `OnCommandRequest`가 `adapter/model/session_id`를 그대로 전달해 `NodeCommandResponse.UsageStatus`로 변환하는지 검증
- `apps/node/internal/node/node_test.go` - command 미지원 adapter는 non-nil transport error 대신 response `Error`에 `"does not support command"` 계열 메시지를 담는지 검증
- `apps/node/internal/node/node_test.go` - adapter error는 response `Error`에 담고 panic 또는 run store 변경이 없는지 검증
#### 중간 검증
```bash
go test ./apps/node/internal/runtime/... ./apps/node/internal/node/...
```
기대 결과: runtime compile과 node command handler 테스트가 통과합니다.
### [API-3] CLI adapter가 현재 선택 agent/profile의 usage status를 제공
#### 문제
현재 `apps/node/internal/adapters/cli/status`는 node의 adapter와 분리되어 있습니다. `apps/node/internal/adapters/cli/cli.go:154-167`의 `Execute` 경로에서만 profile이 사용되고, `status.CodexChecker`는 `apps/node/internal/adapters/cli/status/codex.go:22-24`에서 `"codex"` 명령을 하드코딩합니다. 또한 `ClaudeChecker`, `GeminiChecker`는 `apps/node/internal/adapters/cli/status/claude.go:13-18`, `apps/node/internal/adapters/cli/status/gemini.go:13-20`에서 placeholder 문자열을 정상 결과처럼 반환합니다.
Before (`apps/node/internal/adapters/cli/status/codex.go:22`):
```go
func (c *CodexChecker) Check(ctx context.Context) (*UsageStatus, error) {
cmd := exec.CommandContext(ctx, "codex", "--no-alt-screen")
cmd.Env = append(os.Environ(), "TERM=xterm-256color")
```
Before (`apps/node/internal/adapters/cli/cli.go:154`):
```go
func (c *CLI) Execute(ctx context.Context, spec runtime.ExecutionSpec, sink runtime.EventSink) error {
agent := cliAgentName(spec)
profile, ok := c.profiles[agent]
if !ok {
return fmt.Errorf("cli adapter: unknown agent %q", agent)
}
```
#### 해결 방법
`CLI`가 `runtime.CommandHandler`를 구현하고, `CommandTypeUsageStatus` 요청에서 `req.Model`을 현재 선택 agent/profile 이름으로 해석합니다. profile lookup 실패는 `"unknown agent"` error로 반환합니다. status package는 checker factory를 두어 `agent` 또는 `filepath.Base(profile.Command)` 기준으로 checker를 고릅니다.
Codex checker는 `profile.Command`를 사용해 TUI status를 조회하도록 바꿉니다. 이때 실행 prompt용 profile args(`exec`, `--json` 등)는 사용하지 않고, status 전용으로 `--no-alt-screen`을 붙입니다. 현재 config의 `codex` profile이 `codex exec ...`인 점을 감안해, usage status 조회는 command path만 재사용하는 별도 command로 취급합니다. Claude/Gemini checker는 구현 전까지 placeholder success를 반환하지 말고 `not implemented` error를 반환합니다.
After:
```go
func (c *CLI) HandleCommand(ctx context.Context, req runtime.CommandRequest) (runtime.CommandResponse, error) {
if req.Type != runtime.CommandTypeUsageStatus {
return runtime.CommandResponse{}, fmt.Errorf("cli adapter: unsupported command %q", req.Type)
}
profile, ok := c.profiles[req.Model]
if !ok {
return runtime.CommandResponse{}, fmt.Errorf("cli adapter: unknown agent %q", req.Model)
}
status, err := status.CheckUsage(ctx, req.Model, profile)
if err != nil {
return runtime.CommandResponse{}, err
}
return runtime.CommandResponse{UsageStatus: status.ToRuntime()}, nil
}
```
#### 수정 파일 및 체크리스트
- [ ] `apps/node/internal/adapters/cli/cli.go` - `HandleCommand` 구현 및 `runtime.CommandHandler` 만족 여부 compile로 검증
- [ ] `apps/node/internal/adapters/cli/status/status.go` - status request/checker factory 구조 정리, runtime 변환 helper 추가
- [ ] `apps/node/internal/adapters/cli/status/codex.go` - hardcoded `"codex"` 제거, configured command path 사용, status 전용 args 명확화
- [ ] `apps/node/internal/adapters/cli/status/claude.go` - placeholder success 제거, `not implemented` error 반환
- [ ] `apps/node/internal/adapters/cli/status/gemini.go` - placeholder success 제거, `not implemented` error 반환
- [ ] `apps/node/internal/adapters/cli/status/parser.go` - `context.Context` 미사용이면 제거하고, raw transcript parser table test가 통과하도록 유지
- [ ] `apps/node/internal/adapters/cli/status/codex_test.go` - 기본 skip 테스트를 parser/command factory 중심 자동 테스트로 대체하거나 새 parser 테스트로 이동
- [ ] `apps/node/internal/adapters/cli/lifecycle/cli_test.go` 또는 새 `status` 테스트 - fake checker/factory로 현재 agent의 profile command가 전달되는지 검증
- [ ] renamed/removed symbol call site 점검: `rg -n "NewCodexChecker|ClaudeChecker|GeminiChecker|ParseStatusOutput|UsageStatus" apps/node/internal/adapters/cli`로 signature 변경 반영 확인
#### 테스트 작성
테스트를 작성합니다.
- `apps/node/internal/adapters/cli/status/parser_test.go` - `TestParseStatusOutput_CodexLimits`: sample output의 `5h limit`, `Weekly limit`이 각각 limit/reset 필드로 파싱되는지 검증
- `apps/node/internal/adapters/cli/status/status_test.go` - `TestCheckerFactory_CodexUsesProfileCommand`: `config.CLIProfileConf{Command: "/tmp/codex"}`가 Codex checker 생성에 반영되는지 검증
- `apps/node/internal/adapters/cli/status/status_test.go` - `TestUnsupportedAgentReturnsError`: `claude` 또는 알 수 없는 agent가 placeholder status를 반환하지 않는지 검증
- `apps/node/internal/adapters/cli/lifecycle/cli_test.go` 또는 새 테스트 - `TestCLIHandleCommandUsageStatusUsesSelectedAgent`: profiles에 `codex`와 `cline-m1`을 함께 넣고, `Model: "codex"` 요청이 codex checker로만 전달되는지 검증
#### 중간 검증
```bash
go test ./apps/node/internal/adapters/cli/status/... ./apps/node/internal/adapters/cli/...
```
기대 결과: status parser/factory와 CLI command handler 테스트가 통과합니다. 기존 `./apps/node/internal/adapters/cli/...` 테스트가 환경 문제로 장시간 멈추면, 어떤 하위 패키지가 멈췄는지 `CODE_REVIEW.md`에 기록하고 대상 패키지별 검증 결과를 남깁니다.
### [API-4] edge console `/status` 명령과 bin 수동 검증 경로 추가
#### 문제
edge console은 현재 `/nodes`, `/session`, `/background`, `/terminate-session`, `/exit`만 처리합니다. `apps/edge/cmd/edge/console.go:62`의 도움말에도 `/status`가 없고, `apps/edge/cmd/edge/console.go:72-116`의 switch는 알 수 없는 slash command를 일반 prompt로 보내므로 `/status`를 입력하면 agent에게 일반 메시지로 전달됩니다. 사용자가 원하는 동작은 console의 현재 선택 agent에 대한 사용량 조회를 node command로 보내고, 결과를 사람이 읽을 수 있게 출력하는 것입니다.
Before (`apps/edge/cmd/edge/console.go:62`):
```go
fmt.Fprintln(out, "Commands: /nodes, /session <id>, /background on|off, /terminate-session, /exit")
```
Before (`apps/edge/cmd/edge/console.go:106`):
```go
case lower == "/terminate-session":
if err := sendTerminateSession(ctx, registry, adapter, agent, sessionID); err != nil {
fmt.Fprintf(out, "error: %v\n", err)
} else {
fmt.Fprintf(out, "terminated session %s\n", sessionID)
}
default:
if err := sendConsoleRun(ctx, registry, events, out, adapter, agent, sessionID, background, timeoutSec, message); err != nil {
fmt.Fprintf(out, "error: %v\n", err)
}
```
#### 해결 방법
console switch에 `case lower == "/status":`를 추가하고, `sendConsoleStatus` helper를 통해 현재 `adapter`, `agent`, `sessionID`, `timeoutSec`를 담은 `NodeCommandRequest`를 보냅니다. request id는 `status-<unix-nano>` 형식으로 만들고, `toki.SendRequestTyped[*iop.NodeCommandRequest, *iop.NodeCommandResponse]`를 사용합니다. response error가 있으면 `error: ...`로 출력하고, 성공하면 짧은 status block을 출력합니다.
After:
```go
case lower == "/status":
if err := sendConsoleStatus(ctx, registry, out, adapter, agent, sessionID, timeoutSec); err != nil {
fmt.Fprintf(out, "error: %v\n", err)
}
```
예상 출력:
```text
[edge] sent command=status node=local-node adapter=cli agent=codex session=default
[node-local-node-status] agent=codex session=default
5h limit: 98% left (resets 18:38)
Weekly limit: 22% left (resets 10:20 on 8 May)
```
필드가 비어 있으면 해당 줄은 생략하고, 모든 parsed field가 비면 `raw output did not include parsed limits` 같은 한 줄과 raw output 일부를 표시합니다. `bin/edge.sh`와 `bin/node.sh` 자체는 이미 console/node 실행을 감싸므로 변경이 필수는 아니지만, README에는 `/status` 수동 검증 흐름을 추가합니다.
#### 수정 파일 및 체크리스트
- [ ] `apps/edge/cmd/edge/console.go` - command 도움말에 `/status` 추가, switch case 추가
- [ ] `apps/edge/cmd/edge/console.go` - `buildNodeCommandRequest`, `sendConsoleStatus`, `formatUsageStatus` helper 추가
- [ ] `apps/edge/cmd/edge/console_test.go` - command request 생성과 status 출력 포맷 테스트 추가
- [ ] `apps/edge/README.md` - `bin/edge.sh` console에서 `/status`를 입력하면 현재 선택 agent 사용량을 조회한다는 수동 검증 절차 추가
- [ ] `apps/node/README.md` - node가 command request를 받아 CLI usage status를 조회한다는 설명 추가
- [ ] `bin/edge.sh`, `bin/node.sh` - 스크립트 자체 수정은 기본적으로 생략. 수동 검증 명령은 README에 기록
- [ ] renamed/removed symbol call site 점검: `rg -n "/status|sendConsoleStatus|NodeCommandRequest|NodeCommandResponse" apps proto`로 edge/node 양쪽 연결 확인
#### 테스트 작성
테스트를 작성합니다.
- `apps/edge/cmd/edge/console_test.go` - `TestBuildNodeCommandRequest_StatusUsesCurrentTarget`: adapter=`cli`, agent=`codex`, session=`default`, timeout이 request에 반영되는지 검증
- `apps/edge/cmd/edge/console_test.go` - `TestFormatUsageStatus_PrintsParsedLimits`: `AgentUsageStatus{DailyLimit:"98% left", WeeklyLimit:"22% left"}`가 사람이 읽을 수 있는 줄로 출력되는지 검증
- `apps/edge/cmd/edge/console_test.go` - `TestFormatUsageStatus_Error`: `NodeCommandResponse.Error`가 있으면 raw status block 대신 error를 출력하는지 검증
#### 중간 검증
```bash
go test ./apps/edge/cmd/edge
```
기대 결과: console command request 생성과 status 출력 포맷 테스트가 통과합니다.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `proto/iop/runtime.proto` | API-1 |
| `proto/gen/iop/runtime.pb.go` | API-1 |
| `apps/node/internal/transport/session.go` | API-1, API-2 |
| `apps/node/internal/transport/parser.go` | API-1 |
| `apps/node/internal/transport/parser_test.go` | API-1 |
| `apps/node/internal/transport/integration_test.go` | API-1 |
| `apps/edge/internal/transport/server.go` | API-1 |
| `apps/edge/internal/transport/server_test.go` | API-1 |
| `apps/edge/internal/transport/integration_test.go` | API-1 |
| `apps/node/internal/runtime/types.go` | API-2 |
| `apps/node/internal/node/node.go` | API-2 |
| `apps/node/internal/node/node_test.go` | API-2 |
| `apps/node/internal/adapters/cli/cli.go` | API-3 |
| `apps/node/internal/adapters/cli/status/status.go` | API-3 |
| `apps/node/internal/adapters/cli/status/codex.go` | API-3 |
| `apps/node/internal/adapters/cli/status/claude.go` | API-3 |
| `apps/node/internal/adapters/cli/status/gemini.go` | API-3 |
| `apps/node/internal/adapters/cli/status/parser.go` | API-3 |
| `apps/node/internal/adapters/cli/status/codex_test.go` | API-3 |
| `apps/node/internal/adapters/cli/status/parser_test.go` | API-3 |
| `apps/node/internal/adapters/cli/status/status_test.go` | API-3 |
| `apps/node/internal/adapters/cli/lifecycle/cli_test.go` | API-3 |
| `apps/edge/cmd/edge/console.go` | API-4 |
| `apps/edge/cmd/edge/console_test.go` | API-4 |
| `apps/edge/README.md` | API-4 |
| `apps/node/README.md` | API-4 |
## 최종 검증
```bash
make proto
go test ./apps/node/internal/transport/... ./apps/edge/internal/transport/...
go test ./apps/node/internal/runtime/... ./apps/node/internal/node/...
go test ./apps/node/internal/adapters/cli/status/... ./apps/node/internal/adapters/cli/...
go test ./apps/edge/cmd/edge
go test ./...
```
기대 결과: 모든 명령이 성공합니다. `make`가 없는 환경이면 Makefile의 `proto` 명령을 직접 실행하고, 직접 실행한 명령과 결과를 `CODE_REVIEW.md`에 남깁니다. 수동 검증은 터미널 두 개에서 `./bin/edge.sh`, `./bin/node.sh`를 실행한 뒤 edge console에 `/status`를 입력해 현재 `console.agent`에 대한 status 또는 unsupported-agent error가 표시되는지 확인합니다.