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

Three irrlichd daemons pushing over authenticated wss to a standalone irrlichtrelay, which fans out to the macOS app and web dashboard

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) — irrlichd now builds and runs on Linux (amd64 + arm64), daemon-only, behind a portable ProcessObserver observation layer; install via the same curl … | sh one-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 stays working until it finishes, instead of falsely settling to ready.
  • 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 to waiting instead of looking busy.
  • Codex: settle interrupted turns (#464) — a turn_aborted is treated as turn-end, so an interrupted Codex turn settles instead of hanging in working.
  • 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 under examples/ (#486).
  • Cross-platform observation layer — new ports.ProcessObserver seam with build-tagged process_{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 in core/pkg/capacity/aliases.go synced from codeburn’s BUILTIN_ALIASES. Five Warp codex aliases resolve to gpt-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 session working and recognizes TaskOutput / <task-notification> completion in SDK-harnessed claude as well as BashOutput / KillShell in bare claude (#450, #452, #501); codex turn_aborted treated as turn-end (#464).
  • Onboarding factory (internal fixture tooling — no runtime impact) — eight-phase rewrite of the scenario × adapter coverage matrix: tools/agent-onboardingtools/onboarding-factory, with the of CLI as the sole writer of everything under replaydata/ (#522–#530). Schema cutover to per-scenario shards — one catalog (scenarios.json) plus per-cell metadata.json in id-prefixed folders, every recording moved under recordings/<name>/ (#510, #511, #514, #524). New 4-verb ir:onboarding-factory skill retiring ir:onboard-agent; of validate as 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/sessions response. Settings modal gains a Provider-quota section with the same auto/subscription/usage controls as the macOS app.
  • OpenCode todowrite snapshots 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.sh and the cask postflight are gone.
  • com.apple.security.get-task-allow removed 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-viewer and tools/find-flicker-sessions.sh deleted (#411, #414) — superseded by the unified agent-onboarding viewer at tools/agent-onboarding.

Internal

  • /ir:onboard-agent pipeline 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 UpdateManager wrapping SPUStandardUpdaterController. EdDSA public key nKRcUPAmK6syLFEvp9O30FFvjhTIfGxYVv/6y8zpZI0= baked into both the tracked Info.plist and the tools/build-release.sh heredoc. tools/build-release.sh copies Sparkle.framework into Contents/Frameworks/, adds the @executable_path/../Frameworks rpath, and signs the nested helpers (Downloader.xpc, Installer.xpc, Updater.app, Autoupdate, framework binary) deepest-first before the outer bundle. New site/appcast.xml ships 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.sh signs with the Developer ID cert + hardened runtime + entitlements when DEVELOPER_ID is set; notarizes and staples when NOTARYTOOL_KEYCHAIN_PROFILE is also set. The build exits 1 if DEVELOPER_ID is set without NOTARYTOOL_KEYCHAIN_PROFILE. FocusMonitor.swift detects the DevID signature at runtime and loads INFocusStatusCenter via NSClassFromString rather than statically linking Intents.framework.
  • get-task-allow removed from prod entitlements (#407, #415)Irrlicht-dev.entitlements still 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.Parser is 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 in opencode/metrics.go populates metrics.Tasks on the SQLite metrics path. New TestComputeMetrics_TodowriteTasks drives querySessionMetrics with 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.go pins 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 declare Capabilities and matrix cells fall out automatically. New drive-opencode-interactive.sh driver: each send step is an opencode run --session <id> subprocess; post-run SQLite export to the parts JSONL the parser expects.
  • Installer rate-limit hardening (#401)site/install.sh falls back to the /releases/latest redirect 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

Roadmap page top — Horizon and v1.0 entries on a dashed future spine

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 mapkimi-auto, kimi-code, kimi-for-coding resolve to moonshot.kimi-k2-thinking in 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_reminder snapshots (#396) — the in-memory task list is reconciled against the authoritative snapshot so dropped tasks no longer linger as in_progress indefinitely.

Docs / Tooling

  • Release-flow friction removed from CLAUDE.md (#392).
  • /ir:release release-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.html is a single file with all CSS scoped inline (no docs.css edits). Pure-CSS git-branch via pseudo-elements; ALSO SHIPPED list extracted via git log <prev-tag>..<this-tag> with hex-color and natural-language noise filters. Linked from every docs/*.html sidebar (new "Project" section), the landing-page footer, and the CHANGELOG.md preamble.
  • 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-figure renders 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 .dot swapped for an inline gradient flame SVG, design-system preview tiles updated, AppIcon iconset regenerated via the reproducible tools/build-app-icon.sh builder.
  • Working-state breathing dot (#393) — svgIcons.working swaps the SMIL heartbeat halo for a solid circle with a CSS opacity breathe (no animation under prefers-reduced-motion). macOS SessionStateIcon updated 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_reminder attachments as authoritative; any local in_progress missing from the snapshot is demoted to completed. Mirrors the v0.3.12 phantom-in_progress fix (#289) but for the absent-task case.
  • Model aliases — codeburn sync — adds kimi-auto, kimi-code, kimi-for-coding to core/pkg/capacity/aliases.go, all LOCAL_OVERRIDE to moonshot.kimi-k2-thinking since LiteLLM only ships the dotted-prefix key. Five changed entries in the codeburn diff are intentional LOCAL_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_count events carry rate_limits in-band (parser extension), and Claude Code's statusline JSON is captured via a new statusLine.command that POSTs to /api/v1/hooks/claudecode/statusline. The statusline chain is wrapped in bash -c '...' so the bash-only tee >(...) 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 into SessionMetrics; a 5-sample rolling history feeds a linear-projection forecast; the menu-bar header shows a provider chip (Anthropic / OpenAI inferred from plan_type or 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.swift is rescaled to viewBox 1254 with the single path and the unused core gradient dropped; AppIcon.icns is regenerated via a new reproducible builder tools/build-app-icon.sh (rsvg-convert with qlmanage fallback) that produces a byte-identical .icns on re-runs. The landing-page navbar swaps its 8×8 .sparkle dot 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 swaps svgIcons.working for an inline SMIL heartbeat-halo SVG, drops the now-unused .working spin rules, and hides the animated halo under prefers-reduced-motion. macOS adds a new SessionStateIcon SwiftUI view that renders the halo natively (repeatForever, honors accessibilityReduceMotion) and the pause bars as two RoundedRectangles. Snapshot fixtures impacted by the icon swap are re-recorded. Closes #380
  • /ir:triage assigns release milestones on ready-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-HighACTIVE (in-progress release), Priority-MediumNEXT, Priority-LowFUTURE. 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 on needs-info / wontfix and on issues already carrying a maintainer-set milestone. Bundled into the existing gh issue edit call. The brief template gains a **Milestone:** line

Fixed

  • Detect imperative waiting cues without a literal ? (#381, #383) — IsWaitingForUserInput now 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") as waiting instead of falling through to ready. Adds ExtractWaitingCue alongside the existing ExtractQuestionSnippet — 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. ExtractWaitingCue walks 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 reports turn ended with question or cue → waiting. Adds the agent-imperative-pending replay scenario and refreshes five existing claudecode goldens
  • Web: migrate sessions when project_name lands after the first push (#377) — The web dashboard's applySessionUpdate updated agent fields in place but never reconciled group membership when project_name changed. So if the first session_created WS push arrived before git-metadata enrichment had filled in project_name, the agent was placed in the "unknown" bucket and stayed there permanently. The macOS app dodges this because patchApiGroups schedules a full re-hydration on a missed session id; the web view had no equivalent path. Fix: on a session_updated for an existing top-level entry, detect when the new project_name points 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 their sessionIndex entries re-anchored via indexChildren
  • 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's anthropic--claude-4.6-opus, Antigravity's gemini-3.1-pro-high) priced at $0 because CapacityManager.GetModelCapacity did an exact LiteLLM-key lookup with no normalization. Adds core/pkg/capacity/aliases.go (a 63-entry alias map ported from codeburn's BUILTIN_ALIASES) and resolves it inside GetModelCapacity before 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-aliases skill (standalone-PR + release-inline modes) that diffs against codeburn upstream, wired into /ir:release as Step 1.5 (fail-soft). Verified end-to-end via replay against claudecode/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:7837 now 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 matches SessionListView.swift: state · num · subagent badge · branch · ctx-bar (with token overlay inside) · cost · model · adapter icon, with strict flex-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 mirrors SettingsView.swift minus 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 fire new Notification(...) only when the dashboard isn't the focused tab. Themes pick icon_svg_light vs icon_svg_dark per resolved theme so the Codex icon is now visible on both. Group headers cycle day/week/month/year on click, persist their collapse state in localStorage, 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/version returning {"version": "..."}. The previous timeline-heatmap and Raw-JSON tab — both debug surfaces the overlay never had — are removed. Marked beta in README.md and site/docs/quickstart.html

Fixed

  • Codex split-event token_count priced at $0 (#361) — Codex sessions reported $0 cost because the parser emitted PerTurnContribution{Model: ""} on every token_count event — Codex splits the model name onto turn_context and the usage cursor onto token_count, so the tailer bucketed deltas under cumByModel[""], which has no pricing. In applyContribution, contributions with no model now fall back to t.metrics.ModelName (already maintained by applyModelMetadata from turn_context) so pricing and the UI's model display read the same field by construction. LedgerState persists ModelName so the fallback still works on the first token_count after 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 via CLAUDE_CONFIG_DIR with /projects appended (transcripts only — the PID-metadata directory at ~/.claude/sessions/ remains hardcoded because that hunk is unverified), and Codex via CODEX_HOME with /sessions appended. fswatcher.New treats an absolute dir as-is and falls back to $HOME-relative for relative paths. Non-absolute env values (relative paths, unexpanded ~, trailing slashes) are filepath.Clean-ed and rejected with a log.Printf warning. Env vars are read once at Agent() 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 to 0–60% / 60–80% / 80–90% / 90%+ to match core/pkg/tailer/tailer_metrics.go; menu-bar icon called a "sparkle" corrected to "flame"; six references to the deleted ./validate.sh script replaced with go test ./core/... -race -count=1 + tools/replay-fixtures.sh; processscanner/processlifecycle/ rename propagated; HTTP/WS diagram updated to /api/v1/sessions(/stream); adapter field now lists all 5 wired adapters; never-implemented --format json / --id <prefix> CLI flag docs removed; log filename corrected to events.log; lsof command updated to include the -Fn flag 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 NSFocusStatusUsageDescription from the Info.plist template — with Intents.framework statically linked, TCC preflights kTCCServiceListenEvent at 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-gated NS*UsageDescription keys, and the linked frameworks themselves (the structural lever). (3) Add a framework-link audit between Swift build and signing: otool -L against a FORBIDDEN_FRAMEWORKS list aborts the release on any match. (4) Strengthen smoke-test failure semantics with explicit "DO NOT SHIP" framing, a tccutil reset prereq to clear poisoned cache, and a four-step debugging checklist. (5) Replace Step 9 verify with a real end-to-end install canary that runs curl ... | sh against the just-published release and validates via pgrep — 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 linked Intents.framework via FocusMonitor.swift's direct INFocusStatusCenter calls, which made macOS TCC preflight kTCCServiceListenEvent at process startup and AMFI/TCC SIGABRT the ad-hoc-signed binary with launchd POSIX 153 on every end-user install. The re-cut moves FocusMonitor to dynamic dispatch (NSClassFromString + SecCodeCopySigningInformation gate) 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.mainApp so 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 to UserDefaults; 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 detached userInitiated task so the toggle animation stays smooth on slower Macs. The unsigned-then-signed-build dev edge case is called out inline in applyDefaultIfNeeded so 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 kAXWindowsAttribute omits 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), unwrap menuBarRef before CFGetTypeID, and collapse the imperative lookup into first(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 a proc-<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, and HandlePIDAssigned'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) in processlifecycle/scanner.go), so the daemon now short-circuits adapter-level discovery for them and calls HandlePIDAssigned directly with the parsed PID. The short-circuit sits above the ProcessWatcher == nil / discoverFn == nil guards so it's robust against future adapters that have a process matcher but no PIDForSession. Real sessions (UUID IDs with a transcript path) continue through the adapter unchanged. E2E regression test uses sync/atomic.Int32 for the discovery-call counter so a future regression races visibly under -race

Docs

  • Landscape page refresh against live GitHub data (#346) — site/landscape/index.html and site/landscape/compare/index.html regenerated from a fresh gh api sweep of 38 tracked agents (May 15, 2026 snapshot). Aider and OpenCode flip from planned to live in the landscape table to match their existing adapters under core/adapters/inbound/agents/. Two repo renames propagated: Pi badlogic/pi-monoearendil-works/pi (the v0.74 move) and Warp warpdotdev/Warpwarpdotdev/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. The ir:agent-releases skill's tracked-releases.md adds 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_DISABLED env var (#337) — IRRLICHT_DISABLED was never wired into any code path, and IRRLICHT_USE_FILES gated a fallback path in SessionManager that the WebSocket transport has fully replaced; both are removed. ~165 lines of dead Swift in SessionManager.swift go with them (file watcher, debounce/periodic timers, loadExistingSessions, createInstancesDirectoryIfNeeded); init unconditionally uses WebSocket. Equivalent orphan reaping still happens daemon-side via PIDManager's syscall.Kill(pid, 0) sweep — no safety net was lost. The macOS app no longer reads from the daemon-owned instancesPath; that ordering dependency is now documented inline

Docs

  • configuration: document four real env vars with concrete recipes (#337) — site/docs/configuration.html drops the USE_FILES / DISABLED rows 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 and launchctl setenv flows 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 original IRRLICHT_DEBUG=1 open -a Irrlicht example 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.html no longer mentions the IRRLICHT_DISABLED kill switch (which never existed). SECURITY.md cross-links the network-exposure paragraphs to configuration.html and 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 via gh release edit --notes-file). Step 2 of /ir:release now 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 --notes to --notes-file pointing 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.png and assets/straeter_light.png are 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 set TERM_PROGRAM (upstream kitty #4793); the daemon captured the inherited value and the click was routed to VS Code's activator. ReadLauncherEnv now overrides TermProgram to "kitty" whenever KITTY_WINDOW_ID is set and process ancestry confirms kitty.app is a parent. (2) With multiple kitty processes running, AppActivator.activate(bundleID:) always picked one — typically the oldest — and a post-kitten focus-window re-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 whitelists KITTY_PID; a new Launcher.KittyPID field is plumbed through to the Swift app; KittyActivator calls NSRunningApplication(processIdentifier:).activate(options: []) synchronously inside the click handler. (3) Apple-signed agents like pi (and /bin/zsh) hide their env from sysctl, so KITTY_* env vars never reached their sessions — every click hit bundle fallback. Three new darwin-only helpers in osutil_darwin.go derive these fields without reading the agent's env: kittyAncestryPID, kittyListenOnFor, kittyWindowIDForPID. backfillLauncher was extended so pre-existing sessions get all four kitty fields refreshed on daemon restart

Security

  • kitty: uid-check on /tmp/kitty-{PID} socket probe/tmp is world-writable, so kittyListenOnFor could be tricked into trusting a pre-planted Unix socket at the canonical path and sending kitten @ ls to a hostile listener. kittyListenOnFor now stats the candidate socket and skips any whose owner uid doesn't match os.Getuid() — kitty binds with its own credentials, so a foreign-owned socket at the canonical path is either stale or hostile. Test coverage in osutil_darwin_test.go exercises 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-vscode pi worst-case path was walking the parent-process chain up to three times (each walk shells ps up to maxAncestry times). A new resolveHostFromAncestry returning (termProgram, hostPID) is memoized inside ReadLauncherEnv via a closure, so the chain is walked at most once. resolveTermProgramFromAncestry and kittyAncestryPID become thin wrappers; call-site behavior is unchanged

Docs

  • api-reference, contributing: catch v0.4.0 sweep gaps (#332) — site/docs/api-reference.html still referenced agentCfgs in the GET /api/v1/agents blurb; renamed to allAgents to match cmd/irrlichd/main.go. site/docs/contributing.html adapter-PR checklist still said "Implements the AgentWatcher interface"; replaced with the current Agent() / agent.Agent / allAgents contract

Distribution

  • Release skill hardening (#332) — Step 6 checksum recipe now includes irrlichd-darwin-universal.tar.gz (the curl --daemon-only path verifies it; omitting it shipped a release where the standalone daemon installer failed the integrity check). Step 7b drops --delete-branch from gh pr merge --squash so the release branch remains addressable post-squash. Step 4b trigger table gains rows for adapter-package edits and main.go slice/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/, transcoding mp3/m4a to LPCM-in-CAF via AVAudioFile so UNNotificationSound will play it. "Speak aloud" routes title+body through AVSpeechSynthesizer, 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 a system/away_summary recap ~3 minutes after a turn ends. The parser correctly marked it Skip=true, but the fswatcher's mtime trigger still ran the full classification pipeline, and the force-bounce in processActivity saw the stale LastEventType from the prior turn_done and flipped the ready session back to working indefinitely. The tailer now surfaces a NoSubstantiveActivity signal 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 refreshing LastEvent / EventCount / UpdatedAt and broadcasting so the UI's "last activity" stays current
  • Claude Code: detect AskUserQuestion / ExitPlanMode via PreToolUse hook (#307) — Claude Code can lag flushing the assistant tool_use block to JSONL for minutes after rendering an AskUserQuestion / ExitPlanMode overlay; the transcript-driven detector never saw the open tool call, so the session sat in working while the user stared at the prompt. A PreToolUse hook scoped to AskUserQuestion|ExitPlanMode fires synchronously when the model emits the tool_use and flips permissionPending; the existing PostToolUse matcher is widened to include both tools so the same edge clears the flag when the user answers. Legacy installs are migrated in place by upgradeStaleHookMatchers
  • Codex: treat <proposed_plan> as user-blocking like ExitPlanMode (#322) — Codex's Plan Mode ends a turn with a <proposed_plan>…</proposed_plan> block — semantically identical to Claude Code's ExitPlanMode. The block arrives as plain assistant text, so the classifier never saw an open user-blocking tool and fell through to ready, 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 virtual ExitPlanMode tool-use — same user-blocking path as Claude Code; the existing ClearToolNames hook 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 --resume against 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 both onNewSession and seedAlivePIDs

Changed

  • #159 Phase A — Agent declaration replaces agents.Config — The legacy agents.Config struct + its five per-adapter Config() constructors and four map helpers are removed. Each adapter now exports a single Agent() constructor returning a sealed-sum declaration: Agent = Identity × Process × Source, where Process is ExactName | CommandPattern, Source is FilesUnderRoot | FilesUnderCWD | ProcessOwnedStore, and FileParser is JSONLineParser | RawLineParser. The daemon consumes []agent.Agent directly, with per-projection helpers in adapters/inbound/agents/maps.go. Variant-dispatched watcher wiring in cmd/irrlichd/wiring.go replaces the per-adapter loop. Phase A also lands an M0 contract-test layer (SessionState on-disk, 7 PushMessage shapes, GET /api/v1/agents) to guard the public surface
  • #159 Phase A — Watcher port replaces AgentWatcher; identity carried on the merge pipeline — The inbound watcher port gains an Identity() method and WithIdentity() builder; each per-watcher drain goroutine in SessionDetector.Run() captures identity once and wraps every event with it, so agent.Event.Adapter is removed. The old AgentWatcher interface is deleted. NewSessionDetector panics at construction when any watcher's Identity() is the zero value. metrics.New takes a single Registry struct instead of four positional maps

Docs

  • /ir:release skill — adapt to PR-required main + fix tap-publish race (#306) — main is now protected by a "Changes must be made through a pull request" repo rule. The release flow now stages on a short-lived release/v$NEW_VERSION branch, opens a PR with the drafted release notes, squash-merges, hard-resets local main to origin/main, and tags the merged commit. Step 6.5 patches the in-repo cask template via sed and 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 EventNewSession for every non-archived row whose time_updated fell within maxAge, regardless of whether opencode was 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 via processlifecycle.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 new emitted flag enables EventNewSession to 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-backed TranscriptPath) that neither syscall.Kill nor isStaleTranscript caught. A new branch in isStartupZombie deletes PID=0 sessions 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 initialArchiveCleanupWindowgcExpiredCursors drops cursor entries whose lastObserved predates maxAge so the cursor map can't grow without bound for users who accumulate many sessions but rarely run the CLI. Tracked separately from cur.lastTS so a session whose time_updated bumps 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 liveCWDs per adapter inside CleanupZombies so M ghost candidates sharing an adapter pay one pgrep fork, 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_DIR was unset. update-cask.sh now auto-discovers a sibling ../homebrew-irrlicht clone before bailing and hard-fails on --push without 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.html was missed in v0.3.12 because Step 4b only enumerated site/docs/*.html. Extends the dynamic enumeration to scan site/*.html and 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/part tables, a parser mapping step-finish / text / tool rows to normalized events, a MetricsProvider that bypasses the JSONL tailer for cost + token snapshots, CWD-based PID discovery, parent-child session linking via parent_id from the DB, and EventRemoved emission on session.time_archived. Closes #100
  • ir:triage skill (#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 at ready-for-agent or needs-info with a one-line justification per label decision. Bulk sweep skips already-triaged issues; explicit /ir:triage #N always 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 when states.count <= bucketCount. After #249 lowered bucketCount from 150 → 60, the 150-state test fixture started rendering an all-green bar because offset collapsed to 0 and the loop drew the oldest states inside the canvas while the newest tail was clipped past the right edge. Now takes states.suffix(bucketCount) and recomputes offset against the visible slice
  • Claude Code: reconcile phantom in_progress from task_reminder snapshots (#289) — Claude Code occasionally emits a TaskUpdate against a stale taskId and never sends a follow-up completed, so the UI hung at n / total forever. The tailer now treats the task_reminder attachment as authoritative — any local in_progress whose ID is missing from the snapshot is demoted to completed, and snapshot status wins on any present-with-divergent-status case. Closes #282
  • macOS: sync apiGroups on local session delete + reset (#287) — local deletes/resets only updated sessions (menu bar) and sessionMap and skipped apiGroups (list view), so a deleted session lingered in the list until rehydration and a reset row stayed working in the list while the menu bar already showed ready. Mirrors the WS handler and adds SessionState.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/IconSVGDark to agents.Config and a new GET /api/v1/agents endpoint 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>SVG functions and two switch statements in SessionState.swift are 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. AgentRegistry is @MainActor-isolated for Swift 6 strict-concurrency cleanliness. Closes #260

Docs

  • Adapter interfaces documented with exact Go signatures (#292) — site/docs/adapters.html gains an "Adapter Interfaces" section with the actual agents.Config, tailer.TranscriptParser (plus the optional RawLineParser, IdleFlusher, PendingContributor, ParserStateProvider hooks), agent.PIDDiscoverFunc, and agent.Event / inbound.AgentWatcher types, with file paths so readers can jump from doc to source
  • Release skill sweeps every docs page on each release (#293) — /ir:release Step 4b now enumerates site/docs/*.html and 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_ID up front, captures the gist PATCH response so a non-2xx fails the job with the actual error body instead of dying silently inside curl -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-hello golden (#285) — golden was committed in #255 with a populated source_transcript field that the test zeros before comparison; regenerated via UPDATE_REPLAY_GOLDENS=1 to 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 omitempty dropped estimated_cost_usd from output entirely). Stale pricing is now served to non-daemon callers (replay tool, CLI, tests); IsCacheStale keeps 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 (emitting assistant_message) and synthesizes the turn_done via an idle-flush hook, so sessions don't flip to ready mid-turn
  • Aider: emit turn_done on LLM-layer error (#273) — when aider prints a > litellm.BadRequestError: … blockquote without a > Tokens: line, the session no longer hangs in working forever
  • Tailer: drop bufio.Scanner cap 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 for platforms/web/index.html so 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-agent covers 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_transcript so 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 claude process has exited. The daemon now reaps stale entries on launch
  • Prune deleted sessions from apiGroups synchronously (#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 SessionManagerApiGroupsTests cases 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-agent discovery 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:exec skill — issue-driven plan generation. Reads a GitHub issue and produces a structured implementation plan to start a fresh worktree
  • coverage-viewer dev webview (#222) — local web UI for the agent × scenario fixture matrix; shows which lifecycle events each adapter has recorded
  • tui capability + category taxonomy — adapters can declare tui as a discoverable capability so the canonical scenario matrix can target TUI-style agents
  • IRRLICHT_DEMO_MODE=1 — daemon flag that disables ProcessWatcher and per-adapter AgentWatchers so tools/seed-demo-sessions can stage screenshot scenarios without live processes leaking into the dropdown
  • Process discovery: CommandLineMatch + TranscriptFilename probes — wrapper-launched agents (e.g. invoked via pgrep -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_activity events for agents whose transcript lives next to the working directory

Fixed

  • Tailer survives SendMessage tool across turn_done (#81) — Claude Code emits a turn_done between the assistant message and a follow-up SendMessage tool 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 an NSView-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.41 was 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.Parser after stub removal

Changed

  • /ir:onboard-agent overhaul (#199, #200) — moved from a hardcoded scenario list to a features.json + replaydata layout 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-pending for claudecode/codex/pi. Adds drive-pi-interactive.sh and two pi script-based fixtures
  • Dev scripts consolidated under tools/ — standalone tooling (HTTP viewers, fixture generators, homebrew-tap helper) now lives in top-level tools/ rather than core/cmd/
  • tools/homebrew-tap/update-cask.sh simplified — 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 IsCanonicalState and assertWatchersExited helpers

Distribution

  • Homebrew cask via own tap (#187) — brew tap ingo-eichhorst/irrlicht && brew install --cask irrlicht now resolves to the latest release. The cask is auto-bumped on each release via tools/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 FlowLayout aligns the 7pt task-progress circles with the taller "done/total" label

Fixed

  • Settings overlay background no longer transparent — the NSStatusItem + NSPanel rewrite uses a transparent panel so the rounded-corner clip works, which meant every SwiftUI branch had to paint its own background. SessionListView did, but the Settings pane didn't — the desktop wallpaper bled through. SettingsView now paints the same windowBackgroundColor, and a new SettingsViewTests pixel-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/PostToolUse hooks, 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.go split into _activity/_helpers/_lifecycle/_subagent files; cmd/replay/main.go split into lifecycle/metrics/replay_sidecar/replay_transcript/extended_check/types/fixtures_test; cmd/irrlichd/main.go request handlers factored into handlers.go. Behavior unchanged
  • Unified agent registration via agents.Config — adding a new agent adapter is now one Config() constructor + one line in main.go's agentCfgs slice. PIDDiscoverFunc moved to domain/agent/ so Config can reference it without violating hexagonal layering; metrics adapter inverted so outbound no longer imports inbound
  • Shared constants between daemon and replayHookPermissionRequest/PostToolUse/PostToolUseFailure in the claudecode adapter, exported services.SubagentQuietWindow, and services.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-agent skill — produces a canonical scenario × adapter fixture matrix. Scenarios are defined once, agent-agnostically, with a requires: [capability] list; adapters declare Capabilities, and matrix cells fall out automatically. Unifies refresh, bootstrap, and new-agent-onboarding workflows
  • /ir:agent-landscape hardened 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.json every 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 the AskUserQuestion text) in an orange block beneath the row
  • Claude Code task list progressTaskCreate / TaskUpdate tool 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=1 and 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 HistoryBarView and the web dashboard canvas
  • Waiting-state detection survives long assistant messagesExtractAssistantText keeps the tail (with leading ellipsis) so a trailing question-mark still trips the waiting classifier; AskUserQuestion tool calls with no text block fall back to questions[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 onto SessionManager.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 an NSView-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: Task model renamed to SessionTask so it stops shadowing Swift's built-in concurrency Task

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; $0 cost 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
  • AskUserQuestion text 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.formattedCost two-decimal regression
  • New SessionRowView snapshot suite (waiting-question block + ContextBar token-count label); NSHostingView.appearance pinned to .darkAqua so 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>.json pointing 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 a groups/orchestrator wrapper, 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/gastown endpoint
  • macOS: restore project-group reorder chevrons (#172) — The up/down chevrons in top-level project group headers came back; ordering is derived from apiGroups directly so the UI state stays in sync with persisted order
  • CLI: irrlicht-ls runs from any subdirectory (#175) — go run now invoked from the repo root via --workspace

Changed

  • macOS Launcher split into per-host activators behind a HostActivator protocol — iTerm, Terminal.app, and an AXTitleMatchActivator for 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" identityir:test-mac now signs dev builds with a stable designated requirement so Accessibility and Automation TCC grants survive rebuilds. Run scripts/dev-sign-setup.sh once 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 | sh installer at irrlicht.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/sessions JSON payload live

Changed

  • Capacity: LiteLLM is now the single source of truth (#165) — The hand-maintained core/pkg/capacity/model-capacity.json table 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 on working → waiting and working → ready only. A waiting → ready transition no longer fires a redundant "ready" notification
  • Detector: synthetic waiting for 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 as working
  • Replay: split sidecar timelines at process_exited boundaries (#144, #163) — /continue sessions 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 irrlichd network exposure (#94, #155) — Binds to localhost only and rejects cross-origin WebSocket upgrades
  • Site: curl | sh install command wraps on narrow screens; hero spacing cleaned up
  • Tests: three stale SessionManagerTests updated (#166) — Match the current SessionState decoder and abbreviated RelativeDateTimeFormatter behavior

Distribution / CI

  • ARS badge workflow pinned to v0.0.9; GOPROXY=direct no longer required
  • ir:release skill 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 waiting or ready, with a Settings toggle to opt out
  • Permission-pending state via Claude Code hooks (#108) — The daemon consumes PreToolUse / Notification hooks 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 waiting transitions before clients can drain them
  • Tailer: preserve user-blocking tools across turn_done sweep (#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 .app bundle (#149) — Bundle identifier migrated to io.irrlicht.app so dev and release builds no longer share state
  • macOS: corrected dev fallback path for the bundled irrlichd binary — The menu bar app finds the daemon when running outside an installed bundle
  • Skill: ir:test-mac builds from the active worktree — When invoked from a worktree, builds from there instead of the main checkout

Changed

  • Unified replay tool (#141) — replay-session and replay-lifecycle consolidated 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.md added at the repo root and wired into the ir:release skill 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 ready instead of hanging in working
  • 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-mac runs can now record presession events, session events, and tail output into a sidecar file. The replay harness 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_result events (from --continue resumes or compact replays), leaving metrics in an impossible has_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 (grep misses, failed builds, find on protected dirs), incorrectly flipping the session to ready. New LastWasUserInterrupt signal fires only on the literal [Request interrupted by user text marker.
  • stop_reason allow-list — Only null was treated as intermediate streaming; max_tokens and pause_turn passed through as terminal and tripped IsAgentDone() mid-turn. Flipped to an allow-list of end_turn/stop_sequence/refusal/tool_use — unknown future values default to "assume streaming".

Fixed

  • OpenCode agent registry corrected to anomalyco/opencode and 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/sessions by 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 → StatePolicy lookups, 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 LastWasUserInterrupt signal
  • API reference: session metrics schema updated (last_tool_result_was_error removed, last_was_user_interrupt added)

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 waiting state 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 /clear in 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 from stopReason, extracts tokens/cost/model, and supports Pi subagent linking via parentSession
  • 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 APIGET /api/v1/sessions returns a DashboardResponse with 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 json output, --id prefix 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 isWatching flag with proper ConnectionState enum

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 /clear reuses 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 EstimateCostUSD for 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 .pkg installer 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
  • GetCWDFromTranscript now 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 extractUsage helper
  • Model config updated to v2.1.0 with pricing data for all models
  • build-release.sh now builds both daemon and Swift app, creates .pkg installer

Fixed

  • ESC cancellation detection using is_error flag 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-ls CLI 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