@team-agent/installer 0.3.31 → 0.3.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.lock CHANGED
@@ -566,7 +566,7 @@ dependencies = [
566
566
 
567
567
  [[package]]
568
568
  name = "team-agent"
569
- version = "0.3.31"
569
+ version = "0.3.32"
570
570
  dependencies = [
571
571
  "anyhow",
572
572
  "chrono",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent"]
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.3.31"
12
+ version = "0.3.32"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
package/README.md CHANGED
@@ -2,202 +2,87 @@
2
2
 
3
3
  # Team Agent
4
4
 
5
- > Talk once. Ship a team.
5
+ > Use Claude Code the way you always do — now lead a whole team.
6
6
 
7
- A multi-agent runtime for Claude Code and Codex CLI where **the lead does the orchestration** — you describe a goal in plain language and it builds a team across providers, runs the work, and reports back.
7
+ ![demo](assets/demo-en.gif)
8
8
 
9
- No DAG. No YAML. No Kanban. Just a conversation.
9
+ ## What is this
10
10
 
11
- ```bash
12
- npx @team-agent/installer@latest install
13
- ```
14
-
15
- **Important:** the lead Claude/Codex conversation must run inside a
16
- tmux-managed pane. The easiest path is `team-agent claude` or
17
- `team-agent codex`; existing tmux layouts also work. A plain terminal without a
18
- tmux pane is not enough, because teammates need a concrete pane target to send
19
- verified messages back to the lead.
20
-
21
- ---
22
-
23
- ## Why this exists
24
-
25
- You have a $20 Claude subscription. It's day 18 of the month. You're out of credits but you still have work to ship.
26
-
27
- You could pay $200. You could switch to a cheaper model and lose Claude's taste. You could wait until the 1st.
28
-
29
- Or you could let Claude stay the lead — designing tasks, coordinating, reviewing — while Codex or a third-party-API-routed Claude handles implementation. Same conversation, ~10x cheaper, no quality loss.
30
-
31
- That's one of the things this is for. **The lead stays Claude. The hands can be anyone.**
32
-
33
- ---
34
-
35
- ## What it actually does
36
-
37
- Install once. Then in any Claude Code or Codex CLI conversation, say something like:
38
-
39
- > "Build a small SaaS for tracking client feedback — backend, frontend, tests, and acceptance criteria."
40
-
41
- The lead figures out:
42
-
43
- - What roles you need (and how rich each role's definition should be)
44
- - Which provider each role should run on (Claude for taste, Codex for logic, third-party for cost)
45
- - How teammates communicate (peer-to-peer, with shared task lists)
46
- - When to escalate decisions back to you
47
-
48
- It then spawns the team, runs the work, and reports back. You stay in the same conversation the whole time. Teammates show up in separate terminal windows so you can watch what each is doing without leaving the lead.
49
-
50
- Close your terminal. Come back tomorrow. The team is still there. Open Claude Code again — pick up where you left off.
51
-
52
- ---
53
-
54
- ## What makes it different
55
-
56
- There are good multi-agent tools today. Each picks a different tradeoff:
57
-
58
- | | Form | You configure | Lead | Where it runs |
59
- | -------------------------- | ------------------ | ------------------------------ | ------------------------------------- | ------------------- |
60
- | **agent-teams-ai** (871★) | Electron app | Roles + provisioning prompt in UI | "CTO" watches Kanban | Desktop app |
61
- | **omo** (54.9k★) | OpenCode plugin | `ultrawork` command word | Sisyphus, fixed roles | OpenCode TUI |
62
- | **CCB** (2.5k★) | CLI + TOML | `.ccb/ccb.config` per team | None (you compose) | tmux |
63
- | **ClawTeam** (3.3k★) | CLI + prompt inject | TOML team templates | None | tmux + Web UI |
64
- | **Team Agent** (this) | MCP runtime | Nothing | The native Claude/Codex you're already talking to | Your existing terminal |
65
-
66
- The lead in this project isn't a special "orchestrator agent" with its own personality. **It's just Claude (or Codex)** — the same one you'd talk to about anything else, now with the ability to spawn and manage teammates.
67
-
68
- This matters because:
69
-
70
- - **Orchestration scales with model intelligence**, not with framework features. When Claude 5 ships, the lead gets smarter automatically. No update required on our side.
71
- - **The lead can build any team for any task**, not just predefined coding roles. We've run academic paper revisions, multi-role brainstorming sessions, even adversarial games like Werewolf — none of them programmed, all of them composed by the lead in conversation.
72
- - **You give up the orchestration UI**, you gain the ability to ship work whose specifics you couldn't have specified up front.
11
+ Right now, when you use Claude Code (or Codex, or Copilot CLI), you have one pair of hands: while it writes the frontend, the backend waits; while it runs tests, you wait.
73
12
 
74
- ---
13
+ With Team Agent installed, it's still the same conversation window, but you can say:
75
14
 
76
- ## How it works (briefly)
15
+ > "This is too slow for one person. Build a team: one for backend, one for frontend, one for tests."
77
16
 
78
- Three design choices make this possible:
17
+ Then:
79
18
 
80
- **1. The orchestration layer is the lead.** No external workflow engine. The lead reasons about role definitions, dispatches via MCP tools, and adjusts the team in real time from your conversation. Add a teammate mid-flow, change someone's role, dissolve a team — all in plain language.
19
+ - New windows pop up, one per teammate, **all working in parallel**
20
+ - Teammates message each other directly (frontend asks backend for the API schema — no need to go through you)
21
+ - You only talk to the lead; the lead reports progress and only escalates when there's a real decision
81
22
 
82
- **2. Transport is infrastructure, identity is persistent.** Teammates run as long-lived `claude` or `codex` processes with stable session IDs. When a window dies, the runtime respawns it and restores state — without that event ever entering any agent's context. Identity is injected at the system-prompt level, not via fragile chat-history hacks.
23
+ No config files. No new UI to learn. If you can chat with Claude, you can run a team.
83
24
 
84
- **3. Standards over inventions.** MCP for tool calls, Skill files for role definitions. Anything the broader ecosystem ships, this picks up automatically.
85
-
86
- ---
87
-
88
- ## Quick start
89
-
90
- ### Install
91
-
92
- ```bash
93
- npx @team-agent/installer@latest install
94
- ```
95
-
96
- This sets up the MCP server, registers the Team Agent skill, and wires it into your Claude Code / Codex CLI config.
97
-
98
- Source checkout install:
99
-
100
- ```bash
101
- git clone https://github.com/Florious95/team-agent.git team-agent
102
- cd team-agent
103
- npm exec --yes --package . -- team-agent-installer install
104
- ```
105
-
106
- ### Windows + WSL
107
-
108
- If you run `npx @team-agent/installer@latest install` from
109
- `/mnt/c/Users/<user>/` and see npm warn
110
- `config prefix cannot be changed from project config`, followed by
111
- `team-agent-installer: not found`, npm did not expose the installer bin shim.
112
-
113
- This is usually caused by a project-level `.npmrc` under `/mnt/c/Users/<user>/`
114
- that contains a `prefix=...` setting. Action: move that setting to `~/.npmrc`,
115
- or delete the project-level `prefix` line, then retry from your Linux home:
25
+ ## Install
116
26
 
117
27
  ```bash
118
- cd ~
119
28
  npx @team-agent/installer@latest install
120
29
  ```
121
30
 
122
- Fallback explicit package form:
123
-
124
- ```bash
125
- npx --package @team-agent/installer team-agent-installer install
126
- ```
127
-
128
- ### Use
129
-
130
- Start the lead inside tmux. The shortcut commands create or attach a tmux
131
- leader session when needed:
31
+ Then start like this (instead of typing `claude` / `codex` / `copilot` directly):
132
32
 
133
33
  ```bash
134
34
  team-agent claude
135
- team-agent codex
136
35
  ```
137
36
 
138
- If you already use tmux/Ghostty/Finder layouts, keep using them; the hard
139
- requirement is that the visible lead conversation has a tmux pane. Then talk to
140
- the lead:
37
+ Two steps. Everything else happens in the conversation.
141
38
 
142
- ```
143
- You: I want to refactor this codebase, split it into a monorepo,
144
- and add proper test coverage. Help me plan and run this.
39
+ ## What you can say
145
40
 
146
- Lead: [proposes a team refactor architect (Claude), code mover (Codex),
147
- test author (Claude), reviewer (Codex). Surfaces the tradeoffs,
148
- waits for your confirmation.]
41
+ Team building and management is all natural language. Some real examples:
149
42
 
150
- You: Go.
151
43
  ```
44
+ "Refactor this codebase into a monorepo and add test coverage. Build a team, show me the plan first."
152
45
 
153
- That's it. With the default display, teammates appear in separate Ghostty
154
- windows. If `display_backend: ghostty_workspace` is set, teammates appear in one
155
- Ghostty window with tmux tabs/windows, up to three side-by-side panes per tab
156
- (`4` workers => `3 + 1`, `8` workers => `3 + 3 + 2`). The lead reports
157
- progress, raises decisions when needed, and shuts everything down when you say
158
- so.
46
+ "Add another person just for code review every merge goes through them."
159
47
 
160
- ### Stop / resume
48
+ "The frontend role isn't working out. Reset it and try a different approach."
161
49
 
162
- ```
163
- You: Close the team for now.
164
- Lead: [Saves state, closes panes. ~2 seconds.]
50
+ "Wrap it up for today." ← Team closes, state saved
165
51
 
166
52
  (next day)
167
-
168
- You: Continue yesterday's refactor team.
169
- Lead: [Restores teammates from saved sessions. ~2 seconds. Same context.]
53
+ "Continue yesterday's refactor team." ← Same people, same memory, pick up where they left off
170
54
  ```
171
55
 
172
- ---
56
+ Teammates aren't limited to coding roles. People have used it for multi-round paper reviews (reviewers critique each other then reach consensus), multi-role brainstorming, even having 5 agents play four rounds of Werewolf autonomously. If you can describe the division of labor, the lead can build the team.
57
+
58
+ ## Teammates can come from different CLIs
173
59
 
174
- ## What works today
60
+ The lead and each teammate independently choose which CLI to use:
175
61
 
176
- Verified across multiple real workflows:
62
+ | | Claude Code | Codex CLI | Copilot CLI |
63
+ | -------- | :--: | :--: | :--: |
64
+ | Lead | ✓ | ✓ | ✓ |
65
+ | Teammate | ✓ | ✓ | ✓ |
177
66
 
178
- - **Cross-vendor mixed teams** Claude leading, Codex implementing, third-party-API Claude on tests
179
- - **Web development teams** — 5 roles: frontend / backend / contract / requirements / testing
180
- - **Academic collaboration** — 5-stage paper revision with adversarial reviewers and consensus mechanism
181
- - **Game / experimental** — 4-round "Who's the Spy" experiments (1 lead + 4 players, fully autonomous, surfaced a real LLM theory-of-mind observation)
182
- - **Emergent recovery** — leads automatically pressing enter on Codex permission prompts; teams surviving pane closures and resuming the next day
183
- - **Dialog-driven team mutation** — adding teammates mid-flow, changing roles, dissolving a team, all without leaving the lead's conversation
67
+ In other words: let Claude be the lead for planning and review, let Codex teammates handle bulk implementation. Mix and match whatever subscriptions you have.
184
68
 
185
- ---
69
+ ## FAQ
186
70
 
187
- ## Supported leads and teammates
71
+ **What do I need?**
72
+ macOS or Linux (including WSL), at least one CLI from the table above, and tmux (`team-agent claude` handles tmux automatically — you don't need to know tmux).
188
73
 
189
- | Role | Claude Code (subscription) | Claude Code (third-party API) | Codex CLI |
190
- | -------- | -------------------------- | ----------------------------- | --------- |
191
- | Lead | ✓ | ✓ | ✓ |
192
- | Teammate | ✓ | ✓ | ✓ |
74
+ **Do I have to watch the teammate windows?**
75
+ No. The windows are there if you want to glance at them. All reports come back to the lead's conversation.
193
76
 
194
- Any teammate can be backed by a different provider/tier than the lead. The runtime handles auth, session lifecycle, and resume per teammate independently.
77
+ **Will the team die if I close the terminal?**
78
+ No. The team keeps running in the background. Reopen your conversation and pick up where you left off.
195
79
 
196
- ---
80
+ **How is this different from Claude Code's built-in subagents?**
81
+ Subagents are one-shot, fire-and-forget, and can't talk to each other. Team Agent teammates are persistent roles: they have their own memory, can message each other, and are still there tomorrow.
197
82
 
198
83
  ## Status
199
84
 
200
- **Beta.** Working in real use, but expect rough edges in less common configurations. Issues and PRs welcome.
85
+ **Beta.** Used daily in real projects. Rough edges possible in uncommon configurations. Issues and PRs welcome.
201
86
 
202
87
  ## License
203
88
 
package/README.zh.md CHANGED
@@ -2,175 +2,88 @@
2
2
 
3
3
  # Team Agent
4
4
 
5
- > 说一次。一个 team 跑起来。
5
+ > 你平时怎么用 Claude Code,现在就怎么带一支团队。
6
6
 
7
- 为 Claude Code 和 Codex CLI 设计的多 agent 运行时。**编排由 lead 自己完成**——你用自然语言描述目标,它跨厂商组建团队、执行工作、汇报结果。
7
+ ![demo](assets/demo-zh.gif)
8
8
 
9
- 没有 DAG。没有 YAML。没有 Kanban。只有对话。
9
+ ## 这是什么
10
10
 
11
- ```bash
12
- npx @team-agent/installer@latest install
13
- ```
14
-
15
- **重要:** 主 Agent,也就是 lead 所在的 Claude/Codex 对话,必须运行在
16
- tmux 管理的 pane 里。最省心的方式是 `team-agent claude` 或
17
- `team-agent codex`;你自己已有的 tmux/Ghostty/Finder 分屏布局也可以。普通非
18
- tmux 终端不够,因为队员向 lead 回传消息时必须有一个明确、可验证的 pane 目标。
19
-
20
- ---
21
-
22
- ## 为什么做这个
23
-
24
- 你有 $20 的 Claude 订阅。月中 18 号,额度用完了,但还有活要交。
25
-
26
- 你可以升级到 $200(贵)。可以切到更便宜的模型(损失 Claude 的品味)。可以等 1 号重置(影响工作)。
27
-
28
- 或者你可以让 Claude 继续当 lead——梳理任务、协调、验收——把执行外包给 Codex 或者第三方 API 接入的 Claude。同一个对话,十分之一的成本,质量不下降。
29
-
30
- 这是这个工具的用法之一。**Lead 还是 Claude,执行者可以是任何人。**
31
-
32
- ---
33
-
34
- ## 它具体在做什么
35
-
36
- 装一次。然后在任何 Claude Code 或 Codex CLI 对话里说类似这样:
37
-
38
- > "做一个小型 SaaS 来追踪客户反馈——前后端、测试、验收标准都要。"
39
-
40
- Lead 自己想清楚:
41
-
42
- - 需要哪些角色,每个角色的定义应该多丰富
43
- - 哪个角色用哪个 provider(Claude 写前端、Codex 写后端逻辑、第三方 API 跑测试节省成本)
44
- - 队员之间怎么通信(P2P + 共享任务列表)
45
- - 什么决策需要回来问你
46
-
47
- 然后它 spawn 队员、跑工作、汇报。**你全程在同一个对话里**。队员在独立终端窗口里跑,你可以随时切过去看每个人在做什么。
48
-
49
- 关电脑回家。明天打开 Claude Code 接着说一句"继续昨天那个 team",team 还在,session 状态完整,从昨晚那里接着干。
50
-
51
- ---
11
+ 你现在用 Claude Code(或 Codex、Copilot CLI),一次对话只有一双手:它写前端的时候,后端等着;它跑测试的时候,你等着。
52
12
 
53
- ## 和现有方案的差异
13
+ 装上 Team Agent 之后,还是那个对话窗口,但你可以说:
54
14
 
55
- 市面上已经有几个好的多 agent 工具,各自选了不同的 trade-off:
15
+ > "这个活一个人干太慢了,组个队:一个写后端,一个写前端,一个专门写测试。"
56
16
 
57
- | | 形态 | 你要配置的东西 | Lead | 跑在哪里 |
58
- | ---------------------------- | ------------------- | ------------------------------ | ----------------------------------- | --------------- |
59
- | **agent-teams-ai** (871★) | Electron 桌面应用 | UI 里写 roles + provisioning prompt | "CTO" 看 Kanban | 桌面应用 |
60
- | **omo** (54.9k★) | OpenCode 插件 | `ultrawork` 命令词 | Sisyphus,角色固定 | OpenCode TUI |
61
- | **CCB** (2.5k★) | CLI + TOML | 每个 team 一份 `.ccb/ccb.config` | 无(你自己组队) | tmux |
62
- | **ClawTeam** (3.3k★) | CLI + prompt 注入 | TOML team 模板 | 无 | tmux + Web UI |
63
- | **Team Agent**(本项目) | MCP 运行时 | 不需要配置 | 你本来就在对话的 Claude/Codex | 你现有的终端 |
17
+ 然后:
64
18
 
65
- 本项目的 lead **不是**一个有特定 personality 的 "orchestrator agent",**就是** Claude(或 Codex)——你平时聊天用的那个,加上 spawn 和管理队员的能力。
19
+ - 屏幕上弹出几个新窗口,每个队友一个,**它们同时开工**
20
+ - 队友之间直接互发消息对齐(前端问后端要接口定义,不用经过你)
21
+ - 你只跟队长说话,队长汇总进度、有分歧才来问你
66
22
 
67
- 为什么这件事重要:
23
+ 整个过程没有配置文件,没有新的界面要学。会跟 Claude 聊天,就会带团队。
68
24
 
69
- - **编排能力随模型能力提升**,不随框架 feature 增加。Claude 5 一出,lead 自动变聪明,不需要我发版。
70
- - **Lead 可以为任何任务组建任何 team**,不限于预设的开发角色。我们已经跑通了学术论文修订、多角色头脑风暴、对抗类游戏("谁是卧底"4 局连测)——这些场景都不是预编程的,**全部是 lead 在对话中现场组建出来的**。
71
- - **你放弃了编排 UI,换来"交付你原本无法精确描述的工作"的能力**。
72
-
73
- ---
74
-
75
- ## 工作原理(简要)
76
-
77
- 三件事让它能跑起来:
78
-
79
- **1. 编排层就是 lead。** 没有外部 workflow engine。Lead 自己推理角色定义、通过 MCP 调度、根据你的对话实时调整 team。运行中加队员、改角色、解散 team——全部用自然语言。
80
-
81
- **2. Transport 是基础设施,身份是持久的。** 队员是长寿命的 `claude` 或 `codex` 子进程,有稳定的 session ID。窗口挂了运行时自己拉起来恢复状态——**整个过程不进入任何 agent 的上下文**。身份在 system prompt 层注入,不是塞在对话历史里的脆弱 hack。
82
-
83
- **3. 用标准协议,不发明协议。** 用 MCP 做工具调用,用 Skill 文件做角色定义。生态发什么,本项目自动获得什么。
84
-
85
- ---
86
-
87
- ## 快速开始
88
-
89
- ### 安装
25
+ ## 安装
90
26
 
91
27
  ```bash
92
28
  npx @team-agent/installer@latest install
93
29
  ```
94
30
 
95
- 会自动设置 MCP server、注册 Team Agent skill、写入 Claude Code / Codex CLI 配置。
96
-
97
- 源码安装:
98
-
99
- ```bash
100
- git clone https://github.com/Florious95/team-agent.git team-agent
101
- cd team-agent
102
- npm exec --yes --package . -- team-agent-installer install
103
- ```
104
-
105
- ### 使用
106
-
107
- 在 tmux 内启动 lead。下面两个快捷命令会在需要时创建或附着到 tmux leader
108
- session:
31
+ 然后这样启动(代替你平时直接敲 `claude` / `codex` / `copilot`):
109
32
 
110
33
  ```bash
111
34
  team-agent claude
112
- team-agent codex
113
35
  ```
114
36
 
115
- 如果你已经有自己的 tmux/Ghostty/Finder 分屏布局,可以继续用;硬要求只是可见的
116
- lead 对话必须有 tmux pane。然后在对话里:
37
+ 就这两步。后面发生的一切都在对话里。
117
38
 
118
- ```
119
- 你: 我想重构这个代码库,拆成 monorepo,加测试覆盖。帮我规划并执行。
39
+ ## 你可以说什么
120
40
 
121
- Lead: [提议一个 team:重构架构师 (Claude)、代码搬运工 (Codex)、
122
- 测试作者 (Claude)、reviewer (Codex)。把 trade-off 说清楚后等你确认]
41
+ 组队和管队全是自然语言,举几个真实的例子:
123
42
 
124
- 你: 开始。
125
43
  ```
44
+ "把这个代码库重构成 monorepo,补齐测试。组个队,规划好了给我看。"
126
45
 
127
- 完事。默认显示模式下,队员会分别出现在独立 Ghostty 窗口。设置
128
- `display_backend: ghostty_workspace` 时,队员会出现在同一个 Ghostty 窗口里,
129
- 用 tmux tab/window 分页,每个 tab 最多三列 pane:4 个队员是 `3 + 1`,8 个队员是
130
- `3 + 3 + 2`。lead 推进工作、需要决策时停下来问你,你说"关掉"就关掉。
46
+ "再加一个人专门做 code review,每个合并前都过它一道。"
131
47
 
132
- ### 关闭 / 恢复
48
+ "前端这个角色干得不行,换个写法重新来。"
133
49
 
134
- ```
135
- 你: 先把 team 关了。
136
- Lead: [保存状态、关闭 pane,约 2 秒]
50
+ "今天先收工。" ← 团队关闭,状态保存
137
51
 
138
52
  (第二天)
139
-
140
- 你: 继续昨天那个重构 team。
141
- Lead: [从保存的 session 恢复队员,约 2 秒,同样的上下文]
53
+ "继续昨天的重构团队。" ← 原班人马带着记忆回来
142
54
  ```
143
55
 
144
- ---
56
+ 队友不限于写代码的角色。有人用它跑过论文多轮评审(几个评审员互相挑刺再合议)、多角色头脑风暴,甚至让 5 个 agent 自主玩了四轮谁是卧底。你能描述出来的分工,队长就能组出来。
57
+
58
+ ## 队友可以来自不同的 CLI
145
59
 
146
- ## 当前已验证的能力
60
+ 队长和每个队友各自独立选择用哪个 CLI:
147
61
 
148
- 在多种真实工作流下验证过:
62
+ | | Claude Code | Codex CLI | Copilot CLI |
63
+ | ---- | :--: | :--: | :--: |
64
+ | 队长 | ✓ | ✓ | ✓ |
65
+ | 队友 | ✓ | ✓ | ✓ |
149
66
 
150
- - **跨厂商混合 team**——Claude lead,Codex 实现,第三方 API 接入的 Claude 跑测试
151
- - **Web 开发 team**——5 角色:前端 / 后端 / 契约 / 需求分析 / 测试
152
- - **学术协作**——5 阶段论文修订工作流,审稿人和研究者对抗 + 共识机制
153
- - **博弈 / 实验**——4 局"谁是卧底"实验(1 lead + 4 玩家全自治,意外跑出了关于 LLM theory-of-mind 的真实观察)
154
- - **Emergent 恢复**——lead 自己识别 Codex 权限确认 prompt 并按 enter;窗口关闭后第二天接着用
155
- - **对话驱动的 team 变更**——运行中加队员、改角色、解散 team,全在 lead 的对话里完成
67
+ 也就是说:你可以让 Claude 当队长出方案、做评审,让 Codex 队友批量干实现。你手上有哪几家的订阅,就能怎么搭着用。
156
68
 
157
- ---
69
+ ## 常见问题
158
70
 
159
- ## 支持的 lead 和 teammate
71
+ **需要什么前提?**
72
+ macOS 或 Linux(含 WSL),装好至少一个上面表里的 CLI,有 tmux(`team-agent claude` 会自动处理,你不需要会用 tmux)。
160
73
 
161
- | 角色 | Claude Code(订阅制) | Claude Code(第三方 API) | Codex CLI |
162
- | -------- | -------------------- | ------------------------ | --------- |
163
- | Lead | ✓ | ✓ | ✓ |
164
- | Teammate | ✓ | ✓ | ✓ |
74
+ **队友的窗口我必须盯着吗?**
75
+ 不用。窗口只是给你随时看一眼的,所有汇报都会回到队长对话里。
165
76
 
166
- 任何队员可以用和 lead 不同的 provider/tier。运行时分别处理每个队员的认证、session 生命周期、resume。
77
+ **关掉终端团队会死吗?**
78
+ 不会。团队在后台继续,重新打开对话接着聊就行。
167
79
 
168
- ---
80
+ **它和 Claude Code 自带的 subagent 有什么区别?**
81
+ subagent 是一次性的、干完即焚、彼此不通信。Team Agent 的队友是持续存在的角色:有自己的记忆、能互相对话、明天还在。
169
82
 
170
- ## 项目状态
83
+ ## 状态
171
84
 
172
- **Beta**。已经在真实工作流里跑通,但不常见的配置可能有粗糙的地方。欢迎 issue 和 PR。
85
+ **Beta。** 已在真实项目里日常使用,小众配置下可能有毛边。欢迎 issue 和 PR。
173
86
 
174
87
  ## License
175
88
 
176
- AGPL-3.0-or-later。商业 license 可联系协商。
89
+ AGPL-3.0-or-later,商业授权可联系。
@@ -67,12 +67,9 @@ pub fn leader_start_plan(
67
67
  Some(leader_session_name(provider, workspace))
68
68
  };
69
69
  let in_tmux = std::env::var_os("TMUX").is_some();
70
- if !in_tmux || !external_path {
70
+ if !in_tmux {
71
71
  ensure_tmux_installed()?;
72
72
  }
73
- if !external_path {
74
- validate_managed_tmux_context(workspace)?;
75
- }
76
73
  let existing_session = if external_path && !in_tmux && !attach_existing && attach_session.is_none() {
77
74
  match session_name.as_ref() {
78
75
  Some(session) => tmux_session_exists(workspace, session)?,
@@ -81,7 +78,9 @@ pub fn leader_start_plan(
81
78
  } else {
82
79
  false
83
80
  };
84
- let mode = if !external_path {
81
+ let mode = if !external_path && in_tmux {
82
+ LeaderStartMode::ExecProvider
83
+ } else if !external_path {
85
84
  LeaderStartMode::ManagedTmuxClient
86
85
  } else if in_tmux {
87
86
  LeaderStartMode::ExecProvider
@@ -99,6 +98,11 @@ pub fn leader_start_plan(
99
98
  session_name.as_ref(),
100
99
  &leader_env,
101
100
  )?;
101
+ let plan_session_name = if mode == LeaderStartMode::ExecProvider && !external_path {
102
+ None
103
+ } else {
104
+ session_name
105
+ };
102
106
  let plan_env = if mode == LeaderStartMode::ExecProvider {
103
107
  merged_exec_env(&leader_env)
104
108
  } else {
@@ -110,7 +114,7 @@ pub fn leader_start_plan(
110
114
  provider,
111
115
  workspace: resolve_workspace_for_hash(workspace),
112
116
  socket: LeaderLaunchSocket::Workspace,
113
- session_name,
117
+ session_name: plan_session_name,
114
118
  argv,
115
119
  provider_argv,
116
120
  // 0.3.28 Step 2: leader window inside the dedicated leader session is
@@ -208,7 +212,9 @@ pub fn execute_leader_plan(
208
212
  let detached = plan.mode == LeaderStartMode::NewTmuxSession
209
213
  && !std::io::stdin().is_terminal()
210
214
  && insert_detach_flag(&mut argv);
211
- if plan.is_external_leader {
215
+ if plan.mode == LeaderStartMode::ExecProvider && !plan.is_external_leader {
216
+ persist_exec_provider_leader_binding(plan, workspace)?;
217
+ } else if plan.is_external_leader {
212
218
  persist_external_leader_topology_marker(plan, workspace)?;
213
219
  }
214
220
  let status = run_leader_argv(&argv, &plan.leader_env, plan, workspace)?;
@@ -372,27 +378,6 @@ fn managed_client_argv(
372
378
  Ok(TmuxBackend::argv_for_workspace(workspace, &argv))
373
379
  }
374
380
 
375
- fn validate_managed_tmux_context(workspace: &Path) -> Result<(), LeaderError> {
376
- let Some(current_endpoint) = crate::tmux_backend::socket_name_from_tmux_env() else {
377
- return Ok(());
378
- };
379
- let expected = crate::tmux_backend::socket_name_for_workspace(workspace);
380
- if tmux_endpoint_matches_socket_name(&current_endpoint, &expected) {
381
- return Ok(());
382
- }
383
- Err(LeaderError::Start(format!(
384
- "Error: managed leader launch refused inside a different tmux server\nReason: current TMUX socket is {current_endpoint}, but this team uses {expected}\nAction: detach and run from a normal shell, or use --external-leader for the external leader topology"
385
- )))
386
- }
387
-
388
- fn tmux_endpoint_matches_socket_name(endpoint: &str, socket_name: &str) -> bool {
389
- endpoint == socket_name
390
- || Path::new(endpoint)
391
- .file_name()
392
- .and_then(|name| name.to_str())
393
- == Some(socket_name)
394
- }
395
-
396
381
  fn execute_managed_leader_plan(
397
382
  plan: &LeaderStartPlan,
398
383
  workspace: &Path,
@@ -557,6 +542,116 @@ fn persist_managed_leader_binding(
557
542
  Ok(())
558
543
  }
559
544
 
545
+ fn persist_exec_provider_leader_binding(
546
+ plan: &LeaderStartPlan,
547
+ workspace: &Path,
548
+ ) -> Result<(), LeaderError> {
549
+ let identity = plan
550
+ .identity
551
+ .as_ref()
552
+ .ok_or_else(|| LeaderError::Start("exec provider leader identity missing".to_string()))?;
553
+ let pane = std::env::var("TMUX_PANE")
554
+ .ok()
555
+ .filter(|value| !value.trim().is_empty())
556
+ .ok_or_else(|| LeaderError::Start("exec provider leader pane missing".to_string()))?;
557
+ let pane_id = PaneId::new(pane.clone());
558
+ let target = current_tmux_pane_info(&pane_id);
559
+ let mut state = crate::state::persist::load_runtime_state(workspace)
560
+ .unwrap_or_else(|_| serde_json::json!({}));
561
+ let owner_epoch = state
562
+ .get("owner_epoch")
563
+ .and_then(serde_json::Value::as_u64)
564
+ .or_else(|| {
565
+ state
566
+ .get("team_owner")
567
+ .and_then(|owner| owner.get("owner_epoch"))
568
+ .and_then(serde_json::Value::as_u64)
569
+ })
570
+ .unwrap_or(0)
571
+ .saturating_add(1);
572
+ let now = chrono::Utc::now().to_rfc3339();
573
+ let socket = crate::tmux_backend::socket_name_from_tmux_env();
574
+ let provider = serde_json::to_value(plan.provider)?;
575
+ let mut receiver = serde_json::json!({
576
+ "mode": "direct_tmux",
577
+ "status": "attached",
578
+ "provider": provider.clone(),
579
+ "pane_id": pane,
580
+ "pane": pane,
581
+ "leader_session_uuid": identity.leader_session_uuid,
582
+ "owner_epoch": owner_epoch,
583
+ "attached_at": now,
584
+ "discovery": "current_pane",
585
+ });
586
+ if let Some(target) = target.as_ref() {
587
+ if let Some(obj) = receiver.as_object_mut() {
588
+ obj.insert("session_name".to_string(), serde_json::json!(target.session.as_str()));
589
+ if let Some(window_name) = target.window_name.as_ref() {
590
+ obj.insert("window_name".to_string(), serde_json::json!(window_name.as_str()));
591
+ }
592
+ }
593
+ }
594
+ if let Some(socket) = socket.as_ref() {
595
+ if let Some(obj) = receiver.as_object_mut() {
596
+ obj.insert("tmux_socket".to_string(), serde_json::json!(socket));
597
+ }
598
+ }
599
+ let owner = serde_json::json!({
600
+ "pane_id": pane,
601
+ "provider": provider.clone(),
602
+ "machine_fingerprint": identity.machine_fingerprint,
603
+ "leader_session_uuid": identity.leader_session_uuid,
604
+ "owner_epoch": owner_epoch,
605
+ "claimed_at": now,
606
+ "claimed_via": "claim-leader",
607
+ "os_user": identity.os_user,
608
+ });
609
+ if let Some(obj) = state.as_object_mut() {
610
+ obj.insert(
611
+ "active_team_key".to_string(),
612
+ serde_json::json!(identity.team_id.as_str()),
613
+ );
614
+ if let Some(target) = target.as_ref() {
615
+ obj.insert("session_name".to_string(), serde_json::json!(target.session.as_str()));
616
+ }
617
+ if let Some(socket) = socket.as_ref() {
618
+ obj.insert("tmux_endpoint".to_string(), serde_json::json!(socket));
619
+ obj.insert("tmux_socket".to_string(), serde_json::json!(socket));
620
+ }
621
+ obj.insert("is_external_leader".to_string(), serde_json::json!(false));
622
+ obj.insert(
623
+ "leader_client".to_string(),
624
+ serde_json::json!({
625
+ "diagnostic_only": true,
626
+ "attach_mode": "exec-provider",
627
+ "tmux": std::env::var("TMUX").ok(),
628
+ }),
629
+ );
630
+ obj.insert("leader_receiver".to_string(), receiver);
631
+ obj.insert("team_owner".to_string(), owner);
632
+ obj.insert("owner_epoch".to_string(), serde_json::json!(owner_epoch));
633
+ }
634
+ let entry = crate::state::projection::compact_team_state(&state);
635
+ if let Some(obj) = state.as_object_mut() {
636
+ let teams = obj
637
+ .entry("teams".to_string())
638
+ .or_insert_with(|| serde_json::json!({}));
639
+ if let Some(teams) = teams.as_object_mut() {
640
+ teams.insert(identity.team_id.as_str().to_string(), entry);
641
+ }
642
+ }
643
+ crate::state::persist::save_runtime_state(workspace, &state)?;
644
+ Ok(())
645
+ }
646
+
647
+ fn current_tmux_pane_info(pane_id: &PaneId) -> Option<crate::transport::PaneInfo> {
648
+ tmux_transport_for_current_pane()
649
+ .list_targets()
650
+ .ok()?
651
+ .into_iter()
652
+ .find(|target| target.pane_id == *pane_id)
653
+ }
654
+
560
655
  fn persist_external_leader_topology_marker(
561
656
  plan: &LeaderStartPlan,
562
657
  workspace: &Path,
@@ -968,6 +1063,41 @@ mod tests {
968
1063
  }
969
1064
  }
970
1065
 
1066
+ struct EnvGuard {
1067
+ saved: Vec<(&'static str, Option<String>)>,
1068
+ }
1069
+
1070
+ impl EnvGuard {
1071
+ fn set(vars: &[(&'static str, Option<&str>)]) -> Self {
1072
+ let saved = vars
1073
+ .iter()
1074
+ .map(|(key, _)| (*key, std::env::var(key).ok()))
1075
+ .collect::<Vec<_>>();
1076
+ for (key, value) in vars {
1077
+ unsafe {
1078
+ match value {
1079
+ Some(value) => std::env::set_var(key, value),
1080
+ None => std::env::remove_var(key),
1081
+ }
1082
+ }
1083
+ }
1084
+ Self { saved }
1085
+ }
1086
+ }
1087
+
1088
+ impl Drop for EnvGuard {
1089
+ fn drop(&mut self) {
1090
+ for (key, value) in self.saved.drain(..).rev() {
1091
+ unsafe {
1092
+ match value {
1093
+ Some(value) => std::env::set_var(key, value),
1094
+ None => std::env::remove_var(key),
1095
+ }
1096
+ }
1097
+ }
1098
+ }
1099
+ }
1100
+
971
1101
  #[test]
972
1102
  fn external_exec_provider_persists_topology_before_provider_exec() {
973
1103
  let workspace = std::env::temp_dir().join(format!(
@@ -1019,6 +1149,79 @@ mod tests {
1019
1149
  let _ = std::fs::remove_dir_all(&workspace);
1020
1150
  }
1021
1151
 
1152
+ #[test]
1153
+ #[serial_test::serial(env)]
1154
+ fn default_exec_provider_persists_current_pane_binding_before_provider_exec() {
1155
+ let workspace = std::env::temp_dir().join(format!(
1156
+ "ta-current-pane-pre-exec-{}",
1157
+ std::process::id()
1158
+ ));
1159
+ std::fs::create_dir_all(&workspace).unwrap();
1160
+ let state_path = crate::state::persist::runtime_state_path(&workspace);
1161
+ let command = format!(
1162
+ "test -f {path} && grep -q leader_receiver {path}",
1163
+ path = shlex_quote(&state_path.to_string_lossy())
1164
+ );
1165
+ let _env = EnvGuard::set(&[
1166
+ ("TMUX", Some("/private/tmp/tmux-501/default,88432,187")),
1167
+ ("TMUX_PANE", Some("%77")),
1168
+ ]);
1169
+ let identity = LeaderIdentity {
1170
+ leader_session_uuid: LeaderSessionUuid::derive(
1171
+ "fp",
1172
+ &workspace.to_string_lossy(),
1173
+ "tester",
1174
+ "current",
1175
+ )
1176
+ .unwrap(),
1177
+ leader_session_uuid_source: LeaderSessionUuidSource::Derived,
1178
+ machine_fingerprint: "fp".to_string(),
1179
+ workspace_abspath: workspace.clone(),
1180
+ os_user: "tester".to_string(),
1181
+ team_id: TeamKey::new("current"),
1182
+ };
1183
+ let plan = LeaderStartPlan {
1184
+ mode: LeaderStartMode::ExecProvider,
1185
+ provider: Provider::Fake,
1186
+ workspace: workspace.clone(),
1187
+ socket: LeaderLaunchSocket::Workspace,
1188
+ session_name: None,
1189
+ argv: vec!["sh".to_string(), "-c".to_string(), command],
1190
+ provider_argv: vec!["fake".to_string()],
1191
+ leader_window: None,
1192
+ is_external_leader: false,
1193
+ leader_env: BTreeMap::new(),
1194
+ identity: Some(identity),
1195
+ detached: false,
1196
+ };
1197
+
1198
+ let outcome = execute_leader_plan(&plan, &workspace)
1199
+ .expect("current pane binding must be present before provider argv runs");
1200
+
1201
+ assert_eq!(outcome.status, crate::leader::LeaderLaunchStatus::Exited);
1202
+ let state = crate::state::persist::load_runtime_state(&workspace).unwrap();
1203
+ assert_eq!(state["is_external_leader"], serde_json::json!(false));
1204
+ assert_eq!(state["leader_receiver"]["pane_id"], serde_json::json!("%77"));
1205
+ assert_eq!(
1206
+ state["leader_receiver"]["tmux_socket"],
1207
+ serde_json::json!("/private/tmp/tmux-501/default")
1208
+ );
1209
+ assert_eq!(state["team_owner"]["pane_id"], serde_json::json!("%77"));
1210
+ assert_eq!(
1211
+ state["teams"]["current"]["leader_receiver"]["pane_id"],
1212
+ serde_json::json!("%77")
1213
+ );
1214
+ assert_eq!(
1215
+ state["teams"]["current"]["team_owner"]["pane_id"],
1216
+ serde_json::json!("%77")
1217
+ );
1218
+ assert_eq!(
1219
+ state["leader_client"]["attach_mode"],
1220
+ serde_json::json!("exec-provider")
1221
+ );
1222
+ let _ = std::fs::remove_dir_all(&workspace);
1223
+ }
1224
+
1022
1225
  #[test]
1023
1226
  fn exec_provider_leader_startup_prompt_handler_reuses_copilot_adapter() {
1024
1227
  let transport = ScriptedTransport::new(vec![
@@ -163,50 +163,45 @@ use super::*;
163
163
 
164
164
  #[test]
165
165
  #[serial_test::serial(env)]
166
- fn managed_leader_in_same_tmux_server_switches_client() {
166
+ fn in_tmux_default_leader_runs_provider_in_current_pane() {
167
167
  let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
168
168
  let ws = std::env::temp_dir().join(format!("ta_rs_lsp_switch_{}", std::process::id()));
169
169
  std::fs::create_dir_all(&ws).unwrap();
170
170
  let socket = crate::tmux_backend::socket_name_for_workspace(&ws);
171
171
  let endpoint = format!("/private/tmp/tmux-501/{socket},88432,187");
172
- let _e = EnvGuard::apply(&[("TMUX", Some(&endpoint))]);
172
+ let _e = EnvGuard::apply(&[("TMUX", Some(&endpoint)), ("TMUX_PANE", Some("%7"))]);
173
173
 
174
174
  let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
175
175
 
176
- assert_eq!(plan.mode, LeaderStartMode::ManagedTmuxClient);
177
- assert!(
178
- plan.argv.iter().any(|arg| arg == "switch-client"),
179
- "same-server managed launch must switch the existing tmux client: {:?}",
180
- plan.argv
181
- );
182
- // 0.3.28 Step 2: target is `<dedicated_leader_session>:<provider_wire>`,
183
- // not `team-current:leader`.
184
- let session_name = plan.session_name.as_ref().map(SessionName::as_str).unwrap_or("");
185
- let expected_target = format!("{session_name}:fake");
176
+ assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
177
+ assert!(!plan.is_external_leader);
178
+ assert!(plan.session_name.is_none());
179
+ assert_eq!(plan.leader_window, None);
180
+ assert_eq!(plan.argv, vec!["fake".to_string()]);
186
181
  assert!(
187
- plan.argv.iter().any(|arg| arg == &expected_target),
188
- "managed switch target must be the dedicated leader-session leader \
189
- window `{expected_target}`; got argv {:?}",
182
+ !plan.argv.iter().any(|arg| arg == "switch-client" || arg == "attach-session"),
183
+ "in-tmux default launch must not create or attach a background leader session: {:?}",
190
184
  plan.argv
191
185
  );
192
186
  }
193
187
 
194
188
  #[test]
195
189
  #[serial_test::serial(env)]
196
- fn managed_leader_in_different_tmux_server_refuses_with_n38_hint() {
190
+ fn in_tmux_default_leader_does_not_refuse_different_tmux_server() {
197
191
  let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
198
- let _e = EnvGuard::apply(&[("TMUX", Some("/private/tmp/tmux-501/default,88432,187"))]);
192
+ let _e = EnvGuard::apply(&[
193
+ ("TMUX", Some("/private/tmp/tmux-501/default,88432,187")),
194
+ ("TMUX_PANE", Some("%9")),
195
+ ]);
199
196
  let ws = std::env::temp_dir().join(format!("ta_rs_lsp_refuse_{}", std::process::id()));
200
197
  std::fs::create_dir_all(&ws).unwrap();
201
198
 
202
- let err = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false)
203
- .unwrap_err()
204
- .to_string();
199
+ let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
205
200
 
206
- assert!(err.contains("Error:"), "{err}");
207
- assert!(err.contains("Reason:"), "{err}");
208
- assert!(err.contains("Action:"), "{err}");
209
- assert!(err.contains("--external-leader"), "{err}");
201
+ assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
202
+ assert!(!plan.is_external_leader);
203
+ assert!(plan.session_name.is_none());
204
+ assert_eq!(plan.argv, vec!["fake".to_string()]);
210
205
  }
211
206
 
212
207
  #[test]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.3.31",
3
+ "version": "0.3.32",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -20,9 +20,9 @@
20
20
  "team-agent-installer": "npm/install.mjs"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@team-agent/cli-darwin-arm64": "0.3.31",
24
- "@team-agent/cli-darwin-x64": "0.3.31",
25
- "@team-agent/cli-linux-x64": "0.3.31"
23
+ "@team-agent/cli-darwin-arm64": "0.3.32",
24
+ "@team-agent/cli-darwin-x64": "0.3.32",
25
+ "@team-agent/cli-linux-x64": "0.3.32"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",