콘텐츠로 이동

외부 앱의 터미널 자동화

English / 전체 API 계약

v0.7.0 — 비공유 외부 실행 후 같은 세션을 공유할 수 있습니다. macOS Apple Silicon과 Linux D-Bus 실행·입력 어댑터는 v0.6.0부터 제공됩니다. v0.6.0 배포본은 외부 세션을 끝까지 비공유로 유지하며, 같은 PTY의 공유 전환은 v0.7.0에서 추가되었습니다. Windows 외부 어댑터는 아직 없습니다. 새 공유 흐름의 네이티브 검증은 이전 실행·입력 테스트와 별도로 수행해야 합니다.

설정 → 자동화에서 외부 앱이 사용할 프로필을 선택한 뒤 외부 자동화를 켭니다. 기본값은 꺼짐입니다. 이전 어댑터에서 업그레이드하면 프로필 선택은 유지하지만 직접 한 번 다시 켜야 합니다(설정 메타데이터의 requiresReenable). 이전 승인만으로 명령별 검토 없는 직접 실행을 허용하지 않습니다.

이 허용은 로컬 AppleScript 호출자가 선택한 로컬 프로필을 이용해 지정 프로그램을 실행할 수 있다는 뜻이며, 실행 파일 허용 목록이 아닙니다. 허용된 외부 작업은 에이전트 제어 승인·제안 수락·명령 정책 승인·Grace를 거치지 않습니다. 일반 세션에서 실제 AI 에이전트가 수행하는 작업에는 기존 모드·정책·사람 우선 제어가 계속 적용됩니다.

macOS에서도 발신 앱이 Conn을 제어해도 되는지 물을 수 있습니다. 소유자는 앱 표시 이름이 아니라 네이티브 발신자와 프로세스 수명으로 구분합니다. 생성·입력·상태 조회·반환은 같은 발신 프로세스에서 실행해야 합니다. osascript를 따로 실행하면 이전 프로세스의 세션 핸들을 재사용할 수 없습니다. 런처가 osascript를 사용하면 Conn이 식별하는 발신자는 그 중간 실행 프로세스입니다.

tell application "Conn"
create window with default profile command "/bin/sh -c '__RUN_COMMAND__ ; echo \"Press [Enter] key to exit.\"; read ANSWER;'"
end tell

전체 새 창 템플릿. __RUN_COMMAND__는 외부 앱이 치환합니다. Conn이 확장하거나 치환하지 않습니다. 로컬에서 시험할 때는 pwd로 바꿉니다.

  • command는 해당 세션의 프로필 프로그램과 인자를 대체해 새 PTY에서 직접 실행합니다. 먼저 기본 셸을 연 뒤 프롬프트에 입력하는 방식이 아닙니다.
  • 허용한 로컬 프로필의 작업 폴더와 환경은 유지합니다. 일회성 프로그램·인자는 프로필 파일이나 기본값에 다시 저장하지 않습니다.
  • POSIX 단어·인용부호·이스케이프를 해석해 argv를 구성합니다. 변수 확장·명령 치환· glob·암묵적인 셸은 사용하지 않습니다. 잘못된 인용부호와 인용하지 않은 셸 연산자는 원문 없는 오류로 거부합니다. 셸 문법이 필요하면 /bin/sh -c '…'를 명시합니다.
  • 명령 대체는 로컬 macOS 프로필만 지원합니다. SSH·WSL·Docker 프로필에 명령을 덧씌워 실행 위치를 추측하지 않습니다.
  • 창의 비공유 터미널 화면이 준비된 뒤 자식 프로세스를 실행합니다. command를 생략하면 허용된 기본 프로필을 비공유 상태로 시작합니다.
  • 성공 응답은 프로세스 시작을 뜻하며 명령 완료나 인증 성공을 뜻하지 않습니다. 응답을 잃었어도 실행됐을 수 있으므로 결과가 불분명한 생성 요청을 자동 재시도하지 않습니다.
  • 예제의 안내와 Enter 대기는 echo·read가 담당합니다. Enter 이후 /bin/sh가 종료될 수 있으며, 종료된 터미널 화면은 사람이 닫을 때까지 유지됩니다. 다른 기본 셸을 다시 실행하지 않습니다.
  • 창을 닫으면 그 자식 프로세스와 입력 큐를 정리합니다. 다른 창은 유지됩니다.

반환값은 불투명한 세션 핸들이며 창 객체가 아닙니다. 아래 명령만 지원하며 현재 세션 객체 지정이나 분할 패널 객체 모델은 구현하지 않았습니다.

호출자가 만든 비공유 세션에 입력

섹션 제목: “호출자가 만든 비공유 세션에 입력”
tell application "Conn"
activate
set sessionID to create session
set requestID to write text "pwd" to session sessionID
-- 전체 예제처럼 request state로 전달 상태와 시간 초과를 확인합니다.
end tell

전체 입력·상태 조회 예제. osascript external-connect.applescriptpwd를 입력합니다. 인자로 터미널 입력 한 줄을 전달할 수도 있습니다. 기본 프로필이 허용 목록에 있어야 합니다.

write text는 전용 외부 입력 경로를 사용합니다. Conn은 이를 명령으로 복원하거나 에이전트 정책으로 분석하거나 자체 에코를 추가하지 않습니다. 자식 프로세스가 정상 터미널 동작으로 입력을 에코할 수는 있습니다. 입력은 순서대로 전달하며 호출자가 만든 정확한 비공유 세션에 고정됩니다. 탭을 바꿔도 대상은 바뀌지 않습니다.

PTY가 준비됐다는 사실은 비밀번호 프롬프트나 인증 완료를 뜻하지 않습니다. 다음 입력을 언제 보낼지는 외부 앱이 판단합니다.

명령 결과
create window with default profile [command "프로그램과 인자"] 새 비공유 창을 만들고 프로세스 시작 후 불투명한 세션 핸들 반환
create session [profile "프로필-ID"] 허용된 프로필로 비공유 세션 생성. 생략하면 기본 프로필 사용
write text "입력" to session id [newline true] [intent "사유"] [request id "고유-ID"] 입력 한 줄과 선택적인 Return을 큐에 넣고 요청 ID 반환. intent는 구문 호환만을 위해 받으며 사용하지 않고 버림
request state requestID 요청 상태 문자열
request status requestID 메타데이터만 있는 JSON: requestId, 불투명한 session, state, 선택적인 일반 오류 코드 error
session status sessionID 메타데이터만 있는 JSON: 불투명한 session, processAlive, shared: false, externalOrigin: true, inputAvailable
cancel request requestID 취소 결과. 해당 세션의 외부 입력 권한을 해제하고 남은 입력 큐 취소
release session sessionID 외부 입력 권한 해제. 터미널은 사람이 사용할 수 있도록 유지

입력 상태는 queued, delivering, delivered, cancelled, failed입니다. delivered는 PTY가 입력을 받았다는 뜻이며 명령·접속 성공이 아닙니다. 승인·제안· Grace 상태는 없습니다. detail.proposed, 원문 오류, 입력 내용, 의도, 경로와 터미널 출력도 상태 응답에 포함하지 않습니다.

요청 하나는 NUL·내장 제어 문자가 없는 UTF-8 한 줄, 최대 16 KiB입니다. Return은 newline 옵션으로 보냅니다. 같은 앱 실행에서 명시한 요청 ID와 내용·옵션이 같으면 기존 요청을 반환하며, 다른 내용으로 ID를 재사용하면 거부합니다. 중복 비교에는 실행 중에만 존재하는 키 기반 다이제스트를 사용하며 원문이나 비밀번호 해시를 저장하지 않습니다. 충돌·시간 초과로 결과를 모를 때 자동 재실행하지 않습니다.

한 세션의 대기 입력은 16건, 살아 있는 소유자 연결은 32개, 앱 실행당 요청 ID는 256개, 큐 대기를 포함한 입력 기한은 120초입니다. 완료된 조회 항목은 메타데이터만 남깁니다. 원문 버퍼는 전달·실패·취소·시간 초과 때 해제합니다. 이미 자식에게 전달한 바이트는 취소로 되돌릴 수 없습니다.

비공유 상태, 공유 전환과 사람의 개입

섹션 제목: “비공유 상태, 공유 전환과 사람의 개입”

외부 세션은 생성부터 비공유입니다. MCP·공개 로컬 IPC의 탭 목록과 직접 접근에서 화면·입력·이벤트·상세 상태·제목·작업 폴더를 공개하지 않고, 소유한 창에만 출력합니다.

이번 하드컷에서는 사람이 앱의 공유 컨트롤에서 실제 연결된 에이전트를 선택해 같은 터미널 프로세스와 SSH 연결을 공유할 수 있습니다. 먼저 외부 입력 권한과 큐를 해제하고, 이전 에이전트 작업을 취소한 뒤 현재 사람이 보는 화면부터 새 공유 경계로 제공합니다. 화면을 자동으로 지우거나 다른 셸을 만들지 않으며, 인증 성공을 추측하거나 비공유 입력을 기록으로 복구하지 않습니다. 입력 중인 줄이 추적되고 있다면 완료하거나 취소한 뒤 전환해야 합니다. 외부에서 시작한 출처는 유지하므로 이후 에이전트 명령도 해당 실행 환경에 맞는 검토를 거칩니다.

외부 앱은 AppleScript·D-Bus로 공유를 켜거나 참여자를 선택하거나 입력 권한을 되찾을 수 없습니다. 공유는 사람이 제어권을 가진 상태로 시작하며, 선택된 에이전트가 별도로 제어를 요청합니다. 공유를 끄면 에이전트 접근을 막고 인간과 자식 프로세스는 계속 작업할 수 있습니다. 이미 에이전트에게 전달한 정보는 회수할 수 없습니다.

사람 입력·제어 회수·취소·반환·자동화 비활성화·프로필 허용 제거·종료도 외부 입력 권한과 큐를 해제합니다. 후속 요청이 권한을 다시 얻거나 기존 인간 탭으로 들어갈 수는 없습니다. 새 외부 작업이 필요하면 새 세션을 만듭니다. 런처 종료로 이미 시작한 터미널을 죽이지는 않지만, 발신자를 검증할 수 없으면 대기 입력을 전달하지 않습니다. 반환한 핸들의 상태를 조회해도 권한이 복구되지 않습니다.

xterm의 커서·상태 응답은 터미널 프로토콜이며 사람 입력이 아닙니다. 이 응답만으로 외부 입력 권한을 해제하거나 활동 기록을 만들면 안 됩니다.

Conn은 비공유 외부 세션의 활동을 기록하지 않습니다. 해당 세션과 AppleScript의 감사 이벤트·타임라인 행·최근 활동 설정·원시 입력 기록·원문에서 만든 제목·저장된 요청 결과를 만들지 않습니다. 비공유 상태에서는 사람 입력에도 같은 범위를 적용합니다. 명시적 공유 이후의 에이전트·협업 활동은 기록할 수 있지만, 비공유 시작 인자와 이전 입력을 소급 기록하지 않습니다. 설정에는 권한만 저장하며 재시작 때 프로세스· 소유자 핸들·큐의 원문·요청을 복구하지 않습니다. 비공유 출력의 프로그램적 클립보드 쓰기는 비활성화하고 사람이 직접 복사하는 동작은 유지합니다.

이는 메모리 잔존이 전혀 없거나 OS 수준으로 격리된다는 뜻이 아닙니다. 터미널 화면과 스크롤백은 메모리에 존재합니다. 자식 프로세스·셸 기록·추적 출력·argv·환경·OS 버퍼· 클립보드·충돌 덤프·외부 앱 로그는 별도 경로입니다. 비밀번호·인증 완료 탐지 기능은 없습니다. 별도 내장 모델 제공자의 API 키는 OS 보안 저장소에 저장하며 터미널 인증정보를 수집하지 않습니다. 같은 OS 사용자 권한의 도구는 Conn API 밖의 셸과 파일을 사용할 수 있습니다.

일반 세션에서도 사람의 원시 입력으로 명령을 추정하지 않습니다. 별도로 지원하는 로컬 Bash·Zsh 실행 훅이 확인한 명령은 사람 명령도 기록하며, 앱 내부 입력은 명령 기록으로 수집하지 않습니다. 외부 세션에는 이후 공유 전환 때도 해당 훅을 설치하지 않습니다. 기존 감사 로그· 타임라인 저장 기록·백업도 자동 삭제하지 않습니다. 과거 자료를 공유하기 전에 별도로 검토하세요. 신뢰 모델을 참고하세요.

아래 실행·입력 검사는 v0.6.0 이상에 적용됩니다. 공유 전환 추가 검사는 하드컷 개발본이 필요하며 v0.6.0 배포본에서는 수행할 수 없습니다. 합성 표식만 입력하고, 스크립트가 어떤 Conn 앱을 대상으로 하는지 확인하세요.

  1. 설정 → 자동화에서 로컬 기본 프로필을 선택하고 이전 권한을 직접 다시 켭니다. 이 화면에는 권한 설정만 있고 최근 요청 이력은 없어야 합니다.

  2. 스크립트 편집기에서 아래 코드를 실행합니다. 새 비공유 창에 두 표식이 보이고 Conn 승인·Grace 카드가 나오지 않아야 합니다. Enter를 누르면 자식이 종료되고 종료 화면이 유지되어야 하며 다른 셸을 다시 시작하면 안 됩니다. 앱이 이미 켜진 상태에서도 반복합니다.

    tell application "Conn"
    create window with default profile command "/bin/sh -c 'echo CONN_PRIVATE_START; echo Press-Enter-to-finish; read answer'"
    end tell
  3. 전체 입력 예제echo CONN_PRIVATE_WRITE를 인자로 주어 하나의 프로세스에서 실행합니다. 성공 분기의 release session 앞에 set resultJSON to request status requestID를 넣고 resultJSON을 반환하도록 바꿉니다. 응답에는 메타데이터만 있고 표식이나 명령 원문은 없어야 합니다. 실제 화면에도 표식이 나오는지 확인하세요. delivered만으로 명령 성공을 판단하지 않습니다.

  4. 사람 개입을 확인하려면 그 스크립트를 복사해 release session sessionID 바로 앞에 delay 10write text "echo CONN_MUST_NOT_RUN" to session sessionID를 넣습니다. 대기 중 비공유 터미널에서 Ctrl-C를 누릅니다. 뒤의 입력은 거부되고 표식이 화면에 나오지 않아야 합니다. 뒤의 입력 전에 session status sessionID를 조회하면 inputAvailable: false 또는 해제된 핸들이 이미 제거되었다는 일반 오류가 나와야 합니다. 예상된 오류는 잡아서 다음 입력 거부 확인을 계속합니다. 터미널 프로토콜 응답만으로는 이런 제어 회수가 발생하면 안 됩니다.

  5. 타임라인에 비공유 명령·제어권 행이 새로 생기지 않고 Conn의 저장 활동에도 표식이 없는지 확인합니다. 별도로 연결된 기존 MCP의 terminal_list_tabs에 비공유 세션이 없어야 하고 스냅샷에서도 표식을 읽을 수 없어야 합니다. 일반 세션은 열어 두고 기존 에이전트 협업이 계속 동작하는지 확인합니다.

위 내용은 직접 수행할 검증 절차이며, 사용자의 Mac에서 이미 통과했다는 뜻이 아닙니다.

정확한 argv·한글·인용부호, 화면 준비 전 실행 차단, 기본 셸로 돌아가지 않는 프로세스 종료, 호출자 확인, 큐 제한, 사람 개입과 권한 해제, 모든 Conn 기록·공개 IPC 경로에서의 비공유 데이터 제외를 검증해야 합니다. 일반 에이전트 정책 회귀 테스트도 유지합니다.

macOS CI는 번들 메타데이터·사전·예제 컴파일을 확인합니다. 컴파일은 실제 Apple Event 실행이나 권한 대화상자를 검증하지 않습니다. Mac 개발용 앱에서 비표시·별표 형태의 합성 암호 입력을 확인했지만, 모든 런처나 서명된 설치본의 전체 동작을 검증한 것은 아닙니다. v0.6.0 검증 범위를 참고하세요. 각 배포에서는 서명된 Apple Silicon 앱과 일회용 런처·합성 입력으로 다음을 확인해야 합니다.

  • 앱 종료·실행 중 양쪽의 호출, macOS 최초 권한 허용·거절
  • 직접 시작 프로그램, 명시한 셸 문법, 한글과 인용부호
  • 사람에게 보이는 비공유 출력이 MCP·공개 IPC에는 노출되지 않음
  • 사람 개입·취소·비활성화·프로필 제거·창 닫기·서로 다른 호출자
  • 메타데이터만 있는 상태, 무시되는 intent, 활동 미기록, 업그레이드 후 재활성화
  • 외부 앱에서 실제 사용할 허용된 템플릿

공유 전환은 v0.7.0의 새 기능입니다. 이전 설계 문서는 당시의 비공유 전용 단계를 설명합니다. 일반 로컬 Bash·Zsh에는 셸 명령 연동이 있지만, 외부 세션에는 공유할 때 새 훅을 설치하지 않습니다. 공유 이후 에이전트·협업 기록과 사람의 원시 입력 기록은 구분합니다.

v0.6.0부터 제공합니다.

사용자 세션 버스의 dev.eggp.Conn, 경로 /dev/eggp/Conn/Automation, 인터페이스 dev.eggp.Conn.Automation1을 제공합니다. Call(작업명, JSON 문자열)이 JSON 문자열을 반환합니다. 생성·입력·상태·반환·요청 취소는 영문 문서의 공통 계약을 따릅니다. Linux의 두 생성 작업은 모두 전용 네이티브 창을 엽니다.

설정 → 자동화에서 기능과 프로필을 켠 뒤 허용할 Linux 실행 파일에 절대 경로를 등록하세요. 빈 목록이면 모두 거부합니다. 앱을 먼저 실행하고, 호출자는 세션을 쓰는 동안 동일한 D-Bus 연결을 유지해야 합니다. 매번 새로 실행하는 gdbus 명령으로는 이전 핸들을 이어 쓸 수 없습니다. D-Bus 서비스 자동 실행 등록은 이번 변경에 포함하지 않습니다.

호출자의 UID·PID는 버스에서 얻고 실행 파일·프로세스 시작 시간을 확인합니다. 요청 JSON의 신원은 신뢰하지 않습니다. 연결·프로세스 종료나 실행 파일 변경 시 권한이 유효하지 않으며, 사람 입력과 설정 변경에 따른 공통 권한 회수 규칙도 그대로 적용됩니다.

Python·셸을 허용하면 해당 인터프리터가 실행하는 모든 스크립트를 허용합니다. 실행 파일 경로 검사는 코드 서명이나 같은 계정의 악성 프로세스를 격리하는 기능이 아닙니다. 권한 있는 버스 감시 도구와 OS 메모리까지 비밀정보를 숨긴다고 보장하지 않습니다.

독립 검증은 별도 D-Bus 세션·가상 화면과 CONN_CONFIG_DIR, CONN_SOCKET으로 구성합니다. 실제 창과 렌더러 준비 과정을 거치며 사용자 설정을 변경하지 않습니다.

  1. 일회용 외부 세션에서 합성 숨김·별표 인증을 마칩니다.
  2. 사람이 현재 화면을 확인하고 실제 연결된 에이전트를 선택해 공유합니다.
  3. 자식 PID와 인증된 연결이 유지되는지, 에이전트 화면이 현재 뷰포트와 같은지 확인합니다. 사람이 스크롤을 바꾸면 에이전트도 같은 위치를 봐야 합니다.
  4. 이전 외부 핸들의 지연 입력이 거절되고, 공유 이후 큐의 입력이 끼어들지 않아야 합니다.
  5. 에이전트가 제어를 요청해 무해한 명령을 실행한 뒤 사람이 폴더를 보정합니다. 에이전트가 새 화면을 읽고 이어가는지 확인합니다.
  6. 스냅샷·제안을 기다리는 중 공유를 끕니다. 에이전트 접근은 막히고 사람은 계속 입력할 수 있어야 합니다. 같은 이름으로 다시 연결해도 이전 참여 권한을 물려받으면 안 됩니다.
  7. 새 협업 기록에는 공유 이후 활동만 있어야 합니다. 비공유 시작 명령·합성 인증값·이전 입력이 소급 추가되어서는 안 됩니다.

위 항목은 배포 전 검증 조건이며 서명된 Mac 빌드나 모든 외부 런처에서 이미 통과했다는 뜻이 아닙니다. 외부 세션에는 공유할 때 셸 훅을 새로 설치하지 않으므로 사람 명령 기록을 원시 입력에서 복구하지 않습니다.