Repository navigation
Releases: Heapy/kotgent
Release list
Kotgent 0.10.0 — Linux x64 and ARM64, shared usage meters, and ADHD mode
Linux x64 and ARM64
Kotgent now runs on Linux as well as macOS on Apple Silicon. Each release ships linux-x64 and linux-arm64 archives with .sha256 files beside the macOS archive. Linux builds need glibc 2.35 or newer; Ubuntu 22.04 is the tested baseline. Alpine/musl is not supported.
kotgent install writes a systemd user unit, kotgent.service, and enables it. Stopping, restarting, or uninstalling the service keeps existing tmux sessions. Web Push uses libcurl on Linux. See Install on Linux for dependencies and setup.
All three targets are release gates. Linux ARM64 is cross-compiled on x64, and its test binaries and release archive run on real ARM64 hardware in CI.
Shared usage meters and early reset notices
The sidebar now keeps Claude and Codex quota windows pinned above its footer as progress bars. Each bar has a current-time marker against the expected reset. Hover, keyboard focus, or a tap shows the percentage used, time remaining, reset time, and when the value was last seen.
Kotgent reads usage from output the providers already produce: Claude status-line renders and Codex end-of-turn records. It never polls a provider. Freshness uses the daemon's clock, so a phone with a wrong clock still shows correct ages.
When a weekly window resets more than five minutes early, Kotgent sends a Web Push notice. Scheduled resets and resets of shorter windows do not notify. The service worker reads a new /api/v1/notifications inbox that merges current session-attention items with reset notices from the past hour.
ADHD mode
A pin on a session row marks that session. A pin on a folder head marks that directory, including sessions started there later. ADHD mode then reduces the sidebar to marked sessions. Marks are shared across devices; whether a screen is reduced is a per-device setting. The selected session always keeps its row, and the Done list is never reduced.
Codex
- Kotgent no longer bypasses Codex hook trust for the whole session. It trusts only its own generated hooks, by hash. Codex reviews your other user, project, and plugin hooks as usual again.
- When Codex's startup prompt installs an update and exits, the daemon relaunches the session under the same id. The Web UI terminal reattaches by itself.
- The sidebar shows the CLI version each Codex session was launched with.
Sessions and tasks
- A dead session whose provider no longer keeps the transcript is now
lost, notcrashed. Resume refuses it with the reason instead of opening a pane that dies. Existing rows reclassify on the next daemon start. - Marking one session Done closes its linked task only when every other linked session is also done.
kotgent task donestays unconditional.
API changes
PATCH /api/v1/sessions/{id}acceptsadhd.- New:
POST /api/v1/preferences/adhd-pathsandGET /api/v1/notifications. - New session state
lost. Resuming a lost session answers 409.
Smaller fixes
- The mobile terminal key bar has Shift+Left.
- The installed iOS 27 PWA header no longer blurs.
- The Web UI resynchronizes its snapshots after the page resumes or the clock jumps.
- Enter on a button or link inside a session row activates that control instead of selecting the row.
- Selecting a session from the sidebar focuses its terminal.
- Any 401 response sends the browser to the sign-in page.
- The events socket handler ends when its client closes.
- The build is on Kotlin Toolchain 0.12.2.
Full changelog: v0.9.0...v0.10.0
Upgrade
macOS
brew update
brew upgrade kotgent
kotgent installkotgent install is required: the launchd plist contains a version-qualified Cellar path and otherwise continues starting the previous binary.
Linux
First Linux release: see Install on Linux.
Kotgent 0.9.0 — editable session names, task linking, and a Web UI that stays in sync
Rename a session without replacing it
A session name no longer freezes at creation. Select Rename session from the Web UI’s command palette, or run:
kotgent session rename <id> "new name"The new label reaches every open browser through the live events socket and appears in kotgent list. Clearing the name restores the automatic kt-… label without changing the session’s identity, terminal, task link, or history.
Names behave the same wherever they are set — start, import, CLI rename, browser rename. They are trimmed, bounded to 200 characters, and rejected if they contain control characters. A rename also survives a concurrent background refresh, which previously could restore the old label.
New route: PATCH /api/v1/sessions/{id}.
Link a session to work from the Web UI
A selected running session with no task now offers Link task in the command palette. The searchable picker shows eligible todo, in_progress, and review tasks from that session’s project and writes the same non-exclusive link as kotgent task claim. Linking a todo task starts it; another session may still link the same work.
The picker refreshes projects whenever it opens. A project read that never succeeds now reports the failure with Try again, instead of displaying “Reading open tasks…” forever. A failed refresh keeps an existing usable list visible.
Selection is stable while live updates reorder the list, search behaves consistently under every browser locale, and an IME candidate committed with Enter cannot accidentally link the highlighted task.
A Web UI that no longer disagrees with itself
Every screen now reads its sessions, tasks, projects, and settings from one shared source, so two parts of the page can no longer show different answers after an update arrives.
Only one action runs at a time. Start, import, lifecycle controls, rename, preferences, task linking, and project delete or restore can no longer overlap and overwrite one another’s result, and the command palette names the action that is still running instead of silently accepting another.
The command palette, link picker, and both directory pickers now behave identically: arrow navigation, hover, filtering, dismissal, scrolling, and composition input follow the same rules everywhere.
Breaking change
task move, task dep, and task delete now reject --session. Those commands address their task directly and always ignored the flag; scripts that supplied the ineffective option must remove it.
Smaller fixes
- The stale xterm viewport no longer paints a permanent scrollbar gutter in Safari and Firefox.
- Spellcheck is genuinely disabled on session names and the remaining path, provider, preference, and dependency inputs.
- A failed project refresh no longer makes a successful mutation disappear from the UI.
- Directory suggestions keep the highlighted row under the pointer while the list refreshes.
Build and test infrastructure
The Web UI’s pure state, merge, readiness, mutation, matching, and typeahead rules now run directly under Node’s built-in test runner against the shipped ES modules. There is still no package manager, bundler, node_modules, or browser runtime dependency; Node 24 or newer is required only for ./kotlin test.
Kotlin Toolchain 0.12.0 links custom cinterop into test binaries, which earlier versions did not. The ptycheck fixture binary is therefore gone: the real-PTY checks are now ordinary tests under test/pty/, including a new one that proves a tmux attach runs on the spawned pseudo-terminal. The webuicheck --self-check flag is gone for the same reason, replaced by native tests in the module. Only the browser tier still needs a built binary, so ./kotlin build before ./kotlin test remains the rule.
The build also enables the return-value and name-based destructuring checkers. Contributors can install a working-tree build locally with install-local.sh without cutting a release.
Full changelog: v0.8.0...v0.9.0
Upgrade
brew update
brew upgrade kotgent
kotgent installkotgent install is required: the launchd plist contains a version-qualified Cellar path and otherwise continues starting the previous binary.
Kotgent 0.8.0 — project delete and restore, and a board drag that shows the drop
Delete a project without losing it
A project used to be permanent. Creating one in the wrong checkout, or keeping one for a repository you
stopped working on, left it in every selector forever, because the only alternative was deleting rows that
carried real history.
kotgent project delete <uuid> — or Delete project in the Web UI's command palette — writes a
tombstone instead. The project leaves every selector and stops being a source of new work: task add and
task next refuse in its directory, and a kotgent start there is left unstamped. Everything else is kept
exactly as it was — the tasks, their order, their dependencies, their comments, and the sessions linked to
them.
.kotgent.json is never touched. That is deliberate, and it is why the delete needs no confirmation beyond
the dialog: the file stays committed, the directory keeps resolving to that project, and nothing on disk is
destroyed. Delete or move the file yourself if you want the directory to stop resolving.
Coming back is symmetric. kotgent project restore <uuid> brings the project and its whole backlog back, and
so does simply adopting the directory again with kotgent project init. kotgent project list --archived
finds the uuid of a checkout that no longer exists.
The tombstone is enforced at every layer rather than filtered in one, because a delete races everything that
was already in flight:
- Task creation into a deleted project is refused, not silently resurrected — including through a
deleted ancestor project, which a nested checkout could otherwise walk up to. task nextrefuses when the project is deleted mid-request rather than handing out work from a backlog
that is no longer live.- A tombstone that arrives from the daemon while a browser is mid-write is honoured all the way to the
board, rather than being overwritten by the response to a request that started earlier.
New routes: DELETE /api/v1/projects/{id} and its restore counterpart, with archived on the project DTO.
The board drag now shows where the card lands
The gesture already resolved the destination column and the insertion neighbour on every pointermove — and
drew neither. The column got a tint, the card that was about to move stayed put, and the landing position was
computed and never shown.
Now the other cards part to open the gap, a dashed slot marks the insertion point, and the dragged card
follows the pointer.
The invariant that makes it work is that layout never changes for the duration of a drag. Every movement
is a transform, the dragged card keeps its slot as an invisible in-flow placeholder, and the lifted copy is
position: fixed so the column's overflow cannot clip it on the way to another column. Column ownership is a
separate question from geometry and is answered by paint order through elementFromPoint, so an overlay such
as the open task panel cannot be dragged through.
Three things the drag did wrong are fixed with it:
- It swept a text selection across everything the pointer crossed.
pointermovecancelled its default
and the selection happened anyway, which puts the cause earlier: the compatibilitymousedownthat
pointerdowngenerates had already begun it. Cancellingpointerdownsuppresses that gesture and leaves
clickandcontextmenualone. - Columns never scrolled while dragging, so a drop position outside the visible slice was unreachable. A
frame loop now scrolls the column the pointer is actually over, at a velocity in pixels per second scaled
by elapsed time — a per-frame delta silently doubles on a 120 Hz display. - The lifted copy was reachable by keyboard.
aria-hiddenremoved it from the accessibility tree but
left its duplicated links in the tab order, so focus could land on a link assistive technology had been
told not to describe.inertdoes both halves.
The gesture is also bounded now: it ends through one idempotent abort covering pointerup, pointercancel,
a document-level lostpointercapture, an isConnected check in the frame loop, unmount, and a sidebar
toggle. Deleting the dragged task mid-drag used to strand the gesture, because the handlers lived on the
handle that went away with it.
Dialogs and requests that answer honestly
A submitted dialog that never hears back is worse than one that fails, and several paths could reach that
state.
- API requests could wait forever. Every request now carries a 60-second timeout, composed with caller
signals so unmount cancellation still wins. Uploads opt out, because a large file may legitimately take
longer. A timeout says the outcome is unconfirmed and stays retryable, rather than claiming a failure
it cannot prove. - Dismissing a busy create dialog reported nothing. Both the project and the task create paths now report
what actually happened to the submission they left behind. - A project archive refused late was dropped on the floor; it now reaches the board status.
- A failed refresh discarded a project mutation that had already succeeded.
- Foregrounding the app coalesces its project refresh instead of firing several, and archive derivations are
kept off live frames.
Versioned auth and hook routes
/api/v1/auth/{ticket,exchange,rotate} and /api/v1/hooks/* are the canonical paths. The former root paths
remain aliases, so hook scripts generated before the move keep working and nothing needs regenerating.
GET /auth stays at the root. Newly generated scripts and all bundled clients use the versioned paths.
Unlike 0.7.0's move, this one does not break older clients.
Smaller things
- Done sessions are ordered by recent activity, in both rows and groups.
- The sidebar header stays pinned above its content.
- Sidebar collapse moved off
⌘1, which belongs to tab switching, onto⌘.. - The board card's action menu is gone; the card opens the task.
- Phone board fixes: the column switcher is straight, every card stays inside its column, and the column is
sized by its cards rather than by a max-content row. - The task badge stays inside its session row.
- Unknown approval decisions decode instead of failing the read.
Full changelog: v0.7.0...v0.8.0
Upgrade
brew update
brew upgrade kotgent
kotgent installkotgent install is required, not optional. The launchd plist stores a version-qualified Cellar path, so
after an upgrade the installed service still points at the previous version's binary until the plist is
rewritten.
Kotgent 0.7.0 — the work layer, a kanban board, and a browser test tier
The work layer
kotgent tracked sessions, not work. Ten parallel sessions across three repositories carried no answer
to "what is this one for", "what is left", or "which of these is waiting for me". This release adds the
layer that answers all three: an ordered, dependency-aware backlog per project, stored locally, that a
human grooms on a kanban board and an agent inside a session reads from and writes to.
A project is a .kotgent.json at the root of a working tree — committed, so the backlog travels with the
repository. Tasks rank by gaps rather than by index, so a move is one write and only a collapsed gap
rewrites a column. Dependencies are a graph: blocked is derived from it rather than stored, an edge that
would close a ring is refused, and closing a task re-stamps everything it frees so the board hears about
it. A session can be linked to a task in two writes that need no ordering, its row and terminal header
name the task, and finishing a session with Done closes the task it was working on.
kotgent task add | list | show | next | comment | review | done | move | dep | delete covers the same
surface from the terminal and answers in JSON, so an agent can take the next eligible task without a human
typing it.
A kanban board in the Web UI
Four columns over one selected project, with drag-and-drop, a task detail that floats over the board
rather than squeezing it, an activity feed, and dependency editing. The SPA gained a real router — /s/{id}
and /tasks/{ref} are addressable, Back and Forward work, and a deep link survives a reload. The command
palette reaches the board, a new task, and this session's task; the sidebar carries the app's navigation.
Task frames ride the existing events socket and fold into the same newest-rev-wins list the sessions use,
so the board follows the daemon without a second protocol.
Breaking: the token-gated API moved under /api/v1
Every gated route now lives under /api/v1. /hooks/* and /auth* deliberately did not move: each
adapter bakes its ingress URL into a per-session shell script on disk, so moving hooks would break every
already-running session.
There is no dual-mount grace period, and three things break rather than degrade:
- An older
kotgentbinary cannot talk to a newer daemon — every control call answers404. Upgrade
both together;brew upgrade kotgentand a daemon restart. - An already-open browser tab breaks hard. Its
/eventsupgrade falls to the static catch-all and
answers404instead of401, so the sign-out recovery — which keys on401— never fires. Reload the
page. - An installed service worker outlives its pages. With every tab closed it can wake on a push while
holding the old paths, fetch a404, and show a generic notification instead of a per-session one. This
one is silent and heals only when a navigation replaces the worker.
Four Web UI bugs, and how they were found
The Web UI was guarded by ten test files that asserted the text of served JavaScript and CSS — 8195
lines and 1181 contains( calls. That tier is retired. In its place there is a browser tier: 116
Playwright-for-Java tests driving real Chromium against a new scenario harness binary, with no npm
anywhere (Playwright's Node driver ships inside the Maven artifact).
It found four defects that text could not see, all fixed here:
- Preferences never closed after a save. Its guard against a concurrent edit compared the form's
revision against submit time, and the daemon's echo of our own write beats the PUT response over
loopback every time. - The board's card drag was effectively unclaimable. Pointer capture was taken after an 8 px slop,
which a pointer cannot survive on a 12×16 px handle. - The opt-in unicode addon fetched 65 KB and changed nothing.
term.unicodeis xterm's proposed API
and its getter throws withoutallowProposedApi, and the throw landed in the install's own.catch. - The board's status toast ate clicks across the bottom of the board, so a drop there landed nowhere.
The sharpest artefact of the migration: one retired test asserted that app.js contains the literal
if (sameForm) closeDialogFrom(submittedDialog) — the exact line that was the first bug. That tier had
not failed to catch the defect; it had pinned it as a contract, and it broke when the bug was fixed.
Kotgent 0.6.0 — Junie, shell sessions, and a command palette
Junie is the third provider
junie joins claude and codex as a full agent: launched as a TUI inside tmux, reporting through
hooks into the local ingress, with the same events, approvals, resume and reconciliation as the others.
Three things it does differently are handled rather than papered over. Its hooks ride a per-launch
--config-location file, never ~/.junie/config.json, so kotgent's hooks fire only for the sessions it
starts. Its PermissionRequest hook exits 1 on purpose — an exit of 0 would auto-approve and 2
would auto-deny, and kotgent never answers an approval for you. And its session ids are not UUIDs
(session-260730-015553-1j1h), so discovery enumerates session directories by birth time instead of
waiting for the index row that only appears after the first task.
Shell sessions
A plain login shell is now a fourth agent kind, not a new session dimension: it reuses the one launch
path and all the generic tmux, terminal, control, archive and reconciliation machinery. Pick Shell in
the New-session dialog (or ⌘K t) and you get a tmux pane you can attach to from the browser, the
phone or kotgent attach.
Closing a pane is now observed rather than inferred later: a session-closed hook installed in the same
invocation that creates the session tells the daemon to re-derive that session's state from pane
liveness and the vendor probe.
Import an outside session
kotgent import — and Import mode in the Web UI's New-session dialog — brings a session you started
outside kotgent under management with zero tmux side effects. It writes a resumable row and leaves
the actual launch to the existing resume path, so there is no second launch codepath and a failed start
stays honest. The working directory is canonicalized through the filesystem before it is validated with
the same probe the reconciler re-runs on every daemon start, so an import that succeeds keeps working
across restarts. Without --cwd, the directory is discovered from the provider's own records.
Command palette and one dark theme
⌘K opens a searchable palette; a leader chord reaches the same commands by mnemonic. Rare actions moved
there out of the sidebar chrome, which is now just the notification toggle. The desktop sidebar collapses
and remembers it. The Web UI is dark-only, with a Kotlin-purple accent, pill session rows and floating
cards.
Sessions list
Sessions group into a nested folder tree by path. The list is now built from the /events socket alone —
one snapshot on connect, then per-row frames — and every session row carries a global monotonic rev, so
HTTP responses and WebSocket frames merge safely in any arrival order and the old periodic resync is
gone. Resuming a session that was marked Done un-archives it, instead of reviving the agent under a row
the sidebar still hid.
Terminal
A repaint is shipped as one frame. A TUI hides the cursor before repainting and shows it at the end, so a
read that turns a visible cursor off is now held until ?25h arrives and the whole repaint is broadcast
at once — that removes the ~3 cursor dropouts per second the previous per-read forwarding produced.
Full-screen apps that hide the cursor for their entire run are unaffected.
Phone
Swipes scroll the terminal. xterm's own touch scrolling is off while mouse tracking is on, which kotgent
needs for the desktop wheel, so a claimed one-finger vertical swipe is replayed as wheel events on
xterm's element — banked per gesture and emitted by a frame loop that coasts after the finger lifts.
Files can be uploaded into a session's working directory from the palette's f command, one at a time,
with the daemon accepting a single leaf filename and never overwriting an existing file. The iOS
standalone PWA's safe-area handling is fixed.
Assets
Static assets are invalidated by a content revision in the path (/_v/<rev>/) instead of a hand-bumped
?v= query. Because an ES module specifier resolves against the importing module's URL, one substitution
in index.html reaches the entire import graph — the old token versioned 3 files out of 34. A valid
revision means the bytes can never change, so those responses are immutable; everything else, including
index.html and sw.js, revalidates.
Full changelog: v0.5.0...v0.6.0
Upgrade
brew update
brew upgrade kotgent
kotgent installRe-run kotgent install: the launchd plist stores a version-qualified Cellar path, so without it launchd
keeps starting the previous binary.
Kotgent 0.5.0 — agent icon picker and restart-safe terminal reattach
New session dialog
The agent <select> is now a picker of four cards carrying each vendor's own mark. It opens with no
default: the choice is the dialog's first focus stop, and submitting without one reports why instead of
leaving a dead button — the previous build silently ignored Enter. Junie and Cursor appear as
planned agents; their cards are disabled and stay out of the tab order until an adapter exists.
Mobile terminal survives a daemon restart
A terminal socket lost to suspension or to the daemon going away is now reattached from the events
socket's reconnect — the only signal the daemon came back — as well as on foregrounding. A definitive
answer about the session (a 4xx, or the operator picking a different one) ends the attempt for good,
while an unreachable daemon or an in-flight control action keeps it retryable, so a restart recovers on
reconnect instead of waiting out a visibility change. The terminal's last row is no longer clipped.
Preferences live in the daemon
Base path and grouping level are stored server-side and broadcast over /events, so every connected
browser and phone agrees. Terminal font size stays per-device.
Build
./kotlin do kexePath / releaseKexePath report the built executable's path, which is what the release
workflow now uses to locate the binary it packages. Dependencies updated; Dependabot added.
Full changelog: v0.4.0...v0.5.0
Upgrade
brew update
brew upgrade kotgent
kotgent installRe-run kotgent install: the launchd plist stores a version-qualified Cellar path, so without it launchd
keeps starting the previous binary.
Kotgent 0.4.0 — Mobile PWA and Web Push
Kotgent 0.4.0 makes the Web UI substantially better on phones and adds server-driven notifications when an agent needs attention.
Highlights
- Installable mobile PWA with a service worker, app icons, safe-area-aware layout, responsive drawer navigation, compact controls, keyboard-aware terminal sizing, a special-keys bar, and terminal reattachment after backgrounding.
- Web Push subscriptions and attention notifications backed by persisted subscriptions, VAPID signing, per-session notification topics, deep links, and resilient delivery/lifecycle handling.
- Easier phone sign-in with short, five-minute login codes alongside QR links, plus global rate limiting for ticket exchange.
- More reliable unread/session updates and quieter daemon logs for expected WebSocket connection resets.
Upgrade
brew update
brew upgrade kotgent
kotgent installRun kotgent install again after upgrading. The launchd plist stores a version-qualified Homebrew Cellar path, so reinstalling it is required to point the daemon at Kotgent 0.4.0.
Full changelog: v0.3.0...v0.4.0
Kotgent 0.3.0 — directory autocomplete and tmux help
Highlights
- Autocomplete working directories in the New session dialog, with mouse and keyboard navigation over directories on the Kotgent host.
- Find practical tmux scrolling, copy-mode, and browser-copy instructions directly in the Web UI help.
- Keep PTY shutdown grace periods bounded by a monotonic deadline, avoiding teardown stalls when macOS sleeps longer than requested.
See the full comparison: v0.2.0...v0.3.0
Upgrade
brew update
brew upgrade kotgent
kotgent installRun kotgent install after upgrading. It reinstalls the launchd plist, which stores a version-qualified Homebrew Cellar path.
v0.2.0
Highlights
- Isolated tmux runtime. Kotgent now ignores the user's tmux configuration for its private server and applies a known set of server options, keeping mouse handling, copy mode, attach behavior, and automation predictable.
- Persistent unread state. Per-session read cursors survive daemon restarts, drive unread badges in the Web UI, and synchronize badge clearing across connected clients.
- Better session ergonomics. Directory groups are collapsible, terminal headers can copy the attach command, and the sidebar shows the running Kotgent version.
- More reliable lifecycle handling. Clean
SIGINT/SIGTERMshutdown, immediate daemon restart, quieter first-run SQLite initialization, and hardened PTY/tmux input and teardown paths. - Project polish. Documentation was refreshed and the project now ships under Apache-2.0.
Upgrade
brew update
brew upgrade heapy/tap/kotgentThis release currently ships a macOS Apple Silicon (macos-arm64) binary.
Full changelog: v0.1.2...v0.2.0
v0.1.2
kotgent 0.1.2 — a terminal is the right size the moment you attach (S…