screenagent-ai 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- screenagent_ai-0.3.0/.claude/agents/demo-debugger.md +106 -0
- screenagent_ai-0.3.0/.claude/skills/add-tool/SKILL.md +120 -0
- screenagent_ai-0.3.0/.claude/skills/demo/SKILL.md +117 -0
- screenagent_ai-0.3.0/.claude/skills/perf-audit/SKILL.md +113 -0
- screenagent_ai-0.3.0/.claude/skills/reset-chrome/SKILL.md +123 -0
- screenagent_ai-0.3.0/.claude/skills/test-agent/SKILL.md +124 -0
- screenagent_ai-0.3.0/.claude/skills/tune-prompt/SKILL.md +97 -0
- screenagent_ai-0.3.0/.env.example +4 -0
- screenagent_ai-0.3.0/.gitignore +17 -0
- screenagent_ai-0.3.0/LICENSE +21 -0
- screenagent_ai-0.3.0/PKG-INFO +109 -0
- screenagent_ai-0.3.0/README.md +88 -0
- screenagent_ai-0.3.0/examples/01_basic_agent.py +13 -0
- screenagent_ai-0.3.0/examples/02_no_api_key.py +23 -0
- screenagent_ai-0.3.0/examples/03_browser_cdp.py +42 -0
- screenagent_ai-0.3.0/examples/04_custom_loop.py +31 -0
- screenagent_ai-0.3.0/examples/demo_claude_code.sh +63 -0
- screenagent_ai-0.3.0/examples/demo_python_sdk.py +145 -0
- screenagent_ai-0.3.0/examples/demo_recording.py +50 -0
- screenagent_ai-0.3.0/pyproject.toml +44 -0
- screenagent_ai-0.3.0/src/screenagent/__init__.py +51 -0
- screenagent_ai-0.3.0/src/screenagent/action/__init__.py +1 -0
- screenagent_ai-0.3.0/src/screenagent/action/cdp.py +73 -0
- screenagent_ai-0.3.0/src/screenagent/action/cgevent.py +283 -0
- screenagent_ai-0.3.0/src/screenagent/agent/__init__.py +1 -0
- screenagent_ai-0.3.0/src/screenagent/agent/computer_use.py +632 -0
- screenagent_ai-0.3.0/src/screenagent/agent/loop.py +341 -0
- screenagent_ai-0.3.0/src/screenagent/agent/tools.py +129 -0
- screenagent_ai-0.3.0/src/screenagent/cli.py +438 -0
- screenagent_ai-0.3.0/src/screenagent/config.py +62 -0
- screenagent_ai-0.3.0/src/screenagent/examples/__init__.py +1 -0
- screenagent_ai-0.3.0/src/screenagent/examples/google_search.py +47 -0
- screenagent_ai-0.3.0/src/screenagent/interfaces.py +38 -0
- screenagent_ai-0.3.0/src/screenagent/mcp/__init__.py +1 -0
- screenagent_ai-0.3.0/src/screenagent/mcp/server.py +62 -0
- screenagent_ai-0.3.0/src/screenagent/perception/__init__.py +1 -0
- screenagent_ai-0.3.0/src/screenagent/perception/ax.py +134 -0
- screenagent_ai-0.3.0/src/screenagent/perception/cdp.py +157 -0
- screenagent_ai-0.3.0/src/screenagent/perception/composite.py +105 -0
- screenagent_ai-0.3.0/src/screenagent/perception/screenshot.py +73 -0
- screenagent_ai-0.3.0/src/screenagent/py.typed +0 -0
- screenagent_ai-0.3.0/src/screenagent/sdk.py +82 -0
- screenagent_ai-0.3.0/src/screenagent/shortcuts.py +47 -0
- screenagent_ai-0.3.0/src/screenagent/types.py +90 -0
- screenagent_ai-0.3.0/tests/__init__.py +0 -0
- screenagent_ai-0.3.0/tests/conftest.py +55 -0
- screenagent_ai-0.3.0/tests/test_agent_loop.py +128 -0
- screenagent_ai-0.3.0/tests/test_cli.py +197 -0
- screenagent_ai-0.3.0/tests/test_open_url.py +99 -0
- screenagent_ai-0.3.0/tests/test_sdk.py +114 -0
- screenagent_ai-0.3.0/tests/test_shortcuts.py +101 -0
- screenagent_ai-0.3.0/tests/test_types.py +81 -0
- screenagent_ai-0.3.0/uv.lock +1043 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: demo-debugger
|
|
3
|
+
description: screenagent 데모 실행 중 발생한 에러를 분석하고 코드를 수정한다. 에러 로그, 스크린샷, 코드를 종합 분석하여 버그를 찾고 수정.
|
|
4
|
+
model: inherit
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# demo-debugger Subagent
|
|
8
|
+
|
|
9
|
+
screenagent 데모 실행 중 발생한 에러를 분석하고 수정하는 디버깅 전문 에이전트.
|
|
10
|
+
|
|
11
|
+
## Role
|
|
12
|
+
|
|
13
|
+
데모 실행 중 문제가 발생했을 때, 다음을 종합 분석하여 버그를 찾고 수정한다:
|
|
14
|
+
- 에러 로그 (stderr)
|
|
15
|
+
- 스크린샷 (현재 화면 상태)
|
|
16
|
+
- 소스코드 (관련 모듈)
|
|
17
|
+
|
|
18
|
+
## Procedure
|
|
19
|
+
|
|
20
|
+
### 1. 에러 정보 수집
|
|
21
|
+
|
|
22
|
+
호출 시 전달받는 정보:
|
|
23
|
+
- `error_log`: stderr 로그 내용 또는 로그 파일 경로
|
|
24
|
+
- `screenshot_path`: 에러 발생 시점의 스크린샷 (선택)
|
|
25
|
+
- `instruction`: 실행하려던 시나리오
|
|
26
|
+
- `error_message`: 에러 메시지 요약
|
|
27
|
+
|
|
28
|
+
### 2. 로그 분석
|
|
29
|
+
|
|
30
|
+
에러 로그에서 다음을 추출한다:
|
|
31
|
+
- Python traceback (파일명, 줄 번호, 함수명)
|
|
32
|
+
- 마지막으로 성공한 step
|
|
33
|
+
- 실패한 tool_call과 인자
|
|
34
|
+
- 외부 서비스 에러 (CDP, Anthropic API 등)
|
|
35
|
+
|
|
36
|
+
### 3. 소스코드 탐색
|
|
37
|
+
|
|
38
|
+
에러와 관련된 소스 파일을 읽는다. 주요 파일 위치:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
src/screenagent/
|
|
42
|
+
├── agent/
|
|
43
|
+
│ ├── loop.py # 에이전트 메인 루프 (좌표 파싱, 스텝 실행)
|
|
44
|
+
│ └── tools.py # 도구 스키마 정의
|
|
45
|
+
├── perception/
|
|
46
|
+
│ ├── screenshot.py # 스크린샷 캡처 + 리사이즈
|
|
47
|
+
│ ├── ax.py # 접근성 트리
|
|
48
|
+
│ ├── cdp.py # Chrome DevTools Protocol
|
|
49
|
+
│ └── composite.py # 통합 perceiver
|
|
50
|
+
├── action/
|
|
51
|
+
│ └── cgevent.py # 마우스/키보드 제어
|
|
52
|
+
├── cli.py # CLI 진입점
|
|
53
|
+
├── config.py # 설정 로딩
|
|
54
|
+
└── types.py # 타입 정의
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 4. 원인 분석
|
|
58
|
+
|
|
59
|
+
일반적인 에러 패턴:
|
|
60
|
+
|
|
61
|
+
| 에러 패턴 | 원인 | 수정 위치 |
|
|
62
|
+
|-----------|------|-----------|
|
|
63
|
+
| `KeyError: 'x'` or coordinate parsing failure | LLM이 예상 외 좌표 형식 반환 | `loop.py` - `_parse_coord` |
|
|
64
|
+
| `ConnectionRefusedError` on CDP | Chrome CDP 미연결 | `cdp.py` - retry/fallback |
|
|
65
|
+
| `_downscale_png` TypeError | 스크린샷 바이트 처리 에러 | `screenshot.py` - `_downscale_png` |
|
|
66
|
+
| `anthropic.APIError` | API 키 문제 또는 rate limit | `config.py` 또는 사용자 설정 |
|
|
67
|
+
| `PermissionError` on AX | 접근성 권한 없음 | 사용자에게 권한 설정 안내 |
|
|
68
|
+
| `timeout` on tool execution | 동작이 너무 오래 걸림 | 해당 tool의 timeout 조정 |
|
|
69
|
+
|
|
70
|
+
### 5. 수정 제안/적용
|
|
71
|
+
|
|
72
|
+
분석 결과에 따라:
|
|
73
|
+
|
|
74
|
+
1. **코드 버그**: 직접 수정하고 변경 내용을 설명한다
|
|
75
|
+
2. **설정 문제**: 올바른 설정 방법을 안내한다
|
|
76
|
+
3. **환경 문제**: (접근성 권한, Chrome 설정 등) 해결 단계를 안내한다
|
|
77
|
+
4. **LLM 응답 문제**: 시스템 프롬프트 개선을 제안한다
|
|
78
|
+
|
|
79
|
+
### 6. 수정 검증
|
|
80
|
+
|
|
81
|
+
코드를 수정한 경우:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# 단위 테스트 실행
|
|
85
|
+
python -m pytest tests/ -x -q
|
|
86
|
+
|
|
87
|
+
# 수정된 기능만 빠르게 확인
|
|
88
|
+
screenagent run "test" --dry-run --output json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 7. 결과 보고
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
## Debug Report
|
|
95
|
+
|
|
96
|
+
**에러**: <에러 요약>
|
|
97
|
+
**원인**: <근본 원인>
|
|
98
|
+
**수정**: <수정 내용 또는 안내>
|
|
99
|
+
|
|
100
|
+
### 변경된 파일
|
|
101
|
+
- `src/screenagent/agent/loop.py:42` — 좌표 파싱 로직 수정
|
|
102
|
+
|
|
103
|
+
### 검증
|
|
104
|
+
- 테스트: ✅ 통과
|
|
105
|
+
- dry-run: ✅ 정상
|
|
106
|
+
```
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: add-tool
|
|
3
|
+
description: screenagent에 새로운 tool을 추가한다. 스키마 정의, dispatch 구현, CLI 연동, 테스트까지 한번에 처리.
|
|
4
|
+
argument-hint: <tool-name> <description>
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /add-tool Skill
|
|
9
|
+
|
|
10
|
+
screenagent 에이전트에 새 tool을 추가하는 전체 과정을 자동화한다.
|
|
11
|
+
|
|
12
|
+
## Arguments
|
|
13
|
+
|
|
14
|
+
- `tool-name`: 추가할 도구 이름 (snake_case, 예: `wait_for_element`, `extract_text`)
|
|
15
|
+
- `description`: 도구 설명
|
|
16
|
+
|
|
17
|
+
## 수정 대상 파일
|
|
18
|
+
|
|
19
|
+
새 tool을 추가하려면 반드시 다음 파일들을 수정해야 한다:
|
|
20
|
+
|
|
21
|
+
1. **`src/screenagent/agent/tools.py`** — 도구 스키마 정의 (TOOLS 리스트에 추가)
|
|
22
|
+
2. **`src/screenagent/agent/loop.py`** — `_dispatch_tool()` 메서드에 핸들러 추가
|
|
23
|
+
3. **`src/screenagent/cli.py`** — (선택) CLI 서브커맨드로도 노출할 경우
|
|
24
|
+
4. **`tests/test_cli.py`** — 테스트 추가
|
|
25
|
+
|
|
26
|
+
## Procedure
|
|
27
|
+
|
|
28
|
+
### Step 1: 기존 도구 패턴 파악
|
|
29
|
+
|
|
30
|
+
먼저 현재 도구 목록을 확인한다:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
.venv/bin/screenagent --output json schema | python3 -c "
|
|
34
|
+
import sys, json
|
|
35
|
+
tools = json.load(sys.stdin)['tools']
|
|
36
|
+
for t in tools:
|
|
37
|
+
print(f\" {t['name']}: {t['description'][:60]}\")
|
|
38
|
+
"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Step 2: 스키마 정의
|
|
42
|
+
|
|
43
|
+
`src/screenagent/agent/tools.py`의 TOOLS 리스트에 새 도구 스키마를 추가한다.
|
|
44
|
+
|
|
45
|
+
기존 패턴을 따라:
|
|
46
|
+
```python
|
|
47
|
+
{
|
|
48
|
+
"name": "<tool-name>",
|
|
49
|
+
"description": "<description>",
|
|
50
|
+
"input_schema": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
# ... 파라미터 정의
|
|
54
|
+
},
|
|
55
|
+
"required": [...]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Step 3: dispatch 구현
|
|
61
|
+
|
|
62
|
+
`src/screenagent/agent/loop.py`의 `_dispatch_tool()` 메서드에 elif 블록을 추가한다.
|
|
63
|
+
|
|
64
|
+
패턴:
|
|
65
|
+
```python
|
|
66
|
+
elif name == "<tool-name>":
|
|
67
|
+
# 구현
|
|
68
|
+
return ToolResult(output="...", screenshot_png=png)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
도구 유형별 구현 가이드:
|
|
72
|
+
- **인식(perception) 도구**: `self._perceiver`를 통해 정보 수집 → ToolResult(output=...)
|
|
73
|
+
- **행동(action) 도구**: `self._actor` 또는 `self._cdp_actor`를 통해 실행 → 후속 screenshot
|
|
74
|
+
- **CDP 전용 도구**: `await self._get_cdp_actor()`로 CDP 사용 가능 여부 먼저 확인
|
|
75
|
+
- **순수 정보 도구**: 외부 상태 변경 없이 정보만 반환
|
|
76
|
+
|
|
77
|
+
### Step 4: 시스템 프롬프트 업데이트
|
|
78
|
+
|
|
79
|
+
새 도구의 사용법이 비직관적이면 `loop.py`의 `SYSTEM_PROMPT`에 가이드를 추가한다.
|
|
80
|
+
|
|
81
|
+
### Step 5: CLI 서브커맨드 (선택)
|
|
82
|
+
|
|
83
|
+
사용자가 CLI에서 직접 호출할 수 있게 하려면 `cli.py`에:
|
|
84
|
+
1. `cmd_<tool_name>` 함수 추가
|
|
85
|
+
2. `build_parser()`에 서브커맨드 추가
|
|
86
|
+
3. `dispatch` 딕셔너리에 등록
|
|
87
|
+
|
|
88
|
+
### Step 6: 테스트
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 스키마 확인
|
|
92
|
+
.venv/bin/screenagent --output json schema | python3 -c "
|
|
93
|
+
import sys, json
|
|
94
|
+
tools = json.load(sys.stdin)['tools']
|
|
95
|
+
names = [t['name'] for t in tools]
|
|
96
|
+
assert '<tool-name>' in names, f'Tool not found! Got: {names}'
|
|
97
|
+
print('Schema OK')
|
|
98
|
+
"
|
|
99
|
+
|
|
100
|
+
# 기존 테스트 통과 확인
|
|
101
|
+
.venv/bin/python -m pytest tests/ -x -q
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Step 7: 결과 리포트
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
## Tool Added: <tool-name>
|
|
108
|
+
|
|
109
|
+
**설명**: <description>
|
|
110
|
+
**파라미터**: <params>
|
|
111
|
+
|
|
112
|
+
### 수정된 파일
|
|
113
|
+
- `tools.py` — 스키마 추가
|
|
114
|
+
- `loop.py` — dispatch 핸들러 추가
|
|
115
|
+
- `cli.py` — (선택) 서브커맨드 추가
|
|
116
|
+
|
|
117
|
+
### 테스트
|
|
118
|
+
- 스키마: ✅
|
|
119
|
+
- 기존 테스트: ✅
|
|
120
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: demo
|
|
3
|
+
description: screenagent 데모 시나리오를 실행하고 결과를 검증한다. Chrome 상태 확인, 에이전트 실행, 스크린샷 캡처, 결과 리포트까지 한번에 처리.
|
|
4
|
+
argument-hint: [scenario-description] [--model sonnet|haiku] [--max-steps N]
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /demo Skill
|
|
9
|
+
|
|
10
|
+
screenagent 데모 시나리오를 한 커맨드로 실행하고 결과를 검증한다.
|
|
11
|
+
|
|
12
|
+
## Arguments
|
|
13
|
+
|
|
14
|
+
- 첫 번째 인자: 시나리오 설명 (예: "youtube.com에서 lofi 검색")
|
|
15
|
+
- `--model sonnet|haiku`: 사용할 모델 (기본: haiku)
|
|
16
|
+
- `--max-steps N`: 최대 스텝 수 (기본: 10)
|
|
17
|
+
|
|
18
|
+
## Procedure
|
|
19
|
+
|
|
20
|
+
### Step 1: Chrome 상태 확인
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
screenagent check --output json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- JSON 결과의 `ok` 필드를 확인한다.
|
|
27
|
+
- `ok: false`이면 사용자에게 Chrome 디버그 모드 실행을 안내하고 중단한다:
|
|
28
|
+
```
|
|
29
|
+
Chrome CDP 연결 실패. 다음 명령어로 Chrome을 실행해주세요:
|
|
30
|
+
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug-profile
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Step 2: Chrome 초기화
|
|
34
|
+
|
|
35
|
+
CDP로 모든 탭을 닫고 about:blank 상태로 만든다:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 현재 열린 탭 목록 확인
|
|
39
|
+
curl -s http://localhost:9222/json | python3 -c "
|
|
40
|
+
import sys, json
|
|
41
|
+
targets = json.load(sys.stdin)
|
|
42
|
+
pages = [t for t in targets if t.get('type') == 'page']
|
|
43
|
+
print(f'{len(pages)} tab(s) open')
|
|
44
|
+
for p in pages:
|
|
45
|
+
print(f' - {p.get(\"title\", \"untitled\")}: {p.get(\"url\", \"\")}')
|
|
46
|
+
"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
열린 탭이 있으면 about:blank로 이동시킨다:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# 첫 번째 탭의 websocket URL을 가져와서 about:blank으로 navigate
|
|
53
|
+
curl -s http://localhost:9222/json | python3 -c "
|
|
54
|
+
import sys, json
|
|
55
|
+
targets = json.load(sys.stdin)
|
|
56
|
+
pages = [t for t in targets if t.get('type') == 'page']
|
|
57
|
+
if pages:
|
|
58
|
+
# 첫 번째 탭만 남기고 나머지는 닫기
|
|
59
|
+
for p in pages[1:]:
|
|
60
|
+
import urllib.request
|
|
61
|
+
urllib.request.urlopen(f'http://localhost:9222/json/close/{p[\"id\"]}', timeout=5)
|
|
62
|
+
# 첫 번째 탭을 about:blank으로
|
|
63
|
+
print(f'Reset to 1 tab: {pages[0][\"id\"]}')
|
|
64
|
+
"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 3: 시나리오에서 모델과 max-steps 파싱
|
|
68
|
+
|
|
69
|
+
인자에서 `--model`과 `--max-steps` 값을 파싱한다.
|
|
70
|
+
|
|
71
|
+
- `--model sonnet` → `--model claude-sonnet-4-6`
|
|
72
|
+
- `--model haiku` → `--model claude-haiku-4-5-20251001` (기본값)
|
|
73
|
+
- `--max-steps N` → `--max-steps N` (기본값: 10)
|
|
74
|
+
|
|
75
|
+
나머지 텍스트가 시나리오 instruction이 된다.
|
|
76
|
+
|
|
77
|
+
### Step 4: screenagent 실행
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
screenagent run "<instruction>" --max-steps <N> --model <model> --output json 2>demo_stderr.log
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- stderr 로그를 `demo_stderr.log`에 저장한다.
|
|
84
|
+
- 실행이 끝나면 stdout의 JSON 결과를 파싱한다.
|
|
85
|
+
|
|
86
|
+
### Step 5: 결과 스크린샷 캡처
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
screenagent screenshot --file demo_result.png --output json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
- 캡처된 스크린샷을 Read 도구로 확인하여 사용자에게 보여준다.
|
|
93
|
+
|
|
94
|
+
### Step 6: 결과 리포트
|
|
95
|
+
|
|
96
|
+
다음 형식으로 결과를 정리한다:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
## Demo Result
|
|
100
|
+
|
|
101
|
+
**시나리오**: <instruction>
|
|
102
|
+
**모델**: <model>
|
|
103
|
+
**스텝**: <사용된 스텝> / <max-steps>
|
|
104
|
+
**결과**: ✅ 성공 / ❌ 실패
|
|
105
|
+
|
|
106
|
+
### 에이전트 응답
|
|
107
|
+
<result summary>
|
|
108
|
+
|
|
109
|
+
### 스크린샷
|
|
110
|
+
[demo_result.png 표시]
|
|
111
|
+
|
|
112
|
+
### 로그 요약
|
|
113
|
+
<stderr 로그에서 주요 step 요약>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
- 실패 시: 에러 내용을 분석하고, demo-debugger subagent 사용을 제안한다.
|
|
117
|
+
- 성공 시: 결과를 요약하고 스크린샷으로 확인한다.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: perf-audit
|
|
3
|
+
description: screenagent 에이전트 루프의 성능 병목을 분석하고 코드를 개선한다. 스텝별 시간 측정, 불필요한 스크린샷 제거, sleep 최적화 등.
|
|
4
|
+
argument-hint: [run-log-path or "analyze"]
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /perf-audit Skill
|
|
9
|
+
|
|
10
|
+
screenagent 에이전트 루프의 성능 병목을 찾아내고 코드를 개선한다.
|
|
11
|
+
|
|
12
|
+
## 배경
|
|
13
|
+
|
|
14
|
+
현재 에이전트 루프의 알려진 병목:
|
|
15
|
+
- 매 tool dispatch마다 스크린샷 캡처 (불필요할 수 있음)
|
|
16
|
+
- `asyncio.sleep()` 고정 대기 시간 (0.3s, 2s 등)
|
|
17
|
+
- LLM API 호출 대기 (줄일 수 없지만 토큰 수 줄이기 가능)
|
|
18
|
+
- 스크린샷 리사이즈 (`sips` 호출)
|
|
19
|
+
|
|
20
|
+
## Procedure
|
|
21
|
+
|
|
22
|
+
### Step 1: 현재 성능 측정
|
|
23
|
+
|
|
24
|
+
먼저 에이전트 루프를 dry-run이 아닌 실제 실행으로 시간을 측정한다.
|
|
25
|
+
인자로 로그 파일 경로가 주어지면 해당 로그를 분석한다.
|
|
26
|
+
|
|
27
|
+
로그가 없으면 `loop.py`의 `arun()` 메서드를 읽고 각 단계별 예상 시간을 계산한다:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
src/screenagent/agent/loop.py 의 arun() 분석:
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
각 스텝에서 소요되는 시간 요소:
|
|
34
|
+
1. `_perceive_async()` — AX tree + CDP DOM + screenshot
|
|
35
|
+
2. `_client.messages.create()` — LLM API 호출
|
|
36
|
+
3. `_dispatch_tool()` — tool 실행 + sleep + 후속 screenshot
|
|
37
|
+
4. 메시지 직렬화/역직렬화
|
|
38
|
+
|
|
39
|
+
### Step 2: 병목 식별
|
|
40
|
+
|
|
41
|
+
`loop.py`와 `composite.py`를 읽고 다음을 체크한다:
|
|
42
|
+
|
|
43
|
+
#### 2a. 불필요한 스크린샷
|
|
44
|
+
- `_dispatch_tool`에서 click, type_text, key_press, scroll 후 모두 screenshot를 찍고 있음
|
|
45
|
+
- 연속 동작(click → type → enter)에서 중간 스크린샷은 불필요할 수 있음
|
|
46
|
+
- **개선**: tool 결과에 screenshot를 넣되, LLM이 다음 tool을 연속 호출할 때는 마지막 것만 사용
|
|
47
|
+
|
|
48
|
+
#### 2b. sleep 최적화
|
|
49
|
+
현재 sleep 값들:
|
|
50
|
+
- `_open_url_via_keyboard`: 0.3s + 0.1s + 0.3s + 2.0s = **2.7s** (URL 열 때마다)
|
|
51
|
+
- `click`: 0.3s
|
|
52
|
+
- `type_text`: 0.3s
|
|
53
|
+
- `key_press`: 0.3s
|
|
54
|
+
- `scroll`: 0.3s
|
|
55
|
+
- `navigate` (CDP): 1.0s
|
|
56
|
+
|
|
57
|
+
**개선 방안**:
|
|
58
|
+
- 페이지 로드 대기를 고정 sleep 대신 CDP `Page.loadEventFired` 이벤트 대기로 변경
|
|
59
|
+
- 클릭/타이핑 후 sleep을 0.1s로 줄여볼 수 있음
|
|
60
|
+
|
|
61
|
+
#### 2c. 이미지 크기 최적화
|
|
62
|
+
- 스크린샷이 클수록 LLM 토큰 소모 많고 API 응답 느림
|
|
63
|
+
- 현재 MAX_IMAGE_BYTES = 3.5MB → 너무 큼
|
|
64
|
+
- **개선**: 1MB 이하로 줄이면 API 응답 속도 향상
|
|
65
|
+
|
|
66
|
+
#### 2d. 히스토리 관리
|
|
67
|
+
- MAX_HISTORY = 10이지만 각 메시지에 이미지가 포함됨
|
|
68
|
+
- 오래된 메시지의 이미지를 제거하면 토큰 절약
|
|
69
|
+
|
|
70
|
+
### Step 3: 코드 수정 적용
|
|
71
|
+
|
|
72
|
+
분석 결과에 따라 실제 코드를 수정한다. 수정 대상 파일:
|
|
73
|
+
|
|
74
|
+
| 파일 | 개선 내용 |
|
|
75
|
+
|------|-----------|
|
|
76
|
+
| `src/screenagent/agent/loop.py` | sleep 값 조정, 히스토리 이미지 제거, 스텝 타이밍 로그 |
|
|
77
|
+
| `src/screenagent/perception/screenshot.py` | MAX_IMAGE_BYTES 축소 |
|
|
78
|
+
| `src/screenagent/perception/composite.py` | CDP 우선 screenshot, 불필요한 AX tree 스킵 |
|
|
79
|
+
|
|
80
|
+
수정 시 각 변경에 대해:
|
|
81
|
+
1. 변경 전 동작을 설명
|
|
82
|
+
2. 변경 후 기대 효과를 설명
|
|
83
|
+
3. 위험 요소가 있으면 명시
|
|
84
|
+
|
|
85
|
+
### Step 4: 검증
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# 테스트 통과 확인
|
|
89
|
+
.venv/bin/python -m pytest tests/ -x -q
|
|
90
|
+
|
|
91
|
+
# dry-run 정상 확인
|
|
92
|
+
.venv/bin/screenagent --output json run "test" --dry-run
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Step 5: 결과 리포트
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
## Performance Audit Report
|
|
99
|
+
|
|
100
|
+
### 측정 결과
|
|
101
|
+
| 구간 | 변경 전 | 변경 후 | 절감 |
|
|
102
|
+
|------|---------|---------|------|
|
|
103
|
+
| URL 열기 | 2.7s | 0.8s | -70% |
|
|
104
|
+
| 클릭 후 대기 | 0.3s | 0.15s | -50% |
|
|
105
|
+
| 스크린샷 크기 | ~600KB | ~200KB | -67% |
|
|
106
|
+
| 스텝당 히스토리 토큰 | ~8K | ~4K | -50% |
|
|
107
|
+
|
|
108
|
+
### 적용된 변경
|
|
109
|
+
- [파일별 변경 내용]
|
|
110
|
+
|
|
111
|
+
### 추가 권장
|
|
112
|
+
- [더 할 수 있는 최적화]
|
|
113
|
+
```
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reset-chrome
|
|
3
|
+
description: Chrome의 모든 탭을 닫고 새 창을 열어 깨끗한 상태로 만든다
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /reset-chrome Skill
|
|
8
|
+
|
|
9
|
+
Chrome 브라우저를 깨끗한 상태로 리셋한다.
|
|
10
|
+
|
|
11
|
+
## Procedure
|
|
12
|
+
|
|
13
|
+
### Step 1: CDP 연결 확인
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
screenagent check --output json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `ok: false`이면:
|
|
20
|
+
```
|
|
21
|
+
Chrome CDP 연결 실패. 다음 명령어로 Chrome을 실행해주세요:
|
|
22
|
+
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug-profile
|
|
23
|
+
```
|
|
24
|
+
여기서 중단한다.
|
|
25
|
+
|
|
26
|
+
### Step 2: 현재 탭 상태 확인
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
curl -s http://localhost:9222/json | python3 -c "
|
|
30
|
+
import sys, json
|
|
31
|
+
targets = json.load(sys.stdin)
|
|
32
|
+
pages = [t for t in targets if t.get('type') == 'page']
|
|
33
|
+
print(f'{len(pages)} tab(s) open')
|
|
34
|
+
for p in pages:
|
|
35
|
+
print(f' - {p.get(\"title\", \"untitled\")}: {p.get(\"url\", \"\")}')
|
|
36
|
+
"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 3: 탭 정리
|
|
40
|
+
|
|
41
|
+
모든 탭을 닫고 하나의 about:blank 탭만 남긴다:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
curl -s http://localhost:9222/json | python3 -c "
|
|
45
|
+
import sys, json, urllib.request
|
|
46
|
+
|
|
47
|
+
targets = json.load(sys.stdin)
|
|
48
|
+
pages = [t for t in targets if t.get('type') == 'page']
|
|
49
|
+
|
|
50
|
+
if not pages:
|
|
51
|
+
# 탭이 없으면 새 탭 생성
|
|
52
|
+
urllib.request.urlopen('http://localhost:9222/json/new?about:blank', timeout=5)
|
|
53
|
+
print('Created new blank tab')
|
|
54
|
+
else:
|
|
55
|
+
# 첫 번째 탭 외 모두 닫기
|
|
56
|
+
for p in pages[1:]:
|
|
57
|
+
try:
|
|
58
|
+
urllib.request.urlopen(f'http://localhost:9222/json/close/{p[\"id\"]}', timeout=5)
|
|
59
|
+
print(f'Closed: {p.get(\"title\", \"untitled\")}')
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f'Failed to close {p[\"id\"]}: {e}')
|
|
62
|
+
|
|
63
|
+
# 남은 탭 수 확인
|
|
64
|
+
print(f'Kept 1 tab: {pages[0].get(\"title\", \"untitled\")}')
|
|
65
|
+
"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
첫 번째 탭을 about:blank으로 이동시킨다:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
curl -s http://localhost:9222/json | python3 -c "
|
|
72
|
+
import sys, json, urllib.request
|
|
73
|
+
targets = json.load(sys.stdin)
|
|
74
|
+
pages = [t for t in targets if t.get('type') == 'page']
|
|
75
|
+
if pages:
|
|
76
|
+
target_id = pages[0]['id']
|
|
77
|
+
urllib.request.urlopen(f'http://localhost:9222/json/activate/{target_id}', timeout=5)
|
|
78
|
+
print(f'Activated tab: {target_id}')
|
|
79
|
+
"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
그 다음 about:blank으로 네비게이트한다:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
screenagent run "주소창에 about:blank를 입력하고 엔터를 눌러줘" --max-steps 3 --dry-run
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
대신 CDP로 직접 navigate한다:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
python3 -c "
|
|
92
|
+
import json, http.client
|
|
93
|
+
|
|
94
|
+
# Get first page target
|
|
95
|
+
conn = http.client.HTTPConnection('localhost', 9222)
|
|
96
|
+
conn.request('GET', '/json')
|
|
97
|
+
targets = json.loads(conn.getresponse().read())
|
|
98
|
+
pages = [t for t in targets if t.get('type') == 'page']
|
|
99
|
+
|
|
100
|
+
if pages:
|
|
101
|
+
ws_url = pages[0].get('webSocketDebuggerUrl', '')
|
|
102
|
+
target_id = pages[0]['id']
|
|
103
|
+
# Use CDP HTTP endpoint to navigate
|
|
104
|
+
conn.request('GET', f'/json/navigate/{target_id}?url=about:blank')
|
|
105
|
+
print(conn.getresponse().read().decode())
|
|
106
|
+
print('Navigated to about:blank')
|
|
107
|
+
conn.close()
|
|
108
|
+
"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Step 4: 상태 확인
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
screenagent check --output json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
결과를 보고한다:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Chrome 리셋 완료:
|
|
121
|
+
- 탭: 1개 (about:blank)
|
|
122
|
+
- CDP 연결: 정상
|
|
123
|
+
```
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-agent
|
|
3
|
+
description: screenagent CLI 도구들을 빠르게 검증한다. dry-run, screenshot, ax-tree, click 등 개별 기능을 테스트하고 결과를 리포트.
|
|
4
|
+
argument-hint: [tool-name or "all"]
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /test-agent Skill
|
|
9
|
+
|
|
10
|
+
screenagent의 개별 CLI 도구들을 빠르게 테스트하고 결과를 리포트한다.
|
|
11
|
+
|
|
12
|
+
## Arguments
|
|
13
|
+
|
|
14
|
+
- `all`: 모든 도구를 순서대로 테스트
|
|
15
|
+
- 특정 도구 이름: `dry-run`, `screenshot`, `ax-tree`, `click`, `type`, `key`, `check`, `schema`
|
|
16
|
+
|
|
17
|
+
## Procedure
|
|
18
|
+
|
|
19
|
+
### 테스트 대상별 실행 방법
|
|
20
|
+
|
|
21
|
+
각 테스트는 `--output json`으로 실행하여 결과를 파싱한다.
|
|
22
|
+
|
|
23
|
+
#### 1. `dry-run` — 설정 검증
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
screenagent run "test" --dry-run --output json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- 확인: `ok` 필드가 `true`인지
|
|
30
|
+
- 확인: `config.api_key_set`이 `true`인지
|
|
31
|
+
- 확인: `config.model`이 유효한 모델명인지
|
|
32
|
+
|
|
33
|
+
#### 2. `screenshot` — 스크린샷 캡처
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
screenagent screenshot --file /tmp/test_screenshot.png --output json
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- 확인: `path` 필드가 존재하는지
|
|
40
|
+
- 확인: `bytes`가 0보다 큰지
|
|
41
|
+
- 확인: 파일이 실제로 존재하는지
|
|
42
|
+
|
|
43
|
+
#### 3. `ax-tree` — 접근성 트리
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
screenagent ax-tree "Finder" --output json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
- 확인: JSON 출력이 유효한지
|
|
50
|
+
- 확인: `role` 필드가 존재하는지
|
|
51
|
+
- 에러 시: 접근성 권한 문제인지 확인
|
|
52
|
+
|
|
53
|
+
#### 4. `click` — 마우스 클릭 (안전한 좌표 사용)
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
screenagent click 0 0 --output json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- 확인: `clicked` 필드가 존재하는지
|
|
60
|
+
- 주의: 화면 왼쪽 상단 구석(0,0)을 클릭하여 부작용 최소화
|
|
61
|
+
|
|
62
|
+
#### 5. `type` — 키보드 입력 (빈 앱 없이 테스트하므로 skip 가능)
|
|
63
|
+
|
|
64
|
+
이 테스트는 실제로 키 입력이 발생하므로, 현재 포커스된 앱에 텍스트가 입력될 수 있다.
|
|
65
|
+
`all`로 실행 시에는 이 테스트를 건너뛰고, 명시적으로 `type`을 지정했을 때만 실행한다.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
screenagent type "test" --output json
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- 확인: `typed` 필드가 `"test"`인지
|
|
72
|
+
|
|
73
|
+
#### 6. `key` — 키 입력
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
screenagent key escape --output json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- 확인: `pressed.key`가 `"escape"`인지
|
|
80
|
+
- Escape는 대부분의 상황에서 안전한 키이다.
|
|
81
|
+
|
|
82
|
+
#### 7. `check` — CDP 연결 확인
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
screenagent check --output json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- 확인: JSON 파싱 성공
|
|
89
|
+
- `ok: true`이면 CDP 정상, `ok: false`이면 CDP 미연결 (경고만, 실패 아님)
|
|
90
|
+
|
|
91
|
+
#### 8. `schema` — 도구 스키마
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
screenagent schema --output json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
- 확인: `tools` 배열이 존재하는지
|
|
98
|
+
- 확인: 각 도구에 `name`, `description`, `input_schema`가 있는지
|
|
99
|
+
|
|
100
|
+
### 결과 리포트
|
|
101
|
+
|
|
102
|
+
모든 테스트 완료 후 다음 형식으로 보고한다:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
## Test Report
|
|
106
|
+
|
|
107
|
+
| Tool | Status | Details |
|
|
108
|
+
|------------|--------|----------------------|
|
|
109
|
+
| dry-run | ✅ | API key set, haiku |
|
|
110
|
+
| screenshot | ✅ | 245KB captured |
|
|
111
|
+
| ax-tree | ✅ | Finder tree loaded |
|
|
112
|
+
| click | ✅ | (0, 0) clicked |
|
|
113
|
+
| type | ⏭️ | Skipped (all mode) |
|
|
114
|
+
| key | ✅ | escape pressed |
|
|
115
|
+
| check | ⚠️ | CDP not connected |
|
|
116
|
+
| schema | ✅ | 6 tools found |
|
|
117
|
+
|
|
118
|
+
**Result**: 6/8 passed, 1 skipped, 1 warning
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- ✅: 성공
|
|
122
|
+
- ❌: 실패 (에러 내용 포함)
|
|
123
|
+
- ⚠️: 경고 (기능은 동작하지만 주의 필요)
|
|
124
|
+
- ⏭️: 건너뜀
|