Changelog
All notable changes to Irrlicht, following Keep a Changelog conventions.
v0.4.8 (2026-05-30)
Watch your agents from any machine — the irrlichtrelay ships, alongside a Linux daemon and per-provider quota tracking.
Highlights
Watch every machine from one place
irrlichtrelay is a new standalone binary. Point each machine’s daemon at it and your macOS app and web dashboard show every session from every host in one aggregated list — your laptop, a Linux box, a VM — without being at any of them. The link is secured end-to-end: TLS/wss, bearer-token auth (with a token-minting CLI), an origin allowlist, and per-IP / per-connection caps.
Why it matters: you no longer have to sit at the machine running an agent to see what it’s doing — one relay, every host, from anywhere, over an authenticated connection. (#483, #547, #544, #548, #519)
Added
- Linux daemon (#482, #478) —
irrlichdnow builds and runs on Linux (amd64 + arm64), daemon-only, behind a portableProcessObserverobservation layer; install via the samecurl … | shone-liner. - Per-provider windowed usage spend + subscription empty-state (#441, #386) — usage and quota are tracked per provider, with a clean empty-state before the first window of data lands.
- OpenCode inherits OpenAI rate limits (#424) — OpenCode sessions pick up the OpenAI rate-limit window via the JWT
account_id, so quota burndown is accurate for OpenCode→OpenAI users. - Background Bash processes hold a session
working(#450, #452) — a claudecode session with a live backgrounded process staysworkinguntil it finishes, instead of falsely settling toready. - Editable relay URL + token fields in macOS Settings (#550) — the relay endpoint and bearer token are now editable text fields in the app’s Settings.
Fixed
- Surface claudecode tool-use permission prompts as
waiting(#490) — a session paused on a permission prompt now flips towaitinginstead of looking busy. - Codex: settle interrupted turns (#464) — a
turn_abortedis treated as turn-end, so an interrupted Codex turn settles instead of hanging inworking. - Price Warp / Cursor / Antigravity sessions correctly — added 22 new model aliases synced from codeburn (Warp auto-routers and codex display strings, Cursor dash-form reasoning tiers, Antigravity Gemini 3.5 Flash, and human-readable display-name forms), so these frontends price at real dollars instead of $0. Closes the alias-sync gap deferred in v0.4.7.
- claudecode: strip trailing period from background-process output path (#501).
Changed / Distribution
- Web dashboard split into three files (#418) —
index.html+irrlicht.css+irrlicht.js, with Vitest unit tests; the daemon serves them from disk, no codegen. - Linux replay Dockerfile relocated to
tools/(#499).
Technical appendix
- Relay (
irrlichtrelay) — v0 round-trip daemon → relay → macOS + web (#483); v1·A secure exposure — TLS/wss, bearer-token auth + token-minting CLI, origin allowlist (#547); v1·B hardening — websocket read-limit + per-IP / per-connection caps (#544); v1 epic C–G — compound session keying across hosts, origin glyph, deploy artifacts, fade-on-disconnect, coding-factory demo (#548); macOS auto-connect on relay URL, connection-status dot, restored ⓘ + scroll (#519); editable relay URL + token Settings fields (#550); live cross-host round-trip testbed underexamples/(#486). - Cross-platform observation layer — new
ports.ProcessObserverseam with build-taggedprocess_{darwin,linux,other}.go; the adapters are unchanged (internal seam, not DI), and a Linux daemon falls out — Windows is now “add one file” (#482, #478). - Quota / pricing — per-provider windowed usage spend + subscription empty-state (#441, #386); OpenCode→OpenAI rate-limit inheritance via JWT
account_id(#424); 22 new frontend aliases incore/pkg/capacity/aliases.gosynced from codeburn’sBUILTIN_ALIASES. Five Warp codex aliases resolve togpt-5.3-codex, which LiteLLM does not yet price — they log on miss and are flagged for a follow-up sync rather than blocking the release. - Session detection — claudecode tool-use permission prompts surface as
waiting(#490); backgrounded-Bash tracking keeps the sessionworkingand recognizesTaskOutput/<task-notification>completion in SDK-harnessed claude as well asBashOutput/KillShellin bare claude (#450, #452, #501); codexturn_abortedtreated as turn-end (#464). - Onboarding factory (internal fixture tooling — no runtime impact) — eight-phase rewrite of the scenario × adapter coverage matrix:
tools/agent-onboarding→tools/onboarding-factory, with theofCLI as the sole writer of everything underreplaydata/(#522–#530). Schema cutover to per-scenario shards — one catalog (scenarios.json) plus per-cellmetadata.jsonin id-prefixed folders, every recording moved underrecordings/<name>/(#510, #511, #514, #524). New 4-verbir:onboarding-factoryskill retiringir:onboard-agent;of validateas a CI integrity gate. Per-adapter column recordings landed across claudecode, codex, opencode, aider, and pi (#515, #517, #518, #504, #489, #477, and others). - Tests / CI — per-worktree state isolation + headless daemon startup smoke test (#448); hermetic replay so byte-identity goldens reproduce (#447, #451); fswatcher flake fixes (#485, #487); three-dot diff in the replaydata deletion guard (#466); web dashboard Vitest suite (#418).
v0.4.7 (2026-05-22)
macOS distribution levels up: Sparkle auto-updates and a notarized DMG, plus OpenCode task progress reaches dashboard parity.
Added
- Sparkle 2.x auto-update integration (#413) — the app now checks for updates on launch and offers a one-click upgrade; a manual “Check for Updates…” button lives in both the popover and the Settings panel. Signed appcast served from
irrlicht.io/appcast.xml. First Sparkle-enabled release is v0.4.7; users on v0.4.6 must do one manual upgrade before auto-updates begin. - Web dashboard ports the macOS overlay’s provider quota chip (#417, closes #387) — popover and dashboard now show identical 5h/7d subscription bars (or cumulative usage spend) for the same
/api/v1/sessionsresponse. Settings modal gains a Provider-quota section with the same auto/subscription/usage controls as the macOS app. - OpenCode
todowritesnapshots surface as task-progress dots (#410, closes #277) — parity with Claude Code’s TaskCreate/TaskUpdate pipeline. Content-keyed deltas survive OpenCode’s lack of stable todo IDs.
Fixed
- OpenCode working→ready latency (#412, closes #278) — sessions flip from working to ready ~2.5 s faster.
- Claude Code wrapped command preserves user statusLine output (#404).
- Curl installer survives GitHub API rate limits (#401) — falls back to the redirect-based latest-release URL when the API returns 403.
Changed / Distribution
- Developer-ID signed + Apple-notarized DMG (#406, #409, closes #233) — first launch through the curl installer or Homebrew cask no longer trips Gatekeeper; the quarantine-strip workarounds in
site/install.shand the cask postflight are gone. com.apple.security.get-task-allowremoved from production entitlements (#407, #415) — Apple notarization rejects binaries with the debug entitlement set to true. Build-time + canary-install guards both assert it is absent on the shipping artifact.
Docs
- Kitty terminal-host docs (#402) — clarify that font/config changes require a full app restart, not just a reload.
Removed
tools/coverage-viewerandtools/find-flicker-sessions.shdeleted (#411, #414) — superseded by the unified agent-onboarding viewer attools/agent-onboarding.
Internal
/ir:onboard-agentpipeline lands (#328, #408) — drives every adapter through a shared agent-agnostic scenario catalogue keyed by capabilities; records lifecycle fixtures and surfaces material drift vs. committed recordings. First-class opencode driver added in this release.
Deferred
- Model alias map sync from codeburn skipped — 13 upstream additions remain unsynced; several target canonicals (e.g.
gpt-5.3-codex) are not yet in LiteLLM’s pricing table, so adding them now would still resolve to zero-cost capacity. Will land in a follow-up once LiteLLM catches up.
Technical appendix
- Sparkle 2.x integration (#413) — Sparkle 2.9.2 added via SwiftPM with a thin
UpdateManagerwrappingSPUStandardUpdaterController. EdDSA public keynKRcUPAmK6syLFEvp9O30FFvjhTIfGxYVv/6y8zpZI0=baked into both the trackedInfo.plistand thetools/build-release.shheredoc.tools/build-release.shcopiesSparkle.frameworkintoContents/Frameworks/, adds the@executable_path/../Frameworksrpath, and signs the nested helpers (Downloader.xpc, Installer.xpc, Updater.app, Autoupdate, framework binary) deepest-first before the outer bundle. Newsite/appcast.xmlships with one signed entry for v0.4.6 so the feed serves a valid response immediately. After installing v0.4.7, drag the app to/Applications/— Sparkle refuses to self-update from a Gatekeeper-translocated~/Downloads/path. - Notarized + DevID signed DMG (#406, #409) —
tools/build-release.shsigns with the Developer ID cert + hardened runtime + entitlements whenDEVELOPER_IDis set; notarizes and staples whenNOTARYTOOL_KEYCHAIN_PROFILEis also set. The build exits 1 ifDEVELOPER_IDis set withoutNOTARYTOOL_KEYCHAIN_PROFILE.FocusMonitor.swiftdetects the DevID signature at runtime and loadsINFocusStatusCenterviaNSClassFromStringrather than statically linkingIntents.framework. get-task-allowremoved from prod entitlements (#407, #415) —Irrlicht-dev.entitlementsstill carries it for local Xcode debug. The verification uses value-aware XPath rather than key-grep so an explicit<false/>doesn’t false-fail; the canary install check re-runs the assertion against the actually-shipping bundle.- OpenCode todowrite (#410) —
opencode.Parseris stateful: each snapshot translates into the minimal TaskCreate/TaskUpdate delta sequence the tailer expects, content-keyed because OpenCode’s todos carry no stable IDs. A ~15-line accumulator inopencode/metrics.gopopulatesmetrics.Taskson the SQLite metrics path. NewTestComputeMetrics_TodowriteTasksdrivesquerySessionMetricswith a synthetic SQLite DB. - OpenCode latency (#412) — eliminates a ~2.5 s tail where the SQLite metrics path waited on a debounce timer that no longer applied once the session went idle. New
core/application/services/debounce_test.gopins the timing invariant. - /ir:onboard-agent pipeline (#328, #408) — unifies fixture refresh + adapter bootstrap + new-agent onboarding into one skill. Drives the real CLI through a shared agent-agnostic scenario catalogue keyed by
requires: [capability]; adapters declareCapabilitiesand matrix cells fall out automatically. Newdrive-opencode-interactive.shdriver: eachsendstep is anopencode run --session <id>subprocess; post-run SQLite export to the parts JSONL the parser expects. - Installer rate-limit hardening (#401) —
site/install.shfalls back to the/releases/latestredirect URL when the GitHub API returns 403.
v0.4.6 (2026-05-17)
Public roadmap page lands, with screenshots and concept tiles for every release row
Highlights
Public roadmap at /docs/roadmap.html
A chronological newest-at-top timeline shows every shipped release with big milestones, italic notes for narrower changes, and a compact ALSO SHIPPED roll-call of every other issue/PR. Future versions (v0.5 → v1.0 plus horizon) are bold guesses against the Platform Rollout wiki, with concept tiles for the in-flight relay-v0 work, the planned VS Code panel, iOS / iPadOS, Android + Apple watch, and the v1.0 second-desktop + hosted relay launch.
Why it matters: where the project is going is now discoverable without spelunking GitHub issues. Linked from every docs sidebar, the landing footer, and the CHANGELOG.md preamble. Future releases will migrate items from above the today line down across it as they ship. (#395)
Added
- Moonshot Kimi family in the alias map —
kimi-auto,kimi-code,kimi-for-codingresolve tomoonshot.kimi-k2-thinkingin LiteLLM (codeburn sync). Sessions on Kimi-routed frontends now price at non-zero.
Changed
- Brand: gradient flame refresh propagates across the remaining surfaces (#394) — picks up the docs sidebar dot, design-system preview tiles, and the few last places where the old wisp survived the v0.4.5 rollout.
- Working-state icon: heartbeat halo → breathing solid dot (#393) — the SMIL halo animation was distracting; the solid dot with a gentle opacity breathe reads cleaner at 14px in the menu bar.
- Web dashboard: subagent dot-matrix row dropped to match the macOS overlay (#397) — the 89/96-style dot strip was a web-only detail that no longer matched the unified row anatomy shipped in v0.4.5.
Fixed
- Claude Code task list: prune entries absent from
task_remindersnapshots (#396) — the in-memory task list is reconciled against the authoritative snapshot so dropped tasks no longer linger asin_progressindefinitely.
Docs / Tooling
- Release-flow friction removed from
CLAUDE.md(#392). /ir:releaserelease-notes template + Step 4a-roadmap mechanics — three-layer release notes (Headline → ≤ 3 Highlights with screenshots → Also → Technical appendix) at.claude/skills/ir:release/release-notes-template.md. Step 4a-roadmap codifies the roadmap-update protocol (Python recipe for ALSO SHIPPED, row-shape templates, today-line bump, pill rotation for minor releases).
Technical appendix
- Public roadmap page (#395) —
site/docs/roadmap.htmlis a single file with all CSS scoped inline (nodocs.cssedits). Pure-CSS git-branch via pseudo-elements; ALSO SHIPPED list extracted viagit log <prev-tag>..<this-tag>with hex-color and natural-language noise filters. Linked from everydocs/*.htmlsidebar (new "Project" section), the landing-page footer, and theCHANGELOG.mdpreamble. - Roadmap concept tiles — six future-section composites generated via Python + PIL + rsvg-convert using the new gradient flame brand. Tiles under
assets/roadmap/v<ver>/; CSS.release-figurerenders future tiles at 0.78 opacity and past at 0.92. - Release-notes template + Step 4a-roadmap expansion — three-layer template with worked example from v0.4.5. Step 2 / Step 4a in the skill updated to consume it; Step 4a-img enforces the highlight-image asset checklist; Step 4a-roadmap codifies a 5-substep protocol (extract refs, insert row, bump today, pill rotation, verify).
- Brand refresh continuation (#394) — propagates the v0.4.5 single-path gradient flame to surfaces the original PR missed: docs sidebar
.dotswapped for an inline gradient flame SVG, design-system preview tiles updated, AppIcon iconset regenerated via the reproducibletools/build-app-icon.shbuilder. - Working-state breathing dot (#393) —
svgIcons.workingswaps the SMIL heartbeat halo for a solid circle with a CSS opacity breathe (no animation underprefers-reduced-motion). macOSSessionStateIconupdated to match. - Web subagent dot-matrix row removed (#397) — the 89/96 progress-dot strip below the session row no longer renders; macOS overlay never had it, this brings web in line.
- Claude Code task-list pruning (#396) — the tailer treats
task_reminderattachments as authoritative; any localin_progressmissing from the snapshot is demoted tocompleted. Mirrors the v0.3.12 phantom-in_progressfix (#289) but for the absent-task case. - Model aliases — codeburn sync — adds
kimi-auto,kimi-code,kimi-for-codingtocore/pkg/capacity/aliases.go, allLOCAL_OVERRIDEtomoonshot.kimi-k2-thinkingsince LiteLLM only ships the dotted-prefix key. Five changed entries in the codeburn diff are intentionalLOCAL_OVERRIDEs — not updated.
v0.4.5 (2026-05-16)
Added
- Pro / Max subscription burn-rate forecast in the macOS overlay (#309, #379) — Two ingestion paths cover the major subscription combos: Codex CLI's
token_countevents carryrate_limitsin-band (parser extension), and Claude Code's statusline JSON is captured via a newstatusLine.commandthat POSTs to/api/v1/hooks/claudecode/statusline. The statusline chain is wrapped inbash -c '...'so the bash-onlytee >(...)process substitution survives Claude Code's POSIX-sh invocation; v1 wraps installed before the fix are migrated on the next daemon start without losing the user's original statusline command. Snapshots flow intoSessionMetrics; a 5-sample rolling history feeds a linear-projection forecast; the menu-bar header shows a provider chip (Anthropic / OpenAI inferred fromplan_typeor adapter) with stacked 5h / 7d horizontal progress bars carrying percent + reset time inline, coloured via the existing pressure-level ramp. Version moves out of the header and into a small footer in Settings so the header slot is always reserved for quota data - Brand: new single-path gradient flame across design system + macOS app (#388) — Replaces the old "outer + inner-highlight + tear ellipse" wisp (viewBox 32) with a single-path silhouette designed at viewBox 1254, plus per-state gradient treatments (purple / orange / green) and a flat mono + black/white variant set. Six source SVGs land under
assets/irrlicht_flame_*.svg; the design-system preview gains a "state colors" tile row and refreshed palette swatches per hue;OffFlameImage.swiftis rescaled to viewBox 1254 with the single path and the unused core gradient dropped;AppIcon.icnsis regenerated via a new reproducible buildertools/build-app-icon.sh(rsvg-convert with qlmanage fallback) that produces a byte-identical.icnson re-runs. The landing-page navbar swaps its 8×8.sparkledot for an inline 22×22 gradient flame SVG using the new mark, keeping the breathe animation - Unify per-row state icons across web and macOS (#382) — Replaces the dashed circle (web) and SF Symbol hammer/hourglass (macOS) with a shared visual vocabulary:
working= heartbeat halo (purple),waiting= two-bar pause (orange),ready= unchanged checkmark. Web swapssvgIcons.workingfor an inline SMIL heartbeat-halo SVG, drops the now-unused.workingspin rules, and hides the animated halo underprefers-reduced-motion. macOS adds a newSessionStateIconSwiftUI view that renders the halo natively (repeatForever, honorsaccessibilityReduceMotion) and the pause bars as twoRoundedRectangles. Snapshot fixtures impacted by the icon swap are re-recorded. Closes #380 /ir:triageassigns release milestones onready-for-agent(#375) — Triage was applying labels but leaving milestone empty, so every ready-for-agent issue still needed a manual maintainer pass to land in a release bucket. The skill now picks a milestone from priority and creates one if the bucket doesn't exist:Priority-High→ACTIVE(in-progress release),Priority-Medium→NEXT,Priority-Low→FUTURE. The three buckets are computed once per run from the latest published release tag (gh release view --json tagName), so the skill survives a version bump without an edit. Skipped onneeds-info/wontfixand on issues already carrying a maintainer-set milestone. Bundled into the existinggh issue editcall. The brief template gains a**Milestone:**line
Fixed
- Detect imperative waiting cues without a literal
?(#381, #383) —IsWaitingForUserInputnow classifies turns ending with an imperative ask ("let me know if it's right", "verify locally and reply with the diff", "awaiting your go-ahead before I merge") aswaitinginstead of falling through toready. AddsExtractWaitingCuealongside the existingExtractQuestionSnippet— both are OR'd in the public predicate, leaving snippet semantics unchanged. The cue regex set comes from issue #381's coverage matrix: five buckets (direct asks, approval framings, action gates, curated imperatives, trailing soft asks), all running against the trailing 1–2 sentences so earlier paragraph content can't trigger false positives.ExtractWaitingCuewalks the tail newest-first so the more recent sentence is returned when both the last and second-to-last sentence match. State-classifier Rule 2a now reportsturn ended with question or cue → waiting. Adds theagent-imperative-pendingreplay scenario and refreshes five existing claudecode goldens - Web: migrate sessions when
project_namelands after the first push (#377) — The web dashboard'sapplySessionUpdateupdated agent fields in place but never reconciled group membership whenproject_namechanged. So if the firstsession_createdWS push arrived before git-metadata enrichment had filled inproject_name, the agent was placed in the "unknown" bucket and stayed there permanently. The macOS app dodges this becausepatchApiGroupsschedules a full re-hydration on a missed session id; the web view had no equivalent path. Fix: on asession_updatedfor an existing top-level entry, detect when the newproject_namepoints at a different group than where the agent currently lives, splice it out, find-or-create the target group, and clean up the source group if it goes empty. Children inherit their parent's group and have theirsessionIndexentries re-anchored viaindexChildren - Pricing: normalize frontend-rewritten model names — 63-entry alias map synced from codeburn (#371, #376) — Sessions running through frontends that rewrite the model name before the LLM call (Cursor's
claude-4.6-opus-fast-mode, OMP'santhropic--claude-4.6-opus, Antigravity'sgemini-3.1-pro-high) priced at $0 becauseCapacityManager.GetModelCapacitydid an exact LiteLLM-key lookup with no normalization. Addscore/pkg/capacity/aliases.go(a 63-entry alias map ported from codeburn'sBUILTIN_ALIASES) and resolves it insideGetModelCapacitybefore the existing lookup. Exact-match only; no prefix/fuzzy logic. Five aliases whose codeburn canonicals are missing from LiteLLM are marked with// LOCAL_OVERRIDE: <reason>and re-pointed to LiteLLM-present keys. Adds the/ir:refresh-aliasesskill (standalone-PR + release-inline modes) that diffs against codeburn upstream, wired into/ir:releaseas Step 1.5 (fail-soft). Verified end-to-end via replay againstclaudecode/06-cost-calculation-07f5cca9: byte-identical $86.83 cost vs the canonical baseline
Docs
- List VS Code extension as a planned platform (#370) — Adds "VS Code extension" between CLI (alpha) and Linux (planned) in both the landing-page Platforms column (
site/index.html) and the README Platforms table; the README row links to the tracking issue (#350). Groups editor/UI surfaces (menu bar, web, CLI, VS Code) before OS-target planned items (Linux, Windows, iOS/iPadOS)
v0.4.4 (2026-05-16)
Added
- Web dashboard reaches overlay parity (beta) (#354) — The dashboard served at
http://127.0.0.1:7837now reaches visual and functional parity with the macOS menu-bar overlay, in service of the upcoming VS Code panel (#350) and future relay-served clients. Row anatomy matchesSessionListView.swift: state · num · subagent badge · branch · ctx-bar (with token overlay inside) · cost · model · adapter icon, with strictflex-wrap: nowrap, branch + bar growing into available slack, and a 22 px row height that survives the Context ↔ history mode toggle without shifting rows. Stacked below the row are the waiting question, task progress dots, the right-anchored 89/96-style subagent dot-matrix, and the pressure alert. The header carries the version chip, aggregated state icons (≤ 3 inline, else "N sessions"), a view-mode cycle (Context / 1 Min / 10 Min / 60 Min), a theme toggle (☀/☾), a settings cog (⚙), and a 3-state connection indicator (watching/reconnecting/disconnected) with a banner when the daemon connection drops. A new Settings modal mirrorsSettingsView.swiftminus the bits the web can't do — show-cost, debug mode (toggles row-id/row-elapsed/row-created chips), and three notification toggles (ready / waiting / context pressure) that firenew Notification(...)only when the dashboard isn't the focused tab. Themes pickicon_svg_lightvsicon_svg_darkper resolved theme so the Codex icon is now visible on both. Group headers cycle day/week/month/year on click, persist their collapse state inlocalStorage, and Gas Town groups get a ⛽ glyph. A responsive cascade keeps the context bar — the load-bearing signal — visible at any width: cost drops below 420 px, model below 360, adapter icon below 320. On the daemon side this added exactly one route:GET /api/v1/versionreturning{"version": "..."}. The previous timeline-heatmap and Raw-JSON tab — both debug surfaces the overlay never had — are removed. Marked beta inREADME.mdandsite/docs/quickstart.html
Fixed
- Codex split-event
token_countpriced at $0 (#361) — Codex sessions reported $0 cost because the parser emittedPerTurnContribution{Model: ""}on everytoken_countevent — Codex splits the model name ontoturn_contextand the usage cursor ontotoken_count, so the tailer bucketed deltas undercumByModel[""], which has no pricing. InapplyContribution, contributions with no model now fall back tot.metrics.ModelName(already maintained byapplyModelMetadatafromturn_context) so pricing and the UI's model display read the same field by construction.LedgerStatepersistsModelNameso the fallback still works on the firsttoken_countafter a daemon restart. Six Codex replay goldens regenerated with the corrected pricing - Adapters honor agent-CLI env vars for relocated session dirs (#349) — Three coding-agent adapters now respect their upstream env-var convention for relocating session transcripts: Pi via
PI_CODING_AGENT_SESSION_DIR(the absolute session dir itself), Claude Code viaCLAUDE_CONFIG_DIRwith/projectsappended (transcripts only — the PID-metadata directory at~/.claude/sessions/remains hardcoded because that hunk is unverified), and Codex viaCODEX_HOMEwith/sessionsappended.fswatcher.Newtreats an absolutediras-is and falls back to$HOME-relative for relative paths. Non-absolute env values (relative paths, unexpanded~, trailing slashes) arefilepath.Clean-ed and rejected with alog.Printfwarning. Env vars are read once atAgent()construction, so a daemon restart is required after changing them. Default-install behavior is unchanged. Aider and OpenCode are intentionally untouched
Docs
- Subtle mascot illustrations + sentence-level correctness audit (#365) — Adds 9 thematically-matched flame-mascot figures across
site/docs/*.html, designed to read on both light and dark themes (640 px WebP @ q88, ~908 KB total, ~6% of the 14.4 MB PNG source). Floats reflow to centered blocks at ≤ 720 px. Three parallel review passes then cross-checked every factual claim in the docs against the current Go source and fixed 17 stale or wrong statements: context-pressure thresholds corrected to0–60% / 60–80% / 80–90% / 90%+to matchcore/pkg/tailer/tailer_metrics.go; menu-bar icon called a "sparkle" corrected to "flame"; six references to the deleted./validate.shscript replaced withgo test ./core/... -race -count=1+tools/replay-fixtures.sh;processscanner/→processlifecycle/rename propagated; HTTP/WS diagram updated to/api/v1/sessions(/stream);adapterfield now lists all 5 wired adapters; never-implemented--format json/--id <prefix>CLI flag docs removed; log filename corrected toevents.log;lsofcommand updated to include the-Fnflag actually used
Distribution
- Five release-skill guardrails against v0.4.3's shipping defect (#360) — v0.4.3 shipped a binary that crashed on every end-user install (#357 root-caused; #356 + #358 fixed). Five further guardrails prevent recurrence. (1) Drop
NSFocusStatusUsageDescriptionfrom the Info.plist template — withIntents.frameworkstatically linked, TCC preflightskTCCServiceListenEventat process startup and SIGABRTs ad-hoc-signed builds regardless of the usage description. (2) Rewrite the coupling-rule prose with a per-key include/exclude table explaining the three-way relationship between AMFI-gated entitlements, TCC-gatedNS*UsageDescriptionkeys, and the linked frameworks themselves (the structural lever). (3) Add a framework-link audit between Swift build and signing:otool -Lagainst aFORBIDDEN_FRAMEWORKSlist aborts the release on any match. (4) Strengthen smoke-test failure semantics with explicit "DO NOT SHIP" framing, atccutil resetprereq to clear poisoned cache, and a four-step debugging checklist. (5) Replace Step 9 verify with a real end-to-end install canary that runscurl ... | shagainst the just-published release and validates viapgrep— the installer's"Launching... ✓"lies when AMFI kills the app immediately
v0.4.3 (2026-05-15)
Note: Assets re-cut later the same day. The original v0.4.3 binary statically linkedIntents.frameworkviaFocusMonitor.swift's directINFocusStatusCentercalls, which made macOS TCC preflightkTCCServiceListenEventat process startup and AMFI/TCC SIGABRT the ad-hoc-signed binary withlaunchd POSIX 153on every end-user install. The re-cut movesFocusMonitorto dynamic dispatch (NSClassFromString+SecCodeCopySigningInformationgate) so Intents.framework is never loaded on ad-hoc builds. DND-aware notification silencing is paused until Developer-ID signing lands (#233); restoration tracked in #357. Release-skill regression fix that prevents recurrence: #356.
Added
- macOS: autostart Irrlicht.app at login, on by default (#343) — On first launch the app registers itself as a login item via
SMAppService.mainAppso the menu-bar overlay is up before you open your first terminal. A new toggle in Preferences flips the setting, and the choice is persisted toUserDefaults; a one-time gate (didApplyDefaultLoginItem) ensures the default never re-enables itself after you turn it off. The XPC call to launchd runs on a detacheduserInitiatedtask so the toggle animation stays smooth on slower Macs. The unsigned-then-signed-build dev edge case is called out inline inapplyDefaultIfNeededso future maintainers know why the gate exists
Fixed
- macOS: focus VS Code windows on other Spaces (#344, #348) — When VS Code (or Cursor / Windsurf) was fullscreen on a different macOS Space, clicking a session row in the overlay was a silent no-op. AX's
kAXWindowsAttributeomits cross-Space fullscreen windows for Electron hosts, so the title-matching activator never saw the target window. The fix enumerates the app's Window menu instead — that list is always complete — and AX-presses the title-matching item so macOS performs the Space switch and window raise atomically. Window-menu titles are recognized across the major macOS-supported locales (en/de/fr/es/it/pt/nl/sv/da/no/fi/pl/cs/ru/tr/ja/zh-Hans/zh-Hant/ko) so the path works for non-English users too. Hardening from /simplify review: drop the second-to-last-menu positional fallback (could trigger a destructive action in non-Cocoa-standard apps), unwrapmenuBarRefbeforeCFGetTypeID, and collapse the imperative lookup intofirst(where:) + compactMap - daemon: session disappears when a second Claude is opened in the same VS Code window (#345, #347) — Opening a second Claude Code session inside the same VS Code window briefly leaves the new process in the parent CWD before it
cds into its worktree. The scanner minted aproc-<NEW>pre-session for that CWD; the claudecode adapter's CWD-based PID discovery — with no transcript yet, so the metadata-based filter is bypassed — then returned the neighbor process's PID, andHandlePIDAssigned's same-PID cleanup deleted the legitimate neighbor's session row. The row reappeared later via the activity-driven recovery path, which presented as a confusing flicker. Fix: pre-session IDs already encode the PID by construction (fmt.Sprintf("proc-%d", pid)inprocesslifecycle/scanner.go), so the daemon now short-circuits adapter-level discovery for them and callsHandlePIDAssigneddirectly with the parsed PID. The short-circuit sits above theProcessWatcher == nil/discoverFn == nilguards so it's robust against future adapters that have a process matcher but noPIDForSession. Real sessions (UUID IDs with a transcript path) continue through the adapter unchanged. E2E regression test usessync/atomic.Int32for the discovery-call counter so a future regression races visibly under-race
Docs
- Landscape page refresh against live GitHub data (#346) —
site/landscape/index.htmlandsite/landscape/compare/index.htmlregenerated from a freshgh apisweep of 38 tracked agents (May 15, 2026 snapshot). Aider and OpenCode flip fromplannedtolivein the landscape table to match their existing adapters undercore/adapters/inbound/agents/. Two repo renames propagated: Pibadlogic/pi-mono→earendil-works/pi(the v0.74 move) and Warpwarpdotdev/Warp→warpdotdev/warp. Two plausibility-rule trips noted with explicit reasoning: Warp jumped 26.5k→58.5k stars after open-sourcing its codebase; Ruflo grew 33.1k→51.3k on viral promotion. Their:agent-releasesskill'stracked-releases.mdadds 22 new versions across Claude Code (v2.1.120–v2.1.142), Codex (v0.125–v0.130 stables), Pi (v0.71–v0.74), and Gas Town (v1.0.1, v1.1.0)
v0.4.2 (2026-05-15)
Changed
- macOS app: drop legacy file-polling, retire vaporware
IRRLICHT_DISABLEDenv var (#337) —IRRLICHT_DISABLEDwas never wired into any code path, andIRRLICHT_USE_FILESgated a fallback path inSessionManagerthat the WebSocket transport has fully replaced; both are removed. ~165 lines of dead Swift inSessionManager.swiftgo with them (file watcher, debounce/periodic timers,loadExistingSessions,createInstancesDirectoryIfNeeded); init unconditionally uses WebSocket. Equivalent orphan reaping still happens daemon-side viaPIDManager'ssyscall.Kill(pid, 0)sweep — no safety net was lost. The macOS app no longer reads from the daemon-ownedinstancesPath; that ordering dependency is now documented inline
Docs
- configuration: document four real env vars with concrete recipes (#337) —
site/docs/configuration.htmldrops theUSE_FILES/DISABLEDrows and adds rows for the four env vars that exist in code but were undocumented:IRRLICHT_UI_DIR,IRRLICHT_BIND_ADDR,IRRLICHT_MDNS,IRRLICHT_DEBUG. A "When to use these" section walks three real recipes — LAN phone access (with both shell-env andlaunchctl setenvflows so it works whether the daemon is shell-launched or auto-spawned by the macOS app), "Dashboard UI not found" recovery (showing the full four-place auto-detect order so the override slots in clearly), and a debug state dump. The originalIRRLICHT_DEBUG=1 open -a Irrlichtexample was broken on macOS — LaunchServices spawns GUI apps without inheriting shell env — and is replaced with three working alternatives (direct binary invocation,open --env,launchctl setenv) - architecture, SECURITY: drop vaporware kill-switch bullet, cross-link network-exposure docs (#337) —
site/docs/architecture.htmlno longer mentions theIRRLICHT_DISABLEDkill switch (which never existed).SECURITY.mdcross-links the network-exposure paragraphs toconfiguration.htmland to the planned hub-mode design
Distribution
- Release skill: enforce long-line paragraphs in release notes / PR body (#335) — GitHub renders release-body markdown with the GFM "breaks" extension, so every soft line break inside a paragraph or bullet becomes
<br>. The v0.4.0 and v0.4.1 release bodies were hand-wrapped at ~75 cols and shipped as a stack of short ragged lines on the release page (both since fixed viagh release edit --notes-file). Step 2 of/ir:releasenow carries an explicit line-wrap rule explaining the difference between GFM-with-breaks (release notes, PR body, issue body) and standard CommonMark (CHANGELOG.md); Step 8 switches the example from--notesto--notes-filepointing at a tempfile, so the body is reviewable, re-runnable, and the long lines survive shell escaping - assets: version reference screenshots (#336) —
assets/session_limits.pngandassets/straeter_light.pngare now versioned alongside the other reference shots used in README drafts and social posts
v0.4.1 (2026-05-14)
Fixed
- kitty: click-to-focus lands on the right window and tab (#326) — Three failures in the kitty click-to-focus path, fixed end-to-end. (1) When kitty is launched from a shell whose env contains
TERM_PROGRAM=vscode(e.g. a VS Code integrated terminal), kitty inherits that value because kitty itself does not setTERM_PROGRAM(upstream kitty #4793); the daemon captured the inherited value and the click was routed to VS Code's activator.ReadLauncherEnvnow overridesTermProgramto"kitty"wheneverKITTY_WINDOW_IDis set and process ancestry confirmskitty.appis a parent. (2) With multiple kitty processes running,AppActivator.activate(bundleID:)always picked one — typically the oldest — and a post-kitten focus-windowre-activate fired async, outside the menu-bar click context, racing macOS yield-focus rules and producing the "raises then drops back" symptom. The daemon now whitelistsKITTY_PID; a newLauncher.KittyPIDfield is plumbed through to the Swift app;KittyActivatorcallsNSRunningApplication(processIdentifier:).activate(options: [])synchronously inside the click handler. (3) Apple-signed agents likepi(and/bin/zsh) hide their env fromsysctl, soKITTY_*env vars never reached their sessions — every click hit bundle fallback. Three new darwin-only helpers inosutil_darwin.goderive these fields without reading the agent's env:kittyAncestryPID,kittyListenOnFor,kittyWindowIDForPID.backfillLauncherwas extended so pre-existing sessions get all four kitty fields refreshed on daemon restart
Security
- kitty: uid-check on
/tmp/kitty-{PID}socket probe —/tmpis world-writable, sokittyListenOnForcould be tricked into trusting a pre-planted Unix socket at the canonical path and sendingkitten @ lsto a hostile listener.kittyListenOnFornow stats the candidate socket and skips any whose owner uid doesn't matchos.Getuid()— kitty binds with its own credentials, so a foreign-owned socket at the canonical path is either stale or hostile. Test coverage inosutil_darwin_test.goexercises the current-uid, foreign-uid (root-gated), non-socket, missing-file, and zero-PID branches
Changed
- kitty: cache ancestry walk in
ReadLauncherEnv— The kitty-via-vscodepiworst-case path was walking the parent-process chain up to three times (each walk shellspsup tomaxAncestrytimes). A newresolveHostFromAncestryreturning(termProgram, hostPID)is memoized insideReadLauncherEnvvia a closure, so the chain is walked at most once.resolveTermProgramFromAncestryandkittyAncestryPIDbecome thin wrappers; call-site behavior is unchanged
Docs
- api-reference, contributing: catch v0.4.0 sweep gaps (#332) —
site/docs/api-reference.htmlstill referencedagentCfgsin theGET /api/v1/agentsblurb; renamed toallAgentsto matchcmd/irrlichd/main.go.site/docs/contributing.htmladapter-PR checklist still said "Implements theAgentWatcherinterface"; replaced with the currentAgent()/agent.Agent/allAgentscontract
Distribution
- Release skill hardening (#332) — Step 6 checksum recipe now includes
irrlichd-darwin-universal.tar.gz(the curl--daemon-onlypath verifies it; omitting it shipped a release where the standalone daemon installer failed the integrity check). Step 7b drops--delete-branchfromgh pr merge --squashso the release branch remains addressable post-squash. Step 4b trigger table gains rows for adapter-package edits andmain.goslice/wiring renames so future Phase-A-style shape changes can't slip past doc sweeps
v0.4.0 (2026-05-14)
Added
- macOS: per-event notification sound picker (#253) — Preferences gains a separate row per notification event (ready / waiting / context-pressure) with its own enable toggle, sound picker (Ping / Chime / Funk / Whoosh / Sosumi / None / Speak aloud / Custom), and preview button. Custom audio (
aiff/wav/mp3/m4a/caf) is imported into~/Library/Sounds/, transcodingmp3/m4ato LPCM-in-CAF viaAVAudioFilesoUNNotificationSoundwill play it. "Speak aloud" routes title+body throughAVSpeechSynthesizer, pinned to en-US, and exposes three voice variants (Default / Zoe-Premium / Jamie-Premium); if a premium voice isn't installed, the row renders an inline "Install … in System Settings" button that deep-links to Accessibility → Spoken Content. Defaults: all three events enabled; Ready=Funk, Waiting=Ping, Context=Sosumi. Existing preferences preserved on upgrade
Fixed
- Claude Code: don't bounce ready→working on post-turn
away_summary(#329) — Claude Code writes asystem/away_summaryrecap ~3 minutes after a turn ends. The parser correctly marked itSkip=true, but the fswatcher's mtime trigger still ran the full classification pipeline, and the force-bounce inprocessActivitysaw the staleLastEventTypefrom the priorturn_doneand flipped the ready session back to working indefinitely. The tailer now surfaces aNoSubstantiveActivitysignal when a pass consumed new content but produced no state-relevant change; the detector short-circuits the force-bounce / re-classify path on that signal while still refreshingLastEvent/EventCount/UpdatedAtand broadcasting so the UI's "last activity" stays current - Claude Code: detect AskUserQuestion / ExitPlanMode via
PreToolUsehook (#307) — Claude Code can lag flushing the assistanttool_useblock to JSONL for minutes after rendering anAskUserQuestion/ExitPlanModeoverlay; the transcript-driven detector never saw the open tool call, so the session sat inworkingwhile the user stared at the prompt. APreToolUsehook scoped toAskUserQuestion|ExitPlanModefires synchronously when the model emits the tool_use and flipspermissionPending; the existingPostToolUsematcher is widened to include both tools so the same edge clears the flag when the user answers. Legacy installs are migrated in place byupgradeStaleHookMatchers - Codex: treat
<proposed_plan>as user-blocking likeExitPlanMode(#322) — Codex's Plan Mode ends a turn with a<proposed_plan>…</proposed_plan>block — semantically identical to Claude Code'sExitPlanMode. The block arrives as plain assistant text, so the classifier never saw an open user-blocking tool and fell through toready, leaving the dashboard green while the agent was actually blocked. When an assistant message contains a fully-closed<proposed_plan>block, the codex parser now synthesizes a virtualExitPlanModetool-use — same user-blocking path as Claude Code; the existingClearToolNameshook on user messages closes it when the user replies - Daemon: reject zombie sessions with missing cwd (#321) — A daemon restart within 2 minutes of
claude --resumeagainst a session whose worktree had been deleted re-admitted the session as a ghost: the transcript-mtime check treated the refreshed mtime as live, PID discovery failed, and the steady-state sweep only cleaned it up ~75s later. Admission now checks cwd existence alongside the stale-transcript guard at bothonNewSessionandseedAlivePIDs
Changed
- #159 Phase A — Agent declaration replaces
agents.Config— The legacyagents.Configstruct + its five per-adapterConfig()constructors and four map helpers are removed. Each adapter now exports a singleAgent()constructor returning a sealed-sum declaration:Agent = Identity × Process × Source, whereProcessisExactName | CommandPattern,SourceisFilesUnderRoot | FilesUnderCWD | ProcessOwnedStore, andFileParserisJSONLineParser | RawLineParser. The daemon consumes[]agent.Agentdirectly, with per-projection helpers inadapters/inbound/agents/maps.go. Variant-dispatched watcher wiring incmd/irrlichd/wiring.goreplaces the per-adapter loop. Phase A also lands an M0 contract-test layer (SessionStateon-disk, 7PushMessageshapes,GET /api/v1/agents) to guard the public surface - #159 Phase A —
Watcherport replacesAgentWatcher; identity carried on the merge pipeline — The inbound watcher port gains anIdentity()method andWithIdentity()builder; each per-watcher drain goroutine inSessionDetector.Run()captures identity once and wraps every event with it, soagent.Event.Adapteris removed. The oldAgentWatcherinterface is deleted.NewSessionDetectorpanics at construction when any watcher'sIdentity()is the zero value.metrics.Newtakes a singleRegistrystruct instead of four positional maps
Docs
/ir:releaseskill — adapt to PR-requiredmain+ fix tap-publish race (#306) —mainis now protected by a "Changes must be made through a pull request" repo rule. The release flow now stages on a short-livedrelease/v$NEW_VERSIONbranch, opens a PR with the drafted release notes, squash-merges, hard-resets localmaintoorigin/main, and tags the merged commit. Step 6.5 patches the in-repo cask template viasedand leaves the sibling tap untouched until Step 8.5
v0.3.13 (2026-05-11)
Fixed
- OpenCode: suppress ghost sessions when no opencode process is live — the OpenCode watcher's startup scan emitted
EventNewSessionfor every non-archived row whosetime_updatedfell withinmaxAge, regardless of whetheropencodewas actually running. On every daemon restart, every historical session in the DB became a "live" row in the menu bar with no path that ever removed them. Now gates emission on a live opencode process owning the session's CWD viaprocesslifecycle.LiveCWDs(processName). Sessions in the DB with no live process are tracked (cursor seeded so historical activity isn't back-filled if the process later starts) but not surfaced; a newemittedflag enablesEventNewSessionto fire on the dormant→live transition - OpenCode: clean up carryover ghost state on startup — users upgrading from v0.3.12 had ghost session JSON files (
PID=0, DB-backedTranscriptPath) that neithersyscall.KillnorisStaleTranscriptcaught. A new branch inisStartupZombiedeletesPID=0sessions whose adapter has a registered process name iff no live process of that name owns the session's CWD. Only deletes when the lookup returns a definitive non-nil result
Changed
- OpenCode: GC stale cursors, drop dead initialArchiveCleanupWindow —
gcExpiredCursorsdrops cursor entries whoselastObservedpredatesmaxAgeso the cursor map can't grow without bound for users who accumulate many sessions but rarely run the CLI. Tracked separately fromcur.lastTSso a session whosetime_updatedbumps without new parts isn't wiped prematurely - OpenCode: consolidate DB-backed predicate, cache liveCWDs lookup — DRYs the "is this path DB-backed?" check and caches
liveCWDsper adapter insideCleanupZombiesso M ghost candidates sharing an adapter pay onepgrepfork, not M - Release: keep homebrew tap from silently lagging (#299) — the tap was stranded at v0.3.8 across four releases because Step 8.5 no-op'd silently when
IRRLICHT_TAP_DIRwas unset.update-cask.shnow auto-discovers a sibling../homebrew-irrlichtclone before bailing and hard-fails on--pushwithout a tap dir; the skill's Step 8.5 verifies the published cask version after publish and prints a loud WARNING on mismatch
Docs
- README: supported platforms table (#301, #302, #303) — adds a Platforms table with CLI access references and links the macOS access cell to the releases page
- Landing: mark OpenCode alpha; teach release skill the landing-page grid — the parallel grid on
site/index.htmlwas missed in v0.3.12 because Step 4b only enumeratedsite/docs/*.html. Extends the dynamic enumeration to scansite/*.htmland adds explicit trigger-table rows for adapter maturity-stage changes and new platforms - README — note codeburn alongside other quota & cost trackers in the positioning section
v0.3.12 (2026-05-09)
Added
- OpenCode adapter (#255) — first agent on the new SQLite-backed monitoring path. OpenCode stores all session data in a single WAL-mode database rather than JSONL files; the adapter ships an fsnotify WAL watcher polling
session/parttables, a parser mapping step-finish / text / tool rows to normalized events, aMetricsProviderthat bypasses the JSONL tailer for cost + token snapshots, CWD-based PID discovery, parent-child session linking viaparent_idfrom the DB, andEventRemovedemission onsession.time_archived. Closes #100 ir:triageskill (#283) — strictly diagnostic GitHub-issue triage skill that scores each issue against a 6-axis readiness rubric (Scope / Specification / Verifiability / Context / Independence / Reversibility) and lands it atready-for-agentorneeds-infowith a one-line justification per label decision. Bulk sweep skips already-triaged issues; explicit/ir:triage #Nalways re-triages and edits the prior comment in place
Fixed
- History bar right-anchors when states overflow
bucketCount(#286) —HistoryBarView's anchor math only worked whenstates.count <= bucketCount. After #249 loweredbucketCountfrom 150 → 60, the 150-state test fixture started rendering an all-green bar becauseoffsetcollapsed to 0 and the loop drew the oldest states inside the canvas while the newest tail was clipped past the right edge. Now takesstates.suffix(bucketCount)and recomputes offset against the visible slice - Claude Code: reconcile phantom
in_progressfromtask_remindersnapshots (#289) — Claude Code occasionally emits aTaskUpdateagainst a stale taskId and never sends a follow-upcompleted, so the UI hung atn / totalforever. The tailer now treats thetask_reminderattachment as authoritative — any localin_progresswhose ID is missing from the snapshot is demoted tocompleted, and snapshot status wins on any present-with-divergent-status case. Closes #282 - macOS: sync
apiGroupson local session delete + reset (#287) — local deletes/resets only updatedsessions(menu bar) andsessionMapand skippedapiGroups(list view), so a deleted session lingered in the list until rehydration and a reset row stayedworkingin the list while the menu bar already showedready. Mirrors the WS handler and addsSessionState.withState(_:)so all 10 optional fields (children, role, subagents, adapter, launcher, …) survive the round-trip
Changed
- Co-locate adapter display name + icons with Go adapters (#284) — adds
DisplayName+IconSVGLight/IconSVGDarktoagents.Configand a newGET /api/v1/agentsendpoint serving them. Adapter is now the single source of truth for its own branding; adding a new adapter is a Go-only change. The macOS app and web dashboard look up name and icon from the registry — the five Swift<adapter>SVGfunctions and two switch statements inSessionState.swiftare gone. Web dashboard renders adapter SVGs via<img src="data:image/svg+xml;base64,...">so the browser image-loading sandbox blocks scripts even if the daemon binary is tampered with.AgentRegistryis@MainActor-isolated for Swift 6 strict-concurrency cleanliness. Closes #260
Docs
- Adapter interfaces documented with exact Go signatures (#292) —
site/docs/adapters.htmlgains an "Adapter Interfaces" section with the actualagents.Config,tailer.TranscriptParser(plus the optionalRawLineParser,IdleFlusher,PendingContributor,ParserStateProviderhooks),agent.PIDDiscoverFunc, andagent.Event/inbound.AgentWatchertypes, with file paths so readers can jump from doc to source - Release skill sweeps every docs page on each release (#293) —
/ir:releaseStep 4b now enumeratessite/docs/*.htmland top-level READMEs dynamically (rather than from a hardcoded list) and walks each against the release diff so new pages cannot be silently missed
CI
- Coverage workflow surfaces badge update failures (#281) — validates
GIST_SECRET/COVERAGE_GIST_IDup front, captures the gistPATCHresponse so a non-2xx fails the job with the actual error body instead of dying silently insidecurl -sf … > /dev/null, and adds connect/max-time + retry settings so transient 5xx and stalled handshakes don't hang the step
Tests
- Replay: refresh stale opencode
baseline-hellogolden (#285) — golden was committed in #255 with a populatedsource_transcriptfield that the test zeros before comparison; regenerated viaUPDATE_REPLAY_GOLDENS=1to bring opencode in line with the other 4 adapters
v0.3.11 (2026-05-02)
Fixed
- Serve stale LiteLLM cache instead of zeroing all costs (#275) — when the model-pricing cache was older than 24h, every cost calculation silently fell to zero (and
omitemptydroppedestimated_cost_usdfrom output entirely). Stale pricing is now served to non-daemon callers (replay tool, CLI, tests);IsCacheStalekeeps its job of driving the daemon's background refresh - Aider: keep turn open across multiple
> Tokens:lines (#274) — under--yes-always, aider auto-accepts file-add prompts and re-prompts the model within one user turn. The parser now treats> Tokens:as end-of-one-model-call (emittingassistant_message) and synthesizes theturn_donevia an idle-flush hook, so sessions don't flip toreadymid-turn - Aider: emit
turn_doneon LLM-layer error (#273) — when aider prints a> litellm.BadRequestError: …blockquote without a> Tokens:line, the session no longer hangs inworkingforever - Tailer: drop
bufio.Scannercap so JSONL lines >2 MB don't wedge sessions (#271) — large transcript lines used to silently stop being processed once they exceeded the default 64 KB scanner buffer - Close 13 Code Scanning alerts (#266)
- macOS: use brand off-flame for idle/empty state (#248)
Changed
- Performance: shrink mobile payload, unblock render path (#272) — image optimization and CSS deferral for the marketing site; meaningful drop in mobile LCP/CLS
- Daemon serves dashboard from disk, drops
//go:embed(#267) — runtime walk-up search forplatforms/web/index.htmlso the dashboard can be hot-edited in dev and shipped as a separate file in production bundles - Session history streams over WebSocket; bit-pack 60-bucket rings (#249) — replaces polling with live updates and a more compact wire format
- Centralize per-adapter transcript extension (#251)
Distribution
onboard-agentcovers claudecode/codex multi-turn + interrupted-turn (#269) — fixture matrix gains coverage for two real-world replay scenarios
Docs
- Maturity-stage rubric and adapter onboarding section (#264)
Tests
- Replay: zero
source_transcriptso goldens are worktree-portable (#250)
v0.3.10 (2026-04-27)
Fixed
- Sweep zombie sessions on startup (#242) — Claude Code sessions no longer linger in the menu bar / UI after the underlying
claudeprocess has exited. The daemon now reaps stale entries on launch - Prune deleted sessions from
apiGroupssynchronously (#244) — when a session is removed, the overlay now updates immediately instead of showing a stale row until the debounced rehydrate lands. Walks agents, child subagents, and nested groups; drops project groups that become fully empty (gas-town excepted, since it renders even with no rigs)
Changed
- Daemon: drop recycled-PID predicate from
CleanupZombies— simpler, more reliable startup-cleanup path. Groundwork for #242
Tests
- e2e regression test for the startup zombie sweep (#242)
- 5 new
SessionManagerApiGroupsTestscases covering top-level / child / parent removal, gas-town empty-rigs survival, and unknown-id no-op (#244)
v0.3.9 (2026-04-27)
Added
- Aider adapter — first agent shipped through the new
/ir:onboard-agentdiscovery flow. Includes parser, tmux-driven interactive driver, scenario fixtures, and a pinned trailing-?waiting-state contract. Aider sessions show alongside Claude Code, Codex, Pi, and Gas Town with the same three-state vocabulary /ir:execskill — issue-driven plan generation. Reads a GitHub issue and produces a structured implementation plan to start a fresh worktreecoverage-viewerdev webview (#222) — local web UI for the agent × scenario fixture matrix; shows which lifecycle events each adapter has recordedtuicapability + category taxonomy — adapters can declaretuias a discoverable capability so the canonical scenario matrix can target TUI-style agentsIRRLICHT_DEMO_MODE=1— daemon flag that disables ProcessWatcher and per-adapter AgentWatchers sotools/seed-demo-sessionscan stage screenshot scenarios without live processes leaking into the dropdown- Process discovery:
CommandLineMatch+TranscriptFilenameprobes — wrapper-launched agents (e.g. invoked viapgrep -f) and per-CWD agents that write their transcript next to the project are now detected without a kqueue race - Transcript activity emission for CWD-resident transcripts — processlifecycle now emits
transcript_activityevents for agents whose transcript lives next to the working directory
Fixed
- Tailer survives
SendMessagetool acrossturn_done(#81) — Claude Code emits aturn_donebetween the assistant message and a follow-upSendMessagetool call; the tailer used to drop the second half. Sessions stay coherent across that boundary now - Mid-paragraph question detection + snippet trim (#236) — the waiting-state classifier used to require a question at the end of the assistant message. It now picks up questions mid-paragraph and trims the surfaced snippet for the menu bar block
- Skip rhetorical Q&A pairs in question detection —
"Did X happen? Yes."-style self-answered questions no longer flip a session to waiting - Menu-bar button stays highlighted while panel is open (#224) — the NSPanel migration in 0.3.8 lost the button-pressed appearance; restored with explicit highlight-on-show / unhighlight-on-close
- Tooltips restored after NSPanel migration (#218) — switched from SwiftUI
.help()(silently dropped inside NSPanel) to anNSView-bridged tooltip modifier - History bars align with Context layout; modes renamed to "Min" with tooltips (#210)
- Cost display drops cents at ≥$100 (#214, #215) —
$132.41was line-breaking the row; now renders$132 - Stale session ledger files GC'd (#185) — the per-session ledger directory used to grow without bound; now cleaned alongside session expiry
- Claude Code hook errors silenced when daemon is down (#221) — hooks no longer print noisy connection errors when irrlichd isn't running
- Replay byte-identity test excludes bare
events.jsonl— the bare events file is regenerated and shouldn't be part of the byte-identity check - Coverage-viewer rejects path-traversal in API + uses
aider.Parserafter stub removal
Changed
/ir:onboard-agentoverhaul (#199, #200) — moved from a hardcoded scenario list to afeatures.json+replaydatalayout with 3-subagent discovery, reasoned merge, and cross-agent feature widening. Adds Codex + Pi drivers and scenario columns; gastown gets its own orchestrator scenario axis. Onboarding aider through this flow validated the design end-to-end- Canonical scenario × adapter fixture matrix (#228, #231) — covers the 7 actionable scenario × adapter cells plus
agent-question-pendingfor claudecode/codex/pi. Addsdrive-pi-interactive.shand two pi script-based fixtures - Dev scripts consolidated under
tools/— standalone tooling (HTTP viewers, fixture generators, homebrew-tap helper) now lives in top-leveltools/rather thancore/cmd/ tools/homebrew-tap/update-cask.shsimplified — single source of truth for cask updates; the in-repo template and external tap repo are bumped from one script- Aider parser: single regex match per line; documented interface contract
- e2e tests for processlifecycle crash, concurrent sessions, fswatcher (#205) — extracts
IsCanonicalStateandassertWatchersExitedhelpers
Distribution
- Homebrew cask via own tap (#187) —
brew tap ingo-eichhorst/irrlicht && brew install --cask irrlichtnow resolves to the latest release. The cask is auto-bumped on each release viatools/homebrew-tap/update-cask.sh
Site
- Landing page rewrite — restructured around a "first 30 seconds" pain → state → install flow, with stage-tag legend, install stats strip, and expanded "why" section. New menu-bar explainer screenshot and dark-forest backdrop
- README restaged for first-30-seconds skim, adapters tagged by stage, explainer image promoted to hero banner
- Design system reference added under
tools/irrlicht-design-system/
v0.3.8 (2026-04-24)
Added
- Menu bar rewrite: NSStatusItem + NSPanel — replaces SwiftUI's
MenuBarExtra(.window)so content changes and panel resize land in the same runloop tick. Eliminates the one-frame background flash on group collapse/expand, and keeps the panel top pinned to the status item while height grows downward. Panel opens rightward with 10pt continuous-curve rounded corners; screen-edge clamp + notch fallback cover narrow right-edge displays - SessionListView column rebalance — panel 350 → 380pt; context bar 80 → 100pt; cost column 40 → 36pt. Branch column shrinks to 44pt when a subagent badge is present so the context bar's x-position is constant across rows. Two-pass
FlowLayoutaligns the 7pt task-progress circles with the taller "done/total" label
Fixed
- Settings overlay background no longer transparent — the
NSStatusItem+NSPanelrewrite uses a transparent panel so the rounded-corner clip works, which meant every SwiftUI branch had to paint its own background.SessionListViewdid, but the Settings pane didn't — the desktop wallpaper bled through.SettingsViewnow paints the samewindowBackgroundColor, and a newSettingsViewTestspixel-opacity assertion samples the four corners + center to catch any future regression - "…" overflow indicator beyond 5 menu-bar groups — when more than five project groups are active, the menu bar icon shows a trailing "…" so you know the list is truncated rather than silently dropping the extras
- Replay harness mirrors daemon parent-hold, permission-pending, and orphan promotion — sidecar-driven replay was skipping three pieces of daemon logic (parent-child hold when subagents are active, permission-pending overlay from
PermissionRequest/PostToolUsehooks, and stale-sweep promotion of children whose transcripts go quiet). Extended-check now passes on the subagent and permission-hook fixtures without regenerating their sidecars
Changed
- Core ARS composite 8.0 → 8.2 — large internal refactor:
session_detector.gosplit into_activity/_helpers/_lifecycle/_subagentfiles;cmd/replay/main.gosplit intolifecycle/metrics/replay_sidecar/replay_transcript/extended_check/types/fixtures_test;cmd/irrlichd/main.gorequest handlers factored intohandlers.go. Behavior unchanged - Unified agent registration via
agents.Config— adding a new agent adapter is now oneConfig()constructor + one line inmain.go'sagentCfgsslice.PIDDiscoverFuncmoved todomain/agent/soConfigcan reference it without violating hexagonal layering; metrics adapter inverted so outbound no longer imports inbound - Shared constants between daemon and replay —
HookPermissionRequest/PostToolUse/PostToolUseFailurein the claudecode adapter, exportedservices.SubagentQuietWindow, andservices.ForceReadyToWorkingReason— so hook names, the 30s stale-sweep window, and the force-ready reason string can't drift between the live classifier and the replay
Developer tooling
/ir:onboard-agentskill — produces a canonical scenario × adapter fixture matrix. Scenarios are defined once, agent-agnostically, with arequires: [capability]list; adapters declareCapabilities, and matrix cells fall out automatically. Unifies refresh, bootstrap, and new-agent-onboarding workflows/ir:agent-landscapehardened against hallucinations — every agent in the landscape report is verified against the GitHub API before publishing; the skill refuses to emit entries it can't resolve
Site
- Landing page: Terminals & IDEs column dropped — the click-to-focus host list grew past what fit cleanly in the features grid; replaced with a single "works with your terminal" sentence
v0.3.7 (2026-04-24)
Added
- Agent history bar with 1s/10s/60s granularity — server-side pre-aggregates per-session state buckets (
working/waiting/ready) under/api/v1/sessions/history. A single cycling mode button in the menubar switches between context display and the three history granularities - History persistence across daemon restarts — buffers saved to
~/.local/share/irrlicht/history.jsonevery 60s and on shutdown, so the timeline survives a restart instead of resetting to empty - Waiting-state question block in the session row — when a session goes to
waiting, the menubar row now shows the last assistant question (or theAskUserQuestiontext) in an orange block beneath the row - Claude Code task list progress —
TaskCreate/TaskUpdatetool calls surface as a progress dot strip on the session card with a live "N/M" count - Click-to-focus across 17 terminal/IDE hosts — extending v0.3.6's launcher work to Zed, Rio, Tabby, WaveTerm, Alacritty, Nova, cmux, Kitty (socket-based) and the JetBrains family
- Web UI timeline seeded from persisted daemon history — on page load the dashboard pulls
/api/v1/sessions/history?granularity=1and paints the last 60s immediately instead of starting empty
Fixed
- Accurate per-model cost estimation across all adapters and restarts — the cost tracker now handles usage maps consistently for Claude Code, Codex, and the pi adapter, and survives daemon restarts without double-counting
- Offline-at-startup: LiteLLM capacity fetch retries with backoff — the capacity table no longer stays empty when the laptop is offline at daemon boot
- History timeline ticks flow right→left — new ticks land in the rightmost bucket and older ones shift left as time passes, in both the Swift
HistoryBarViewand the web dashboard canvas - Waiting-state detection survives long assistant messages —
ExtractAssistantTextkeeps the tail (with leading ellipsis) so a trailing question-mark still trips the waiting classifier;AskUserQuestiontool calls with no text block fall back toquestions[0].question - Menubar popover: dynamic height + collapse state survives session refreshes — single ScrollView with
.fixedSize(vertical:)lets the popover size to content up to 560pt; collapse state lifted ontoSessionManager.collapsedGroupNames - Tasks: state resets on transcript rotation, stable across schema bump — in-memory task list cleared on file rotation; ledger schema bumped to v2 to force re-scan
- Menubar tooltips work inside MenuBarExtra panels — switched from
.help()to anNSView-bridged.tooltip(...)modifier - Launcher: ProcessRunner calls dispatched off the main thread — focus/open operations no longer stall the UI
- Launcher: fullscreen Space handling — correctly raises windows on a fullscreen Space
- Swift:
Taskmodel renamed toSessionTaskso it stops shadowing Swift's built-in concurrencyTask
Changed
- Parsers split per-adapter — format-specific transcript parsers (Claude Code
AskUserQuestion,TaskCreate/Update) live under the agent adapter packages instead of the shared tailer - Cost tracker usage-map extraction and ledger hot-path simplified; history granularity parsing cleaned up;
$0cost toggle stays visible so the timeframe cycle button remains reachable - Task parsing switch flattened; status constants lifted to one place
Tests
- History persistence round-trip (save/load/missing-file/corrupt-file/version-mismatch) on
HistoryTracker AskUserQuestiontext fallback (short message) and long-text tail storage on the Claude Code parser- Integration test for
GET /api/v1/sessions/history(response shape + bad-granularity 400) SessionMetrics.formattedCosttwo-decimal regression- New
SessionRowViewsnapshot suite (waiting-question block + ContextBar token-count label);NSHostingView.appearancepinned to.darkAquaso snapshots are deterministic
v0.3.6 (2026-04-19)
Added
- Jump to launching terminal or IDE on session click (#170) — Clicking a session row or delivered desktop notification brings the originating iTerm2 tab, Terminal.app window, or AXTitle-matched editor to the front. Host resolution walks the session's ppid chain; iTerm2 sessions are matched by UUID, Terminal tabs by tty, and generic apps by scoring window titles against the session CWD's deepest ancestor segment. The daemon captures the launcher env (
$TERM_PROGRAM,$ITERM_SESSION_ID, tty) on first PID assignment so the lookup works even after the agent has been running for hours
Fixed
- claude-code: gate PID negative filter on metadata mtime (#169) — After
/clear, Claude leaves~/.claude/sessions/<pid>.jsonpointing at the old session for up to two minutes. The detector no longer holds onto the dead session; once the new transcript's mtime exceeds the stale metadata, the PID is reassigned and the old session is cleaned up immediately - Web UI: render sessions on initial load (#167) — Initial-load handler now reads the bare-array response from
GET /api/v1/sessions(previously expected agroups/orchestratorwrapper, so the dashboard stayed empty until the 30s rehydrator or a WebSocket delta arrived). Also removed three stale references to the removed/api/v1/orchestrators/gastownendpoint - macOS: restore project-group reorder chevrons (#172) — The up/down chevrons in top-level project group headers came back; ordering is derived from
apiGroupsdirectly so the UI state stays in sync with persisted order - CLI:
irrlicht-lsruns from any subdirectory (#175) —go runnow invoked from the repo root via--workspace
Changed
- macOS Launcher split into per-host activators behind a
HostActivatorprotocol —iTerm,Terminal.app, and anAXTitleMatchActivatorfor generic apps each live in their own file. Window raising uses the Accessibility API to raise a specific window rather than the frontmost one
Distribution / Dev
- Persistent self-signed "Irrlicht Dev" identity —
ir:test-macnow signs dev builds with a stable designated requirement so Accessibility and Automation TCC grants survive rebuilds. Runscripts/dev-sign-setup.shonce to install it
v0.3.5 (2026-04-19)
Added
- Per-group cost display with switchable time frames (#83, #162) — Project group headers now surface day / week / month / year cost totals via a timeframe toggle instead of a single hard-coded window
curl | shinstaller atirrlicht.io/install.sh— One-line install pulls the latest release zip, verifies the sha256, and registers the app with LaunchServices. Rerunning removes any previous install cleanly- Raw tab in the web UI — Inspect the
/api/v1/sessionsJSON payload live
Changed
- Capacity: LiteLLM is now the single source of truth (#165) — The hand-maintained
core/pkg/capacity/model-capacity.jsontable is removed; lookups go through a process-wide singleton that hot-reloads the LiteLLM cache when it's refreshed, so the daemon no longer needs a restart to see a new model. Fixes the 200K/1M flip on Claude Opus 4.7 and enables 1M context for Sonnet 4.6
Fixed
- macOS: only notify on transitions from
working(#161) — State-transition notifications fire onworking → waitingandworking → readyonly. Awaiting → readytransition no longer fires a redundant "ready" notification - Detector: synthetic
waitingfor collapsed user-blocking tools (#150, #160) — Sessions that ended on a user-blocking tool whose start was collapsed out of the transcript no longer linger asworking - Replay: split sidecar timelines at
process_exitedboundaries (#144, #163) —/continuesessions with multiple process lifetimes no longer report spurious extra transitions - Detector: unstick subagents via parent task-notification (#134, #156)
- Installer: preserve provenance and register with LaunchServices (#158) — First launch is no longer quarantined
- Security: lock down
irrlichdnetwork exposure (#94, #155) — Binds to localhost only and rejects cross-origin WebSocket upgrades - Site:
curl | shinstall command wraps on narrow screens; hero spacing cleaned up - Tests: three stale
SessionManagerTestsupdated (#166) — Match the currentSessionStatedecoder and abbreviatedRelativeDateTimeFormatterbehavior
Distribution / CI
- ARS badge workflow pinned to v0.0.9;
GOPROXY=directno longer required ir:releaseskill guards against stale Swift binaries and missing SwiftPM resource bundles — the two root causes of the broken v0.3.4 bundle
v0.3.4 (2026-04-14)
Added
- Gas Town full role support with recursive group nesting (#154) — First-class role registry surfaces all roles (mayor, deacon, witness, refinery, polecat, scribe, …) instead of treating them as ad-hoc strings, and codebase/rig groups can nest recursively for richer worker hierarchies
- Desktop notifications on macOS state transitions (#147) — System notifications fire when sessions transition into
waitingorready, with a Settings toggle to opt out - Permission-pending state via Claude Code hooks (#108) — The daemon consumes
PreToolUse/Notificationhooks to detect modal permission prompts directly, instead of inferring them from transcript heuristics - Landscape page — 3-month growth trend lines, head-to-head comparison page, and alternative agent metrics
Fixed
- Push: broadcast buffer increased (#152) — Bursty session updates no longer drop
waitingtransitions before clients can drain them - Tailer: preserve user-blocking tools across
turn_donesweep (#148) — In-flight permission prompts (Bash, AskUserQuestion, …) are no longer cleared when the assistant briefly stops streaming - Daemon: prevent fswatcher event drops (#143) — Switched to a non-blocking event pump so user-blocking tool starts are never missed
- macOS dev workflow builds a real
.appbundle (#149) — Bundle identifier migrated toio.irrlicht.appso dev and release builds no longer share state - macOS: corrected dev fallback path for the bundled
irrlichdbinary — The menu bar app finds the daemon when running outside an installed bundle - Skill:
ir:test-macbuilds from the active worktree — When invoked from a worktree, builds from there instead of the main checkout
Changed
- Unified
replaytool (#141) —replay-sessionandreplay-lifecycleconsolidated into a single tool with subcommands, removing duplicated transcript-loading code
Docs
- README problem section tightened to lead with concrete user pain rather than the architecture
CHANGELOG.mdadded at the repo root and wired into their:releaseskill so every release updates it
v0.3.3 (2026-04-11)
Fixed — Subagent lifecycle reliability
Several edge cases in parent/child session tracking caused subagents to linger in working after their parent's turn ended, or to disappear entirely when transcripts were still being written.
- Subagent count unified at the adapter level (#132) — In-process and file-based counts are now reconciled in the adapter so the daemon sees a single authoritative value, eliminating drift between the two sources
- Subagent quiet window bumped to 30s — API response gaps on long tool calls were tripping the previous window and prematurely marking subagents idle. 30s survives the observed gap distribution
- Fast-forward orphaned subagents when parent turn is done — When the parent session finishes its turn but a child was left in an intermediate state, the child is now fast-forwarded to
readyinstead of hanging inworking - Don't fast-forward subagents whose transcripts are still being written — Guards the fast-forward path so in-flight children aren't killed mid-stream
- Re-evaluate parent when liveness sweep deletes last child — When the liveness sweep removes the final child session, the parent is immediately re-classified so its state reflects the new reality
- FS watcher recursively watches newly-created subtrees — New project/session directories created after daemon startup are now observed without a restart
Added
- Cost display toggle (#130) — The macOS menu bar cost readout can now be toggled in Settings and is off by default for users who prefer a lighter status line
- Full session lifecycle recording & replay (#107, #138) —
ir:test-macruns can now record presession events, session events, and tail output into a sidecar file. Thereplayharness replays any recording byte-identically against the production tailer + classifier, and fails loudly on sidecar drift - Curated subagent fixtures — Real-world subagent transcripts (including an 11-background-agent fixture) are bundled with their lifecycle events so regressions in parent/child tracking are caught offline
- Tailer FIFO replaced with id-keyed map (#117) — Open-tool tracking now uses a stable map keyed by tool use id instead of a FIFO, removing ordering assumptions that broke with parallel tool calls
Docs
- README rewritten around real user pain and the competitive landscape of agent monitoring tools
- GitHub-recognized community health files added:
CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md, issue and PR templates
v0.3.2 (2026-04-07)
Fixed — Claude Code session state flicker (#102)
Four distinct bugs were causing long-running Claude Code sessions to bounce between working, waiting, and ready. All are fixed in this release.
- Stale-tool timer disabled for Claude Code — The 15s heuristic tripped on every multi-second Bash invocation (builds, tests,
find, network calls). Permission-pending modals and long-running Bash are indistinguishable in the JSONL stream, so the false-positive rate swamped the signal. Brings Claude Code in line with the Pi adapter's existing policy. - Tailer open-tool tracking collapsed to a single source of truth — Parallel integer counters and a name slice could desync on orphan
tool_resultevents (from--continueresumes or compact replays), leaving metrics in an impossiblehas_open=false / open_tool_names=[Bash]state. Now derived solely from the name slice — orphans are idempotent no-ops. - ESC interrupts distinguished from benign tool errors — The classifier's cancellation rule fired on any
tool_result.is_error=true(grepmisses, failed builds,findon protected dirs), incorrectly flipping the session toready. NewLastWasUserInterruptsignal fires only on the literal[Request interrupted by usertext marker. stop_reasonallow-list — Onlynullwas treated as intermediate streaming;max_tokensandpause_turnpassed through as terminal and trippedIsAgentDone()mid-turn. Flipped to an allow-list ofend_turn/stop_sequence/refusal/tool_use— unknown future values default to "assume streaming".
Fixed
- OpenCode agent registry corrected to
anomalyco/opencodeand marked as planned - Empty-state text updated from "Claude Code" to the generic "coding agent"
Added
- Offline replay harness (
core/cmd/replay) — Takes any Claude Code, Codex, or Pi transcript and feeds it through the production tailer + classifier using virtual time. A 500-hour session replays in under a second. Every transition is logged with reason, metric snapshot, and trigger cause - Regression fixtures under
testdata/replay/<adapter>/— Four Claude Code, one Codex, one Pi — all post-fix flicker-clean - Local session scanner (
scripts/find-flicker-sessions.sh) — Ranks every transcript under~/.claude/projects,~/.codex/sessions, and~/.pi/agent/sessionsby flicker count, for harvesting new regressions - Adapter registry (
core/adapters/inbound/agents/registry.go) — Single source of truth for adapter name → parser and adapter name →StatePolicylookups, consumed by the replay harness
Docs
- README updated with cost tracking, subagent visibility, and corrected state detection behavior
- State machine docs: cancellation section rewritten to reflect the new
LastWasUserInterruptsignal - API reference: session metrics schema updated (
last_tool_result_was_errorremoved,last_was_user_interruptadded)
v0.3.1 (2026-04-06)
Added
- Dynamic model capacity from LiteLLM — Context window sizes and pricing are now fetched from the LiteLLM API at daemon startup, removing hardcoded fallback assumptions
- Token usage in debug mode — Debug mode now shows token usage metrics for all models, with percentage kept next to the context bar
- Reorderable project groups — Project groups in the macOS popup and menu bar can now be reordered by the user
Changed
- Removed client-side session expiry setting — Session expiry is now handled entirely by the daemon, simplifying the macOS app settings
- Adapter-driven stale-tool waiting — Stale-tool timeout is now configured per adapter policy rather than globally
Fixed
- Codex gpt-5.3 context window mapping corrected to 256K
- Large appended transcript lines no longer skip events
- Wrapped Codex transcript schema now parsed correctly
- Pi compaction events treated as activity for working-state detection
- Subagent tool tracking and debug row display preserved correctly
- False ready state during tool calls and subagent work prevented
- Session state flicker between tool calls — First streaming chunk of each new assistant message (new message ID,
stop_reason=null) was misclassified as final, triggering false working→ready→working transitions on every tool call cycle - Stale-tool timeout increased from 5s to 15s to reduce false waiting transitions during long-running builds
Site
- Agent landscape page with 63 tracked agents, 3-month trends, and deny list
v0.3.0 (2026-04-05)
Added
- Permission-pending detection — Sessions with non-blocking tool calls awaiting user approval now transition to
waitingstate after a 5-second stale-tool timeout, correctly reflecting that the agent is blocked on permission - Last question display — When an agent asks a question and enters waiting state, the question text is captured and available via the API for display in the menu bar
- Session state dots — Colored dots in the macOS menu bar and popover headers provide at-a-glance status for each session
- Gas Town educational UI — Role hierarchy visualization and active tool display for Gas Town orchestrator sessions
- Web dashboard redesign — Compact rows, DOM reconciliation for flicker-free updates, and timeline heatmap
- Adapter-specific PID lifecycle — Codex and Pi sessions now have tailored PID discovery and process lifecycle handling alongside Claude Code
- ir:agent-releases skill — Tracks upstream coding agent releases and reports changes that impact irrlicht monitoring
Changed
- Per-adapter transcript parsers — Each agent adapter (Claude Code, Codex, Pi) now has its own transcript parser instead of a single shared parser, enabling format-specific handling and cleaner architecture
- Shared CWD extraction logic extracted, dead code removed
Fixed
- PID assignment race condition — Serialized PID assignment with state transitions to prevent concurrent sessions from claiming the same process
- Orphan session cleanup after /clear — Sessions left behind when a user runs
/clearin Claude Code are now properly cleaned up - Stuck working state from local commands — Local command events (shell escapes) no longer prevent sessions from transitioning out of working state
- Daemon performance with many sessions — Resolved performance degradation when monitoring many concurrent sessions
- Git root resolution for deleted worktree directories
- Project name detection for non-git directories on activity refresh
- Expanded click target for Settings and Quit buttons
- URL-encoded dynamic badge URLs
- Pi nested message parsing for role/stopReason/content fields
- Pi PID discovery via command-line pattern matching
- Include “assistant” in IsAgentDone fallback for turn detection
Site
- Refined hero section — removed glow, shortened copy, cleaned up labels
- Added counter.dev analytics to all pages
v0.2.5 (2026-04-05)
Added
- Pi coding agent adapter — Monitor Pi sessions alongside Claude Code and Codex. Watches
~/.pi/agent/sessions/, parses JSONL v3 transcripts, detects state fromstopReason, extracts tokens/cost/model, and supports Pi subagent linking viaparentSession - Pi icon in macOS menu bar — Greek letter pi (π) icon distinguishes Pi sessions from other agents
v0.2.4 (2026-04-05)
Added
- Will-o’-the-wisp app icon — Custom purple wisp flame icon for the macOS app bundle, replacing the default icon
- Will-o’-the-wisp favicon — SVG and PNG favicons added to landing page and all documentation pages
- SEO metadata — Open Graph, Twitter Card, and canonical URL tags across all site pages
- CLAUDE.md development guide — Project conventions, build commands, and architecture overview for AI-assisted development
Fixed
- Ready sessions no longer deleted while process is alive — Sessions in ready state are preserved as long as their Claude Code process is still running, preventing premature cleanup during idle periods
Distribution
- ir:release skill — Automated release pipeline with DMG, PKG, universal binary builds, changelog updates, and GitHub release creation
- DMG background asset for branded installer experience
v0.2.3 (2026-04-04)
Added
- Subagent session lifecycle — Background and foreground subagents are detected as child sessions linked to their parent, with real-time state tracking and automatic cleanup when finished
- Purple badge — Parent sessions display a live count of active subagents in a purple circle badge in the menu bar
- Hierarchical dashboard API —
GET /api/v1/sessionsreturns aDashboardResponsewith Orchestrator → Group → Agent → Children hierarchy - PID discovery retry with backoff — Retries at 500ms, 1s, 2s intervals with CWD-based fallback when lsof fails
- macOS app improvements — Debug mode (
IRRLICHT_DEBUG=1), dev daemon support, clean shutdown, version display in UI, WebSocket keepalive with auto-reconnect - irrlicht-ls enhancements —
--format jsonoutput,--idprefix filter, hierarchical display with indented child sessions and agent count badges
Changed
- Modular SessionDetector — Refactored into focused collaborators:
StateClassifier(pure state transitions),MetadataEnricher(git/metrics),PIDManager(process lifecycle) - Unified processlifecycle package — Process scanner and process watcher merged into a single adapter
- Cascade delete removes all child sessions when a parent session is deleted
- Stale child sessions (transcript >2min old) cleaned up automatically in periodic sweep
Fixed
- No false “waiting” state during tool execution — Only user-blocking tools (AskUserQuestion, ExitPlanMode) trigger waiting; Agent, Bash, Read etc. correctly show as working
- Multi-instance session assignment — Running two Claude Code sessions in the same repo no longer causes PID conflicts; disambiguator ensures unique assignment
- Subagent badge no longer persists after agent finishes
- WebSocket connection state replaced
isWatchingflag with properConnectionStateenum
v0.2.2 (2026-04-03)
Added
- Embedded daemon in app bundle — Irrlicht.app bundles both SwiftUI UI and Go daemon; no separate services needed
- DaemonManager — Auto-spawns, monitors, and restarts embedded daemon with exponential backoff
- Session tooltips on hover in menu bar popover
- DMG and PKG distribution artifacts
Fixed
- Delete sessions immediately on process exit
- Skip orphan transcript files on startup
- Delete old session when
/clearreuses same PID - Filter daemon self-PID from lsof
- Ready-session TTL cleanup (30min default)
v0.2.0 (2026-04-03)
Added
- OpenAI Codex adapter with recursive directory watching for
sessions/YYYY/MM/DD/structure - Per-model pricing data and
EstimateCostUSDfor cost tracking - Cost display in menu bar UI per session and per project group
- Token breakdown tracking (input, output, cache read, cache creation)
- Codex transcript event parsing (
message,response_item,function_call,turn_context) - Codex model detection from
~/.codex/config.toml - Content character counting for token estimation when explicit counts unavailable
- GPT-5.4 model capacity entry
- Dark mode adaptive Codex SVG icon
- Project name coloring by max context utilization (green/yellow/orange/red)
- macOS
.pkginstaller bundling daemon + app + LaunchAgent
Changed
- Filesystem watcher now recursively watches all subdirectories (supports deep nesting)
- Git adapter resolves main repo root via
--git-common-dir(worktree-aware) - Git adapter strips
worktree-prefix from branch names GetCWDFromTranscriptnow tail-reads last 32KB for latest CWD (supports mid-session worktree switches)- CWD/branch/project refreshed on every activity (not just on first detection)
- Context utilization tests updated for 1M context windows
- Token extraction refactored with shared
extractUsagehelper - Model config updated to v2.1.0 with pricing data for all models
build-release.shnow builds both daemon and Swift app, creates.pkginstaller
Fixed
- ESC cancellation detection using
is_errorflag on tool results - Parent session state adjustment based on sub-agent activity
- Permission prompt detection as waiting state
- Filesystem watcher race condition on directory creation
v0.1.0 (2026-03-20)
Added
- Initial release
- Claude Code session monitoring via filesystem watching
- Three-state model: working, waiting, ready
- macOS SwiftUI menu bar application
- Go daemon (
irrlichd) with HTTP API and WebSocket streaming - Context utilization tracking with pressure levels
- Process exit detection via kqueue
- Pre-session detection via process scanning
- Git branch and project name resolution
- mDNS/Bonjour service advertisement
- Embedded web dashboard
irrlicht-lsCLI listing tool- Structured JSON logging with rotation
- Gas Town orchestrator integration
- Subagent detection and parent-child relationships
Format
This changelog follows the Keep a Changelog conventions. Versions use Semantic Versioning.
Types of changes:
- Added — new features
- Changed — changes in existing functionality
- Deprecated — soon-to-be removed features
- Removed — now removed features
- Fixed — bug fixes
- Security — vulnerability fixes