diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d7363fc..bfe9d73ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 author's, a line above the list names the accounts they landed under and the addresses no account owns, while the branch can still be fixed. +- **The README says what each agent actually gives you.** All eight providers run + the same way, but what lich can read back out of a session depends on what that + CLI writes down, and until now the only record of the difference was a + contributor document. A Provider support table now sits beside the provider + list, in what you see: the footer's context window, cost and plan gauge, the + card's spinner and bell, whether the machine stays awake, the Review tab's last + turn, palette search, and forking a conversation into a worktree. + ### Changed - **The two sandbox grants say what they hand over.** In Settings › Sandbox, the @@ -31,6 +39,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 regains focus, so a key added with `ssh-add` after the pane was opened is no longer handed over by a switch that never named it. +- **The Review tab says when a provider has no last turn, instead of hiding the + switch.** On a Crush or Cursor CLI session the Working tree / Last turn control + simply never appeared, which reads the same as a bug. It is now drawn with + Last turn dead and a line saying that the provider reports neither the start + nor the end of a turn. A session whose provider does report is unchanged: its + switch appears as soon as it says anything. + ### Fixed - **A session name that wrapped, or followed a wide character, stopped being a diff --git a/CLAUDE.md b/CLAUDE.md index d60e6ec2a..2e1d6ffd0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,8 +73,10 @@ Non-negotiable rules. A violation means the work is not done. Claude Code, Codex, Antigravity, opencode, oh-my-pi, Crush, Cursor CLI, Kiro CLI. Anything a session touches (spawn flags, hooks, resume, transcripts, plugin install, MCP) is designed against all eight, and `docs/adding-a-provider.md` is the map of every file one lands in. Equal behaviour is not always possible — - but the gap must be deliberate and written down in the same PR: a `docs/ceilings.md` bullet naming which - providers are out and why. A feature that silently works on a single provider is not done. + but the gap must be deliberate and written down in the same PR, in both places: a `docs/ceilings.md` bullet + naming which providers are out and why, and a row in the Provider support table of `README.md` and + `README.zh-CN.md`, written in what the user sees. A feature that silently works on a single provider is not + done, and neither is one whose gap only a contributor can find. ## Releases diff --git a/README.md b/README.md index 17cf8057f..c21590542 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,10 @@ lich lets you: [opencode](https://github.com/sst/opencode), oh-my-pi, [Crush](https://github.com/charmbracelet/crush), the [Cursor CLI](https://cursor.com/docs/cli) and the - [Kiro CLI](https://kiro.dev/docs/cli/) are all first-class. Point lich at - each binary once, then pick the default or choose per session. + [Kiro CLI](https://kiro.dev/docs/cli/) all run here the same way. Point lich + at each binary once, then pick the default or choose per session. What lich + can read back *out* of a session differs by provider, and + [Provider support](#provider-support) is the table. - **Keep a real terminal.** PTY-backed shells, several per project, rendered on the GPU — searchable scrollback that survives a full page reload. Give one an entrypoint — `lazygit`, `k9s`, `pnpm dev` — and it opens straight into that @@ -87,6 +89,36 @@ Development is active: bugs and feature requests belong in [Issues](https://github.com/omartelo/lich/issues), and what changed in each version is in [CHANGELOG.md](CHANGELOG.md). +## Provider support + +Every provider is spawned in a real terminal, resumed by conversation id, +confined by the same sandbox and handed the same way of reaching your other +sessions. What differs is what lich can *read* back out of a session, because it +reads what each CLI writes down and no two of them write down the same things. + +| What you get | Claude Code | Codex | Antigravity | opencode | oh-my-pi | Crush | Cursor CLI | Kiro CLI | +| --- | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | +| Context window in the footer | yes | yes | no | no | no | no | no | yes | +| Cost in the footer | yes | yes | no | yes | yes | yes | no | credits | +| How much of your plan is left | yes | yes | no | no | no | no | no | no | +| Spinner while a turn runs, ring when it ends | yes | yes | yes | yes | yes | no | no | yes | +| Bell when the agent is blocked on you | yes | yes | no | yes | no | no | no | no | +| Machine kept out of idle sleep while a turn runs | yes | yes | yes | yes | yes | no | no | yes | +| Review tab's **Last turn**, with the agent's recap | yes | yes | yes | yes | yes | no | no | yes | +| Search the conversation from the palette | yes | yes | yes | yes | yes | yes | no | yes | +| Fork a conversation into a new worktree | yes | yes | no | yes | no | no | no | no | + +Crush and Cursor CLI report neither the start nor the end of a turn, and that is +where four of those rows go at once: nothing opens a window for the card's +spinner, for the bell, for the Review tab's last turn, or for the hold that keeps +the machine awake. The Review tab says so on the session itself rather than +leaving you to notice the switch never appeared. Kiro CLI meters spend in credits +rather than dollars, so its own footer is the only place that figure can be read. + +Every gap is deliberate, none of them is lich withholding something the CLI +reports, and [`docs/ceilings.md`](docs/ceilings.md) says what was measured behind +each one. + ## Install One line — detects your distro, verifies the checksum, and installs the native @@ -181,7 +213,7 @@ Package a Linux release locally (needs task package # .deb + .rpm + Arch .pkg.tar.zst in bin/ ``` -Adding another agent CLI to the seven lich runs is the one change that lands in a +Adding another agent CLI to the eight lich runs is the one change that lands in a dozen files across two repositories: [`docs/adding-a-provider.md`](docs/adding-a-provider.md) is the map. diff --git a/README.zh-CN.md b/README.zh-CN.md index d19016df0..9082ce471 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -37,8 +37,10 @@ [opencode](https://github.com/sst/opencode)、oh-my-pi、 [Crush](https://github.com/charmbracelet/crush)、 [Cursor CLI](https://cursor.com/docs/cli) 和 - [Kiro CLI](https://kiro.dev/docs/cli/) 都是一等公民。把 lich - 指向各自的二进制文件,这只需一次,之后选一个默认的,或者逐个会话单独指定。 + [Kiro CLI](https://kiro.dev/docs/cli/) 在这里都以同样的方式运行。把 lich + 指向各自的二进制文件,这只需一次,之后选一个默认的,或者逐个会话单独指定。而 + lich 能从一个会话里*读*回什么,则因智能体而异, + [各智能体支持到什么程度](#各智能体支持到什么程度)是那张表。 - **留住一个真正的终端。** 由 PTY 支撑的 shell,每个项目可以开好几个,在 GPU 上渲染 —— 滚动缓冲区可以搜索,还能挺过整页刷新。给其中一个设一个入口命令 —— `lazygit`、 `k9s`、`pnpm dev` —— 它每次启动都会直接进到那个工具里。底栏跟随 `cd` 并标明分支 @@ -74,6 +76,32 @@ [Issues](https://github.com/omartelo/lich/issues),每个版本改了什么见 [CHANGELOG.md](CHANGELOG.md)。 +## 各智能体支持到什么程度 + +每个智能体的启动方式都一样:在真实终端里拉起,按会话 id 恢复,用同一套沙箱隔离,也用 +同一套办法去找你的其他会话。不一样的是 lich 能从一个会话里**读**回什么,因为它读的是 +各家 CLI 自己写下来的东西,而没有两家写下的是同一批。 + +| 你能看到什么 | Claude Code | Codex | Antigravity | opencode | oh-my-pi | Crush | Cursor CLI | Kiro CLI | +| --- | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | +| 底栏里的上下文窗口 | 有 | 有 | 无 | 无 | 无 | 无 | 无 | 有 | +| 底栏里的花费 | 有 | 有 | 无 | 有 | 有 | 有 | 无 | 按点数计 | +| 套餐额度还剩多少 | 有 | 有 | 无 | 无 | 无 | 无 | 无 | 无 | +| 一轮进行中转圈,结束后成环 | 有 | 有 | 有 | 有 | 有 | 无 | 无 | 有 | +| 智能体卡在等你时响铃 | 有 | 有 | 无 | 有 | 无 | 无 | 无 | 无 | +| 一轮跑着时不让机器进入空闲休眠 | 有 | 有 | 有 | 有 | 有 | 无 | 无 | 有 | +| Review 页的 **Last turn**,带智能体自己的收尾话 | 有 | 有 | 有 | 有 | 有 | 无 | 无 | 有 | +| 从命令面板搜索对话里说过的内容 | 有 | 有 | 有 | 有 | 有 | 有 | 无 | 有 | +| 把一段对话分叉到新的 worktree | 有 | 有 | 无 | 有 | 无 | 无 | 无 | 无 | + +Crush 和 Cursor CLI 既不报告一轮的开始,也不报告结束,上面有四行是因此一起掉的:没有 +东西去开启那个窗口,卡片的转圈、响铃、Review 页的上一轮,以及让机器不休眠的那次占用, +都无从谈起。Review 页会在会话上直接说明这一点,而不是让你自己去发现那个切换从来没出现 +过。Kiro CLI 按点数而不是按美元计费,所以那个数字只有它自己的底栏能看到。 + +每一处缺口都是刻意的,没有一处是 lich 藏起了 CLI 已经报告的东西, +[`docs/ceilings.md`](docs/ceilings.md) 写着每一处背后量到了什么。 + ## 安装 一行命令 —— 识别你的发行版、校验校验和,并通过你的包管理器安装原生软件包及其依赖: @@ -155,7 +183,7 @@ task test # Go 与前端测试套件 task package # bin/ 下生成 .deb + .rpm + Arch .pkg.tar.zst ``` -在 lich 已经跑着的七个之外再加一个智能体 CLI,是唯一一处会落到两个仓库、十几个文件里 +在 lich 已经跑着的八个之外再加一个智能体 CLI,是唯一一处会落到两个仓库、十几个文件里 的改动:[`docs/adding-a-provider.md`](docs/adding-a-provider.md) 就是那张地图。 ## 赞助 diff --git a/docs/adding-a-provider.md b/docs/adding-a-provider.md index 191a7d346..097ed15b5 100644 --- a/docs/adding-a-provider.md +++ b/docs/adding-a-provider.md @@ -157,6 +157,13 @@ Not optional, and all in the same PR: released build. - **`README.md`, `README.zh-CN.md`, `CLAUDE.md`** — the provider list, which is also the checklist rule 5 points at. +- **The Provider support table in both READMEs** — a column, and an answer in + every row of it. That table is what a stranger reads before choosing a binary, + and nothing derives it from the code, so a column left out of it reads as a + provider that gives you nothing. `turnUnavailableReason` + (`frontend/src/lib/git/last-turn.ts`) is a hand-written list of the same shape: + a provider that reports no session state belongs in it, and one that starts + reporting has to leave. ## The gate diff --git a/docs/ceilings.md b/docs/ceilings.md index caa582aba..af8f292cc 100644 --- a/docs/ceilings.md +++ b/docs/ceilings.md @@ -187,9 +187,12 @@ work when nobody knows it and that the call site never shows. The mechanism and no ref reaches, so a `git gc --prune` in that checkout between one run and the next leaves the panel reporting a failure rather than an absent turn. And the boundary is the session-state contract, so **Crush and Cursor CLI have no last turn at all**: neither reports a state - (`docs/hooks/session-state.md`), so nothing ever opens or closes a window there, and neither the switch nor - the recap band beside it is ever drawn — a rule read off the session's own reports, not a list of providers, - so it corrects itself the day either one starts reporting. + (`docs/hooks/session-state.md`), so nothing ever opens or closes a window there, and the recap band beside it + is never drawn either. Whether the switch is *offered* is read off the session's own reports and corrects + itself the day either one starts reporting (`turnSwitchable`), but the sentence under the dead switch naming + the provider is a hand-written list (`turnUnavailableReason`, + `frontend/src/lib/git/last-turn.ts`): the two disagree, and the panel names a provider that has since started + reporting, until that list is moved with the contract. - **A finished turn is unread until its own card is watched** (`frontend/src/lib/session/session-status-store.ts`, `frontend/src/providers/projects.tsx`): the solid emerald ring means "back from the agent, not read yet", and it fades only for the session whose terminal is on screen **while the window has focus**. A card left focused in a diff --git a/frontend/src/components/diff/ReviewPanel.tsx b/frontend/src/components/diff/ReviewPanel.tsx index 58a19ef88..35829534a 100644 --- a/frontend/src/components/diff/ReviewPanel.tsx +++ b/frontend/src/components/diff/ReviewPanel.tsx @@ -6,7 +6,12 @@ import type { LastSaid, LastTurn } from "@/lib/api-types" import { onAppEvent } from "@/lib/app-events" import { readDiffSource, writeDiffSource, type DiffSource } from "@/lib/dock-prefs" import { discardTargets, parseDiff, type DiffFile } from "@/lib/git/diff" -import { lastTurnNotice, saidNote, turnSwitchable } from "@/lib/git/last-turn" +import { + lastTurnNotice, + saidNote, + turnSwitchable, + turnUnavailableReason, +} from "@/lib/git/last-turn" import { addReviewComment } from "@/lib/review-comments" import { ProjectService, Terminal } from "@/lib/rpc" import { useActiveSession } from "@/lib/session/use-active-session" @@ -45,13 +50,17 @@ const LAST_TURN_HINT = // the project root. The dock (RightDock) owns the surrounding chrome: width, // full screen, the tab bar and the close button. export function ReviewPanel({ bulk }: { bulk: DiffBulk }) { - const { sessionId, path, hasLastTurn } = useActiveSession() + const { sessionId, path, kind, hasLastTurn } = useActiveSession() const inject = useInject(sessionId) const status = useGitStatus(path) // The source switch is earned, not assumed: a provider that never reports its // state and holds no record has no turn to bracket, so it is offered the // working tree alone (turnSwitchable). const reported = useSessionEverReported(sessionId) + // Where that is standing rather than momentary, the strip is still drawn and + // the dead option carries the reason, so the absence is read once instead of + // being inferred from a control that never appears (turnUnavailableReason). + const unavailable = turnUnavailableReason(kind) // The recap band reads the last thing the agent said, which mid-turn is the // previous turn's, and the band says so rather than leaving the reader to the // card's spinner (saidNote). @@ -209,7 +218,14 @@ export function ReviewPanel({ bulk }: { bulk: DiffBulk }) { return (