@team-agent/installer 0.3.31 → 0.3.33
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 +1 -1
- package/Cargo.toml +1 -1
- package/README.md +40 -155
- package/README.zh.md +42 -129
- package/crates/team-agent/src/leader/start.rs +231 -28
- package/crates/team-agent/src/leader/tests/identity.rs +19 -24
- package/crates/team-agent/src/lifecycle/profile_launch.rs +3 -1
- package/package.json +4 -4
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -2,202 +2,87 @@
|
|
|
2
2
|
|
|
3
3
|
# Team Agent
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> Use Claude Code the way you always do — now lead a whole team.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+

|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## What is this
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
|
|
15
|
+
> "This is too slow for one person. Build a team: one for backend, one for frontend, one for tests."
|
|
77
16
|
|
|
78
|
-
|
|
17
|
+
Then:
|
|
79
18
|
|
|
80
|
-
|
|
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
|
-
|
|
23
|
+
No config files. No new UI to learn. If you can chat with Claude, you can run a team.
|
|
83
24
|
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
60
|
+
The lead and each teammate independently choose which CLI to use:
|
|
175
61
|
|
|
176
|
-
|
|
62
|
+
| | Claude Code | Codex CLI | Copilot CLI |
|
|
63
|
+
| -------- | :--: | :--: | :--: |
|
|
64
|
+
| Lead | ✓ | ✓ | ✓ |
|
|
65
|
+
| Teammate | ✓ | ✓ | ✓ |
|
|
177
66
|
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.**
|
|
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
|
-
>
|
|
5
|
+
> 你平时怎么用 Claude Code,现在就怎么带一支团队。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+

|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## 这是什么
|
|
10
10
|
|
|
11
|
-
|
|
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
|
-
|
|
15
|
+
> "这个活一个人干太慢了,组个队:一个写后端,一个写前端,一个专门写测试。"
|
|
56
16
|
|
|
57
|
-
|
|
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
|
-
|
|
19
|
+
- 屏幕上弹出几个新窗口,每个队友一个,**它们同时开工**
|
|
20
|
+
- 队友之间直接互发消息对齐(前端问后端要接口定义,不用经过你)
|
|
21
|
+
- 你只跟队长说话,队长汇总进度、有分歧才来问你
|
|
66
22
|
|
|
67
|
-
|
|
23
|
+
整个过程没有配置文件,没有新的界面要学。会跟 Claude 聊天,就会带团队。
|
|
68
24
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
lead 对话必须有 tmux pane。然后在对话里:
|
|
37
|
+
就这两步。后面发生的一切都在对话里。
|
|
117
38
|
|
|
118
|
-
|
|
119
|
-
你: 我想重构这个代码库,拆成 monorepo,加测试覆盖。帮我规划并执行。
|
|
39
|
+
## 你可以说什么
|
|
120
40
|
|
|
121
|
-
|
|
122
|
-
测试作者 (Claude)、reviewer (Codex)。把 trade-off 说清楚后等你确认]
|
|
41
|
+
组队和管队全是自然语言,举几个真实的例子:
|
|
123
42
|
|
|
124
|
-
你: 开始。
|
|
125
43
|
```
|
|
44
|
+
"把这个代码库重构成 monorepo,补齐测试。组个队,规划好了给我看。"
|
|
126
45
|
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
+
**需要什么前提?**
|
|
72
|
+
macOS 或 Linux(含 WSL),装好至少一个上面表里的 CLI,有 tmux(`team-agent claude` 会自动处理,你不需要会用 tmux)。
|
|
160
73
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
| Lead | ✓ | ✓ | ✓ |
|
|
164
|
-
| Teammate | ✓ | ✓ | ✓ |
|
|
74
|
+
**队友的窗口我必须盯着吗?**
|
|
75
|
+
不用。窗口只是给你随时看一眼的,所有汇报都会回到队长对话里。
|
|
165
76
|
|
|
166
|
-
|
|
77
|
+
**关掉终端团队会死吗?**
|
|
78
|
+
不会。团队在后台继续,重新打开对话接着聊就行。
|
|
167
79
|
|
|
168
|
-
|
|
80
|
+
**它和 Claude Code 自带的 subagent 有什么区别?**
|
|
81
|
+
subagent 是一次性的、干完即焚、彼此不通信。Team Agent 的队友是持续存在的角色:有自己的记忆、能互相对话、明天还在。
|
|
169
82
|
|
|
170
|
-
##
|
|
83
|
+
## 状态
|
|
171
84
|
|
|
172
|
-
**Beta
|
|
85
|
+
**Beta。** 已在真实项目里日常使用,小众配置下可能有毛边。欢迎 issue 和 PR。
|
|
173
86
|
|
|
174
87
|
## License
|
|
175
88
|
|
|
176
|
-
AGPL-3.0-or-later
|
|
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
|
|
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(¤t_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
|
|
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::
|
|
177
|
-
assert!(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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 ==
|
|
188
|
-
"
|
|
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
|
|
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(&[
|
|
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
|
|
203
|
-
.unwrap_err()
|
|
204
|
-
.to_string();
|
|
199
|
+
let plan = leader_start_plan(Provider::Fake, &[], &ws, false, false, None, false).unwrap();
|
|
205
200
|
|
|
206
|
-
|
|
207
|
-
assert!(
|
|
208
|
-
assert!(
|
|
209
|
-
|
|
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]
|
|
@@ -157,7 +157,9 @@ fn prepare_profile_launch(
|
|
|
157
157
|
let mut claude_projects_root = None;
|
|
158
158
|
let mut managed_mcp_config = false;
|
|
159
159
|
|
|
160
|
-
if matches!(agent.provider, Provider::Claude | Provider::ClaudeCode)
|
|
160
|
+
if matches!(agent.provider, Provider::Claude | Provider::ClaudeCode)
|
|
161
|
+
&& agent.auth_mode == AuthMode::CompatibleApi
|
|
162
|
+
{
|
|
161
163
|
let dir = compatible_claude_config_dir(workspace, &agent.id)?;
|
|
162
164
|
if let Some(config) = mcp_config {
|
|
163
165
|
ensure_compatible_claude_mcp_config(&dir, workspace, config)?;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.33",
|
|
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.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.3.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.3.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.3.33",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.3.33",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.3.33"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|