Skip to content

Releases: youngminnnn/wooi

v1.33.0

Choose a tag to compare

@github-actions github-actions released this 20 Sep 07:26
4076eaa

New in v1.33.0

  • 🖼️ Open Codex visualizations right from the conversation — Open HTML visualizations created by Codex from their conversation card into a dedicated tab. Wooi validates that each file is regular HTML inside the worktree, no larger than 1 MiB, then displays it in an offline sandbox (#550).
  • 📎 Find Codex output files at a glance — Instead of leaving raw directives in the conversation, Wooi renders output files as clickable filename chips (#548).
  • 🧹 Keep session starts concise — Successful session-start hooks now show only Workspace context loaded instead of the full injected context. Failed, blocked, and stopped hook details remain visible (#549).

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.33.0 새 소식

  • 🖼️ Codex 시각화를 대화에서 바로 엽니다 — Codex가 만든 HTML 시각화를 대화 카드에서 전용 탭으로 열 수 있습니다. 파일이 worktree 안의 일반 HTML인지, 1 MiB 이하인지 확인한 뒤 오프라인 샌드박스에서 표시합니다 (#550).
  • 📎 Codex 출력 파일을 한눈에 찾습니다 — 대화에 원시 directive를 그대로 보여주는 대신, 출력 파일을 클릭 가능한 파일명 칩으로 표시합니다 (#548).
  • 🧹 세션 시작 메시지를 간결하게 줄였습니다 — 성공한 session-start 훅이 주입한 긴 컨텍스트 대신 Workspace context loaded만 표시하고, 실패·차단·중단 정보는 그대로 남깁니다 (#549).

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

Full Changelog: v1.32.2...v1.33.0

v1.32.2

Choose a tag to compare

@github-actions github-actions released this 18 Sep 11:18
d586349

New in v1.32.2

  • 🧹 Keep successful Codex auto-reviews out of the conversation — Successful automatic reviews left status messages behind and interrupted the actual conversation. Wooi now handles successful reviews quietly while still showing declined, timed-out, and interrupted outcomes that need attention (#544).
  • 🛰️ Explain empty Codex subagent conversations accurately — Codex subagents do not expose their internal transcript, but the empty view previously showed onboarding meant for a new session. It now explains the activity and status information that is available (#546).

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.32.2 새 소식

  • 🧹 성공한 Codex 자동 검토가 대화를 어지럽히지 않습니다 — 자동 검토가 정상적으로 끝날 때도 상태 메시지가 남아 실제 대화 흐름을 끊었습니다. 이제 성공한 검토는 조용히 처리하고, 거부·시간초과·중단처럼 확인이 필요한 결과만 표시합니다 (#544).
  • 🛰️ Codex 서브에이전트의 빈 화면을 정확히 안내합니다 — Codex 서브에이전트에는 내부 transcript가 없지만 새 세션용 안내가 나타났습니다. 이제 이용 가능한 활동과 상태를 설명하는 전용 안내를 보여줍니다 (#546).

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

Full Changelog: v1.32.1...v1.32.2

v1.32.1

Choose a tag to compare

@github-actions github-actions released this 16 Sep 05:43
e56cf60

New in v1.32.1

  • 🏷️ See the model behind Default — Composer and Settings previously showed only Default, making it hard to know which model would run. They now display the resolved model name alongside the default label (#541).
  • ∑ Render LaTeX in Markdown messages — Inline and block equations could remain as plain text or overflow the message layout. Wooi now normalizes math delimiters and renders equations with KaTeX (#542).

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.32.1 새 소식

  • 🏷️ 기본 모델이 실제 이름으로 보입니다 — Composer와 Settings에서 Default만 표시되면 어떤 모델이 선택되는지 알기 어려웠습니다. 이제 기본값 옆에 실제 모델 이름을 함께 보여줍니다 (#541).
  • ∑ Markdown 메시지에서 LaTeX 수식을 제대로 표시합니다 — 인라인 수식과 블록 수식이 일반 텍스트로 남거나 레이아웃을 넘치던 문제를 고쳤습니다. 수식 구분자를 정규화하고 KaTeX로 렌더링합니다 (#542).

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

Full Changelog: v1.32.0...v1.32.1

v1.32.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 11:17
9c06d59

New in v1.32.0

  • 🪄 Act on Codex follow-up suggestions with one click — Codex responses can suggest concrete next steps, but acting on them meant copying prompts manually. Wooi now renders those suggestions as buttons and sends the selected prompt to the same workspace (#539).
  • 🔁 Claude retries no longer stall after login failures — After an OAuth failure, reusing the same message UUID could make Claude CLI treat the retry as a duplicate and return no response. Failed-turn retries now receive fresh UUIDs, while user interruptions cancel any pending automatic restart (#538).

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.32.0 새 소식

  • 🪄 Codex가 제안한 다음 작업을 버튼으로 바로 실행합니다 — 완료된 응답에 담긴 후속 작업 제안을 버튼으로 보여줍니다. 버튼을 누르면 같은 워크스페이스로 프롬프트가 전달돼, 제안된 작업을 복사해 붙여넣는 과정 없이 곧바로 이어갈 수 있습니다 (#539).
  • 🔁 Claude 로그인 실패 뒤 재시도가 멈추던 문제를 고쳤습니다 — OAuth 실패 뒤 같은 메시지 UUID를 다시 보내면 Claude CLI가 중복 입력으로 판단해 응답 없이 재시도를 끝냈습니다. 이제 실패한 turn을 재시도할 때 UUID를 새로 발급하고, 사용자가 중단하면 예약된 자동 재시도도 함께 멈춥니다 (#538).

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

Full Changelog: v1.31.3...v1.32.0

v1.31.3

Choose a tag to compare

@github-actions github-actions released this 15 Sep 05:24
4d7c055

New in v1.31.3

  • 🧭 Codex plan mode keeps collaboration tools available — entering plan mode used to omit collaboration settings, leaving team and agent tools unavailable while planning. Plan-mode sessions now receive the complete collaboration settings, so planning and delegation stay connected (#536).

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.31.3 새 소식

  • 🧭 Codex plan mode에서도 협업 도구를 계속 쓸 수 있습니다 — plan mode로 전환하면 collaboration 설정이 빠져 팀·에이전트 도구를 사용하지 못했습니다. 이제 plan mode 세션에도 collaboration 설정을 넣어 계획할 때부터 협업과 위임을 이어갈 수 있습니다 (#536).

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

Full Changelog: v1.31.2...v1.31.3

v1.31.2

Choose a tag to compare

@github-actions github-actions released this 14 Sep 12:04
41a0a97

New in v1.31.2

  • 🏷️ Codex subagents show names you can actually read — subagent cards used to expose internal paths like /root/review_code, and follow-up activity could erase even a useful label when it omitted the path. They now show role names such as review code, keep that name through later activity, and fall back to Codex agent for root or placeholder paths (#533).

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.31.2 새 소식

  • 🏷️ Codex 서브에이전트 이름을 한눈에 알아볼 수 있습니다 — 이제 /root/review_code 같은 내부 경로 대신 review code처럼 읽기 쉬운 역할명을 보여 줍니다. 후속 activity에 경로가 빠져도 이미 표시한 이름을 그대로 유지합니다 (#533).

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

Full Changelog: v1.31.1...v1.31.2

v1.31.1

Choose a tag to compare

@github-actions github-actions released this 13 Sep 04:25
1afed24

New in v1.31.1

  • 🧹 Closing a tab actually gives the memory back — watching a running app every 30 seconds for 20 minutes showed 1.65–3.26GB resident, and most of it was by design: six agent CLI children is the configured ceiling. What wasn't by design is that nothing was letting go. Closing a tab never destroyed its guest view — the close path didn't reference the view manager at all, the detach that ran instead never lowered visible, and the renderer physically cannot send a final visible:false layout because unmount clears the registration before detaching. So any view that had ever been on screen froze at visible:true and fell out of both the 6-view budget and the 10-minute hibernation sweep, the two mechanisms meant to catch exactly that. One web tab is 50–150MB. Fixed alongside it: renderer transcripts are now capped at 8 (main had a 20-entry LRU; the renderer had no cap at all, and chat events were accumulating for workspaces you'd never opened), script output shares main's 256KiB limit through one module instead of growing unbounded in the renderer, per-worktree file indexes are capped at 4 (the 5-second TTL only judged freshness and discarded nothing, so every worktree you'd typed @ in stayed), and archive, delete and repo-removal now release the per-workspace caches they were skipping — context usage, the usage ledger, PR status, git status, file index, and pending Codex permission requests. Closing a tab now discards that page; clicking it reopens from its URL, the same as an evicted view (#530).
  • 🪟 The new-tab menu no longer runs off the edge of the window — the + sits 8px from the right edge, and the 176px panel was anchoring to the button's left corner and opening rightwards, so most of it was clipped and you saw only the icons. It now opens leftwards, the way the saved-prompt picker already did (#529).

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.31.1 새 소식

  • 🧹 탭을 닫으면 메모리가 실제로 돌아옵니다 — 도는 앱을 20분간 30초 간격으로 재 봤더니 1.653.26GB 를 오갔습니다. 대부분은 설계된 상한입니다. 에이전트 CLI 자식 6개가 설정된 최대치이고 실제로 정확히 6개가 떠 있었습니다. 설계가 아닌 것은 따로 있었습니다. 놓아 주는 쪽이 아무것도 없었습니다. 탭을 닫아도 게스트 뷰가 파괴되지 않았습니다. 탭 닫기 경로는 뷰 매니저를 아예 참조하지 않았습니다. 대신 도는 detach 는 visible 을 내리지 않았습니다. 언마운트 정리가 등록을 먼저 지운 다음 detach 를 부릅니다. 그래서 렌더러는 마지막 visible:false 레이아웃을 물리적으로 보낼 수 없습니다. 한 번이라도 화면에 뜬 뷰는 그렇게 visible:true 로 굳었습니다. 바로 그런 뷰를 잡으려고 만든 장치가 둘 있었습니다 — 6개 뷰 예산과 10분 동면 스윕. 굳은 뷰는 그 양쪽 필터에서 통째로 빠졌습니다. 웹 탭 하나가 50150MB 입니다. 함께 고친 것도 있습니다. 렌더러의 상주 트랜스크립트를 8개로 묶었습니다. 메인에는 20개 LRU 가 있었지만 렌더러에만 상한이 없었습니다. 열어 본 적 없는 워크스페이스의 채팅 이벤트까지 쌓이고 있었습니다. 스크립트 출력은 렌더러에서 끝없이 늘어나는 대신 메인과 같은 256KiB 상한을 한 모듈로 공유합니다. 파일 인덱스의 TTL 5초는 신선도만 판정할 뿐 아무것도 버리지 않았습니다. 그 바람에 @ 를 한 번이라도 친 워크트리가 전부 남아 있었습니다. 워크트리별 파일 인덱스는 4개로 묶었습니다. 아카이브·삭제·리포 제거가 그냥 지나쳤던 워크스페이스별 캐시도 이제 함께 비웁니다 — 컨텍스트 사용량, 사용량 원장, PR 상태, git 상태, 파일 인덱스, 매달린 Codex 권한 요청입니다. 이제 탭을 닫으면 그 페이지가 사라집니다. 다시 누르면 축출된 뷰와 똑같이 주소로 다시 열립니다 (#530).
  • 🪟 새 탭 메뉴가 창 밖으로 나가 잘리지 않습니다 — + 는 창 오른쪽 가장자리에서 8px 안쪽에 있습니다. 그런데 폭 176px 짜리 패널이 버튼의 왼쪽 모서리에 붙어 오른쪽으로 펴지고 있었습니다. 그래서 대부분이 잘려 나가고 아이콘만 보였습니다. 이제 저장된 프롬프트 선택기가 이미 하던 방식대로 왼쪽으로 펴집니다 (#529).

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

  • fix: 새 탭 메뉴가 창 밖으로 나가 잘리지 않게 한다 by @youngminnnn in #529
  • fix: 놓아야 할 것을 놓게 한다 — 닫은 탭·사라진 워크스페이스가 메모리를 붙들지 않게 by @youngminnnn in #530
  • release: v1.31.1 by @youngminnnn in #531

Full Changelog: v1.31.0...v1.31.1

v1.31.0

Choose a tag to compare

@github-actions github-actions released this 10 Sep 14:30
982f015

New in v1.31.0

  • 🗂️ The workspace is a tab strip now — conversation, dev preview, the web, files, artifacts and the stack all live side by side — Work (the conversation plus the right-hand panel) is pinned first; everything else opens to its right. Underneath, guest web content moved out of <webview> and into a WebContentsView owned by main. That one move settles a pile of things that used to be structural: a page survives being torn into a detached window and put back (reparenting instead of a fresh DOM node), and a preview can be opened and its console errors read in a background workspace — "the preview only exists in the workspace on screen" is gone. Screenshots still need the window on screen, because an unrendered surface has no pixels. The BOOT_URL/GUEST_PREFS boot trick, resolveGuest, the preview:watchIssues IPC pair, six navigation subscriptions and the 292-line FileViewerOverlay all came out with it, and PreviewPanel got its first render test — jsdom has no <webview>, so it had never been testable. Views are capped at 6 with a 10-minute hibernation, and an evicted tab restores from its URL when you click it. Sessions are partitioned three ways — dev (persist:wooi-preview), web (persist:wooi-browser), and artifacts (per-workspace, non-persistent) — through a switch with a never fallthrough, so adding a kind breaks the build instead of quietly dropping it into the dev session. The agent can open tabs too: open_web_tab, open_file_tab and open_artifact_tab, plus /wooi:web, /wooi:file and /wooi:open-artifact. Only open_web_tab goes through an approval card — the web session carries cookies you're logged in with, so the usual "the agent already has a shell, this opens no new door" reasoning doesn't hold there. Along the way: a web tab no longer overwrites the dev preview's URL (which had quietly widened open_preview's boundary from "this workspace's dev server" to "wherever the web tab was last"), console issues no longer clear the neighbouring tab, pages stop reloading every time you switch tabs, unsaved edits in a tab you aren't looking at now prompt before closing, and a file in Files opens its tab on the first click instead of the second.
  • ⌨️ Modifier keys now say what they act on — ⌘ is tabs and the page, ⌃⇧ is the terminal, ⌥⌘ is workspaces and windows. Tabs take the ⌘ row (⌘T ⌘W ⇧⌘T ⌘1–9 ⌘R ⌘[] ⌘L), so what used to live there moved out: nth workspace is ⌥⌘1–9, previous/next workspace ⌥⌘↑↓, visit history ⌥⌘[], the work panel ⌥⌘J, and reopen-archived ⇧⌘Z. Tab shortcuts are bound as menu accelerators rather than renderer keydown, because once a guest takes focus the renderer never sees the key at all — one click into a preview would otherwise kill every tab shortcut at once. That required installing an application menu, which the repo never had, and it takes back ⌘R/⇧⌘R (until now a hard reload was firing instead of PR review), ⌘W and zoom from Electron's default menu.
  • 🎨 What the agent builds, you can run inside the app — create_artifact and an Artifacts tab render html, react, svg, mermaid and markdown, identically on both Claude and Codex backends. Everything compiles in main — JSX through sucrase, import specifiers, Tailwind — and the guest only ever receives finished static files. The reason is who reads the error: a failure inside the guest is trapped in a sandbox console the model never sees, while a throw in the handler becomes that turn's tool error and gets fixed on the spot. It also takes the transpiler bundle, blob: URLs, the import map and CSP's 'unsafe-eval' out of the guest entirely. Artifact guests can't navigate anywhere and can't reach the network: fetch, WebSocket, sendBeacon, XHR, <img>, <iframe>, static and dynamic <script src>, stylesheets, window.open and location.href were all tried against a real HTTP server in Electron — zero requests arrived, zero shell.openExternal calls. CSP can't cover this on its own (navigate-to was dropped from the standard), so the guard runs down to will-frame-navigate to catch subframes. Re-running the tool with the same id bumps the version rather than replacing it. Cleanup is wired only to the three permanent-delete paths — not to archiving, which keeps the conversation, and not to /clear, which empties the model's context rather than throwing work away. Ships with @anthropic-ai/claude-agent-sdk 0.3.233 → 0.3.261.
  • 🧵 Pick a sub-agent from the sidebar and read what it's actually doing — until now "n running" was the whole story, and that's a signal, not content. Two things were wrong: the Agent SDK discards a sub-agent's text and thinking unless forwardSubagentText is on (nothing in the repo set it), and the tool calls that did arrive carried no parent_tool_use_id, so they stacked flat among the parent's own cards — one saved transcript had 12 Bash calls sitting between an Agent call and its result, and zero assistant entries. Sub-agent entries are now stored in the same transcript JSONL tagged with parentToolId, so nothing new had to be built for search, pagination or restore, and they survive a restart. Stepping into one gives you the same conversation view — MessageList and Composer unchanged, so density (⌃O), search (⌘F), jump, scroll anchoring, tool cards, grouping, drafts and ↑ history all come along; only the header differs. The sidebar shows what's running plus the last 5 finished in the workspace you're in. You can talk to a running Claude native Task, addressed by its SDK task id — the host has no SendMessage of its own, so the parent relays it, under instructions to pass it through verbatim and not do the work itself. Where that isn't possible it says why instead of greying out silently: Wooi's own delegate runs and Codex collab aren't SDK tasks. Fixed alongside: per-stream message ids are kept in a Map (interleaved message_start events used to let one stream overwrite the other's slot, splicing the parent's speech into someone else's bubble), a sub-agent's model no longer gets reported as the session model, the page budget counts only parent entries, and a stray wrapper around MessageList no longer pushes the composer off screen in long conversations.

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.31.0 새 소식

  • 🗂️ 워크스페이스가 탭 스트립이 됐습니다 — 대화·dev 프리뷰·웹·파일·아티팩트·스택이 나란히 놓입니다 — Work 탭(대화 + 우측 작업 패널)이 고정된 첫 자리이고 나머지가 그 오른쪽에 붙습니다. 그 아래에서는 게스트 웹 콘텐츠가 <webview> 에서 main 이 소유하는 WebContentsView 로 옮겨 갔습니다. 그동안 구조 때문에 안 되던 것들이 이 한 번의 이동으로 풀립니다. 분리 창으로 떼었다 다시 붙여도 페이지가 죽지 않고(DOM 노드를 새로 만드는 대신 재부모화합니다) 백그라운드 워크스페이스에서도 프리뷰를 열고 콘솔 에러를 읽습니다 — "프리뷰는 화면에 열린 워크스페이스에만 존재한다" 는 제약이 사라졌습니다. 스크린샷만 여전히 화면을 요구합니다. 그려지지 않는 화면에는 픽셀이 없기 때문입니다. BOOT_URL·GUEST_PREFS 부팅 트릭, resolveGuest, preview:watchIssues IPC 한 쌍, 내비게이션 이벤트 구독 6개, 292줄짜리 FileViewerOverlay 가 함께 빠졌습니다. PreviewPanel 에는 처음으로 렌더 테스트가 생겼습니다. jsdom 에 <webview> 가 없어 그동안은 테스트 자체가 불가능했습니다. 뷰는 6개까지만 살고 10분이 지나면 동면합니다. 축출된 탭은 다시 누르면 주소로 되살아납니다. 세션은 dev(persist:wooi-preview)·웹(persist:wooi-browser)·아티팩트(워크스페이스별 비영속) 셋으로 갈라 두었습니다. switch 에 never 를 달아 두었으니 종류를 더하면 조용히 dev 세션으로 떨어지는 대신 빌드가 깨집니다. 에이전트도 탭을 엽니다. open_web_tab·open_file_tab·open_artifact_tab, 그리고 /wooi:web·/wooi:file·/wooi:open-artifact 입니다. 승인 카드를 타는 것은 open_web_tab 하나뿐입니다. 웹 세션은 사용자가 로그인해 둔 쿠키를 달고 나가므로 "어차피 에이전트에게는 셸이 있으니 새로 열리는 문이 없다" 는 논리가 여기서만 통하지 않습니다. 이참에 몇 가지를 함께 고쳤습니다. 웹 탭이 dev 프리뷰 주소를 덮어썼습니다(그 탓에 open_preview 의 경계가 "자기 워크스페이스의 dev 서버" 에서 "웹 탭이 마지막으로 있던 곳" 으로 조용히 넓어졌습니다). 콘솔 문제가 옆 탭 것까지 지웠고 탭을 오갈 때마다 페이지가 다시 로드됐습니다. 보고 있지 않은 탭의 저장 안 한 편집은 말없이 사라졌고 Files 에서는 파일을 두 번 눌러야 탭이 열렸습니다.
  • ⌨️ 수식키가 무엇을 다루는지 말해 줍니다 — ⌘ 는 탭과 페이지, ⌃⇧ 는 터미널, ⌥⌘ 는 워크스페이스와 창입니다. ⌘ 줄은 탭이 가져갑니다(⌘T ⌘W ⇧⌘T ⌘1–9 ⌘R ⌘[] ⌘L). 그래서 그 자리에 있던 것들이 옮겨 갔습니다. n번째 워크스페이스는 ⌥⌘1–9, 이전/다음 워크스페이스는 ⌥⌘↑↓, 방문 이력은 ⌥⌘[], 작업 패널은 ⌥⌘J, 아카이브 되살리기는 ⇧⌘Z 입니다. 탭 단축키는 렌더러의 keydown 이 아니라 메뉴 accelerator 로 답니다. 게스트가 포커스를 쥐면 렌더러는 키를 아예 못 보기 때문입니다 — 그대로 두면 프리뷰를 한 번 클릭하는 것만으로 탭 단축키가 통째로 먹통이 됩니다. 이를 위해 레포에 없던 애플리케이션 메뉴를 새로 깔았습니다. 덕분에 Electron 기본 메뉴가 쥐고 있던 ⌘R·⇧⌘R(그동안 PR 리뷰 대신 강제 새로고침이 걸리고 있었습니다), ⌘W, 줌을 되찾았습니다.
  • 🎨 에이전트가 만든 것을 앱 안에서 실행해 봅니다 — create_artifact 도구와 Artifacts 탭이 html·react·svg·mermaid·markdown 을 그리고 Claude·Codex 두 백엔드에서 똑같이 동작합니다. 컴파일은 전부 main 에서 합니다. sucrase 로 JSX 를 트랜스파일하고 import 지정자를 해석하고 Tailwind 를 굽습니다. 게스트는 완성된 정적 파일만 받습니다. 이유는 그 에러를 누가 읽느냐입니다. 게스트 안에서 실패하면 그 실패는 샌드박스 콘솔에 갇히는데, 모델은 그 콘솔을 볼 수 없습니다. 핸들러에서 throw 하면 그 턴의 도구 에러가 되어 곧바로 고쳐집니다. 덤으로 트랜스파일러 번들, blob: URL, import map, CSP 의 'unsafe-eval' 이 게스트에서 통째로 사라집니다. 아티팩트 게스트는 어디로도 이동하지 못하고 네트워크에도 닿지 못합니다. fetch·WebSocket·sendBeacon·XHR·<img>·<iframe>·정적/동적 <script src>·스타일시트·window.open·location.href 를 실제 HTTP 서버를 세워 Electron 에서 전부 시도해 봤습니다. 결과는 서버에 도달한 요청 0건, shell.openExternal 호출 0회였습니다. CSP 만으로는 못 막습니다(navigate-to 지시문은 표준에서 빠졌습니다). 그래서 가드를 will-frame-navigate 까지 걸어 서브프레임도 덮습니다. 같은 id 로 도구를 다시 부르면 덮어쓰는 대신 버전이 오릅니다. 정리는 영구 삭제 세 곳에만 겁니다. 대화를 보존하는 아카이브에는 걸지 않고 /clear 에도 걸지 않습니다. /clear 는 모델의 컨텍스트를 비울 뿐 산출물을 버리지는 않기 때문입니다. @anthropic-ai/claude-agent-sdk 0.3.233 → 0.3.261 이 함께 올라갑니다.
  • 🧵 서브에이전트를 사이드바에서 골라 들어가 무엇을 하고 있는지 읽습니다 — 그동안은 "n running" 한 줄이 전부였는데, 그건 돌고 있다는 신호이지 내용이 아닙니다. 두 가지가 잘못돼 있었습니다. Agent SDK 는 forwardSubagentText 를 켜지 않으면 서브에이전트의 말과 사고를 버리는데, 레포에 그 플래그를 쓴 곳이 한 군데도 없었습니다. 들어오던 도구 호출마저 parent_tool_use_id 를 달지 않아 부모 카드와 같은 층에 평평하게 쌓였습니다. 저장된 트랜스크립트 하나에서는 Agent 호출과 그 결과 사이에 Bash 12개가 놓여 있었고 assistant 항목은 0개였습니다. 이제 서브에이전트 항목을 같은 트랜스크립트 JSONL 에 parentToolId 를 달아 저장합니다. 그래서 검색·페이지네이션·복원을 하나도 새로 만들지 않았고 앱...
Read more

v1.30.0

Choose a tag to compare

@github-actions github-actions released this 07 Sep 04:16
f0a1cc5

New in v1.30.0

  • 🗂️ The agent can archive and delete any open workspace, and fold up its own — archive_workspace was locked twice over. resolveTargetWorkspace only let through workspaces whose createdByWorkspaceId was the caller, so neither the ones you made by hand nor the ones a sibling made could be touched, and the handler refused the caller's own workspace outright, because archiving starts with sessions.dispose — which kills the very session the call would return to. Permanent deletion wasn't available to the agent at all: the procedure lived inline in the IPC.workspaceRemove handler, with the sidebar as its only entrance. The grounds for permission move from "I created it" to "you approved it on the card". Since target.ts deliberately does not lean on approval cards (needsApproval waves things through under fullAccess), opening the creator check with allowAnyCreator had to come with a guarantee that the card actually appears: the two tools that set it go into HANDLER_APPROVES, so the transport leaves them alone and the handler asks with always: true itself. Exactly one case gets no card — a workspace you made, clean and idle — which is what the existing tidy-up-after-yourself flow stands on. Uncommitted work no longer blocks an archive; it is counted with getStatus and written onto the card in the same sentence the sidebar's delete confirmation uses (It loses 2 uncommitted files and 1 commit not in main.), and a failed git lookup says "unknown" rather than 0, because an empty string reads as "nothing to lose" and that lie can't be checked by eye. Archiving your own workspace is scheduled through AgentOrchestrator.archiveAfterTurn and carried out at handleTurnEnd — only on a clean finish, and only if you didn't intervene, since a turn that ended in an error is better left with its worktree alive so you can read what went wrong. Deletion is a separate delete_workspace rather than a flag on the existing tool, because the two differ on the one thing that matters — whether it can be undone — and it refuses its own workspace, since the conversation that asked would go with it. A workspace mid-turn is still refused. /wooi:delete joins /wooi:archive, which now takes no argument to mean "this one".
  • 🧟 Quitting Wooi takes everything it launched with it — the battery kept draining after the app was gone. Not a resident process: orphans. On one dev machine, qemu-system-aarch64 -avd wooi-store had been running 6 days 16 hours at 20–30% CPU, an expo start for 11 days 18 hours, all reparented to launchd with no LaunchAgent and no login item behind them. The root is the claude CLI — we spawn the CLI, but the emulator that CLI spawns through its own Bash tool is a grandchild no tracking list can ever hold, and claude/host.ts had none of the exit safety net codex/host.ts already had. Four leaks are closed at the source (the missing process.on('exit') net, disposeAll() now calling abortAllSubAgents() since sub-agents run in main rather than the host, detached children from runOnce() that were never registered, and the login ptys from claude auth login / gh auth login), and a new reaper.ts is the last line: on will-quit it walks the process tree and kills our pid's descendants by lineage, because the targets are grandchildren no name match would find. SIGTERM first, 300ms, then SIGKILL on whatever survived. Exactly two things are immune — Electron helpers under Contents/Frameworks/ and Squirrel's updater. Widening that immunity to the whole app bundle would spare the claude CLI living in asar.unpacked, a trap hit during implementation and now pinned by a test. It runs on the clean-quit path only, so orphans from a crash or a force-quit still survive, and orphans launchd already adopted need one manual cleanup.
  • 🚪 ⌘Q always quits — with work in flight, ⌘Q used to close the window and call app.dock.hide(), leaving the app polling every 5 seconds from somewhere you couldn't see it. To you it had quit. Background mode is gone: backgroundMode.ts, the three tray icons, the settings toggle and the e2e spec all came out. Nothing in flight is lost — an interrupted turn is still recorded by shutdownResume as pendingShutdownResume and picked up by the next launch, and restart-after-update continuity is unchanged.
  • 🔋 An idle dashboard stops spawning CLIs — the 60-second usage poll in Overview.tsx had no gate on it. With no live session, that round trip spawns a fresh short-lived claude CLI process and keeps the codex app-server alive — leave the dashboard open overnight and that is one process spawn per minute, all night. It now goes through the pollingAwake() gate that already existed (focused, visible, input within 5 minutes), and the existing focus listener catches up within 15 seconds when you come back. useNow also stops its timer while the document is hidden and refreshes once when it returns, fixed in one place so Overview, CommitsPanel, QuickSwitcher and StackLayerCard all follow.

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.30.0 새 소식

  • 🗂️ 에이전트가 열려 있는 아무 워크스페이스나 아카이브·삭제하고 자기 자신도 접습니다 — archive_workspace 는 두 겹으로 잠겨 있었습니다. resolveTargetWorkspace 가 createdByWorkspaceId 로 호출자가 만든 워크스페이스만 가려 통과시켜서 사용자가 손으로 만든 것도, 옆 워크스페이스가 만든 것도 손댈 수 없었습니다. 아카이브의 첫 걸음인 sessions.dispose 가 그 호출의 결과가 돌아갈 세션을 죽여서, 핸들러는 자기 자신을 아예 거부했습니다. 영구 삭제는 절차가 IPC.workspaceRemove 핸들러 안에 인라인으로만 있었고 입구도 사이드바뿐이라 에이전트에게는 아예 없었습니다. 이제 권한의 근거가 "내가 만들었다" 에서 "사용자가 카드에서 승인했다" 로 바뀝니다. target.ts 에서는 일부러 승인 카드를 전제하지 않습니다. fullAccess 에서는 needsApproval 이 그냥 통과시키기 때문입니다. 그래서 생성자 검사를 allowAnyCreator 로 열려면 카드가 반드시 뜨는 것부터 보장해야 했습니다. 그 옵션을 켜는 두 도구를 HANDLER_APPROVES 에 넣어 전송 계층이 손대지 않고 핸들러가 always: true 로 직접 묻습니다. 카드가 안 뜨는 경우는 하나뿐입니다. 내가 만든, 깨끗한, 놀고 있는 자식입니다. 에이전트가 자기 뒷정리를 하는 기존 흐름은 그 예외 하나로 돌아갑니다. 미커밋 변경은 이제 아카이브를 막지 않습니다. getStatus 로 세어 승인 카드에 적습니다. 사이드바 삭제 확인창과 같은 문장입니다(It loses 2 uncommitted files and 1 commit not in main.). git 조회가 실패하면 0 이 아니라 "모른다" 로 둡니다. 빈 문자열은 "잃을 것이 없다" 로 읽히고 그 거짓말은 카드에서 사람이 검증할 수 없습니다. 자기 워크스페이스 아카이브는 AgentOrchestrator.archiveAfterTurn 에 예약하고 실제 파괴는 handleTurnEnd 가 합니다. 정상 종료일 때만, 그것도 사용자가 개입하지 않았을 때만 실행합니다. 오류로 끝난 턴은 무엇이 잘못됐는지 볼 수 있게 워크트리를 살려 두는 편이 낫기 때문입니다. 아카이브와 삭제는 되돌릴 수 있는가라는 가장 중요한 지점이 정반대라, 삭제는 기존 도구의 플래그가 아니라 별도의 delete_workspace 로 냈습니다. 한 도구에 묶으면 이름이 거짓말을 합니다. 그리고 삭제를 요청한 대화까지 함께 사라지니 자기 자신은 거부합니다. 도는 중인 워크스페이스는 여전히 거부합니다. /wooi:delete 가 /wooi:archive 옆에 붙었고, /wooi:archive 는 이제 인자가 없으면 "이 워크스페이스" 를 뜻합니다.
  • 🧟 앱을 종료하면 Wooi 가 띄운 것들도 같이 사라집니다 — 앱을 껐는데도 배터리가 줄었습니다. 상주가 아니라 고아였습니다. 개발 머신에서 qemu-system-aarch64 -avd wooi-store 가 6일 16시간 동안 CPU 20~30% 를 쓰며 돌고 있었고, expo start 는 11일 18시간째였습니다. 전부 launchd 에 입양된 채였는데 LaunchAgent 도 로그인 항목도 없었습니다. 뿌리는 claude CLI 입니다. CLI 는 우리가 띄우지만 그 CLI 가 자기 Bash 도구로 띄운 에뮬레이터는 손자여서 어느 추적 목록에도 들어올 수 없습니다. 게다가 claude/host.ts 에는 codex/host.ts 가 이미 갖고 있던 종료 안전망이 아예 없었습니다. 새는 자리 네 곳을 근원에서 막았습니다. process.on('exit') 안전망이 빠져 있었고 서브에이전트가 호스트가 아니라 메인에서 돌기 때문에 disposeAll() 이 abortAllSubAgents() 를 불러야 했습니다. runOnce() 는 detached 로 띄운 자식을 등록하지 않았고 claude auth login·gh auth login 의 로그인 pty 도 마찬가지였습니다. 마지막 방어선은 새로 넣은 reaper.ts 입니다. 이름으로는 찾을 수 없는 손자가 대상이라 will-quit 에서 프로세스 트리를 훑어 우리 pid 의 자손을 혈통으로 지웁니다. SIGTERM 을 보내고 300ms 를 기다린 뒤 살아남은 것에만 SIGKILL 합니다. 면역은 Contents/Frameworks/ 아래의 Electron 헬퍼와 Squirrel 업데이터 둘뿐입니다. 면역을 앱 번들 전체로 넓히면 asar.unpacked 에 있는 claude CLI 가 같이 살아남습니다. 구현 중에 실제로 밟았던 함정이라 테스트로 고정해 뒀습니다. 정상 종료 경로에서만 도는 장치라 크래시나 강제 종료로 남는 고아는 여전히 남고, 이미 launchd 가 입양한 고아는 한 번 손으로 정리해야 합니다.
  • 🚪 ⌘Q 는 언제나 진짜로 종료합니다 — 백그라운드 모드를 걷어냈습니다. 그전까지는 도는 일이 있을 때 ⌘Q 를 누르면 창만 닫고 app.dock.hide() 로 사라진 채 5초마다 폴링하며 계속 돌았습니다. 사용자 눈에는 종료된 앱입니다. backgroundMode.ts 와 트레이 아이콘 3종, 설정 토글, e2e 스펙까지 전부 빠졌습니다. 일은 잃지 않습니다. 끊긴 턴은 예전처럼 shutdownResume 이 pendingShutdownResume 으로 기록하고 다음 실행이 이어받습니다. 업데이트 재시작 연속성도 그대로입니다.
  • 🔋 유휴 상태의 대시보드가 CLI 를 새로 스폰하지 않습니다 — Overview.tsx 의 60초 사용량 폴링에 게이트가 없었습니다. 라이브 세션이 없으면 그 왕복이 단명 claude CLI 프로세스를 새로 띄우고 codex app-server 를 살려 두는 탓에 대시보드를 띄운 채 밤새 두면 분당 한 번씩 프로세스가 스폰됩니다. 이제 이미 있던 pollingAwake() 게이트(포커스, 가시성, 5분 내 입력)를 지나갑니다. 자리에 돌아오면 기존 focus 리스너가 15초 안에 따라잡습니다. useNow 도 문서가 보이지 않는 동안 타이머를 멈추고 다시 보이면 즉시 한 번 갱신합니다. 한 곳만 고쳐 Overview·CommitsPanel·QuickSwitcher·StackLayerCard 가 전부 따라옵니다.

설치

brew install --cask youngminnnn/tap/wooi

이미 .dmg를 직접 설치해 쓰고 계시다면 brew install --cask --adopt youngminnnn/tap/wooi로 그 자리에서 인수하세요.

또는 서명·공증된 Wooi-arm64.dmg를 직접 받을 수 있습니다.


What's Changed

  • fix: 앱을 종료하면 Wooi 가 띄운 것들도 같이 사라진다 by @youngminnnn in #517
  • feat: 에이전트가 옆 워크스페이스를 정리하고 자기 자신도 접는다 by @youngminnnn in #518
  • release: v1.30.0 by @youngminnnn in #520

Full Changelog: v1.29.0...v1.30.0

v1.29.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 05:38
a4631b0

New in v1.29.0

  • 🚂 The merge train starts from any layer and waits for CI — three places where the train stalled in real use. It refused to start at the bottom of a stack: the range it walks is "stack root → current workspace," so the bottom is always exactly one layer, and the plan rejected anything under two with A merge train needs at least two layers. A one-layer train still has work — merge that PR, then retarget, rebase and force-push every layer above it, a cascade prMerge deliberately leaves alone. Second, that force-push restarts CI on the layer above, and reading a running check as a blocker ended the train at the second layer every time; ci_pending now waits without a deadline, polling from 2s out to 30s to spare gh calls, while ci_failed, draft, conflict and changes_requested still stop it immediately with no wait. Right after a force-push the new head has no statusCheckRollup yet and GitHub answers BLOCKED, which surfaces as review_required — for 60 seconds, and only on layers this run pushed itself, that verdict is held instead of believed. A review_required on a layer we didn't push is a real review request and still stops the train. Waiting means running detached, so stackTrainRun returns immediately and progress flows over events: the titlebar chip, the sidebar row (· merge train (0/1)), BaseSyncControl and the stack layer cards all say the train is moving, a window opened late catches up, and a toast lands at the end. Third, "Merge train in progress" was a dead button — no onClick, and it had swapped the modal's onClose for a no-op, so ESC and X were both stuck. It is now Cancel merge train and Run in background, and the dialog reads its step from the run's progress, so closing and reopening returns you to the same place. Cancel only cuts at layer boundaries and during CI waits; cutting mid-cascade would leave the layer below merged and the ones above still pointing at an old base.
  • ⏳ Workspaces waiting on a usage limit resume in a queue, not all at once — the limit is per account, but retryTime() only sees the shared snapshot and the reset time the error carried, so every reservation on one backend gets a byte-identical retryAt; the 60-second wake poll then sends all of them on the first tick past it. Two costs stack up. The prompt cache is certainly dead after the wait, so each one writes its cache again — over the last 7 days, 81 of 100 cache breaks carried a resume prompt, averaging 275k tokens. And burning a freshly opened window all at once walks straight back into the limit: four sessions resumed twice in a row (36.3M, 21.0M, 16.0M, 9.2M). A per-backend gate at the single continueNow() entry now spaces them 20 seconds apart — shorter than the 60-second wake poll, so a gated workspace still wakes near its own time instead of waiting out another tick. A gated reservation is re-armed untouched; clearing its pending state first would erase the grounds for coming back at all. No jitter was added: retryTime() is a pure function and its value is shown to you verbatim, so jittering would make the displayed time disagree with what actually happens.
  • 🎯 Early resume stops tearing up its own reservation — auto-continue was sending a turn when the limit had not actually lifted, getting limited again 1.2 seconds later, and repeating. The two sides read the same snapshot in opposite directions: the scheduler counted a window at ≥90% as exhausted (numbers right after a limit lag by exactly the requests that were refused) and set 2:00 AM, while the early check counted anything under 100% as not limited and tore that up five minutes later. limitLifted now decides on knownResetAt(snapshot, now) === null — the same basis the reservation was made on. Two more holes closed: a snapshot older than the last limit we saw can no longer pull a resume forward (rate limits: refresh failed left stale-but-healthy snapshots lying around to be believed), and a reservation is eligible for early resume only when the usage snapshot itself pointed at that reset time. A reset time the server handed us in the error text is waited out, because the snapshot was never the eye that could see that limit.
  • 🧹 Workspaces you opened and never used are offered for cleanup too — of 336 workspaces over a week, 268 spent under 1M tokens; each still writes the system prompt, CLAUDE.md and the tool schemas into a fresh cache on its first turn (cache_create is 4.5% of everything). The only trigger for a cleanup suggestion was a merged PR, so these never got caught — resync returned early without a prNumber, and a workspace that never opened a PR is exactly the one to catch. Detection now runs alongside the merge check rather than inside it, on fields already on disk: unused (no session, no PR, untouched since creation, 24 hours old) and fanoutLoser (a fan-out candidate left unadopted). Detection is automatic; archiving still isn't — you get a banner, and ⌘Z still undoes it.
  • 🧵 Cut the context loose without losing the transcript — carrying on in a workspace after its PR merged meant hauling the finished work's context into the next task, and the only tool for that was /clear, which also deletes the conversation. Those were one button, not one mechanism: cutting the context is sessionId = null, and deleting the transcript was never required for it. The banner now offers Start fresh session once the context passes 100k — the same threshold the cold-resume notice uses, since both are talking about how much a resume would drag back in. The conversation stays on screen and a system message marks the boundary, so it's clear the agent can no longer see what you can.
  • 🎚️ New workspaces start at the density you chose — density was a per-workspace localStorage value with 'normal' hardcoded as the fallback, so anyone who reads in Summary pressed ⌃O on every new workspace. Settings › General › Appearance › Conversation density now sets the default, and a workspace that hasn't picked its own follows it. It's a fallback rather than a snapshot taken at creation — workspaces get made from the sidebar, from issues and over MCP, so a snapshot would need a hook on every path, and a fallback also carries existing workspaces along. Picking the same density as the global default clears the workspace's own value, putting it back to "unset" so a later change to the setting reaches it without a restart.

Install

brew install --cask youngminnnn/tap/wooi

Already using a hand-installed .dmg? Run brew install --cask --adopt youngminnnn/tap/wooi to let Homebrew take it over in place.

Or download the signed and notarized Wooi-arm64.dmg.


한국어

v1.29.0 새 소식

  • 🚂 머지 트레인이 어느 층에서든 걸리고 CI 를 기다립니다 — 실사용에서 막히던 세 자리를 고쳤습니다. 첫째, 스택 최하단에서는 트레인을 걸 수 없었습니다. 트레인이 훑는 범위가 "스택 뿌리 → 지금 워크스페이스" 라 최하단은 언제나 1층입니다. 계획은 2층 미만을 A merge train needs at least two layers. 로 거절했습니다. 1층 트레인에도 할 일이 있습니다. 그 PR 을 머지하고 위층 전부를 새 base 로 리타겟·rebase·force-push 합니다. prMerge 가 일부러 하지 않는 캐스케이드입니다. 둘째, 그 force-push 가 위층 CI 를 처음부터 다시 돌립니다. 도는 검사를 차단으로 읽으면 트레인은 사실상 늘 두 번째 층에서 끝났습니다. 이제 ci_pending 이면 무제한 기다립니다. 폴링 간격은 gh 호출을 아끼려고 2초에서 30초로 늘려 갑니다. ci_failed·draft·conflict·changes_requested 는 예전처럼 기다리지 않고 즉시 중단합니다. force-push 직후에는 새 head 에 아직 statusCheckRollup 이 없습니다. GitHub 이 BLOCKED 를 돌려주고 그것이 review_required 로 보입니다. 이번 실행이 직접 민 층에 한해 60초 동안 그 판정을 유보합니다. 우리가 밀지 않은 층의 review_required 는 진짜 리뷰 요구라 그대로 멈춥니다. 기다리려면 백그라운드여야 하므로 stackTrainRun 은 곧바로 반환합니다. 진행은 이벤트로 흐릅니다. 타이틀바 칩, 사이드바 행(· merge train (0/1)), BaseSyncControl, 스택 층 카드가 트레인이 도는 중임을 말합니다. 늦게 뜬 창도 따라잡습니다. 끝나면 토스트가 뜹니다. 셋째, "Merge train in progress" 는 죽은 버튼이었습니다. onClick 이 없었습니다. 같은 자리에서 모달 onClose 도 no-op 으로 갈아끼워져 ESC 와 X 가 전부 먹통이었습니다. 이제 Cancel merge train 과 Run in background 입니다. 화면 단계는 진행 상태에서 뽑으므로 닫았다 다시 열어도 같은 자리로 돌아옵니다. 취소는 층 경계와 CI 대기 중에만 끊습니다. 캐스케이드 도중에 끊으면 아래층은 병합됐는데 위층이 옛 base 를 가리킨 채 남기 때문입니다.
  • ⏳ 사용량 제한을 기다리던 워크스페이스가 한꺼번에 나가지 않고 줄을 섭니다 — 제한은 계정 단위입니다. retryTime() 이 보는 것은 공유 스냅샷과 오류가 실어 준 해제 시각뿐입니다. 같은 백엔드의 예약은 한 글자도 다르지 않은 retryAt 을 갖습니다. 60초 폴링이 그 시각을 지난 첫 틱에 전부 내보냅니다. 겹쳐서 나갈 때 나쁩니다. 기다리는 동안 프롬프트 캐시는 확실히 죽으므로 각자 캐시를 새로 씁니다. 지난 7일 캐시 브레이크 100건 중 81건이 재개 프롬프트를 물고 있었습니다. 평균 275k 였습니다. 그리고 갓 열린 창을 한꺼번에 태우면 곧바로 다시 걸립니다. 한 세션에서 재개가 2회 연속 일어난 것이 4건(36.3M, 21.0M, 16.0M, 9.2M)이었습니다. 이제 continueNow() 진입 한 곳에 백엔드당 게이트를 뒀습니다. 20초 간격으로 내보냅니다. 60초 폴링보다 짧아야 게이트에 걸린 워크스페이스가 다음 틱을 기다리지 않고 제 시각 가까이에 깨어납니다. 걸린 예약은 건드리지 않고 그대로 되돌립니다. 대기 상태부터 지우면 되돌아올 근거가 사라집니다. 지터는 주지 않았습니다. retryTime() 은 순수 함수입니다. 그 값이 사용자에게 시각 그대로 표시됩니다. 지터를 주면 표시와 실제가 어긋납니다.
  • 🎯 조기 재개가 제 예약을 스스로 깨지 않습니다 — 제한이 실제로 풀리지 않았습니다. 자동 이어가기가 턴을 보내고 1.2초 만에 다시 걸리기를 반복했습니다. 같은 스냅샷을 양쪽이 반대로 읽고 있었습니다. 예약을 거는 쪽은 사용률 90% 이상이면 그 창이 소진된 것으로 셉니다. 제한 직후의 수치는 거절당한 요청만큼 늦기 때문입니다. 그래서 02:00 으로 예약합니다. 조기 재개는 100% 미만이면 제한이 아니라고 보고 5분 뒤 그 예약을 깼습니다. 이제 limitLifted 는 knownResetAt(snapshot, now) === null 로 판정합니다. 예약을 건 근거와 같은 것입니다. 남은 구멍도 막았습니다. 스냅샷이 제한을 마지막으로 본 시각보다 오래됐으면 재개를 앞당기지 못합니다. rate limits: refresh failed 가 찍힐 때마다 몇 분 전의 멀쩡한 스냅샷이 그대로 남아 근거로 쓰였습니다. 그리고 해제 시각을 사용량 스냅샷이 직접 짚어 줬을 때만 조기 재개합니다. 제한 오류 문구가 실어 준 해제 시각은 예약해 둔 시각까지 기다립니다. 애초에 스냅샷으로는 그 제한이 보이지 않습니다.
  • 🧹 열어 놓고 쓰지 않은 워크스페이스도 정리 대상으로 제안합니다 — 일주일 동안 워크스페이스 336개 중 268개가 토큰 1M 미만이었습니다. 그래도 각자 첫 턴에 시스템 프롬프트와 CLAUDE.md, 도구 스키마를 캐시에 새로 써 넣습니다(cache_create 가 전체의 4.5%입니다). 지금까지 정리 제안의 트리거는 PR 병합 하나뿐이었습니다. 이것들은 그물에 걸리지 않았습니다. 재동기화가 prNumber 없이는 조기 return 합니다. 정확히 잡아야 할 대상이 PR 을 연 적 없는 워크스페이스였습니다. 이제 감지를 병합 판정 바깥에 나란히 뒀습니다. 이미 저장돼 있는 필드만으로 판정합니다. unused(세션 없음, PR 없음, 만든 뒤 손대지 않음, 24시간 경과)와 fanoutLoser(채택 없이 남은 fan-out 후보)입니다. 감지는 자동이지만 실행은 자동이 아닙니다. 배너로 제안하고 ⌘Z 되돌리기도 그대로 됩니다.
  • 🧵 기록은 남기고 맥락만 끊습니다 — PR 이 병합된 뒤에도 같은 워크스페이스에서 이어 가면 다음 작업이 끝난 작업의 컨텍스트를 통째로 이고 갑니다. 그걸 끊는 수단은 /clear 뿐이었는데 그건 대화 기록까지 지웁니다. 둘은 한 버튼이었을 뿐 한 장치가 아니었습니다. 맥락을 끊는 것은 sessionId = null 하나로 끝납니다...
Read more