# pi-patty-bg-tasks

<p align="center">
  <a href="README.md">English</a> · <strong>한국어</strong> · <a href="README.zh.md">中文</a>
</p>

<p align="center">
  <strong>긴 명령에 에이전트가 묶일 이유는 없습니다 — 알아서 백그라운드로 넘기고, 멈춤 없이 계속 달리세요.</strong>
</p>

<p align="center">
  <a href="https://www.npmjs.com/package/@esso0428/pi-patty-bg-tasks"><img alt="npm" src="https://img.shields.io/npm/v/@esso0428/pi-patty-bg-tasks?color=cb3837&label=npm&logo=npm"></a>&nbsp;
  <img alt="Pi v0.37+" src="https://img.shields.io/badge/Pi-v0.37%2B-5b50f0">&nbsp;
  <img alt="dependencies: zero" src="https://img.shields.io/badge/dependencies-zero-3fb950">&nbsp;
  <img alt="tmux: not required" src="https://img.shields.io/badge/tmux-not_required-3fb950">&nbsp;
  <img alt="license: MIT" src="https://img.shields.io/badge/license-MIT-blue">
</p>

**빌드 도는 동안 에이전트가 멍하니 기다리고 있을 이유는 없습니다.** Claude Code의 백그라운드 작업 경험을 그대로 Pi에 옮겨왔습니다. 오래 걸리는 명령을 던지면 세션 전체를 붙잡고 늘어지는 대신, 슬그머니 백그라운드로 빠지고 에이전트는 하던 일을 계속합니다. 120초가 지나면 알아서 백그라운드로, 급하면 Ctrl+B로 그 자리에서, 출력 캡처에 정체 감지에 제대로 된 작업 관리자까지 — 익스텐션 하나에 다 담았습니다.

## 설치

```
pi install npm:@esso0428/pi-patty-bg-tasks
```

GitHub에서 바로 받아도 됩니다.

```
pi install git:github.com/ESSO0428/pi-patty-bg-tasks
```

필요한 건 Pi v0.37 이상, 그게 전부입니다. **외부 의존성도 없고 tmux도 안 씁니다.** 백그라운드 잡은 그냥 평범한 Node.js 자식 프로세스로 돌아가고, 출력은 곧장 파일 디스크립터로 흘려보냅니다. 따로 설치할 것도, 신경 써서 돌볼 것도 없습니다.

## 이게 왜 필요하냐면

**이제 세션이 멈춰 서는 일은 없습니다.** 개발 서버, 테스트 스위트, 빌드 — 120초 넘게 붙잡고 있는 녀석은 뭐든 조용히 백그라운드로 넘어갑니다. 에이전트는 그 사실만 슬쩍 전달받고, 스피너만 쳐다보는 대신 곧장 다음 일로 넘어갑니다. 더 빨리 치우고 싶으면? 아무 명령이나, 아무 때나 직접 백그라운드로 보내면 됩니다.

**Claude Code처럼 느껴지는 이유는, 실제로 Claude Code를 본떠 만들었기 때문입니다.** 백그라운드와 포그라운드를 오가는 그 흐름 전체를 — Ctrl+B로 백그라운드, 출력 캡처, 완료 알림, 정체 감지까지 — Claude Code 구현 위에 그대로 얹었습니다. 메시지 형식도 같고, 터미널에 그대로 녹아드는 아이콘도 같고, "에이전트는 절대 멈추지 않는다"는 그 감각도 똑같습니다. 손에 익은 그 동작, 여기 그대로 있습니다.

**구색만 갖춘 게 아니라, 진짜 작업 관리자입니다.** `/bg-list`를 치면 인터랙티브 작업 관리자가 열립니다. 잡 목록을 훑고, 출력을 들여다보고, 폭주하는 녀석은 붙잡아 죽이고, 필요하면 attach해서 결과가 나올 때까지 기다릴 수 있습니다.

## 빠르게 시작하기

```
# 에이전트가 긴 명령을 실행 — 120초 뒤 자동으로 백그라운드
bash({ command: "npm run build" })

# 기다릴 것 없이 처음부터 백그라운드로
bash({ command: "npm run dev", run_in_background: true })

# 아예 던져두고 신경 끄고 싶을 땐
bash_bg({ command: "npm run dev", name: "devserver" })

# 지금 뭐가 돌고 있는지 확인
jobs({ action: "list" })

# 모든 잡의 출력을 한 번에 grep
jobs({ action: "search", pattern: "error|warning" })

# 작업 하나를 통째로 백그라운드 에이전트에게 넘기기
agent_bg({ prompt: "auth 모듈 리팩터링" })
```

명령이 돌고 있을 때 **Ctrl+B**를 누르면 그 자리에서 바로 백그라운드로 넘어갑니다 — 명령이 몇 초 이상 돌면 입력창 아래에 `(ctrl+b to run in background)` 힌트가 흐릿하게 떠줍니다. 에이전트는 알림을 받고, 손가락을 키에서 떼기도 전에 이미 다음 일을 하고 있습니다.

## 도구

### bash (오버라이드)

내장 bash 도구에 생존 본능을 심었습니다. 명령은 평소대로 실행되지만, 120초를 넘기는 순간 자동으로 백그라운드로 넘어가고, 에이전트는 `job_decide`로 이제 어떻게 할지 — 그대로 둘지, 죽일지, 출력부터 볼지 — 결정해 달라는 요청을 받습니다.

| 파라미터 | 설명 |
|---------|------|
| `command` | 실행할 셸 명령 |
| `timeout` | 타임아웃 직접 지정 (초 단위, 기본값: 120) |
| `run_in_background` | 포그라운드 실행과 자동 백그라운드 타이머를 건너뛰고 명령을 곧장 백그라운드로 시작 |

### bash_bg

처음부터 오래 걸릴 게 뻔할 때 씁니다. 포그라운드에서 눈치 보는 시간도, 타임아웃을 기다리는 시간도 없이 명령을 곧바로 백그라운드로 띄웁니다.

| 파라미터 | 설명 |
|---------|------|
| `command` | 실행할 셸 명령 |
| `name` | 잡에 붙일 사람이 알아보기 쉬운 라벨 (선택) |
| `timeout` | 타임아웃 (초, 선택); 동일한 자동 백그라운드 결정 흐름을 발동시킵니다 |
| `notify` | 완료 알림 전송 (기본값: true) |

### jobs

백그라운드에서 돌아가는 모든 것을 위한 관제탑입니다. 목록 보기, 출력 읽기, 종료, attach, 검색, 정리, 통계까지 한곳에서.

| 액션 | 설명 |
|------|------|
| `list` | 실행 중인 잡과 최근 끝난 잡을 모두 표시 |
| `output` | 특정 잡의 로그 끝부분 읽기 |
| `kill` | 실행 중인 잡 종료 |
| `attach` | 잡이 끝날 때까지 기다렸다가 출력 반환 |
| `search` | 모든 잡 로그를 정규식으로 검색 |
| `cleanup` | 완료/실패한 잡을 비우고 디스크 회수 |
| `stats` | 집계 지표: 총 시작 수, 실행 중, 완료, 실패, 평균 소요 시간 |

### job_decide

자동으로 백그라운드된 명령에 대해 에이전트가 내리는 답입니다. 120초 타이머가 울리는 바로 그 순간 이 프롬프트가 날아옵니다.

| 파라미터 | 설명 |
|---------|------|
| `jobId` | 백그라운드로 넘어간 잡의 ID |
| `decision` | `keep` (계속 실행), `kill` (종료), `check` (먼저 출력 확인) |

### agent_bg

나 대신 일할 동료를 하나 복제한다고 보면 됩니다. 현재 세션에서 뽑아낸 연속성 프롬프트를 들고 분리된 `pi -p` 프로세스를 띄운 다음, 그 진행 상황을 실시간으로 흘려보내 줍니다.

| 파라미터 | 설명 |
|---------|------|
| `prompt` | 백그라운드 에이전트에게 맡길 작업 설명 |
| `cwd` | 작업 디렉터리 (기본값: 현재) |

### monitor

한 번 기다리는 대신 이벤트를 스트리밍합니다. `bash_bg`/`run_in_background`가 완료 시 **한 번** 알려준다면, `monitor`는 프로세스를 **실시간 이벤트 스트림**으로 바꿉니다 — stdout 한 줄(또는 WebSocket 프레임)마다 알림 하나가 에이전트의 턴으로 바로 전달되고, 에이전트는 그동안 계속 일합니다. 이것이 Claude Code가 둘로 나눈 스트리밍 절반입니다: 일회성 "끝나면 알려줘"는 `run_in_background`가, 이벤트마다 "X가 일어날 때마다 알려줘"는 `monitor`가 맡습니다.

```js
// 모든 에러 줄을 무기한 알림
monitor({ command: "tail -f deploy.log | grep --line-buffered -E 'ERROR|Traceback'", description: "deploy.log 에러" })

// CI 체크가 들어올 때마다 내보내고, 실행이 끝나면 종료
monitor({ command: "…종료되는 폴링 루프…", description: "PR 123 CI 체크" })

// WebSocket 피드 구독 — 각 텍스트 프레임이 이벤트
monitor({ ws: { url: "wss://events.example.com/stream" }, description: "배포 이벤트", persistent: true })
```

| 파라미터 | 설명 |
|---------|------|
| `command` | 셸 스크립트. stdout 한 줄이 이벤트 하나. `ws`와 상호 배타적. |
| `ws` | WebSocket 소스 `{ url, protocols? }`. 텍스트 프레임 하나가 이벤트 하나. `command`와 상호 배타적. |
| `description` | 모든 알림에 표시됨(구체적으로). **필수.** |
| `persistent` | 세션 전체 동안 실행(타임아웃 없음). `jobs action='kill'`로 중지. 기본값 `false`. |
| `timeout_ms` | 워치를 종료할 데드라인(기본 `300000`, 최대 `3600000`). `persistent`면 무시. |

모니터는 백그라운드 도구와 같은 잡 레지스트리·사이드바(`◉` 표시)·`jobs` 매니저를 공유합니다 — stdout만 이벤트 스트림이고(stderr는 별도 `.err` 파일에 캡처), 출력은 줄 단위 버퍼링이므로 `grep --line-buffered`/`awk fflush()`를 쓰고 `head`는 절대 쓰지 마세요. 이벤트를 쏟아내는 모니터는 자동으로 중지되니 더 좁은 필터로 다시 시작하면 됩니다. `ws` 소스는 전역 `WebSocket`(Node 22+)이 있는 런타임이 필요하며, 없으면 `websocat` 같은 `command`를 쓰세요.

> **지속 모니터와 디스크:** `persistent`가 아닌 모니터의 출력 로그는 상한이 있습니다(출력이 너무 커지면 종료됨). 하지만 `persistent` 모니터는 세션 내내 도는 것을 전제로 하므로 로그에 크기 상한이 **없습니다** — 장기 `tail -f`는 firehose 대신 필터링된 스트림에 걸고, 끝나면 `jobs action='kill'`로 중지하세요.

## 키보드 단축키

손은 키보드 위에 그대로 두세요.

| 단축키 | 동작 |
|-------|------|
| **Ctrl+B** | 실행 중인 포그라운드 명령을 백그라운드로 — 에이전트는 계속 작업 (Claude Code와 동일). tmux 안에서는 두 번 누르세요 (tmux가 Ctrl+B를 차지함). |
| **Ctrl+Shift+B** | Ctrl+B와 동일 (별칭) |
| **Ctrl+Shift+J** | 백그라운드 작업 관리자 열기 |
| **Shift+Down** | 백그라운드 작업 관리자 열기 |
| **Ctrl+Shift+X** | 가장 최근에 실행 중인 잡 종료 |

## 커맨드

슬래시가 편하다면? 들어가는 문만 다를 뿐, 할 수 있는 건 똑같습니다.

| 커맨드 | 설명 |
|-------|------|
| `/bg` | 현재 프로세스를 백그라운드로 (Ctrl+B와 동일) |
| `/bg-list` | 인터랙티브 백그라운드 작업 관리자 열기 |
| `/bg-version` | 로드된 익스텐션 버전/경로 표시 (리로드 진단용) |

## 동작 원리

마법 같은 건 없습니다. 깔끔한 상태 머신 하나가 전부입니다.

```
명령 시작 (Node.js child_process.spawn 직접 호출)
  → 2초 안에 끝났나?        결과를 즉시 반환
  → 120초까지 돌고 있나?    자동 백그라운드 → 에이전트에 job_decide 프롬프트
  → Ctrl+B를 눌렀나?        즉시 백그라운드 → 에이전트는 계속 진행

백그라운드 잡 실행 중
  → 출력을 파일 디스크립터로 /tmp/pi-bg/<id>.log에 캡처
  → 정체 감지: 출력이 인터랙티브 프롬프트처럼 보이면 에이전트에 경고
  → 과대 출력 감지: 출력이 한도를 넘어서면 잡을 종료
  → 완료 시: 에이전트에 상태 + 출력 경로를 알림
```

백그라운드 잡은 stdout/stderr을 로그 파일 디스크립터에 곧장 연결한 detached Node.js
자식 프로세스로 돌아갑니다 — Claude Code가 쓰는 바로 그 패턴입니다. tmux도, 외부 프로세스
관리자도, 명령과 로그 사이를 가로막는 무언가도 없습니다. 백그라운드 잡은 한 번에 최대
**16개**까지 동시에 돌고, 17번째를 요청하면 자리가 빌 때까지 정중하게 거절합니다. 24시간이
지난 오래된 로그는 세션 시작 때 쓸어 담아 치우니, `/tmp`가 잡동사니 서랍으로 변할 일은 없습니다.

## 협조적 스티어링 (Claude Code 동등성)

백그라운드로 넘길 수 있는 포그라운드 명령이 돌아가는 도중에 메시지를 입력하면, Pi가 그 텍스트를 스티어링으로 큐에 넣기 **전에** 익스텐션이 먼저 끼어듭니다.

1. 지금 돌고 있는 포그라운드 명령이 백그라운드로 미끄러져 들어갑니다 (출력은 계속 캡처되니 잃는 건 없습니다).
2. 현재 에이전트 턴은 중단됩니다.
3. 에이전트가 idle 상태가 되는 순간, 입력한 메시지가 새 사용자 턴으로 곧장 다시 올라옵니다.

이게 정확히 Claude Code가 동작하는 방식입니다. 중단 가능한 도구가 도는 도중에 입력을 넣으면, 그 도구를 멈추고 새 턴을 시작합니다. 메시지가 오래 걸리는 호출 뒤에 줄 서서 갇혀 있는 일은 없습니다. 폴링도 없고, 차례를 기다릴 일도 없습니다.

**적용 범위:** 이 익스텐션이 직접 관리하는 `bash` 도구에만 해당합니다. 익스텐션이 감싸지 않는 다른 오래 걸리는 도구는 Pi 기본 스티어링(큐에 쌓였다가 다음 턴이 시작될 때 전달)으로 처리됩니다.

## 상태 바

라이브 pill 위젯이 실행 중인 잡을 소요 시간과 명령 미리보기까지 곁들여 늘 눈앞에 띄워 둡니다. 완료/실패 개수는 상태 줄에 같이 따라붙습니다. 전체 그림을 보고 싶을 땐 Shift+Down이나 `/bg-list`로 작업 관리자를 열면 됩니다.

## 릴리스

### 1.1.2 — 알림은 벽이 아니라 요약 한 줄로

- **백그라운드 알림이 턴 경계에서 합쳐집니다.** 긴 에이전트 턴 동안 끝난 잡과 모니터가 더는 답변 뒤에 `[job-finished]` / `[bg-monitor-event]` 줄을 벽처럼 쏟아내지 않습니다. 모아 두었다가 턴이 끝날 때(`agent_end`) **요약 한 줄**로 내보냅니다 — *"4 background events — 1 completed (job-19), 1 failed (job-30 exit 1); 2 monitors ended (API health, port 4000)."* 에이전트가 유휴 상태면 짧은 대기창으로 합칩니다. 모니터 *스트림* 이벤트(지켜보는 매칭 줄)는 실시간 그대로입니다.
- 비정상적으로 끝난 턴이 남긴 알림을 비워 내는 가드가 있어, 전달 안 된 채 묶여 있는 알림이 없습니다.

### 1.1.1 — 동등성 수정, 데이터 손실 방지, 실시간 진행 표시

- **사이드바에 실시간 진행 표시.** 실행 중인 잡의 알약이 이제 명령 대신 **최신 출력 줄**을 (매초 갱신) 보여줍니다 — 긴 폴링/빌드의 진행을 한눈에 (`◉ qdrant: {"indexed":8540629,"status":"grey"} (2m10s)`). ANSI/제어 시퀀스는 제거되어 위젯이 깔끔하게 유지되고 이스케이프 주입도 막습니다.
- **`sleep` 잡이 더는 어슬렁대지 않습니다.** 순진한 `sleep N` 대기는 (`cd x; sleep 600; check`처럼 중간에 끼어 있거나, 줄바꿈으로 나뉘었거나, 백그라운드여도) 이제 `bash`와 `bash_bg` 양쪽에서 차단되고, 작업이 끝날 때 함께 끝나는 도구로 안내합니다: `jobs attach`, `monitor` 도구, 또는 준비되면 빠져나오는 `until` 루프. 진짜 폴링 루프 안의 sleep은 절대 차단하지 않습니다.
- **취소에 대한 Claude Code 동등성 (CC 소스로 검증).** **Esc**를 누르면 실행 중인 포그라운드 명령이 종료되고(의도적 취소), 새 메시지 입력·**Ctrl+B**·자동 백그라운드 타임아웃은 대신 백그라운드로 옮깁니다 — CC의 `user-cancel` vs `interrupt` 동작 그대로입니다. 긴 작업은 취소를 무시하는 게 아니라 타임아웃 시 자동 백그라운드 + `run_in_background`로 보호합니다.

### 1.1.0 — monitor 도구 & 완료 알림 합치기

- **새 `monitor` 도구** — Claude Code가 둘로 나눈 것 중 스트리밍 절반입니다. stdout 한 줄(또는 WebSocket 프레임)이 발생하는 즉시 알림 하나가 됩니다. 이벤트마다 알림이 필요한 스트림(`tail -f | grep`, 폴링 루프, 파일 워치, ws 피드)에 쓰고, 일회성 "끝나면 알려줘"는 `run_in_background`가 맡습니다. `command`/`ws` 소스, `persistent` 워치, `timeout_ms` 데드라인, 줄 단위 정확한 추적, firehose 자동 중지를 지원합니다. 사이드바와 `jobs` 매니저에 `◉` 표시로 나타납니다.
- **`[job-finished]` 줄의 벽이 사라졌습니다** — 백그라운드 잡 여러 개가 한꺼번에 끝나도, 잡마다 한 줄씩 쏟아내는 대신 하나의 요약 알림으로 합쳐집니다.
- 내부: `MonitorSession`을 `MonitorSource` 심(command + ws 어댑터) 뒤로 분리해 스트리밍/종료 라이프사이클을 단위 테스트할 수 있게 했습니다. 새 의존성은 0 (ws는 런타임의 전역 `WebSocket`, Node 22+ 사용).

### 1.0.2 — Ctrl+B 동등성 & 더 친절한 jobs

- 이제 **Ctrl+B**가 기본 백그라운드 단축키입니다 (Ctrl+Shift+B는 별칭으로 유지). 명령이 도는 동안 입력창 아래에 `(ctrl+b to run in background)` 힌트가 흐릿하게 떠 Claude Code와 동일합니다. tmux 안에서는 "(twice)" 안내가 함께 표시됩니다.
- **`jobs attach`가 대기하는 동안 잡의 실시간 출력을 스트리밍**합니다 (이전엔 조용했습니다). 문구도 "Following … live output"으로 바뀌었고, 떼어내도 잡은 계속 실행됩니다.
- **사이드바 알약이 실시간으로 갱신**됩니다 — 그려진 시점 값에서 멈추지 않고 소요 시간이 매초 올라갑니다.
- 잡 완료/타임아웃 알림이 **간결**해졌습니다 (한 줄 에이전트 follow-up + UI 토스트) — 박스형 스팸 없이 에이전트가 계속 상황을 압니다.

### 1.0.1 — Claude Code 동등성 (첫 1.x 배포)

큰 한 방입니다. 백그라운드 엔진을 Claude Code 아키텍처에 맞춰 바닥부터 다시 짰고, 외부 의존성은 0입니다. npm에 올라가는 첫 1.x이며, 동등성 재작성과 함께 정확성·성능을 단단히 다지는 작업을 한 묶음으로 담았습니다.

**호환성을 깨는 변경**
- **tmux를 걷어냈습니다.** 이제 백그라운드 잡은 파일 디스크립터로 출력을 캡처하는 Node.js `child_process.spawn` 프로세스로 직접 돌아갑니다. tmux는 더 이상 쓰지도, 필요하지도 않습니다 — 따로 설치할 게 없어졌습니다.
- **기본 자동 백그라운드 타임아웃이 120초로** 바뀌었습니다 (이전엔 15초), Claude Code와 동일합니다. 다르게 쓰고 싶으면 `timeout`을 직접 넘기면 됩니다.
- 백그라운드 로그가 `/tmp/pi-bg-<id>.log`에서 전용 디렉터리 `/tmp/pi-bg/<id>.log`로 옮겨졌습니다.

**주요 기능**
- `bash` 도구에 `run_in_background: true` 파라미터 추가.
- `agent_bg`가 이제 진행 상황을 실시간으로 스트리밍하고 `pi` 바이너리 경로를 직접 찾아냅니다 (표준 `$PATH` 밖에 설치된 경우에도 동작).
- 협조적 스티어링이 메시지를 다음 턴(follow-up)으로 전달합니다 — 폴링 루프 없음.
- 백그라운드 잡 동시 실행 상한 16개.
- 코드와 UI를 영어 전용으로 정리.

**버그 수정 및 내부 정리 (재작성 후 보강)**
- 협조적 스티어링이 방금 자기가 백그라운드로 보낸 그 명령을 더 이상 죽이지 않습니다.
- 스폰 실패(`ENOENT`/`EMFILE`/`EAGAIN`)가 에이전트를 죽이지 않고 매끄럽게 처리됩니다.
- 세션 복원은 현재 프로세스에서 시작된 잡만 되살립니다 — 재활용됐을지 모를 PID에는 절대 신호를 보내지 않습니다.
- 네 갈래로 흩어져 있던 스폰 경로를 `startBackgroundJob` 서비스 함수 하나로 합쳤고, 포그라운드 정리를 `finally`로 옮겨 어느 종료 경로에서도 잡이 붕 떠버리지 않게 했습니다.
- 로그 검색을 잡별로 동시에 돌리고, 오래된 로그 청소도 상한을 둔 비동기 방식으로 바꿨습니다.

### 0.3.1 및 그 이전

tmux 기반 백그라운드 잡, 15초 자동 백그라운드, 협조적 스티어링, 인터랙티브 작업 관리자.

## 개발

```
git clone https://github.com/ESSO0428/pi-patty-bg-tasks.git
cd pi-patty-bg-tasks
pnpm install
pnpm check    # 타입 체크
pnpm test     # 테스트 실행
```

Node.js 22 이상, pnpm 10 이상이 필요합니다. tmux를 비롯한 외부 의존성은 없습니다 — clone 받은 그대로 바로 돌아갑니다.

## 기여하기

PR 환영합니다. 순서는 이렇습니다.

1. 저장소 포크
2. 기능 브랜치 생성 (`git checkout -b feat/my-feature`)
3. `pnpm check`와 `pnpm test`가 통과하는지 확인
4. [컨벤셔널 커밋](https://www.conventionalcommits.org/)으로 커밋
5. `main`을 대상으로 PR 열기

## 라이선스

[MIT](LICENSE) © Patty · [ESSO0428](https://github.com/ESSO0428)가 유지보수하는 Fork

## 저자

**Patty** (원작자) · [GitHub](https://github.com/patty-io)
