Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ baton 是一个 terminal-native 的统一 coding agent 会话:用户始终在

第一阶段只聚焦两件事:

1. **交互体验**:尽量保留单独使用 coding agent 时的输入、补全、命令、流式输出、工具调用和审批体验,baton 主要增加 `/provider` 用于切换 agent。
1. **交互体验**:尽量保留单独使用 coding agent 时的输入、补全、命令、流式输出、工具调用和审批体验,baton `/codex`、`/claude` 一步切换 agent。
2. **数据打通**:BatonSession 是用户拥有的持久会话,也是跨 provider 的统一逻辑历史;只要 BatonSession 仍在,任一 provider 都应能恢复所需上下文。同一次会话内切换 provider,以及关闭后重新打开同一 BatonSession 再切换 provider,都应自然续聊,无需用户手工复制上下文或显式 `@` 当前会话。

v1 明确不做 agent 互相委派、worktree / 并发写文件隔离、多人多设备云端协作、遥测上报与账号体系。它们并非永远不做,而是不应挤占第一阶段“单一逻辑会话中的原生交互与上下文接力”;多 agent 同 cwd 时只提示文件冲突风险。
Expand Down Expand Up @@ -82,7 +82,7 @@ baton/
- **用户安装与开发运行时分离**:普通用户统一通过 npm 安装,包内 launcher 自带所需 runtime,不暴露 Bun 前置条件;仓库开发仍使用 Bun,避免为分发方式改写开发工具链。
- 同一 BatonSession 内的 provider 接力由 baton 自动完成;`@` 只承担跨 BatonSession / turn / 产物的显式引用。
- session / turn / message 的 ID 必须稳定可外部引用;fork 复制的前缀与源**共享对象 ID**(同一段逻辑历史,git-branch 语义),跨会话引用 turn/message 时以 `bs_ + 对象 ID` 限定消歧,why 见 `docs/resume-fork.md`。
- `/provider` 是 baton 额外提供的 agent 切换入口;其余命令与引用在能力允许时保持 provider 原生语义,由 baton/Adapter 显式映射,不做不可控的文本透传。
- `/codex`、`/claude` 是 baton 自有的直接 agent 切换入口;其余命令与引用在能力允许时保持 provider 原生语义,由 baton/Adapter 显式映射,不做不可控的文本透传。

- 界面按信息的**时态与寿命**分层:越是“现在时”的信息越靠下、越固定,不随历史滚动;条件层(pinned plan、队列)无内容即整层消失。这是 baton projection 与页面装配的产品语义;chat-tui 只负责按展示结构渲染。分层图与合成规则见 `docs/design.md` 5.9。

Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

> Pass context between coding agents like a baton.

baton is a terminal-native coding-agent session, inspired by [tutti](https://github.com/tutti-os/tutti). A BatonSession remains the same durable conversation while you switch providers with `/provider`, including after closing and reopening baton. Claude Code and Codex are the first bundled providers, not a closed support list.
baton is a terminal-native coding-agent session, inspired by [tutti](https://github.com/tutti-os/tutti). A BatonSession remains the same durable conversation while you switch providers with `/codex` or `/claude`, including after closing and reopening baton. Claude Code and Codex are the first bundled providers, not a closed support list.

Provider-native sessions are resume optimizations; BatonSession history remains available even when a native session cannot be resumed.

Expand All @@ -15,7 +15,7 @@ The most common shape of multi-agent work today is a human acting as a context c
Two fundamentals are in place today:

- **Context portability**: a BatonSession is a durable, unified history owned by the user that outlives any single provider. Switching agents requires no context carrying; provider-native sessions only accelerate resume and are never a prerequisite for the history to survive.
- **Native experience**: baton preserves each agent's own input, completion, streaming, tool-call, and approval experience as much as possible, adding only a few commands of its own (such as `/provider`).
- **Native experience**: baton preserves each agent's own input, completion, streaming, tool-call, and approval experience as much as possible, adding only a few commands of its own (such as `/codex` and `/claude`).

On top of these, three directions are on the roadmap (**none implemented yet**):

Expand All @@ -26,7 +26,7 @@ On top of these, three directions are on the roadmap (**none implemented yet**):
## Features

- Use Claude Code and Codex from the same terminal interface
- Switch between Claude Code and Codex with `/provider`, and configure the active provider with `/model`
- Switch directly with `/codex` or `/claude`, and configure the active provider with `/model`
- Open a previous BatonSession with `/sessions`, or start a clean one with `/new`
- Continue the latest session in a project with `baton -c`, or open one by ID with `baton -s <id>`
- Reference previous sessions with `@<session-id>` and inject a compact summary automatically
Expand Down Expand Up @@ -69,9 +69,8 @@ If Claude Code uses a custom executable, set `claudeExecutable` in the configura
Start the TUI and type a prompt to send it.

```text
/provider Open the provider picker
/provider claude Switch to Claude Code
/provider codex Switch to Codex
/claude Switch to Claude Code
/codex Switch to Codex
/model Open the model picker for the active provider
/model <id> Select the model used by subsequent turns
/sessions Open the BatonSession picker
Expand Down
11 changes: 5 additions & 6 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

> 像传递接力棒一样,在 coding agents 之间传递上下文。

baton 是一个 terminal-native 的统一 coding agent 会话,受 [tutti](https://github.com/tutti-os/tutti) 启发。你可以用 `/provider` 在不同 coding agent 间切换,而 BatonSession 始终是同一段持久对话;关闭 baton 后重新打开,也可以换一个 provider 自然续聊。Claude Code 和 Codex 是首批内置 provider,不是封闭支持列表。
baton 是一个 terminal-native 的统一 coding agent 会话,受 [tutti](https://github.com/tutti-os/tutti) 启发。你可以用 `/codex` 或 `/claude` 直接切换 coding agent,而 BatonSession 始终是同一段持久对话;关闭 baton 后重新打开,也可以换一个 provider 自然续聊。Claude Code 和 Codex 是首批内置 provider,不是封闭支持列表。

各家的原生会话只是恢复加速;即使原生会话无法恢复,BatonSession 历史仍然存在。

Expand All @@ -15,7 +15,7 @@ baton 是一个 terminal-native 的统一 coding agent 会话,受 [tutti](http
当前已落地的两个基本点:

- **上下文打通**:BatonSession 是用户拥有的持久统一历史,跨 provider 存续。换 agent 不需要搬运上下文;各家原生会话只承担恢复加速,不是历史存续的前提。
- **原生体验**:尽量保留单独使用各 agent 时的输入、补全、流式输出、工具调用与审批体验,baton 只增加少量自己的命令(如 `/provider`)。
- **原生体验**:尽量保留单独使用各 agent 时的输入、补全、流式输出、工具调用与审批体验,baton 只增加少量自己的命令(如 `/codex` 和 `/claude`)。

在此之上有三个演进方向(**均尚未实现**):

Expand All @@ -26,7 +26,7 @@ baton 是一个 terminal-native 的统一 coding agent 会话,受 [tutti](http
## 功能

- 在同一个终端界面中使用 Claude Code 和 Codex
- 使用 `/provider` 切换 Claude Code / Codex,使用 `/model` 配置当前 provider
- 使用 `/codex` 或 `/claude` 直接切换 agent,使用 `/model` 配置当前 provider
- 使用 `/sessions` 打开历史 BatonSession,或用 `/new` 新建干净会话
- 使用 `baton -c` 继续当前项目最近会话,或用 `baton -s <id>` 打开指定会话
- 使用 `@<session-id>` 引用历史会话,并自动注入紧凑摘要
Expand Down Expand Up @@ -69,9 +69,8 @@ showThoughts: true
启动 TUI 后直接输入内容即可发送。

```text
/provider 打开 provider 选择器
/provider claude 切换到 Claude Code
/provider codex 切换到 Codex
/claude 切换到 Claude Code
/codex 切换到 Codex
/model 打开当前 provider 的模型选择器
/model <id> 设置后续 turn 使用的模型
/sessions 打开 BatonSession 选择器
Expand Down
10 changes: 5 additions & 5 deletions docs/design.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# baton 设计(v1)

baton 是一个 terminal-native 的统一 coding agent 会话:用户始终在 BatonSession 中工作,可用 `/provider` 在不同 coding agent 间切换,而不需要随 provider 一起切换或搬运会话历史。Claude Code 和 Codex 是首批内置 provider,用于打样原生协议接入,不构成封闭支持列表。
baton 是一个 terminal-native 的统一 coding agent 会话:用户始终在 BatonSession 中工作,可用 `/codex`、`/claude` 直接切换 coding agent,而不需要随 provider 一起切换或搬运会话历史。Claude Code 和 Codex 是首批内置 provider,用于打样原生协议接入,不构成封闭支持列表。

命名取"指挥棒 + 接力棒"双关:编排多个 agent,并把上下文递给下一棒(npm 裸名被死包占用,发布走 scope + bin 名 `baton`)。

Expand Down Expand Up @@ -62,7 +62,7 @@ ID 规则:全部用带前缀的 ULID(`bs_` / `ps_` / `t_` / `m_` / `tc_`)
| 普通文本 | `ContentBlock[]` → `prompt()` | SDK `query()` | `turn/start` | 已支持;当前 adapter 最终只发送 text |
| 图片等富输入 | `ContentBlock` 可表示 image | 原生协议可表达 | `UserInput` 可表达 | 未接入;TUI composer 与 adapter 均按纯文本处理 |
| 模型切换 | `/model` → `ModelConfigurable` | SDK model discovery + 下一次 `query()` 配置 | `model/list` + 下一次 `turn/start` override | 两者已支持;只影响后续 turn,不改变正在运行的 turn |
| baton 自有 slash command | command registry → baton core | 不下发 provider | 不下发 provider | 已支持 `/provider`、`/model`、`/sessions`、`/status`、`/new`、`/exit`;其中 `/model` 是 baton 统一的 provider 控制面 |
| baton 自有 slash command | command registry → baton core | 不下发 provider | 不下发 provider | 已支持 `/codex`、`/claude`、`/model`、`/sessions`、`/status`、`/new`、`/exit`;其中 `/model` 是 baton 统一的 provider 控制面 |
| provider-compatible slash command | 待定义 command discovery + adapter execute capability | SDK 可发现 `supportedCommands()` | 需按 app-server 能力显式映射 | 未支持;未知命令当前报错,不作为普通文本透传 |
| 中断当前 turn | `AgentAdapter.cancel()` | `Query.interrupt()` | `turn/interrupt` | 已支持 |
| 排队 follow-up | `BatonSessionRuntime` 全局 FIFO | provider 无感知 | provider 无感知 | 已支持;当前 turn 结束后才开始下一 turn |
Expand Down Expand Up @@ -94,15 +94,15 @@ ID 规则:全部用带前缀的 ULID(`bs_` / `ps_` / `t_` / `m_` / `tc_`)

1. **permission、user input、elicitation 是三类不同契约**。permission 决定是否允许某个动作;user input 是 agent 为继续推理索取答案;elicitation 是工具 / MCP server 索取结构化数据。不能都塞进 `ApprovalRequest`。
2. **chat-tui picker 不等于 agent question**。picker 只适合单题单选的产品命令;provider question 通过独立 QuestionCard 处理多题、多选、自由文本和 preview,secret 遮罩与超时仍待补齐。
3. **baton command 不等于 provider command**。`/provider`、`/sessions` 等由 baton core 消费;`/model` 是 baton 统一后再调用 adapter capability;Claude/Codex 私有 slash command 必须经能力发现和显式 adapter 映射,不能把未知 `/xxx` 当文本盲透传。
3. **baton command 不等于 provider command**。`/codex`、`/claude`、`/sessions` 等由 baton core 消费;`/model` 是 baton 统一后再调用 adapter capability;Claude/Codex 私有 slash command 必须经能力发现和显式 adapter 映射,不能把未知 `/xxx` 当文本盲透传。
4. structured question 已在 baton 事件层建立独立 request/response 模型,并由 chat-tui 的对应展示形状消费;provider 原始 payload 继续放在 `raw` 中保真。

## 4. 架构总览

```
┌────────────────────────── baton (Bun 进程) ──────────────────────────┐
│ TUI (chat-tui · opentui) │
│ ├─ session/provider picker │ transcript │ composer │ 审批卡片 │
│ ├─ session picker │ transcript │ composer │ 审批卡片
│ core │
│ ├─ BatonSessionRuntime(全局 turn 队列、provider 恢复与上下文同步) │
│ ├─ Session open / @ 解析 / turn-summary │
Expand Down Expand Up @@ -254,7 +254,7 @@ Footer 常驻状态栏(usage、队列计数、plan 进度摘要、c
- **run status 不塞 `state_update`、不建模成 tool_call**:前者驱动 runtime 的 busy/idle finalize(adapter 终态硬约定),是生命周期语义,混入阶段信息会污染 finalize;后者没有输入输出契约,也不值得在 transcript 占工具卡。
- **交互提示挂在交互发生地**:↑ 召回队列的提示在 composer placeholder(按键发生在 composer,且 placeholder 天然只在空输入时可见,与召回的生效条件一致),不在队列块上占一行。

输入语义刻意分开:`/provider` 选择当前输入目标,`/model` 配置该 ProviderSession 后续 turn 使用的模型,`@` 只引用 baton session / turn / 产物。所有普通输入先进入 BatonSessionRuntime 的全局串行队列,因此切换 provider 不会分裂出两条并发逻辑历史。
输入语义刻意分开:`/codex`、`/claude` 直接选择当前输入目标,`/model` 配置该 ProviderSession 后续 turn 使用的模型,`@` 只引用 baton session / turn / 产物。所有普通输入先进入 BatonSessionRuntime 的全局串行队列,因此切换 provider 不会分裂出两条并发逻辑历史。

排队中的 turn 由 BatonSessionRuntime 单点持有,TUI 只在 composer 上方读取快照展示,避免界面状态与真实执行队列分叉。尚未开始的消息立即可见,并可在空 composer 时用 ↑ 按 LIFO 撤回编辑;正常执行仍按 FIFO,turn 被 runtime 取走开始执行后才进入持久事件流,不再允许撤回。

Expand Down
2 changes: 1 addition & 1 deletion src/cli/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Usage:
baton [--cwd <dir>] [-c|--continue] [-s|--session <id>]
start the chat TUI; creates a new BatonSession by default,
-c continues the latest session in the cwd, -s opens a
specific session; /provider switches provider
specific session; /codex and /claude switch provider
baton repl [--agent codex|claude] [--cwd <dir>] headless REPL
baton resume [bs_xxx] resume a BatonSession in the TUI; without an id shows a
session list first (enter resume · esc new session ·
Expand Down
11 changes: 5 additions & 6 deletions src/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PROVIDERS, parseProvider, type ProviderName } from "../providers/ids.ts

export { PROVIDERS, parseProvider, type ProviderName };

export type CommandName = "provider" | "model" | "sessions" | "status" | "new" | "exit";
export type CommandName = ProviderName | "model" | "sessions" | "status" | "new" | "exit";

export interface CommandDefinition {
name: CommandName;
Expand All @@ -16,12 +16,12 @@ export interface CommandDefinition {
}

export const COMMANDS: readonly CommandDefinition[] = [
{
name: "provider",
description: "Select the input target (codex / claude)",
...PROVIDERS.map((name) => ({
name,
description: `Switch the input target to ${name}`,
scope: "baton",
runPolicy: "always",
},
}) satisfies CommandDefinition),
{
name: "model",
description: "Set the model for the current provider's next turns",
Expand All @@ -48,4 +48,3 @@ export const COMMANDS: readonly CommandDefinition[] = [
},
{ name: "exit", description: "Exit baton", scope: "baton", runPolicy: "always" },
];

2 changes: 1 addition & 1 deletion src/providers/ids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
export const PROVIDERS = ["codex", "claude"] as const;
export type ProviderName = (typeof PROVIDERS)[number];

/** 用户输入(/provider 参数、--agent、config.defaultAgent)归一为 canonical id;未知返回 null */
/** 用户输入(slash command、--agent、config.defaultAgent)归一为 canonical id;未知返回 null */
export function parseProvider(value: string): ProviderName | null {
const normalized = value.trim().toLowerCase();
return PROVIDERS.find((provider) => provider === normalized) ?? null;
Expand Down
2 changes: 1 addition & 1 deletion src/providers/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface ProviderAdapterOptions {
}

export interface ProviderDefinition<Id extends string = string> {
/** canonical id:用户侧词汇(/provider、--agent、config.defaultAgent) */
/** canonical id:用户侧词汇(slash command、--agent、config.defaultAgent) */
id: Id;
/** picker / 帮助文案里的展示长名 */
label: string;
Expand Down
2 changes: 1 addition & 1 deletion src/tui/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// UI 层来自 chat-tui(github.com/qiankunli/chat-tui):ChatShell 消费视图快照、回传 intents;
// baton 侧只有 BatonChatProtocol(runtime/store → 视图投影 + intents → runtime 操作)。
// - 直接输入 → 发给当前 agent(默认 codex)
// - /provider 选择输入目标;/model 配置当前 provider 后续 turn 的模型
// - /codex、/claude 直接选择输入目标;/model 配置当前 provider 后续 turn 的模型
// - 切换 agent 时自动注入对方最新进展(buildCatchUpContext),无需手动搬运上下文
// - @bs_xxx 引用其它 baton 会话;@ 不承担 provider 路由
// 用法:baton [--root <batonRoot>] [--cwd <dir>] [-c|--continue] [-s|--session <id>]
Expand Down
Loading