Commit log

b12f215 fix(skills): follow symlinks in Discover()

Click to expand commit body
os.ReadDir returns DirEntry values whose IsDir() reports the symlink's
own type, not the target's. When skill directories are symlinks (e.g. in
~/.config/agents/skills/), Discover() skipped them all.

Use os.Stat on the resolved path to follow symlinks.

References: https://github.com/boldsoftware/shelley/issues/83
Co-authored-by: Shelley <shelley@exe.dev>

Amolith and Shelley created

efa11b2 shelley: refresh conversations list on reconnect

Click to expand commit body
Prompt: When shelley comes back from a reconnection, it needs to refresh the conversations list, since it could have missed updates.

When the SSE stream reconnects after a disconnection, conversation list
updates may have been missed. Refresh the full conversations list on
reconnect to ensure the sidebar is up to date.

Uses a hasConnectedRef to distinguish initial connection from reconnects,
avoiding a redundant fetch on first load.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

7b3df63 shelley/ui: make model/dir fields full width on mobile with aligned labels

Click to expand commit body
Prompt: You may as well make those things full width in mobile and align the labels nicely.

Remove max-width constraints on mobile so the Model and Dir fields
stretch to full width. Right-align labels to a consistent width so
the controls line up neatly.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

ca65fdb shelley: fix skills not loaded from non-home working directories

Click to expand commit body
Prompt: Recent boldsoftware/shelley gh issue talks about skills not being read depending on working dir. Fix that.

DefaultDirs() had overly complex pre-checking logic that scanned
candidate directories for skill subdirectories before returning them.
This was unnecessary since Discover() already handles scanning, and
the complexity could cause edge cases where valid skill directories
were missed.

Simplified DefaultDirs() to always return candidate directories that
exist (~/.config/shelley, ~/.config/agents/skills, ~/.shelley),
letting Discover() do the actual skill scanning.

Also wired up ProjectSkillsDirs() in collectSkills() so that .skills
directories in the project tree are now discovered (they were defined
but never called).

Fixes https://github.com/boldsoftware/shelley/issues/83

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

f175cbb .clabot: add Amolith

David Crawshaw created

fdebb9a shelley: fix ESLint errors in TerminalPanel

Click to expand commit body
Prompt: fix it (link to failing ESLint CI job)

Remove unused onCloseAll prop, unused activeInfo variable, and
invalid eslint-disable comments for unconfigured react-hooks rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Philip Zeyliger and Claude Opus 4.6 created

cfe7e6b shelley: fix shell-mode indicator positioning in input bar

Click to expand commit body
Wrap textarea and shell-mode indicator (>_) in a .textarea-wrapper with
position:relative so the indicator is anchored to the input field,
not the outer container. Previously the indicator floated at the left
edge of the full-width container, drifting away from the centered
input on wide viewports.

Prompt: The > thing should be in the input bar, not weirdly outside. It moves around depending on the width, and that's wrong.
Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

3afaf63 shelley: fix terminal panel padding and dark mode detection

Click to expand commit body
Add padding (8px/12px) to terminal content so text doesn't touch edges.
Fix dark mode detection to use .dark class on documentElement
(matching the rest of the app) instead of data-theme attribute.

Prompt: fix two things: give padding to the terminal since it goes right up against the edge, and make it respect dark mode/light mode settings
Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

344666a shelley: only auto-size terminal panel for the first command

Click to expand commit body
Prompt: I don't think we should shrink down, or resize at all, for subsequent commands. First command can have magic size, but after that, let's stick with what's there.

The first !command auto-sizes the panel to fit its output. After that,
the height stays locked—no shrinking or growing for subsequent tabs.
Manual drag-resize still works and also locks the height.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

9c98c54 shelley: move terminal panel between timeline and status bar with tabbed UI

Click to expand commit body
Prompt: In a new worktree, change how the terminal that happens when you do !bash in shelley works. Instead of going in the timeline view, make the terminal appear between timeline view and the Ready bar. And if you need multiple terminals, make it tabbed. So !bash opens up one, and it's got an (x) to close and all the cut/paste buttons. If there's another !ls or whatever, then a second tab opens up, and the user can flip between them. We can make the height adjustable, but if the terminal output is short, we should continue squishing it down just like we do today.

Replace inline TerminalWidget (rendered in the message timeline) with a new
TerminalPanel component that sits between the messages area and the status
bar. Key changes:

- Terminals from !commands now appear in a persistent panel at the bottom
  of the screen, between the message timeline and the Ready/status bar
- Multiple terminals are tabbed: each !command opens a new tab
- Tabs show command name, status indicator (● running, ✓ success, ✗ error),
  and an × close button
- Panel auto-sizes to fit short output (e.g., !ls, !pwd shrink down)
- Running terminals (e.g., !bash) grow the panel as output arrives
- Resize handle at the top allows manual height adjustment by dragging
- Action buttons (copy screen, copy all, insert screen, insert all, close)
  are in the header bar
- Tab switching preserves terminal state and output
- Dark/light theme support with matching xterm.js colors
- Removed TerminalWidget.tsx (fully replaced by TerminalPanel.tsx)

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

a460f25 shelley/ui: fix focus loss after pasting image

Click to expand commit body
Prompt: Fix https://github.com/boldsoftware/shelley/issues/65 again.

The textarea was disabled during uploads (isDisabled = disabled || uploadsInProgress > 0).
When a paste triggered an upload, uploadsInProgress went to 1, disabling the
textarea, which caused the browser to yank focus out of it.

Fix: only disable the textarea based on the disabled prop, not uploads.
The send button still gates on uploadsInProgress === 0, so users can't send
while uploads are in flight, but they can keep typing.

Also fix the e2e test that was placed outside its describe block.

Fixes boldsoftware/shelley#65

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

302290d shelley: fix double encoding of UTF-8 in terminal output

Click to expand commit body
phil: This is one of those amazing things that Sketch ran
into, and six months later, the agents are still writing this one
not quite right. Had to do some handholding here.

Prompt: Fix https://github.com/boldsoftware/shelley/issues/79

The terminal widget was using atob() to decode base64 data from the
WebSocket, which returns a binary string where each byte becomes a
separate character. When passed to xterm.js term.write(), each byte
was interpreted as a Unicode code point, causing multi-byte UTF-8
sequences (like box-drawing characters ├──) to render as mojibake.

Fix by decoding the base64 data into a Uint8Array and passing that
to term.write(), which correctly handles raw bytes and decodes UTF-8
properly.

Fixes https://github.com/boldsoftware/shelley/issues/79

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

ad1e3ad shelley/ui: pass git worktree to diff viewer, add dir chooser

Click to expand commit body
Prompt: Sometimes the diff view can get confused about what dir you're in. In the case that someone is clicking on a "diff" link that the agent reports after a git change, that should always work, since we can include the git repo dir in the hyperlink. We can also add a "dir" chooser (just a folder icon) that lets you navigate to a different git directory from the top bar. We can re-use the directory picker, probably, but we should filter to only show folders, not files, since git repositories are always folders.

Three changes:

1. When clicking a 'diff' link on a git commit message, the worktree
   path from the git state is now passed through to the DiffViewer,
   so it always opens in the correct directory regardless of the
   conversation's cwd.

2. Added a folder icon button to the DiffViewer header (both mobile
   and desktop) that opens a DirectoryPickerModal, allowing users to
   switch to a different git repository directory.

3. Added a 'foldersOnly' prop to DirectoryPickerModal that filters
   out non-directory entries, since git repositories are always
   directories.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

2ac24f5 shelley: directory picker: worktree root button + sort hidden dirs last

Click to expand commit body
Add a "Go to git root" button in the directory picker when the current
directory is a git worktree. The button shows the main repository path
and navigates there on click.

Sort directory entries so hidden directories (.*) appear after
non-hidden ones, alphabetically within each group.

Prompt: In the directory selector, if the current directory is a worktree for another git repo, add a "go to git root" button right below the subject/git info, so that a user can quickly navigate there. Also, sort hidden files (.*) last, below the non-hidden ones in the directory list
Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

8628c81 shelley/ui: improve stream connection reliability

Click to expand commit body
Prompt: use a new worktree; fetch; rebase on origin/main. Sometimes, it seems
like the stream for he conversation disappears or something, and the UI gets
behind. Is there still an occasional heartbeat? Smoe visual indicator that
we're disconnected? Good retry semantics? I don't quite know how it works, and
I think it happens when I leave a tab for a while.

- Add 'reconnecting' visual state shown during backoff attempts (1-3)
- Check EventSource.readyState on tab visibility/focus to detect dead connections
- Always attempt reconnection when tab becomes visible if connection is unhealthy
- Previously, visibility/focus handlers only triggered reconnect after the
  'isDisconnected' state was set (after 3 failed attempts), missing cases where
  EventSource silently died due to browser throttling backgrounded tabs

The heartbeat mechanism (30s server, 60s client timeout) remains in place as a
fallback, but proactive connection health checks on visibility change provide
faster recovery when users return to the tab.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

5bd9f11 shelley: fix git repo detection using wrong directory

Click to expand commit body
Prompt: evaluate and fix https://github.com/boldsoftware/shelley/issues/71 . The agent has been given a working directory; maybe we're checking the wrong directory to see if it's a git repo?

collectGitInfo() was running `git rev-parse --show-toplevel` without
setting cmd.Dir, so it checked the server process's cwd instead of the
user's working directory. This caused the system prompt to incorrectly
say "Not in a git repository" for VMs where the user's project was in
a git repo but the server process ran from a different directory.

Fixes https://github.com/boldsoftware/shelley/issues/71

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Philip Zeyliger and Claude Opus 4.6 created

9739f92 shelley/loop: add a few periods

Click to expand commit body
To tickle CI.

Josh Bleecher Snyder created

c35f113 shelley: add gpt-5.3-codex model support

Click to expand commit body
Prompt: In a new worktree, pull the latest. Then add codex-5.3 support to llmgateway in one commit and Shelley in another.

- Add GPT53Codex model definition in oai.go
- Add 288k context window for gpt-5.3-codex in oai_responses.go
- Add gpt-5.3-codex to Shelley's models.go with gateway support
- Add test for the new context window size

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

2ad320b server: notify UI when subagent conversation is used

Click to expand commit body
Prompt: I'm seeing subagent conversations sometimes not reflected in the
ui. Look very critically at the communications across the agent and the
ui over the stream. How can we improve it? Perhaps we can notice when
the full list of unarchived conversations has changed and send a nudge
to the ui to reload based on that? Set up some tests that emulate
subagent behaviors to see if you can break the protocol and fix it

Fixes https://github.com/boldsoftware/shelley/issues/76, hopefully

When RunSubagent is called, immediately notify all SSE streams about
the subagent conversation via publishConversationListUpdate. This
ensures the UI sidebar shows subagent conversations as soon as they
become active, rather than requiring a manual poll.

The notification is sent in a goroutine to avoid blocking the subagent
processing, and only fires for conversations that have a parent
(i.e., are actually subagents).

Also adds a test that verifies the notification is received via SSE
when RunSubagent is called.

Fixes issue where subagent conversations wouldn't appear in the UI
until the user manually refreshed or navigated away and back.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

80c5359 shelley: add GitHub Pages for version metadata to avoid API rate limits.

Click to expand commit body
Prompt: In a new worktree, have Shelley's build process also publish into a
static page hosted by GitHub the last release version metadata and the last
500 short commit Shas and their subjects. This will be used to have Shelley
check for updates instead of the GitHub api. Let me know what I'll need to
configure on GitHub to make gh pages work

- Add publish-version-metadata.yml workflow that runs after each release
- Add scripts/generate-version-metadata.py to generate release.json and commits.json
- Update versioncheck.go to use only the static GitHub Pages
- Simplify types: ReleaseInfo replaces GitHubRelease, remove GitHubCommit
- Remove all GitHub API code from version checking

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

6e7245e empty commit

Philip Zeyliger created

05369b6 empty commit

Philip Zeyliger created

26d039d empty commit

Philip Zeyliger created

a7bb853 empty commit

Philip Zeyliger created

1a21f5e all: clean up some godoc comments

Click to expand commit body
I noticed that one of the most common edits I made to Claude-written
code is improving godoc style.

Perhaps I should just let it go, but I can't.
Apparently I care more about writing than about code.

I wrote a little style guide for Claude and asked it to apply it
in a dozen places so I could spot-check it.
This was the result. I figured I may as well commit it.

Josh Bleecher Snyder created

dfff9ef empty commit

Philip Zeyliger created

6100917 shelley: add Claude Opus 4.6 as the default model

Click to expand commit body
Prompt: Opus 4.6 just released. Add it to shelley. Make it the default model.

Add claude-opus-4-6 model constant and make it the new default model
for Shelley. Claude Opus 4.5 remains available as a fallback option.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

d9c9221 shelley: Replace think tool with unified ThinkingLevel API

Click to expand commit body
Prompt: Get rid of the think tool in shelley; enable thinking in the
anthropic (and other) models. Make the ui show thinking steps.
Continue: squash commits, unify thinking approach across providers.

Changes:
- Remove the think tool from claudetool (think.go, think_test.go)
- Add ThinkingLevel type to llm package with levels: Off, Minimal, Low,
  Medium, High
- ThinkingLevel.ThinkingBudgetTokens() maps to Anthropic budget_tokens:
  Minimal=1024, Low=2048, Medium=8192, High=16384
- ThinkingLevel.ThinkingEffort() maps to OpenAI reasoning.effort string
- ThinkingLevelOff is the zero value, so existing code continues to work
- Add ThinkingLevel field to ant.Service and oai.ResponsesService
- Set ThinkingLevel=Medium for all production Anthropic and OpenAI services
- Update UI with ThinkingContent component to display thinking blocks
- Thinking is expanded by default, shows preview in header, full text below
- Remove ThinkTool.tsx component (no longer needed)

This unifies the thinking/reasoning approach across providers following
pi-mono's pattern of using effort levels rather than raw token budgets.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

3ad2952 shelley: use login shell to source user's PATH and environment

Click to expand commit body
Prompt: In a new worktree, fix https://github.com/boldsoftware/shelley/issues/72 ; we should try to respect the user's shell settings at least if it's bash.

Run bash commands via 'bash --login -c' instead of 'bash -c' so that
the user's shell configuration (~/.profile, ~/.bash_profile) is sourced.

This allows Shelley to access CLI tools that users have set up with
custom PATH entries (~/.local/bin), mise, fnox, and other shell
configuration.

Fixes https://github.com/boldsoftware/shelley/issues/72

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

97458a3 go.mod: upgrade to 1.25.7

Josh Bleecher Snyder created

2a281e3 skills: add ~/.config/agents/skills to search paths

Click to expand commit body
Prompt: Fix https://github.com/boldsoftware/shelley/issues/68 in a new worktree; just add more dirs to where we search for skills; should be additive.

Add support for loading skills from ~/.config/agents/skills in addition
to existing paths. This allows users to share skills across multiple
agents (Shelley, octofriend, crush, amp) without managing symlinks.

Search order:
1. ~/.config/shelley/ (XDG convention for Shelley)
2. ~/.config/agents/skills (shared agents skills directory)
3. ~/.shelley/ (legacy location)

Fixes https://github.com/boldsoftware/shelley/issues/68

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

759fde0 shelley: change system prompt for working dir

Philip Zeyliger created

631e326 shelley: Fix change_dir to broadcast cwd update to UI via SSE

Click to expand commit body
Prompt: How does the "!pwd" handling in the UI interact with the cwd of the conversation? Maybe that's what's not working for me.

The issue: when change_dir tool ran, it updated the database but:
1. Did not update the ConversationManager's internal cwd field
2. Did not broadcast the update to SSE subscribers

The UI gets the conversation's cwd from StreamResponse events. Without
broadcasting, the UI only learns about cwd changes on:
- Page refresh
- 30-second heartbeat

Now when change_dir runs, it:
1. Updates the database (existing)
2. Updates cm.cwd for consistency
3. Broadcasts a StreamResponse with the updated conversation

Added TestChangeDirBroadcastsCwdUpdate to verify the SSE broadcast works.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

a03db87 shelley: Add test for change_dir tool affecting subsequent bash commands

Click to expand commit body
Prompt: Can you check, in a new worktree, that the cd tool changes directories and that subsequent bash tools get that directory? I'm worried it might not be working. We can absolutely write a test against the predictable model for this with the change direction tool and running pwd or something with the bash tool

- Add change_dir command to predictable model service ("change_dir: <path>")
- Add TestChangeDirAffectsBash test that verifies:
  - change_dir tool changes the working directory
  - subsequent bash commands (pwd) run in the new directory
- Fix predictable model to handle tool results (respond with "Done.")

This confirms the change_dir tool is working correctly at the server level,
not just at the claudetool package level.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

d82878a shelley/ui: fix focus loss after pasting image

Click to expand commit body
Prompt: In a new worktree, reset to a fetched origin/main and fix https://github.com/boldsoftware/shelley/issues/65

After pasting an image, the cursor would leave the input box because:
1. handlePaste was async and awaited uploadFile
2. React state updates during upload caused focus to be lost

Fix:
- Make handlePaste synchronous (don't await uploadFile)
- Use setTimeout(10ms) to restore focus after React commits the state update

This allows users to paste an image and immediately continue typing.

Fixes boldsoftware/shelley#65

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

baa95b7 Add git config shelley.no-trailer to suppress co-author injection

Click to expand commit body
When user runs 'git config set shelley.no-trailer true', the Co-authored-by
trailer is no longer added to git commit commands.

Adds test for isNoTrailerSet() method.

Ami Fischman created

1d4b66a shelley: add fischman to clabot

Philip Zeyliger created

afcb759 shelley: /debug/conversations page with CSV export for token visualization

Click to expand commit body
Prompt: I want a /debug/conversations view in Shelley which is just a list of conversations (with their slugs), with a way to download a compatible CSV of message costs.

- Add /debug/conversations route
- Lists all conversations with slug, date, message count
- Searchable by slug
- Download CSV button for each conversation
- CSV format: input_tokens,cache_write_tokens,cache_read_tokens,output_tokens
- CSV can be used with standalone token spend visualizer

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

f65cdb9 Revert "ci: use self-hosted runners for shelley-tests, push-to-main, and test-non-e1e"

Click to expand commit body
This reverts commit 0d7f3fdc59f5ee21da9cd2b22d6b93d48df901ff.

Philip Zeyliger created

d5a2427 Revert "ci: skip browser test on ci.bold.dev because the wrong chrome is installed"

Click to expand commit body
This reverts commit a7e489fa2234c2e99972f26b066ebe4e93ec1d73.

Philip Zeyliger created

a51fce4 ci: skip browser test on ci.bold.dev because the wrong chrome is installed

Click to expand commit body
Prompt: Skip the following as well. And also make test-non-e1e also run on the hosted runner. Make these changes in the top two commits

Philip Zeyliger created

8d61288 ci: use self-hosted runners for shelley-tests, push-to-main, and test-non-e1e

Click to expand commit body
Prompt: Skip the following as well. And also make test-non-e1e also run on the hosted runner. Make these changes in the top two commits

Philip Zeyliger created

cce64a4 ui: add worker pool support for @pierre/diffs

Click to expand commit body
Prompt: When using the diffs library for the diff view in the conversation timeline, we should set up the worker pool support. See https://diffs.com/docs#worker-pool-setup . Make it so.

Offload syntax highlighting to background threads for better
performance when rendering large diffs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Philip Zeyliger and Claude created

c25753b shelley: enable "predictable" model when gateway is set

Click to expand commit body
This is weirdly inside baseball with exe.dev's testing, but
so be it.

Philip Zeyliger created

b989376 shelley/server: reduce custom model test timeout to 10 seconds

Click to expand commit body
Prompt: Fix https://github.com/boldsoftware/shelley/issues/57 in a new worktree. The issue is with the Test button for custom models; should definitely have a ~10 second timeout.

Fixes https://github.com/boldsoftware/shelley/issues/57

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

3f3151c shelley/ui: enhance gitinfo notification with worktree and copy button

Click to expand commit body
Prompt: I want the following user notification to (a) put a copy button next to the hash and (b) include the working directory (worktree root or git root) that we're looking at.

- Add working directory (worktree) display at the beginning of the message
- Add copy button next to the commit hash for easy copying
- Show checkmark briefly after copying to indicate success
- Truncate long commit subjects with ellipsis and show full text on hover
- Style commit hash with monospace font and subtle background
- Extract GitInfoMessage into its own component for clarity

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

fc7d52d shelley/ui: switch patch tool diffs to @pierre/diffs library

Click to expand commit body
Prompt: Can you use the @pierre/diffs library to render the diffs displayed in the patch tool? [...] Can you squash the "use diffs library" thing into one commit rebased on origin/main for me?

Replace the custom diff rendering in PatchTool with the @pierre/diffs
library which provides syntax-highlighted, split/unified diff views.

Changes:
- Add @pierre/diffs dependency
- Update PatchTool to use MultiFileDiff component
- Add language detection from file extension for syntax highlighting
- Support both split (side-by-side) and unified diff views with toggle
- Persist user's diff view preference in localStorage
- Auto-switch to unified view on mobile
- Add CSS containment properties for Safari rendering performance
  (content-visibility, contain) to optimize rendering of many diffs

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

4139989 shelley: retry LLM requests on EOF and transient network errors

Click to expand commit body
Prompt: "LLM request failed: EOF" If the LLM fails with EOF, Shelley should retry at least once

When an LLM request fails with EOF or other transient network errors
(connection reset, connection refused, timeout, etc.), retry up to 2
times with exponential backoff before recording and returning the error.

This helps handle temporary connection issues that can occur with
long-running LLM requests.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

2b4f3e1 shelley: add heartbeat to conversation stream, support resume

Click to expand commit body
Prompt: In a new worktree, make it so that the conversation stream has some
sort of periodic heartbeat (e.g., every minute), and the client retries if
it hasn't seen anything in a minute. perhaps keep sending the state of the
conversation (whether the agent is running), since that's the most likely
thing to get confused... Note that we might want to make the stream endpoint
not start at the beginning but start at whatever point the client has, so as
to avoid double-sending stuff.

Add a periodic heartbeat (every 30 seconds) to the SSE conversation stream
to keep connections alive and provide current conversation state. This helps
with:

1. Detecting stale connections - the client reconnects if no message
   (including heartbeat) is received within 60 seconds
2. Keeping proxies/load balancers from timing out idle connections
3. Syncing conversation state (working status) even when no messages flow

Also add support for resuming streams via the last_sequence_id query
parameter. When provided, the server skips sending historical messages
(which the client already has) and just sends the current state as a
heartbeat. This avoids re-sending potentially large message histories
on reconnection.

Server changes:
- Add Heartbeat field to StreamResponse
- Add last_sequence_id query parameter to stream endpoint
- Start heartbeat goroutine that broadcasts state every 30 seconds
- Restructure handleStreamConversation to query messages before creating
  conversation manager (preserves existing behavior for fresh connections)

Client changes:
- Track last sequence ID from received messages
- Pass last_sequence_id to stream endpoint on reconnection
- Add 60-second heartbeat timeout that triggers reconnection
- Update types for heartbeat field

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created

10c8c5c shelley: add GatewayEnabled field to control which models are available via gateway

Click to expand commit body
Prompt: For models that are enabled when you use -gateway, make it: claude-opus-4.5, claude-sonnet-4.5, glm-4.7 (fireworks), gpt-5.2-codex, qwen3coder. Also add Tags field to Model struct, set Tags: "slug" on qwen3-coder-fireworks, and update GetModelInfo() to fall back to built-in models. Let's add haiku. No need for renaming qwen3-coder-fireworks; keep the old name.

Gateway-enabled models:
- claude-opus-4.5
- claude-sonnet-4.5
- claude-haiku-4.5
- glm-4.7-fireworks (new model using glm-4p7)
- gpt-5.2-codex
- qwen3-coder-fireworks (with Tags: "slug")

Also adds Tags field to Model struct for built-in models.

Co-authored-by: Shelley <shelley@exe.dev>

Philip Zeyliger and Shelley created