트렌딩 딥다이브 · 2026-07-19 · OSSInsight 24h #56

injaneity/pi-computer-use 딥다이브
— AI 에이전트에게 눈과 손을 달아주기

pi-computer-use는 코딩 에이전트 Pi에게 화면 속 앱을 직접 조작하는 능력을 붙여주는 확장(extension)이다. 에이전트는 창을 열어 보고, 버튼과 글자를 이해하고, 클릭·타이핑·스크롤·대기를 수행한다. API도 터미널 명령도 없이, 오직 화면에 떠 있는 그 앱과 대화해야 할 때 쓰는 도구다. 이 문서가 파고드는 건 "컴퓨터 유즈 도구 하나 더 나왔다"가 아니다 — macOS(Swift)와 Windows(Rust) 두 네이티브 헬퍼를 각각 접근성 API 위에 구현하고, 관측을 불변(immutable) 상태로 봉인하고, 여러 에이전트 호출이 동시에 들어와도 서로 덮어쓰지 않게 만드는 일을 실제 프로덕션 코드로 어떻게 해내는가이다. 멀티루트 포레스트 · 점진적 공개(progressive disclosure) · 에포크 기반 stale-write 거부 · 검증된 결과만 신뢰 · 유령 커서(ghost cursor) — 데스크톱 자동화의 어려운 문제들이 한 저장소 안에 정리돼 있다. (저장소: injaneity/pi-computer-use · TypeScript + Swift + Rust · MIT · 별 137 · 포크 2 · 버전 v0.5.0 · 소스 약 15,300줄)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트
  7. 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크
읽기 전에
이 도구는 당신의 실제 데스크톱을 조작한다

pi-computer-use는 에이전트에게 진짜 마우스 클릭과 키보드 입력을 발생시킬 권한을 준다. macOS에서는 손쉬운 사용(Accessibility)화면 기록(Screen Recording) 권한을 헬퍼에 부여해야 하며, 이는 곧 헬퍼가 화면 전체를 캡처하고 어떤 앱에든 입력을 보낼 수 있다는 뜻이다.

그래서 설계는 "실수해도 덜 위험하게"에 집착한다 — 불확실하면 실패로 처리하고(fail closed), 애매한 포인터 동작은 절대 재시도하지 않으며, headless 모드에서는 사용자의 포커스·커서를 건드리지 않는다. 그래도 원리상 에이전트가 오작동하면 실제 앱을 잘못 조작할 수 있으니, 중요한 작업 화면에서는 신중히 쓰는 게 맞다.

1프로젝트 한줄 요약

한 문장으로 pi-computer-use가 무엇인지
한 줄 정의

텍스트만 다루던 코딩 에이전트에게 "화면을 보는 눈"과 "클릭하는 손"을 달아주는 Pi 확장

Pi 에이전트에 이 패키지를 설치하면 observe_ui, act_ui 같은 도구가 생긴다. 에이전트는 observe_ui로 지금 떠 있는 창의 구조(버튼·텍스트필드·목록)를 글자로 정리된 아웃라인으로 받아 보고, act_ui로 "이 버튼을 눌러", "이 칸에 글자를 넣어" 같은 동작을 시킨다. API가 없는 데스크톱 앱을 상대할 때, 사람이 하듯 화면을 매개로 일을 시키는 것이다.

일상 비유

전화로 원격 지원해 주는 IT 헬프데스크를 떠올리면 된다. 헬프데스크 직원(에이전트)은 당신 화면을 직접 못 보지만, 당신이 "지금 화면에 파란 로그인 버튼이 왼쪽 위에 있고, 그 아래 아이디 칸이 있어요"라고 설명(observe_ui의 아웃라인)하면 "그 아이디 칸에 이걸 입력하고 로그인 버튼을 누르세요"라고 지시(act_ui)한다.

pi-computer-use는 이 "화면을 글자로 설명해 주는 사람"을 네이티브 접근성 API로 자동화한 것이다. 사람 대신 macOS의 AX나 Windows의 UIA가 화면 구조를 읽어 에이전트에게 넘긴다. 그래서 매번 스크린샷 픽셀을 추측하는 것보다 훨씬 정확하다.

용어
컴퓨터 유즈 (Computer Use)
AI 모델이 사람처럼 화면을 보고 마우스·키보드를 움직여 컴퓨터를 조작하는 능력. 2024년 Anthropic이 Claude에 도입하며 대중화됐다. 핵심 어려움은 두 가지다 — (1) 화면을 어떻게 "이해"시키나(스크린샷 픽셀만 줄지, 접근성 트리를 줄지), (2) 동작이 실제로 먹혔는지 어떻게 확인하나. pi-computer-use는 픽셀 대신 접근성 트리를 1차 근거로 삼고, 동작 후 결과를 반드시 검증하는 쪽을 택했다.

pi-computer-use의 정체성은 "Pi의 확장"이라는 데 있다. Pi는 @earendil-works/pi-coding-agent라는 이름의 코딩 에이전트 런타임인데, pi-computer-use는 여기에 pi.registerTool(...)로 컴퓨터 유즈 도구 11개를 등록한다. 즉 이 저장소 자체는 "에이전트"가 아니라 "에이전트에게 손발을 붙이는 부품"이다. 덕분에 프로젝트 범위가 명확하다 — 모델을 만들거나 프롬프트를 설계하는 게 아니라, 관측·행동·검증이라는 세 동작을 얼마나 정확하고 안전하게 제공하느냐에만 집중한다.

2왜 주목받는가

별 137개 · 흔한 "스크린샷+클릭" 도구와 무엇이 다른가

컴퓨터 유즈 도구는 이미 여럿 있다. 대부분은 스크린샷을 찍어 모델에게 이미지로 주고, 모델이 "여기 클릭"이라고 좌표를 뱉으면 그 좌표를 클릭하는 방식이다. 단순하지만 약점이 크다 — 화질·배율·폰트에 따라 좌표가 흔들리고, 클릭이 실제로 먹혔는지 확인할 방법이 없다. pi-computer-use는 이 두 약점을 정면으로 겨냥한다.

2-1. 접근성 트리 우선, 픽셀은 최후의 수단

방식화면을 어떻게 주나한계
스크린샷 + 좌표이미지 픽셀만좌표가 배율·테마에 취약, 버튼 "이름"을 모름, 결과 검증 불가
OCR 기반이미지에서 글자 추출글자는 읽지만 "이게 누를 수 있는 버튼인지"는 모름
pi-computer-use접근성 트리(AX/UIA)를 글자 아웃라인으로, 필요시 이미지·OCR 보강네이티브 헬퍼를 OS별로 구현해야 함(그게 이 저장소의 무게)
핵심 차이
"픽셀을 추측"이 아니라 "구조를 관측"

접근성 트리는 OS가 스크린리더(시각장애인용 화면 낭독기)를 위해 이미 만들어 둔 화면의 의미 구조다. "여기 좌표에 뭔가 있다"가 아니라 "이건 이름이 저장인 버튼이고 누를 수 있다"를 알려준다. pi-computer-use는 이걸 1차 근거로 삼아, 좌표 클릭은 접근성 요소가 없는 그림 영역(pictureOnly)에만 예외적으로 쓴다.

2-2. "먹혔다고 우기지 않는" 정직한 결과

이 프로젝트가 진짜 공들인 지점이다. act_ui는 동작을 보낸 뒤 결과를 worked(먹힘) / didnt(안 먹힘) / unknown(모름) 세 가지로 정직하게 보고한다. "이벤트를 보냈다"는 사실만으로는 성공이라고 절대 치지 않는다. 버튼을 눌렀는데 앱이 그 이벤트를 씹었을 수도 있으니, 원하면 expect로 "누른 뒤 '저장됨'이라는 글자가 뜰 것"이라는 사후조건(postcondition)을 붙일 수 있고, 그게 확인돼야만 성공 처리된다.

일상 비유

택배 기사가 초인종을 눌렀다고 해서 "배송 완료"가 아니다. 문이 열리거나 부재중 표시가 확인돼야 상황이 끝난 것이다. pi-computer-use의 expect가 바로 이 "문이 열렸는지 확인"이다. 이벤트를 보낸 것(초인종)과 결과가 바뀐 것(문 열림)을 엄격히 구분한다.

2-3. 두 OS를 "같은 규칙"으로 묶은 설계

macOS는 Swift로 AX(Accessibility)와 ScreenCaptureKit를, Windows는 Rust로 UIA(UI Automation)를 쓴다. 내부 구현은 완전히 다르지만, 이 저장소는 두 헬퍼가 똑같은 불변식(invariant) 집합을 지키도록 강제한다. 헬퍼는 시작할 때 자기가 구현한 불변식 목록과 architectureVersion을 보고하고, 필수 불변식이 빠지면 기동 자체가 실패(fail closed)한다. "OS별로 동작이 미묘하게 다른" 흔한 함정을 구조로 막은 것이다.

또 하나 눈에 띄는 건 테스트·검증 스크립트의 밀도다. scripts/ 폴더에 스키마 검사, 세션 생명주기 검사, 런타임 동시성 검사, 출력 상한 검사, 커밋 메시지 검사, 서명 검사까지 10여 개의 자체 검증 러너가 있고, pi-cubench-agent.mjs라는 벤치마크(Cubench)로 두 OS에서 같은 블랙박스 속성을 돌린다. 별 137개짜리 신생 프로젝트치고 엔지니어링 규율이 상당히 촘촘하다는 신호다.

3기술 스택 전체 지도

TypeScript 오케스트레이션 · Swift/Rust 네이티브 헬퍼 · CDP 브라우저

3-1. 브리지 계층 (TypeScript, src/)

에이전트와 네이티브 헬퍼 사이를 잇는 두뇌다. 모델의 의도를 해석하고, 상태를 저장·복원하고, 자원 스케줄링을 담당한다. 실제 그라운딩(어디를 누를지 확정)과 입력 전달은 네이티브 헬퍼에 맡긴다.

모듈줄 수역할
bridge.ts2,283도구 실행 코디네이터 + 자원 스케줄링. 11개 도구의 execute* 진입점
outline.ts651완전한 UI 트리를 파싱·질의(접힘/펼침, 검색 랭킹)
cdp.ts658Chrome DevTools Protocol로 브라우저 페이지를 데스크톱과 같은 아웃라인으로 변환
note.ts195에이전트에게 주는 프롬프트 스니펫·가이드라인 문구
view.ts147공개 ref(@r/@e) 안정화 + 전체뷰/변경뷰 렌더링
state.ts146불변 UI 상태 저장·복원·직렬화, 요청-로컬 상태
output.ts134모델 출력 상한(48KiB/2000줄) + @o 연속 읽기
actions.ts130동작 검증·정규화·타깃 해석·의존 포커스·안전 재시도 판정
runtime.ts129요청 1건의 실행 파이프라인(상태 로드→준비→실행→관측→저장)
config.ts·permissions.ts113·111설정 병합(파일+env) / 권한 상태 점검
platform/*OS별 관측·입력·네이티브 프로토콜 번역(플랫폼 중립 seam)
용어
브리지(bridge)와 헬퍼(helper)
이 저장소에서 브리지는 TypeScript로 된 조율 계층(모델 의도 해석·상태 관리)이고, 헬퍼는 각 OS에서 실제로 화면을 캡처하고 입력을 쏘는 네이티브 실행파일이다. 브리지는 "무엇을 할지"를 정하고, 헬퍼는 "실제로 그 일을 하고 증거를 가져온다". 둘은 소켓(macOS)이나 라인 프로토콜(Windows)로 통신한다.

3-2. macOS 네이티브 헬퍼 (Swift, native/macos/)

핵심은 bridge.swift 3,544줄 단일 파일이다. AXRefStore(접근성 요소 참조 저장), LookNode(관측 트리 노드), Bridge(소켓 서버 본체), InputSuppressionGuard(입력 억제 가드)가 들어 있다. 화면 캡처는 ScreenCaptureKit, UI 구조는 AX(Accessibility) API, 유령 커서 오버레이는 AppKit으로 그린다(agent_cursor.swift, agent_cursor_motion.swift). 소켓 서버는 메인 스레드 밖에서 돌고, AppKit이 메인 런루프를 소유한다.

3-3. Windows 네이티브 헬퍼 (Rust, native/windows/bridge-rs/)

파일줄 수역할
main.rs1,683라인 프로토콜 서버, 요청 디스패치, 물리 입력 락, 워커 상태
uia.rs1,252UIA(UI Automation) 트리 추출·경계 있는 탐색
window.rs718창 열거·루트 스냅샷·이벤트 저널(SetWinEventHook)
capture.rs518화면 캡처
input.rs501마우스·키보드 입력 전달
refs.rs·state.rs·protocol.rs·error.rs237·55·53·81ref 관리·헬퍼 상태·프로토콜·에러

Windows 헬퍼는 macOS의 TCC 권한 흐름이나 헬퍼 앱을 쓰지 않고, 대화형 데스크톱 세션에서 UIA로 바로 동작한다. 고정 크기 워커 풀을 쓰고 각 워커 스레드마다 UIA를 초기화하며, 물리 입력이 필요한 배치만 전역 입력 락을 잡는다.

3-4. 언어·의존성 요약

영역내용
언어 비중TypeScript 65% · Swift 30% · JavaScript 5% (+ Windows용 Rust)
런타임Node.js 20.6+ (TS를 --experimental-strip-types로 직접 실행)
피어 의존성@earendil-works/pi-ai, @earendil-works/pi-coding-agent, typebox(도구 스키마)
브라우저Chrome DevTools Protocol(CDP) — chrome 또는 helium 관리 브라우저
배포npm(@injaneity/pi-computer-use) + prebuilt 헬퍼 바이너리 동봉, 없으면 첫 실행 시 로컬 빌드
라이선스MIT

4아키텍처 심화 분석

에이전트의 한 번의 도구 호출이 화면 조작으로 이어지기까지

이 장이 문서의 심장이다. pi-computer-use의 아키텍처는 한 문장으로 요약된다 — "루트를 찾고 → 한 루트를 관측하고 → 그 관측 상태 안에서 검색/펼침/조사하고 → 그 상태로부터 행동한다." 여기서 반복해서 등장하는 세 가지 참조를 먼저 익히면 나머지가 쉽다.

참조
@r1 (root)제어 가능한 UI 루트 하나 — 데스크톱 창, 메뉴/시트 같은 임시 표면, 또는 CDP 브라우저 페이지
@e12 (element)한 관측 상태 안의 UI 요소 하나 — 버튼·텍스트필드 등. 그 상태의 stateId에만 속함
@o1 (output)너무 길어 잘린 텍스트의 연속 읽기용 불변 참조. UI 상태가 아니라 세션-로컬
stateId관측 한 번의 결과에 붙는 도장. 모든 @e 참조는 자기 stateId와 함께 써야 함

4-1. 전체 흐름 — 요청 하나의 생애

[에이전트] [브리지 (TypeScript)] [네이티브 헬퍼] macOS: AX + ScreenCaptureKit Windows: UIA + capture/input │ │ ① find_roots ┌───────────────────────────────┐ │─────────────────────────▶│ 제어 가능한 루트 랭킹 → @r 목록 │──▶ 창/페이지 열거 │◀─── @r1 @r3 @r8 ─────────└───────────────────────────────┘ │ │ ② observe_ui(root:@r1) ┌───────────────────────────────┐ │─────────────────────────▶│ 한 루트 관측 요청 │──▶ 접근성 트리 + (필요시) │ │ 완전한 불변 상태 저장 │ 이미지/OCR 캡처 │◀── 접힌 아웃라인 + stateId└───────────────────────────────┘ │ @e1 문서 @e7 버튼 … │ │ ③ search_ui / expand_ui / inspect_ui ← 전부 "캐시된 상태" 질의 (헬퍼 안 감) │─────────────────────────▶│ 저장된 완전한 트리에서 검색·펼침 │ │◀── 랭킹된 매치, 지역 펼침 └───────────────────────────────┘ │ │ ④ act_ui(stateId, actions:[press @e7], expect:{text:"저장됨"}) │─────────────────────────▶│ 상태 로드 → 에포크 확인 │ │ │ → 자원 레인 획득 → 헬퍼에 전송 │──▶ 그라운딩 → 사전점검 │ │ │ → 입력 전달 → 검증 │◀── 후속 상태 diff + 새 stateId ─────────────────────────────┘ → 루트 델타 정착 │ (worked / didnt / unknown + 증거)
용어
멀티루트 포레스트 (multi-root forest)
화면에는 여러 개의 독립된 UI 루트가 동시에 존재한다 — 여러 앱의 창, 팝업 메뉴, 브라우저 탭 등. pi-computer-use는 이들을 하나의 "숲(forest)"으로 보고 각각에 @r 참조를 준다. 세션 전체를 관통하는 "지금 보고 있는 창" 같은 전역 상태가 없다는 게 핵심이다. 매 호출이 자기 stateId로부터 필요한 상태를 새로 불러오므로, 서로 다른 호출이 실수로 상대를 덮어쓸 수 없다.

4-2. 핵심 설계 패턴 ① — 불변 상태 + 에포크로 stale-write 막기

가장 독창적인 부분이다. 일반적인 자동화 도구는 "현재 화면"이라는 가변(mutable) 전역 상태를 두고 매번 갱신한다. 그런데 Pi는 도구 호출을 동시에(concurrently) 날릴 수 있다. 이러면 A 호출이 화면을 바꾸는 사이 B 호출이 옛 화면 기준으로 클릭해 버리는 경합(race)이 생긴다.

pi-computer-use의 해법은 "현재 화면을 아무도 공유하지 않는다"이다. 관측 결과는 전부 불변(immutable)으로 저장되고, 각 물리 자원(프로세스·CDP 타깃)에는 단조 증가하는 에포크(epoch)가 붙는다. 화면을 바꾸는 호출은 자기 상태가 캡처한 에포크를 반드시 제시해야 하고, 그 사이 다른 쓰기가 이겼으면 오래된 호출은 전송 전에 stale 에러로 거부된다.

일상 비유

공유 문서를 여럿이 편집할 때의 버전 충돌 감지와 똑같다. 내가 3번 버전을 보고 수정하려는데 그새 누가 4번으로 올려놨으면, 시스템이 "당신은 옛 버전 기준입니다. 다시 불러오세요"라고 막는다. 조용히 덮어써서 남의 변경을 날리는 것보다, 명시적으로 거부하는 편이 안전하다. 에포크가 바로 이 버전 번호다.

4-3. 핵심 설계 패턴 ② — 자원별 스케줄링(전체가 아니라 자원 단위로 직렬화)

동시성을 무조건 막으면 느리다. pi-computer-use는 같은 물리 자원을 건드리는 라이브 동작만 순서대로 처리하고, 서로 다른 자원은 병렬로 흘려보낸다.

스케줄링 키규칙
데스크톱프로세스(pid) 단위로 직렬화. 창이 아니라 프로세스인 이유: 접근성 포커스·메뉴·물리 입력이 한 앱의 창 경계를 넘나들기 때문
브라우저CDP 페이지 타깃 단위로 직렬화
캐시 질의search_ui·expand_ui·inspect_ui는 스케줄러를 완전히 우회(저장된 불변 상태만 읽으므로 얼마든 겹쳐도 안전)
물리 입력전역 물리 입력(HID)은 네이티브 헬퍼에서 뮤텍스로 보호. 의미론적 AX/UIA 작업은 플랫폼이 허용하면 겹칠 수 있음

4-4. 핵심 설계 패턴 ③ — 점진적 공개(progressive disclosure)

큰 앱의 접근성 트리는 수천 개 노드로 폭발한다. 이걸 통째로 모델에게 주면 토큰이 낭비되고 정확도도 떨어진다. 그래서 observe_ui는 처음에 접힌(folded) 요약뷰만 준다. 세부는 필요할 때만 캐시된 완전한 트리에서 꺼낸다.

// 1) 처음엔 컴팩트한 관측
observe_ui({ root: "@r1" })

// 2) 캡처를 새로 하지 않고 저장된 상태만 질의
search_ui({ stateId, text: "저장" })       // 텍스트/역할/능력으로 랭킹 검색
expand_ui({ stateId, ref: "@e7", depth: 3 }) // 한 요소 주변만 펼침
inspect_ui({ stateId, ref: "@e12" })         // 필드·좌표·능력·증거 자세히

관측 모드는 3단계다 — semantic(접근성만, 가장 쌈), fused(기본, 자동 선택), visual(이미지·OCR 강제). 검색은 정확→접두→부분 일치를 보수적 퍼지 매치보다 우선해 랭킹하고, 고정된 상위 집합 + 총 매치 수를 돌려주며, 검색이 너무 넓으면 "좁혀서 다시 검색하라"고 요구한다(매치를 페이지 넘김하며 훑지 않는다).

4-5. 핵심 설계 패턴 ④ — 행동, 배치, 그리고 정직한 결과

act_ui의존적인 동작 목록 하나를 받는다. 배열 길이가 1이면 단일 동작, 여러 개면 한 트랜잭션이다. 단, 배치는 "중간 관측이 필요 없을 때만" 허용된다 — 두 번째 동작이 첫 번째의 결과를 봐야 한다면 한 개씩 보내고 관측해야 한다.

// 편집 영역 클릭 → 그 포커스로 타이핑 (ref 생략 = 방금 만든 포커스에 입력)
act_ui({
  stateId,
  actions: [
    { action: "click", x: 420, y: 300 },
    { action: "typeText", text: "hello" },
  ],
  expect: { text: "hello" },   // 이 글자가 떠야 성공
})
보안·정확성 설계 원칙
"이벤트 전달 ≠ 성공"

사후조건이 붙으면 헬퍼는 지정한 텍스트·역할이 나타날(또는 until:absent로 사라질) 때까지 기다린 뒤 성공을 보고한다. 검증 결과는 verified(새로 확인됨)·preexisting(이미 그 상태였음, 인과 증거 아님)·failed로 구분된다. 사후조건이 실패하면 실행 결과는 didnt로 바뀐다 — 이벤트를 보낸 것만으로는 절대 의미론적 성공으로 치지 않는다.

여기에 headless 플래그가 두 세계를 가른다. headless: true배경 경계가 엄격하다 — 앱을 앞으로 띄우지도, 사용자 포커스 창을 바꾸지도, 전역 커서를 움직이지도, 날 입력을 쏘지도 않는다. headless: false(기본)면 배경에서 의미론적 활성화를 시도하고, 검증에 실패한 부작용 없는 키보드 입력은 전경(foreground)으로 자동 승격해 재시도한다. 단 애매한 포인터 동작은 절대 맹목적으로 재시도하지 않는다(같은 클릭을 두 번 하면 두 번 눌릴 위험).

4-6. 후속 diff — "바뀐 것만" 보여주기

동작이 끝나면 화면 전체를 다시 주는 건 낭비다. view.ts는 동작 후 상태를 이전 상태와 비교해 신뢰할 수 있는 변경분(added/updated/removed)만 렌더링하고 다음 stateId를 준다. 요소가 확실히 매칭되면 모델이 쓰던 @e 참조를 그대로 유지한다.

Successor diff (1 change, S1 → S2): ~ @e9 (@e1 > @e9) value="hello" Use stateId S2 for subsequent actions and queries.

단, 루트 자체가 바뀌었거나(창이 통째로 교체) 매칭 신뢰도가 낮거나 변경분이 너무 많으면 접힌 전체뷰로 폴백한다. 어느 경우든 search_ui 등 캐시 질의는 언제나 저장된 완전한 상태를 본다 — 부분 적용된 모델측 트리가 아니라.

4-7. 브라우저 지원 — 페이지도 그냥 하나의 루트다

브라우저 페이지는 별도의 "두 번째 세계"가 아니라 포레스트의 또 다른 @r 루트다. launch_browser가 관리형 CDP 브라우저를 띄워 페이지 상태를 돌려주면, observe_ui·act_ui·read_text·wait_for가 데스크톱과 똑같이 동작한다. CDP 접근성 트리를 데스크톱과 동일한 아웃라인 모양으로 변환하기 때문이다. navigate_browser·evaluate_browser만 CDP 전용이고, 나머지는 계약(contract)이 완전히 공유된다.

4-8. 플랫폼 드리프트 방지 — 소스가 아니라 불변식으로 통일

불변식 기반 파리티 · platform seam

macOS(AX·ScreenCaptureKit·AppKit 커서)와 Windows(UIA·Windows 캡처/입력)는 메커니즘이 다르다. 하지만 상태 소유권·경계·점진적 공개·트랜잭션 경계·정직한 결과는 달라선 안 된다. 각 헬퍼가 architectureVersion과 구현한 불변식 목록을 보고하고, 필수 불변식이 빠지면 기동이 실패한다. TypeScript 백엔드 인터페이스와 적합성 검사가 두 플랫폼에 같은 관측·텍스트 소유·배치·생명주기 연산을 강제한다.

유령 커서(ghost cursor) · agent_cursor

cursor_overlay: true면 비-headless macOS 배경 포인터 동작이 클릭-스루 유령 커서 애니메이션을 네이티브 그라운딩 지점으로 흘려보낸다 — 전달을 지연시키지 않으면서 "에이전트가 지금 여기를 누른다"를 시각적으로 보여준다. 이 오버레이는 헬퍼 안에 산다(그라운딩이 최종 화면 좌표를 소유하므로), 루트 탐색에서는 자기 자신을 제외하고, 애니메이션은 순수 관측용이라 렌더링이 동작 전달·검증을 절대 막지 않는다.

4-9. 경계 있는 출력

모델에게 보이는 모든 텍스트 결과는 48KiB 또는 2,000줄로 제한된다. 초과분은 16KiB 미리보기 + 좁히기 안내 + 불변 @o 연속 참조를 준다. read_text({ ref: "@o1", offset })로 이어 읽되, @o 오프셋은 UTF-8 바이트 단위, @e 텍스트 오프셋은 유니코드 문자 단위라는 미묘한 차이가 있다.

5디렉토리 구조 해부

어느 폴더가 무슨 일을 하는가
디렉토리 / 파일역할
extensions/computer-use.tsPi 확장 진입점(203줄). 11개 도구를 typebox 스키마로 정의하고 pi.registerTool로 등록, /computer-use 명령·세션 훅 연결
src/브리지 계층 전체 — 상태·행동·뷰·아웃라인·CDP·출력·설정·권한(3-1 표 참고)
src/platform/플랫폼 중립 seam. types.ts가 공용 타입(PlatformRoot·HelperActPerformed 등), macos/·windows/가 각 백엔드
src/contract.ts도구 파라미터·동작·조건의 순수 타입 계약 + 에이전트 도구 이름 집합
native/macos/Swift 헬퍼 — bridge.swift(3,544줄 본체)·agent_cursor*.swift(유령 커서)
native/windows/bridge-rs/Rust 헬퍼 — src/(UIA·창·캡처·입력) + tests/(프로토콜·refs·상태 단위 테스트)
prebuilt/macos/{arm64,x64}/bridge미리 컴파일된 macOS 헬퍼 바이너리(설치 시 ~/Applications로 복사)
scripts/빌드(build-native.mjs)·설치(setup-helper.mjs) + 10여 개 자체 검증 러너 + Cubench 벤치(pi-cubench-agent.mjs) + 서명 엔타이틀먼트
skills/computer-use/에이전트에게 "이 도구들을 어떻게 신뢰성 있게 쓰는지" 가르치는 스킬 파일
docs/architecture·usage·configuration·development·troubleshooting·windows-bridge 6종 문서
notes/v0.1.7~v0.4.3 릴리스 노트 아카이브(설계 진화 추적 가능)
.github/workflows/CI·npm-audit·publish-npm 파이프라인
구조 읽는 법

src/contract.tssrc/platform/types.ts가 이 코드베이스의 설계 감각을 보여준다. 계약(contract)을 먼저 순수 타입으로 못 박고, macOS·Windows 백엔드가 그 계약을 각자 구현하게 한다. 그래서 "TypeScript 브리지는 OS를 몰라도 되고, 네이티브 헬퍼는 모델을 몰라도 된다". 이 분리가 두 OS를 같은 규칙으로 묶는 힘의 원천이다 — native/windows에 단위 테스트가 따로 있는 이유이기도 하다.

6학습 포인트

실제 코드로 보는, 그리고 배울 수 있는 것들

6-1. 실제 코드 발췌

도구 등록 (extensions/computer-use.ts)

export default function computerUseExtension(pi: ExtensionAPI): void {
  for (const tool of [findTool, observeTool, searchUiTool, expandUiTool,
                     inspectUiTool, actTool, readTextTool, waitForTool,
                     launchBrowserTool, navigateBrowserTool, evaluateBrowserTool])
    pi.registerTool(tool);

  pi.on("session_start", async (_e, ctx) => {
    loadComputerUseConfig(ctx.cwd);
    reconstructStateFromBranch(ctx);          // 이전 대화 분기에서 상태 복원
    if (ctx.hasUI) await ensureComputerUseSetup(ctx); // 권한 셋업 안내
  });
}

동작 스키마 — 참조 클릭 vs 좌표 클릭 (typebox 유니온)

const clickByRef   = Type.Object({ action: Type.Literal("click"), ref: Type.String(), … });
const clickByPoint = Type.Object({ action: Type.Literal("click"), x: Type.Number(), y: Type.Number(), … });
const uiAction = Type.Union([
  Type.Object({ action: Type.Literal("press"),   ref: Type.String() }),
  clickByRef, clickByPoint,
  Type.Object({ action: Type.Literal("setText"), ref: Type.String(), text: Type.String() }),
  Type.Object({ action: Type.Literal("typeText"),ref: Type.Optional(Type.String()), text: Type.String() }),
  // keypress, scroll, drag, moveMouse …
]);

사후조건이 붙는 검증형 행동 (docs/usage.md)

act_ui({
  stateId,
  actions: [{ action: "press", ref: "@e12" }],
  expect: { text: "Archive completed", until: "present", timeoutMs: 3000 }
})
// 결과: verified(새로 확인) / preexisting(이미 그랬음) / failed
// 앱이 이벤트를 씹으면 → didnt + postcondition_failed

설정 우선순위 — 기본값 < 파일 < 환경변수 (src/config.ts)

const DEFAULT_CONFIG = { browser_use: true, headless: false,
                        cursor_overlay: true, managed_browser: "chrome" };
// ~/.pi/.../pi-computer-use.json → cwd/.pi/computer-use.json → PI_COMPUTER_USE_* env
for (const source of sources) if (source.values) Object.assign(config, source.values);
Object.assign(config, env); // env가 최종 승자

6-2. 이 저장소에서 배울 수 있는 기술

주제구체적으로 무엇을
접근성 API 활용macOS AX·Windows UIA로 화면 구조를 트리로 추출하는 저수준 패턴. 스크린리더가 쓰는 그 API를 자동화에 재활용
불변 상태 + 에포크가변 전역 대신 불변 스냅샷을 저장하고, 자원별 에포크로 동시 쓰기 경합을 전송 전에 거부하는 설계
자원 단위 스케줄링전체 직렬화도 완전 병렬도 아닌, "같은 물리 자원만 순서 보장" 스케줄러 만들기
점진적 공개거대한 트리를 접힌 요약 + 지역 펼침으로 다뤄 토큰·정확도를 지키는 LLM 친화 인터페이스
정직한 결과 계약"이벤트 전달"과 "의미론적 성공"을 분리하고 worked/didnt/unknown으로 보고하는 API 설계
크로스 플랫폼 파리티소스 구조가 아니라 불변식·architectureVersion·적합성 테스트로 두 OS 동작을 통일
typebox 도구 스키마LLM 도구 파라미터를 유니온·리터럴로 엄격히 정의하고 프롬프트 스니펫을 함께 붙이기
네이티브↔JS 브리지Swift 소켓 서버 / Rust 라인 프로토콜을 요청 id로 다중화하고 메인 런루프와 공존시키기

7시스템 요구사항

무엇이 있어야 돌아가나
항목요구사항
Pi 런타임@earendil-works/pi-coding-agent(Pi 코딩 에이전트). 이 패키지는 그 위의 확장이다
Node.js20.6+ (TypeScript를 컴파일 없이 --experimental-strip-types로 직접 실행)
macOSmacOS 14+ · 손쉬운 사용 + 화면 기록 권한을 헬퍼 앱에 부여 필요. 헬퍼는 ~/Applications/pi-computer-use.app에 사용자별 설치
Windows대화형 데스크톱 세션. UIA 사용, macOS식 TCC 권한 흐름·헬퍼 앱 불필요
브라우저(선택)CDP 대상 브라우저 — chrome 또는 helium (browser_use가 켜져 있을 때)

설치

# npm 배포판
pi install npm:@injaneity/pi-computer-use

# 또는 GitHub에서 직접 (전역/프로젝트-로컬)
pi install git:github.com/injaneity/pi-computer-use
pi install -l git:github.com/injaneity/pi-computer-use

설정 (선택)

# 환경변수로 오버라이드 (파일 설정보다 우선)
export PI_COMPUTER_USE_HEADLESS=true         # 배경 전용, 사용자 포커스 안 건드림
export PI_COMPUTER_USE_CURSOR_OVERLAY=false  # 유령 커서 끄기
export PI_COMPUTER_USE_MANAGED_BROWSER=helium

# Pi 안에서 현재 설정 확인
/computer-use
운영 주의
권한 = 전체 화면 캡처 + 임의 입력

macOS 헬퍼에 부여하는 손쉬운 사용·화면 기록 권한은 곧 헬퍼가 화면 전체를 보고 어떤 앱에든 클릭·타이핑할 수 있다는 뜻이다. 신뢰할 수 없는 프롬프트를 처리하는 에이전트에 이 확장을 붙일 때는 특히 조심해야 한다 — 프롬프트 인젝션이 성공하면 실제 데스크톱을 조작당할 수 있다. 민감한 작업 화면(뱅킹·비밀번호 관리자 등)이 떠 있을 때는 headless로도 화면 관측은 되므로, 필요 없다면 확장을 꺼두는 게 안전하다. 프로젝트는 v0.5.0으로 활발히 바뀌는 중이니 버전 고정을 권한다.

8직접 해볼 수 있는 실습 과제

난이도별 — 컴퓨터 유즈 개념을 손으로 익히기
난이도 ★ · 입문

접근성 트리를 눈으로 보기 30분

이 저장소를 설치하지 않아도 된다. macOS라면 Accessibility Inspector(Xcode 도구)를, Windows라면 Accessibility Insightsinspect.exe를 켜서 아무 앱 위에 올려보자. 버튼·텍스트필드가 이름·역할·좌표를 가진 트리로 드러나는 걸 직접 확인하면, pi-computer-use의 observe_ui가 무엇을 하는지 한 번에 이해된다. "픽셀이 아니라 구조"라는 문장이 몸에 들어온다.

난이도 ★★ · 초급

도구 스키마 읽고 흐름 그려보기 반나절

extensions/computer-use.tssrc/contract.ts만 읽고, 11개 도구가 각각 어떤 파라미터를 받고 무엇을 돌려주는지 표로 정리한다. 그다음 "메모장을 열어 제목을 입력하고 저장" 시나리오를 find_roots → observe_ui → search_ui → act_ui(+expect) 호출 시퀀스로 직접 써본다. 실제 실행 없이 인터페이스만으로 워크플로를 설계하는 연습이다.

난이도 ★★★ · 중급

불변 상태 + 에포크 미니 구현 1~2일

src/state.ts의 아이디어만 떼어내, 언어 무관하게 "관측 스냅샷을 불변으로 저장하고, 자원별 에포크를 붙여, 오래된 에포크의 쓰기를 거부하는" 작은 상태 저장소를 직접 만든다. 동시 요청 두 개를 흉내 내 한쪽이 stale 에러로 거부되는 걸 재현하면, 이 프로젝트의 핵심 안전장치를 코드로 소유하게 된다.

난이도 ★★★★ · 심화

접근성 트리 → 아웃라인 직렬화기 만들기 2~3일

macOS AX나 Windows UIA(또는 CDP Accessibility 도메인)로 한 창의 트리를 읽어, outline.ts처럼 접힌 요약 + 지역 펼침이 되는 텍스트 아웃라인으로 직렬화한다. 노드마다 역할·이름·능력(canPress/canSetValue)을 붙이고, 검색을 정확>접두>부분 순으로 랭킹해보면 점진적 공개가 왜 토큰과 정확도를 동시에 지키는지 체감한다.

난이도 ★★★★★ · 도전

사후조건 검증형 클릭 엔진 1주

실제 앱을 대상으로, "버튼을 누른 뒤 특정 텍스트가 나타날 때까지 기다렸다가 worked/didnt/unknown을 반환"하는 최소 실행기를 만든다. 이벤트 전달과 결과 변화를 엄격히 분리하고, 부작용 없는 실패만 전경 재시도하되 애매한 포인터 동작은 재시도하지 않는 규칙을 구현하면, 컴퓨터 유즈에서 가장 어려운 "정직한 결과" 문제를 정면으로 다루게 된다.

9심화 학습 로드맵

이 저장소를 발판으로 4주
주차주제무엇을
1주차접근성 API 기초macOS AX / Windows UIA / 웹 ARIA·CDP Accessibility 도메인. 트리·역할(role)·능력(action)·좌표(rect) 개념 잡기. Accessibility Inspector로 실습
2주차LLM 도구 설계typebox/JSON Schema로 도구 파라미터 정의, 프롬프트 스니펫·가이드라인, 경계 있는 출력(토큰 상한·연속 읽기). MCP 도구 설계 원칙과 비교
3주차동시성·상태 관리불변 스냅샷, 에포크/버전 벡터, 자원 단위 직렬화, stale-write 거부. 낙관적 동시성 제어(OCC) 이론과 연결
4주차컴퓨터 유즈 & 에이전트 보안Anthropic Computer Use, 스크린리더 자동화의 윤리·안전, 프롬프트 인젝션이 데스크톱 제어와 만날 때의 위험, headless 경계·fail-closed 설계

10핵심 키워드 사전

이 문서에 나온 용어 총정리
용어
접근성 트리 (Accessibility Tree)
OS가 스크린리더를 위해 제공하는 화면의 의미 구조. 각 노드가 역할(버튼·텍스트필드), 이름, 능력, 좌표를 가진다. macOS는 AX, Windows는 UIA, 웹은 ARIA로 노출한다. pi-computer-use가 픽셀 대신 1차 근거로 삼는 데이터다.
용어
AX / UIA
AX(Accessibility)는 macOS의 접근성 API, UIA(UI Automation)는 Windows의 접근성 API. 이름은 다르지만 둘 다 "화면 요소를 트리로 읽고 조작"하는 같은 목적의 인터페이스다. 이 저장소는 두 API를 각각 Swift·Rust로 감싸 같은 계약으로 노출한다.
용어
멀티루트 포레스트
여러 앱 창·메뉴·브라우저 페이지를 하나의 "숲"으로 보고 각각에 @r 참조를 주는 모델. "지금 보는 창"이라는 전역 상태를 두지 않아, 동시 호출이 서로 간섭하지 않는다.
용어
에포크 (epoch)
각 물리 자원(프로세스·CDP 타깃)에 붙는 단조 증가 버전 번호. 화면을 바꾸는 호출은 자기 상태의 에포크를 제시해야 하고, 그새 다른 쓰기가 이겼으면 오래된 호출은 stale로 거부된다. 조용한 덮어쓰기를 막는 안전장치.
용어
점진적 공개 (Progressive Disclosure)
거대한 UI 트리를 한 번에 다 주지 않고, 처음엔 접힌 요약만 준 뒤 필요한 부분만 펼쳐 보여주는 방식. 토큰 낭비와 정확도 저하를 동시에 막는 LLM 친화 인터페이스 설계 원칙.
용어
사후조건 (postcondition) · expect
동작 후 "이 텍스트가 떠야 한다" 같은 성공 판정 기준. act_uiexpect로 붙이면, 그 조건이 확인돼야만 성공(worked)으로 친다. verified/preexisting/failed로 세분해 "이벤트 전달"과 "실제 결과 변화"를 구분한다.
용어
headless 모드
배경 전용 실행 경계. headless: true면 앱을 앞으로 띄우거나, 사용자 포커스·전역 커서를 바꾸거나, 날 입력을 쏘지 않는다. 사용자가 다른 일을 하는 중에도 방해 없이 화면을 관측·조작(가능한 범위에서)하려는 설계.
용어
CDP (Chrome DevTools Protocol)
크롬 계열 브라우저를 프로그램으로 제어하는 프로토콜. pi-computer-use는 CDP의 Accessibility 도메인을 읽어 브라우저 페이지를 데스크톱 창과 똑같은 아웃라인 모양으로 변환한다 — 그래서 브라우저도 그냥 하나의 @r 루트가 된다.
용어
fail closed (안전하게 실패)
결과가 불확실하면 "성공한 셈 치자"가 아니라 "실패로 간주하고 멈추자"를 택하는 원칙. pi-computer-use는 동작 결과가 애매하면 unknown/didnt로 보고하고, 필수 불변식이 빠진 헬퍼는 기동 자체를 거부한다.
용어
유령 커서 (ghost cursor)
에이전트가 배경에서 클릭할 때, 사용자의 실제 커서를 뺏지 않으면서 "지금 여기를 누른다"를 보여주는 클릭-스루 오버레이 애니메이션. macOS에서 AppKit으로 헬퍼 안에 그린다. 순수 관측용이라 동작 전달·검증을 지연시키지 않는다.

11참고 링크

더 파고들 곳