Repository navigation
Added
-
Agent chat in
xpo board: talk to your local Claude Code or Codex about the project, and let it implement issues.- Opening it: the Agent button in the status bar (⌘J / Ctrl+J) opens a chat popup from any view, centered above its pill. The popup is resizable from its top edge, sides and top corners, from 360×400 up to 600×900, and remembers its size. Minimized chats stay as pills in the status bar.
- Agent view: Agent is the first entry in the personal section of the sidebar (
g a). It opens a full-page chat at#/agent, and each chat has its own URL (#/agent/<id>). The new-chat page asks "What should we build in ?" and shows suggestion cards and the 5 most recent chats. The header has a chat switcher and a "⋯" menu to rename or delete a chat. The popup's expand button opens its chat in the Agent view and removes its pill. A dock button puts it back as a popup and returns to the view you were on. - Agents: the board detects which of
claudeandcodexare installed.chat.agentsets the default, and an unknown or uninstalled agent logs a warning and falls back to the first installed one. With 2+ agents, the composer shows an agent picker, which locks once a chat has messages. Each chat keeps one agent process across turns, in the project root with your settings. Codex runs throughcodex app-server, and its models and effort levels come frommodel/listat board start. - Composer: one footer row with the model, effort and mode:
✳ Claude Opus 5.5 ⌄ | Medium · 1M ⌄ | Chat. One model menu lists every installed agent's models, grouped by agent. Models show their full names, learned from the model each turn ran. Medium effort is pre-selected, and the trigger shows the model's context window once known. A chat restores the agent, model and effort of its last message, and your last pick is remembered for new chats. - Issue context: opening an issue adds it to the composer as a chip, and the next message carries it. Sending clears the chip, and a removed chip stays removed until you open an issue again.
- Drafts: each chat keeps its own composer draft, text and attachments, also when it moves between the popup and the Agent view. The popup's new chat and the Agent view's new chat have separate drafts. Drafts last until a reload.
- Keyboard: in the Agent view, Esc leaves the composer and keeps the draft, so global shortcuts work, and
/focuses it again. In the popup, Esc minimizes. - Look: the popup, the composer, cards, message bubbles and their menus use their own monochrome surface scale (
--color-*-ai-*tokens,.surface-ai), a step above the app's. - Storage: chats are stored per user in
.xpo/chats/(git-ignored). - API:
/api/local/agentlists every agent with its models and efforts./api/local/chats/*holds the chats, withPATCH(rename) andDELETEon/api/local/chats/{id}.POST /messagestakesproviderand returns 409 for a chat of another agent. - Config:
chat.agentandchat.model. The stream-json parser is shared by drive and chat.
(xpo-56ae3a, xpo-c047ce, xpo-5b3157, xpo-74390c, xpo-fb4f9b, xpo-b45049, xpo-b204f1, xpo-c94d85, xpo-4e78ba, xpo-de9f71, xpo-127a26, xpo-296ea8, xpo-03fa37)
-
Agent chat: Chat Mode and Work Mode in one chat. A chat can implement an issue in its worktree, and the conversation carries over.
- Chat Mode: the default. The agent reads the repo and changes issues.
mergeis denied, andstartasks first: "Enter Work Mode on xpo-…?". After you allow it, the chat continues in Work Mode on its own. - Work Mode: the agent runs in the issue's worktree. Read, Edit, Write and Bash run there without a card, and so do edits to the issue's own
.xpo/artifacts/<issue>/folder. Other edits outside the worktree, andmerge, ask first. After a successfulmerge, the chat goes back to Chat Mode. - Start Work: opens a new chat in Work Mode, titled with the issue title. The first message is prefilled but not sent. If a chat is already working on the issue, Start Work opens it.
- Continue Work: on an in-progress issue, opens its Work Mode chat, or creates one on the existing worktree. It is a secondary button while a chat is in Work Mode on the issue.
- Composer: the mode segment reads "Chat", or shows the hammer and the issue ID, with "Go to issue" and "Exit work mode". Exiting keeps the worktree, and the issue stays in progress.
- Transcript: "Entered Work Mode on xpo-…" and "Entered Chat Mode" dividers mark each switch.
- Skills: the agent loads skills without an approval card. Its prompt tells it to load the
xposkill before it plans, files issues or changes anything, so Work Mode follows the project's workflow. - Codex: read-only sandbox in Chat Mode, workspace-write in Work Mode, with approval policy
on-request. - API:
POST /api/local/issues/{id}/work-chat(optional{"chat_id"}),POST /api/local/chats/{id}/leave-work. Chats includemode,workandworked_issues, and aturn_continuedevent opens a continued turn.
(xpo-00f0e5, xpo-219de9, xpo-ad6069, xpo-374792, xpo-2c724e, xpo-a058b4)
- Chat Mode: the default. The agent reads the repo and changes issues.
-
Agent chat: approval cards. The agent asks before a tool call runs, and the chat shows a card.
- Layout: the headline ("Merge upe-1 via
merge"), the call's arguments as key/value rows in a monospace panel, then[Deny] [Allow once ▾]. "Allow for this chat" is in the chevron's menu. Cards with arguments open collapsed on one row; click the title to expand them. - Keyboard: the waiting card takes focus, unless you are typing in a field. Enter allows once and Esc denies. Focus moves to the next card, then back to the composer.
- Allow for this chat: xpo keeps the grant with the chat. It never writes to Claude Code's settings files.
- States: cancelling a turn resolves its cards as "Cancelled", and a card whose turn ended shows "Expired".
- Agents: Claude Code turns use
--permission-prompt-tool stdio. Codex command, file-change and MCP-tool approval requests go to the same cards. - API:
POST /api/local/chats/{id}/approvals/{approval_id}with{decision}. Chat lists includewaiting.
(xpo-7bc86e, xpo-aaf8b1, xpo-8579fb)
- Layout: the headline ("Merge upe-1 via
-
Agent chat: Claude can ask you questions with
AskUserQuestion, in both modes. The question card asks one question at a time.- Answering: pick one option or several, or type into "Other…". A single-choice pick moves on after 250 ms. With focus in the card, ←/→ switch steps, 1–9 pick an option, and Enter presses Next or "Submit answers". The card takes focus when it appears, unless you are typing in a field.
- Several questions: tabs list the questions (✓ when answered), and a Review tab shows each answer, or "Not answered", with a "Change" button. "Submit answers" sends from there.
- Chat about this: tells the agent to end its turn and wait for your message. The thread reads "Chat instead".
- Thread: keeps each question with your answer.
- API:
POST /api/local/chats/{id}/approvals/{approval}takes{"decision":"answer","answers":{…}}, andapproval_resolvedevents carryanswers. - Claude only for now; Codex follows in xpo-eafa9b. Animations are off under reduced motion.
(xpo-447687, xpo-46afc0, xpo-9761dd)
-
Agent chat: the transcript shows the agent's work live.
- Work list: narration is a regular message. The actions between messages collapse into one line: while the agent works, the current action's icon and target with a shimmer and a timer; afterwards "6 actions · 2 failed". Click it to see the actions on a timeline. A Lightbulb line shows while the agent thinks. The turn's total time sits under its last message.
- Actions in words: "Read board.go", "Searched the code for …", "Created issue …", "Wrote the spec of xpo-1". Each action shows an icon, and its verb is the tooltip and screen-reader label. Calls that did not run read as what was asked ("Merge upe-1").
- Failures: a denied or failed action has a red icon and ends with "failed: "; the full error opens on hover, focus or click. A denied call is not counted as a failure.
- Following: an open chat follows new output while you're at the bottom, also when a card or code block grows. Scrolling up stops following, and scrolling back to the end resumes it.
- Turn rail: in the Agent view, one bar per user message on the thread's left edge. Hovering shows the message, and clicking scrolls to it.
- Cold cache divider: "Context cache expired" marks when the next message re-sends the whole conversation. The lifetime comes from what each Claude turn reports (1h or 5m); Codex assumes 5 minutes.
- Events:
tool_callandtool_startcarrytool_id,tool_callhasstatus(done,error,denied) andmessage, andturn_doneincludescache_ttl.
(xpo-855e28, xpo-8673f4, xpo-6afc58, xpo-63b755, xpo-260a28, xpo-0e4df5, xpo-219de9)
-
Agent chat: background tasks. A process the agent started (a dev server, a monitor) does not block the chat; a turn ends when the agent replies.
- Composer: shows "Monitoring N tasks", or "N agents working" for subagents. A card lists the tasks, and its Stop button ends the agent with its tasks. The next message resumes the session.
- Agent turns: when a task ends and Claude Code replies on its own, the reply shows as its own turn, marked with what woke the agent.
- Stop interrupts the turn only; background tasks keep running. Model and effort changes apply without restarting the agent.
- Lifecycle: a mode change restarts the agent. Agents idle for 30 minutes without tasks, deleted chats and board shutdown end their agents. A starting board stops agents a crashed board left running.
- API:
POST /api/local/chats/{id}/background/stop,backgroundonGET /chats/{id}, andbackground_tasksandagent_turnevents.
(xpo-fb4f9b)
-
Agent chat: more tools. Both modes get WebSearch, WebFetch and LSP, and Work Mode also gets the task list (TaskCreate, TaskUpdate, TaskList, TaskGet) and subagents (Agent). WebFetch asks before every fetch. Codex turns get live web search. (xpo-80bf05)
-
Agent chat: attach files and images by pasting, dropping, or the paperclip button next to Send. Up to 10 per message, images up to 5 MB, other files up to 10 MB, and a message can have attachments and no text.
- Agents: PNG, JPEG, GIF and WebP go in as images, detected from the file's content. Every attachment is also listed with its absolute path in the prompt, so the agent can read text files and PDFs with its own tools.
- Storage: files are saved with the chat in
.xpo/chats/<id>/attachments/and deleted with it. Theuser_messageevent records them asattachments. - Transcript: image thumbnails open the full image; other files download.
GET /api/local/chats/{id}/attachments/{aid}serves only real images as images.
(xpo-c1d423)
-
Agent chat: chats get a short, generated title after their first finished reply. Claude chats use Haiku and Codex chats use Codex's fast model. A title you renamed is never replaced, and titles keep up to 200 characters. Chats carry
auto_titlewhile their title is a placeholder, and the chat stream sends a livetitleevent. (xpo-714245, xpo-1446f2) -
Agent chat: chat state at a glance.
- Status dots: chat pills and the recent-chats lists show yellow while an approval waits, red when interrupted, and blue for a finished reply you have not seen. A red alert circle marks a chat whose last turn failed, with the error in its tooltip. Running chats show a spinner, and idle Work Mode chats a hammer.
- Favicon: a dot when a chat needs you: amber while any chat waits on an approval, blue when a reply finished while the tab was hidden.
- Desktop notifications: opt in with the bell in the Agent view header. When a chat starts waiting on you while the tab is hidden, a notification shows "Agent needs your input" with the chat title. Clicking it opens the chat. Each chat has at most one notification, and it closes when the chat stops waiting or the tab becomes visible.
- API: chats include
last_error.
(xpo-f2d4c8, xpo-c56e70, xpo-bbcab4, xpo-a3f62b, xpo-714245)
-
Agent chat: an "All chats" page (
#/agent/history), opened from the Agent view header and the status-bar history menu.- Find: search by title, Active/Archived tabs, and agent, period and issue filters (
?issue=<id>). - Usage: a summary strip totals chats, tokens in/out and cost for the filtered rows. Chats without a reported cost (Codex) show "—".
- Manage: rename, archive, unarchive and delete per row, or in bulk.
- Archive: an archived chat leaves the recent lists and the pills, and opens read only with an Unarchive banner. Archive is refused while a reply runs or the chat is in Work Mode. At startup the board archives chats older than 30 days; only you delete chats.
- API:
POST /api/local/chats/{id}/archiveand/unarchive. Chats carryarchived_atandusage(turns, tokens, cost).
(xpo-1446f2, xpo-66fce3)
- Find: search by title, Active/Archived tabs, and agent, period and issue filters (
-
Issues show the chats that worked on them.
- Chat pill: backlog rows and board cards show a chat pill with the chat count. Its icon gives the most urgent state: waiting on you, running, or interrupted. A click opens the chat, or a searchable list of the issue's chats.
- Issue details: a "k chats" row below Relations opens the same list, with "N archived chats".
- Context menu: "Open work chat" and a "Chats" submenu.
- Back to work: when no chat is working on the issue, a chat's row actions (right-click,
⋯or Shift+F10) put it back into Work Mode, prefilled with "Let's continue with xpo-…". - API: chats include
cache_ttlandcache_expires_at.
(xpo-219de9, xpo-1446f2)
-
Issue ID chips: issue IDs in chat messages, tool rows, descriptions, specs, walkthroughs and comments show as chips that start with the issue's status icon. A click opens the issue and hovering shows a preview. IDs in inline code are chips too, and IDs in fenced code blocks stay plain. An ID inside a link is not wrapped again, and unknown IDs show a gray chip with a "not found" tooltip. Chips sit centered on the line and don't make it taller. (xpo-289f40, xpo-b83f1c, xpo-1d9d5d, xpo-b47189)
-
Merged issues get a "View changes" button that opens a read-only Merged view: the walkthrough, commits, diff and conversation, read from the merge commit, with who merged it and when. Merging while the merge view is open switches it to the Merged view.
GET /api/issues/{id}/mergedreturns the merge's commits and diff, and issues in the API carrymerged: true. (xpo-64be51) -
Issue drafts: the new issue dialog has a "Save draft" button, and the discard prompt offers "Save draft" too. Drafts are stored per user in
.xpo/drafts/(git-ignored, outside the event log). A "Drafts" sidebar item with a count badge appears while drafts exist (g r). Reopen a draft to keep editing, create the issue from it, or discard it. New/api/issue-draftsendpoints (GET/POST/PUT/DELETE,issue.createcapability). (xpo-758398) -
Read-only git MCP tools for the local
xpo mcpserver:git_status,git_diff,git_log,git_showandcode_history. Agents can see what an issue changed and which issues touched a file or lines. xpo builds every git command from an issue ID, a path, a SHA, a line range and a limit. External diff, textconv, fsmonitor and hooks never run, and the tools take no index locks. Output is capped at 64 KiB, withtruncated: trueand a marker line. (xpo-a7f115) -
MCP
add/updatetakebeforeorafter(an issue ID) to place an issue directly above or below another issue in the manual order, so agents can reorder the board. The CLI gets the same asxpo add/xpo update --before/--afterand in--jsonpayloads. An issue already in place is left alone ("X is already after Y"). (xpo-61232d) -
README section "Upgrading from 1.4.x" (xpo-bb6beb)
Changed
-
Live updates: each board tab holds one event stream,
GET /api/events. Issues, drafts, the chat list and every open chat arrive over it, so tabs no longer use up the browser's 6 connections per host.- Topics: a new broadcaster,
internal/live, carries signal topics (issues,drafts,chats: "changed, refetch") and log topics (chat:<id>: ordered events with seq ids). A tab adds and removes chats withPOST /api/events/{stream}/topicsand the last seq it has. The chat list updates live in every open browser and is no longer polled. - Never silently behind: before, a frozen background tab could stop receiving events while its connection stayed open, and the board missed new issues and status changes until a reload. Now repeated signals merge into one, and a chat whose queue overflows replays from the store. After any reconnect (sleep, network drop, board restart), the tab refetches the signal topics and replays each open chat. The browser retries with backoff (1s up to 30s) and reconnects as soon as the tab is visible again.
- Server: a 10s deadline on each write, a keepalive every 25s, and open streams end on shutdown.
- Removed: the
issue_*SSE events.
(xpo-6d1102, xpo-ae6385, xpo-7e9c4f, xpo-bbcab4)
- Topics: a new broadcaster,
-
A
blocked_byblocker counts as resolved only when it is DONE. CANCELED and DUPLICATE blockers still block, until you remove the link or point it at another issue.- Board: Start Work is disabled while any blocker is unresolved. Its tooltip lists each blocker and its status, with a one-click Remove link for canceled and duplicate blockers.
startand drive:startrefuses such issues, and the error says to remove the link. Drive skips them.
(xpo-35943d)
-
Agent instructions: agents search before they file an issue. Step 1 of the
xposkill asks for alist(match) search and arationalesearch before anyadd, and the agent tells the user what the searches found when it proposes the issue. (xpo-ed312f) -
Labels: creating a label in the label picker (
Create "…") applies it to the issue in one step, uncolored; the color step is gone. Colors are managed in the Labels view.- Labels view: "Built-in labels" and "Custom labels" are separate sections. Clicking a custom label opens an Edit label dialog with the name, a 12-color palette, and a live preview of the label on its issues. Delete label sits in the dialog behind a confirmation. "N issues" on each row links to the filtered issue list.
- Built-in labels (
default_labels) are read-only: the board and the API (400) refuse to rename, recolor, re-add or delete them, or to rename another label onto one. - Palette: the 12 swatches avoid the built-in colors and are clearly different from each other.
- Persistence: coloring a label that was only used on issues saves it to
config.yaml, so the color survives a restart. - Dialogs: the Edit label dialog and every confirm dialog share one frame (
DialogPanel).
(xpo-e35f16)
-
Deleting an issue: the context menu (click, Enter, ⌘⌫) and the issue sidebar open the same confirmation dialog, which replaces the two hand-built popovers. The dialog shows the issue in a card (status, ID and title). With sub-issues it offers Keep sub-issues (soft red) or Delete all (solid red). A toast confirms the delete: "Deleted ", or "Deleted and its sub-issues". Every confirm dialog has a footer bar with an "esc to cancel" hint and a secondary Cancel button. Red menu items such as Delete stay red on hover and keyboard focus. (xpo-33c92b)
-
Issue rows: one pill style for row indicators, after Linear. Sub-issue progress, branch, chats and attachments sit in the same faint, fully rounded pill, with a muted 12px icon. The branch, chat and attachment pills show their counts; details are in the tooltips. Pills and label chips have the surface background, so they stay dark on a hovered or highlighted row. Applies to backlog rows, board cards and My Issues. (xpo-219de9, xpo-199a69)
-
Links in Markdown prose (chat transcript, descriptions, comments) use a new
--color-linktoken,#adbbff, in place of the accent color. They have about 9–11:1 contrast instead of about 4:1. They have no underline at rest and underline on hover. (xpo-1d9d5d) -
Buttons look the same on any surface.
secondaryis a wash of its own text color for rest, hover and pressed, with an optionalborderedoutline and atone(danger,warning) for colored text.ghostuses the same wash on hover.primarydarkens when pressed.dangerrests a little darker and hovers a little lighter than its base color. A newwarningvariant works the same way, with dark text. The "Delete branch" checkbox in the merge view uses the accent color. (xpo-aaf8b1, xpo-1446f2) -
Dependencies upgraded. Frontend: vite 8 (Rolldown; vendor chunks move to
rolldownOptions.output.codeSplitting.groups), @vitejs/plugin-react 6, eslint 10, fractional-indexing 4, React 19.3, recharts 3.10, tiptap 3.32, lucide-react 1.57 and the rest to latest. TypeScript stays on 5.9 (typescript-eslint supports <6.1) and eslint-plugin-react-hooks on 7.0 (xpo-09fa6f).bun.lockis regenerated and the unusedweb/package-lock.jsonis removed. Go: glamour 1.0, MCP go-sdk 1.8, golang.org/x/* and the rest to latest. Building from source now requires Go 1.26 (required by golang.org/x/*). (xpo-b2ca41, xpo-855e28)
Removed
- Remote mode: xpo is local-only. Issue data travels with the code through git (
.xpo/issues.db merge=union), and teammates get the latest copy when they sync. Remote mode reported branch state, and ran start and merge, in the server's checkout, which nobody works in. Team mode will be designed separately. (xpo-67281a)- Commands:
xpo serve,xpo loginandxpo logoutare gone.xpo whoamiprints only the identity xpo records on events: the configureduser, else git config. - Board:
xpo boardno longer proxies to a remote server. The board's--hostaccess token still works.GET /api/local/modeno longer returnsremote. The interim remote-mode guards for Start Work, Review Changes and the MCPstartandmergetools are gone with it (xpo-a60065). - Config:
remote.url,remote.token(XPO_REMOTE_URL,XPO_REMOTE_TOKEN),permissions, and theservers:entries in~/.config/xpo/user.yamlare ignored. The.xpo/authorized_keysand.xpo/server.keyfiles are no longer used. - Other: the Dockerfile,
docker-compose.ymlandmake dockerare gone, andgolang.org/x/cryptois no longer a dependency.
- Commands:
Fixed
-
Start Work in a project without a git repository no longer moves the issue to DOING and then fails with "not a git repo". The board refuses before anything changes, with
Work Mode needs a git repository. Run "git init" in <path>.With an agent available, Start Work and "Enter work mode" are disabled, and their tooltip says the same. Without an agent, Start Work still only starts the issue. Runninggit inittakes effect without restarting the board.GET /api/local/modealso returnsgitandroot. (xpo-e627e1) -
Issue sidebar: Start Work is disabled while the issue is in BACKLOG, since
startrequires PLANNED. Before, clicking it only showed an error toast. Its tooltip says "Move the issue to Planned to start work", followed by any unresolved blockers. (xpo-31520e) -
Review Changes shows when a worktree's only changes are new, untracked files. Branch stats count them as uncommitted (files and line insertions; gitignored files and
.xpo/excluded). The dialog opens on "Uncommitted" when the branch has no commits yet, instead of an empty "All changes". When Review Changes shows, it is the primary, top button, and Start Work or Continue Work turns secondary. (xpo-926f0b) -
Merging an issue while its merge view is open no longer shows a raw
{"error":"no branch found for this issue"}screen. An issue without a branch shows a clear "No branch found" screen, and API errors across the UI show their message instead of raw JSON. (xpo-64be51) -
MCP
updateandxpo update --jsonclearassignee,cycle_id,priority,story_pointsandparentwhen the field isnull. They used to fail with "no fields set", or leave the field unchanged when another field was set.nullondescription,labelsandlinksstill means "unchanged"; clear those with""or[]. A literal quoted empty string ("\"\"") is treated as""instead of failing withissue "" not found. Each nullable field's schema description says how to clear it. (xpo-bbfcf9, xpo-328824) -
Issue activity timeline: every field change is described, cleared values read as words, and rows read as muted sentences. (xpo-ca763f, xpo-bbfcf9)
- New descriptions: priority ("set priority to High" / "cleared the priority"), parent ("set parent to [issue]" / "removed the parent issue") and cycle ("added this issue to Cycle 12" / "removed this issue from its cycle").
- Cleared values: a cleared estimate reads "cleared the estimate" instead of "set estimate to 0 Points". Removing the assignee reads "removed the assignee"; before, such an event had no text. Removing all labels reads "removed all labels" on the web and in the CLI.
- Same wording in the CLI:
xpo historyuses the same phrases, replacing "unassigned", "removed parent", "removed from cycle" and "added to cycle X". - Muted rows: actor names and values stay muted and turn white with a pointer cursor on hover. Comment headers keep the white name.
- Spacing and alignment: each row is one run of text with normal word spacing instead of 8px gaps, and long rows wrap like a sentence. Status, estimate and priority chips line up with the row's text, and the estimate triangle no longer hangs below it (xpo-d3578a).
-
Menus: items that open a next step inside their menu keep it open. In the label picker,
Create "…"creates the label, where before it closed the picker and did nothing. In the issue context menu, Delete shows its confirmation; before, only ⌘⌫ / Ctrl⌫ did.MenuItemtakes a newkeepOpenprop for such items. (xpo-2790cd) -
Menu items with a suffix (relative times, filter counts, "⌘⌫") no longer overflow their row. (xpo-c047ce)
-
XPO_*environment variables set every config key, as the README promised. Before, a key without a default was ignored unless a config file already set it. List keys take a comma-separated value. Map keys (labels) can still only be set in a config file. (xpo-e81612) -
Unknown
/api/…,/auth/…and/mcp/…paths return 404 again in builds with the web app embedded. Before, the app's catch-all route answered them withindex.htmland a 200. -
Markdown: rendered checklist items wrap like the editor. Wrapped lines line up with the first line of text instead of the checkbox, and the checkbox sits at the editor's height. (xpo-d4e6e4)
-
Dashboard: Active Epics rows no longer nest the copyable issue ID button inside the row button, which was invalid HTML and logged a React error on every Dashboard load. (xpo-66c6a4)