@team-agent/installer 0.1.0 → 0.1.2
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/LICENSE +661 -0
- package/README.md +114 -136
- package/README.zh.md +175 -0
- package/docs/README.md +10 -0
- package/docs/team-agent-foundation-and-boundaries.md +292 -0
- package/package.json +17 -2
- package/src/team_agent/__init__.py +1 -1
- package/src/team_agent/mcp_server.py +1 -1
- package/src/team_agent/runtime.py +24 -6
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Team Agent 基础设施原点与边界
|
|
2
|
+
|
|
3
|
+
生成日期:2026-05-14
|
|
4
|
+
|
|
5
|
+
本文是 Team Agent 后续设计、开发、验收和提示词优化的核心约束文档。后续 goal、skill、runtime、provider adapter、display backend 和文档改动不得违背本文。
|
|
6
|
+
|
|
7
|
+
## 1. 功能原点
|
|
8
|
+
|
|
9
|
+
Team Agent 的原点不是让大模型变聪明,也不是预设一套固定团队模板。大模型本身已经足够强,能够通过对话完成需求澄清、角色定义、任务拆解和结果判断。
|
|
10
|
+
|
|
11
|
+
Team Agent 要做的是基础设施:
|
|
12
|
+
|
|
13
|
+
1. 让不同 CLI agent 能稳定互相通信。
|
|
14
|
+
2. 让不同 provider、模型、订阅制资源和 API 资源能被统一编排。
|
|
15
|
+
3. 让 agent 的上下文、会话、产物、状态和交接能被保存、恢复和观测。
|
|
16
|
+
4. 让主 leader 的上下文尽量只承载业务决策,不被底层脚本、协议、日志和重试细节污染。
|
|
17
|
+
5. 让团队启动和运行具备可见、可操作、可诊断的使用体验。
|
|
18
|
+
|
|
19
|
+
因此本项目的核心定位是:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
跨 CLI、跨订阅/API、可恢复、可运维的 Agent 团队基础设施。
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
不是:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
固定角色模板库。
|
|
29
|
+
固定任务计划引擎。
|
|
30
|
+
Claude Team Agent 的简单复刻。
|
|
31
|
+
Web UI 产品。
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 2. 不替代模型决策
|
|
35
|
+
|
|
36
|
+
Team Agent 不应把模型已经能做好的事情写成死流程。
|
|
37
|
+
|
|
38
|
+
不应固化:
|
|
39
|
+
|
|
40
|
+
1. 某类任务一定要哪些角色。
|
|
41
|
+
2. 某个团队必须按固定计划执行。
|
|
42
|
+
3. 某个角色必须使用固定提示词模板。
|
|
43
|
+
4. 默认恢复旧会话或默认新开会话。
|
|
44
|
+
5. observer、reviewer、QA 等角色必须自动开启。
|
|
45
|
+
|
|
46
|
+
应提供:
|
|
47
|
+
|
|
48
|
+
1. 事实:已有角色、会话、上下文占用、上次任务、handoff、provider 状态、额度/成本提示。
|
|
49
|
+
2. 工具:校验、编译、启动、通信、恢复、关闭、compact、diagnose。
|
|
50
|
+
3. 约束:secret 不泄露、控制平面不污染上下文、投递状态可验证。
|
|
51
|
+
|
|
52
|
+
leader 根据事实和用户目标做判断,必要时向用户确认。
|
|
53
|
+
|
|
54
|
+
## 3. 用户可见的一等入口
|
|
55
|
+
|
|
56
|
+
用户可见的一等入口应是文档和对话,不是机器 manifest。
|
|
57
|
+
|
|
58
|
+
推荐的用户可见输入包括:
|
|
59
|
+
|
|
60
|
+
1. 需求分析文档。
|
|
61
|
+
2. 团队决策文档。
|
|
62
|
+
3. 角色定义文档。
|
|
63
|
+
4. provider/profile 选择说明。
|
|
64
|
+
5. 交接文档和恢复记录。
|
|
65
|
+
|
|
66
|
+
`team.spec.yaml` 或未来的 runtime manifest 应被视为编译产物。它是机器执行入口,不应要求用户或 leader 一上来手写长配置。
|
|
67
|
+
|
|
68
|
+
计划不是长期一等资产。计划会随任务推进变化,可以由 leader 临时生成、调整、废弃。角色定义、团队边界、provider/profile 选择、会话恢复信息更适合作为可复用资产。
|
|
69
|
+
|
|
70
|
+
## 4. 角色定义边界
|
|
71
|
+
|
|
72
|
+
角色定义由 leader 和用户通过对话形成。Team Agent 不负责判断角色是否“业务上最优”,只负责让已形成的角色定义可执行。
|
|
73
|
+
|
|
74
|
+
角色文档应能表达:
|
|
75
|
+
|
|
76
|
+
1. 名称和职责。
|
|
77
|
+
2. provider 和模型选择。
|
|
78
|
+
3. 授权模式:订阅制、官方 API、第三方 API compatible。
|
|
79
|
+
4. profile/credential 引用。
|
|
80
|
+
5. 工具权限。
|
|
81
|
+
6. 上下文边界。
|
|
82
|
+
7. 输出契约。
|
|
83
|
+
|
|
84
|
+
角色文档可以由 leader 创建,也可以由用户长期维护。系统只做结构校验和运行时编译。
|
|
85
|
+
|
|
86
|
+
## 5. Provider、Model 和资源编排
|
|
87
|
+
|
|
88
|
+
Team Agent 的重要亮点是模型资源编排,而不是简单支持多个 provider。
|
|
89
|
+
|
|
90
|
+
系统必须支持以下组合:
|
|
91
|
+
|
|
92
|
+
1. Claude 订阅制作为 leader,Codex 订阅制作为代码实现 worker。
|
|
93
|
+
2. Codex 订阅制作为 leader,Claude 订阅制作为分析、文档或评审 worker。
|
|
94
|
+
3. Codex/Claude 订阅制与第三方 API compatible 模型混合使用。
|
|
95
|
+
4. 低成本模型作为执行 worker,高能力模型作为澄清、编排、审查和纠偏者。
|
|
96
|
+
5. observer 角色使用订阅制下富余模型额度,例如低成本代码审查、可维护性检查、提交前检查。
|
|
97
|
+
|
|
98
|
+
这里的产品价值是:
|
|
99
|
+
|
|
100
|
+
1. 更充分利用订阅制资源。
|
|
101
|
+
2. 让不同模型能力互补,实现 1+1>2。
|
|
102
|
+
3. 让主节点优化低成本模型的输入、过程和输出,从而提高低成本模型的实际效果。
|
|
103
|
+
|
|
104
|
+
provider/model 配置必须区分:
|
|
105
|
+
|
|
106
|
+
1. CLI 外壳:Codex CLI、Claude Code CLI 等。
|
|
107
|
+
2. 授权模式:subscription、official_api、compatible_api。
|
|
108
|
+
3. 模型名称。
|
|
109
|
+
4. base URL。
|
|
110
|
+
5. credential 引用。
|
|
111
|
+
|
|
112
|
+
secret 不得写入角色文档或 team manifest 明文。系统可以生成空白 profile 文件模板,让用户自行填写 API key。真实 profile 文件必须默认被 `.gitignore` 忽略。runtime 只能报告 secret 是否存在、是否可用,不应把 secret 内容注入 agent 上下文或日志。
|
|
113
|
+
|
|
114
|
+
## 6. Agent 与 MCP/Runtime 职责边界
|
|
115
|
+
|
|
116
|
+
Agent 只负责表达语义。
|
|
117
|
+
|
|
118
|
+
例如 worker 只需要表达:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
send_message(to="leader", task_id="...", content="...")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
worker 不负责:
|
|
125
|
+
|
|
126
|
+
1. leader pane 是哪个。
|
|
127
|
+
2. 需要按 Tab 还是 Enter。
|
|
128
|
+
3. 是否需要重新 attach。
|
|
129
|
+
4. 是否需要重试。
|
|
130
|
+
5. tmux/Ghostty/PTY 是否成功。
|
|
131
|
+
6. delivery 状态如何验证。
|
|
132
|
+
|
|
133
|
+
MCP/runtime 负责可靠投递:
|
|
134
|
+
|
|
135
|
+
1. 接收语义。
|
|
136
|
+
2. 写入 SQLite/event log。
|
|
137
|
+
3. 发现真实目标。
|
|
138
|
+
4. 校验目标身份。
|
|
139
|
+
5. 渲染用户友好的上屏内容。
|
|
140
|
+
6. 注入到正确 CLI。
|
|
141
|
+
7. 验证是否上屏。
|
|
142
|
+
8. 失败时重试或返回结构化错误。
|
|
143
|
+
|
|
144
|
+
任何鲁棒性逻辑都不应要求 worker 或 leader 在自然语言上下文里手动执行底层协议。
|
|
145
|
+
|
|
146
|
+
## 7. 控制平面与模型上下文
|
|
147
|
+
|
|
148
|
+
必须区分控制平面和模型上下文。
|
|
149
|
+
|
|
150
|
+
控制平面包括:
|
|
151
|
+
|
|
152
|
+
1. message_id。
|
|
153
|
+
2. task_id。
|
|
154
|
+
3. sender/recipient。
|
|
155
|
+
4. pane/terminal id。
|
|
156
|
+
5. delivery status。
|
|
157
|
+
6. retry 记录。
|
|
158
|
+
7. diagnose 细节。
|
|
159
|
+
8. provider/profile 状态。
|
|
160
|
+
9. session id 和恢复信息。
|
|
161
|
+
|
|
162
|
+
这些信息应保存在 SQLite、event log、state 文件和诊断报告中。
|
|
163
|
+
|
|
164
|
+
模型上下文只应接收必要业务内容。默认不应把完整 JSON 协议直接注入给 leader 或 worker。
|
|
165
|
+
|
|
166
|
+
推荐 leader 上屏格式:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
Team Agent message from backend_developer for task_backend_build:
|
|
170
|
+
|
|
171
|
+
<content>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
raw JSON 只用于 DB、event log、debug 模式或测试,不作为默认上屏内容。
|
|
175
|
+
|
|
176
|
+
## 8. 可靠送达的业务定义
|
|
177
|
+
|
|
178
|
+
可靠送达不应等同于 tmux 命令返回成功。
|
|
179
|
+
|
|
180
|
+
至少区分以下状态:
|
|
181
|
+
|
|
182
|
+
1. `accepted`:MCP/runtime 已接受消息并写入存储。
|
|
183
|
+
2. `target_resolved`:runtime 找到了当前可信目标。
|
|
184
|
+
3. `injected`:文本已写入目标终端。
|
|
185
|
+
4. `visible`:capture 或等价机制验证消息已在目标 CLI 上屏或排队。
|
|
186
|
+
5. `consumed`:目标 agent 已处理该消息。这个状态需要 ack 或可识别行为确认,不作为第一阶段硬承诺。
|
|
187
|
+
6. `failed`:明确失败。
|
|
188
|
+
7. `ambiguous`:发现多个候选目标,不能自动判断。
|
|
189
|
+
|
|
190
|
+
短期核心验收应达到 `visible`。不能把 `injected` 直接称为 `delivered`。
|
|
191
|
+
|
|
192
|
+
## 9. 会话生命周期与恢复
|
|
193
|
+
|
|
194
|
+
Team Agent 应记录每个 agent 的上下文空间和会话身份,使下一次任务可以选择恢复或新开。
|
|
195
|
+
|
|
196
|
+
runtime 应提供事实:
|
|
197
|
+
|
|
198
|
+
1. agent id。
|
|
199
|
+
2. provider/model/profile。
|
|
200
|
+
3. session id 或 resume id。
|
|
201
|
+
4. context usage。
|
|
202
|
+
5. last task。
|
|
203
|
+
6. status。
|
|
204
|
+
7. handoff path。
|
|
205
|
+
8. terminal target。
|
|
206
|
+
|
|
207
|
+
恢复策略不应硬编码。leader 根据事实判断:
|
|
208
|
+
|
|
209
|
+
1. 直接恢复旧 agent。
|
|
210
|
+
2. 基于 handoff 新开 agent。
|
|
211
|
+
3. 只恢复部分角色。
|
|
212
|
+
4. 向用户确认。
|
|
213
|
+
|
|
214
|
+
## 10. 可见可操作的显示体验
|
|
215
|
+
|
|
216
|
+
Team Agent 启动团队时,应尽量提供可见、可操作、具有视觉冲击的体验。
|
|
217
|
+
|
|
218
|
+
用户期望:
|
|
219
|
+
|
|
220
|
+
1. 开启 Team Agent 时 worker 窗口自动弹出。
|
|
221
|
+
2. 每个 worker 是干净、原生、可滚动、可操作的 CLI 界面。
|
|
222
|
+
3. 不要求用户学习 tmux 快捷键在多 window/pane 间切换。
|
|
223
|
+
4. 多角色团队可以先实现每个 agent 一个 Ghostty 窗口,后续再实现 tab/多列布局。
|
|
224
|
+
|
|
225
|
+
tmux 可以继续作为控制层和 headless backend,但不应强迫用户把 tmux UI 当成主要展示层。如果 tmux UI 破坏 Codex/Claude TUI 的渲染、滚动或输入体验,应优先探索 Ghostty 原生窗口、PTY target 或其他 display backend。
|
|
226
|
+
|
|
227
|
+
## 11. 前期准备脚本化
|
|
228
|
+
|
|
229
|
+
主 leader 上下文浪费主要来自底层脚本和观察结果,而不是来自角色思考本身。
|
|
230
|
+
|
|
231
|
+
应将以下多步操作合并为事务命令:
|
|
232
|
+
|
|
233
|
+
1. provider/profile preflight。
|
|
234
|
+
2. role docs 结构校验。
|
|
235
|
+
3. manifest 编译。
|
|
236
|
+
4. launch dry-run。
|
|
237
|
+
5. leader attach。
|
|
238
|
+
6. worker readiness 检查。
|
|
239
|
+
7. MCP approval 预检。
|
|
240
|
+
8. status/diagnose 汇总。
|
|
241
|
+
|
|
242
|
+
事务命令应返回短摘要,长日志留在文件中。
|
|
243
|
+
|
|
244
|
+
示例:
|
|
245
|
+
|
|
246
|
+
```text
|
|
247
|
+
team-agent preflight --team .team/current
|
|
248
|
+
team-agent start --team .team/current --yes
|
|
249
|
+
team-agent wait-ready --timeout 120 --json
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
leader 只需要读结论,不需要把每个底层命令输出塞进上下文。
|
|
253
|
+
|
|
254
|
+
## 12. 后续模块拆分方向
|
|
255
|
+
|
|
256
|
+
建议模块边界:
|
|
257
|
+
|
|
258
|
+
1. Authoring input:读取需求、团队、角色和 profile 文档。
|
|
259
|
+
2. Compiler:把用户可读文档编译为 runtime manifest。
|
|
260
|
+
3. Provider adapter:处理 Codex/Claude 启动、授权模式、MCP 注入、模型/profile。
|
|
261
|
+
4. Terminal controller:处理 tmux、PTY、Ghostty target、注入和 capture。
|
|
262
|
+
5. Message bus:处理 SQLite、消息状态、重试、ack。
|
|
263
|
+
6. Delivery verifier:验证目标和上屏。
|
|
264
|
+
7. Prompt renderer:把结构化消息渲染为人类可读内容。
|
|
265
|
+
8. Session lifecycle:记录 resume、handoff、compact、shutdown。
|
|
266
|
+
9. Display backend:处理可见窗口、布局、标题和用户操作体验。
|
|
267
|
+
|
|
268
|
+
Rust 迁移应在模块边界稳定后逐步进行。优先迁移底层稳定模块,例如 terminal controller、message bus、delivery verifier、provider adapter core。角色协商、文档生成、提示词渲染等变化快的部分不应急于 Rust 化。
|
|
269
|
+
|
|
270
|
+
## 13. 非目标
|
|
271
|
+
|
|
272
|
+
当前非目标:
|
|
273
|
+
|
|
274
|
+
1. 预设完整角色模板市场。
|
|
275
|
+
2. 替用户决定所有团队结构。
|
|
276
|
+
3. 固化一次任务的执行计划。
|
|
277
|
+
4. 把所有 provider 组合做成排列组合式 demo。
|
|
278
|
+
5. 用 UI 产品替代 CLI-native 工作流。
|
|
279
|
+
6. 让 agent 自己在上下文里学习底层运维协议。
|
|
280
|
+
7. 把 secret 写进可提交文件或 agent 上下文。
|
|
281
|
+
|
|
282
|
+
## 14. 后续工作必须遵守
|
|
283
|
+
|
|
284
|
+
任何后续 goal 文档和实现必须说明:
|
|
285
|
+
|
|
286
|
+
1. 是否保持 Agent/MCP/runtime 职责边界。
|
|
287
|
+
2. 是否避免控制平面污染模型上下文。
|
|
288
|
+
3. 是否把用户可读文档和机器 manifest 解耦。
|
|
289
|
+
4. 是否保护 secret。
|
|
290
|
+
5. 是否保留 provider/model/profile 的可组合性。
|
|
291
|
+
6. 是否减少主 leader 上下文消耗。
|
|
292
|
+
7. 是否提升或至少不破坏 CLI 可见操作体验。
|
package/package.json
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"codex",
|
|
7
|
+
"claude",
|
|
8
|
+
"tmux",
|
|
9
|
+
"multi-agent"
|
|
10
|
+
],
|
|
5
11
|
"bin": {
|
|
6
12
|
"team-agent-installer": "npm/install.mjs"
|
|
7
13
|
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "PYTHONPATH=src python3 tests/run_tests.py",
|
|
16
|
+
"test:regression": "PYTHONPATH=src python3 scripts/run_regression_tests.py --iterations 3",
|
|
17
|
+
"test:rust": "cargo test --manifest-path crates/team-agent-core/Cargo.toml",
|
|
18
|
+
"pack:dry-run": "npm pack --dry-run"
|
|
19
|
+
},
|
|
8
20
|
"files": [
|
|
9
21
|
"npm",
|
|
10
22
|
"scripts",
|
|
@@ -13,10 +25,13 @@
|
|
|
13
25
|
"templates",
|
|
14
26
|
"examples",
|
|
15
27
|
"schemas",
|
|
28
|
+
"docs/team-agent-foundation-and-boundaries.md",
|
|
16
29
|
"crates/team-agent-core/Cargo.toml",
|
|
17
30
|
"crates/team-agent-core/src",
|
|
18
31
|
"pyproject.toml",
|
|
19
32
|
"README.md",
|
|
33
|
+
"README.zh.md",
|
|
34
|
+
"LICENSE",
|
|
20
35
|
"!**/__pycache__/**",
|
|
21
36
|
"!**/*.pyc",
|
|
22
37
|
"!**/target/**"
|
|
@@ -24,5 +39,5 @@
|
|
|
24
39
|
"engines": {
|
|
25
40
|
"node": ">=18"
|
|
26
41
|
},
|
|
27
|
-
"license": "
|
|
42
|
+
"license": "AGPL-3.0-or-later"
|
|
28
43
|
}
|
|
@@ -505,7 +505,7 @@ def handle_mcp(tools: TeamOrchestratorTools, request: dict[str, Any]) -> dict[st
|
|
|
505
505
|
"result": {
|
|
506
506
|
"protocolVersion": request.get("params", {}).get("protocolVersion", "2024-11-05"),
|
|
507
507
|
"capabilities": {"tools": {}},
|
|
508
|
-
"serverInfo": {"name": "team_orchestrator", "version": "0.1.
|
|
508
|
+
"serverInfo": {"name": "team_orchestrator", "version": "0.1.2"},
|
|
509
509
|
},
|
|
510
510
|
}
|
|
511
511
|
if method == "tools/list":
|
|
@@ -170,6 +170,8 @@ def _process_info(pid: int) -> dict[str, Any] | None:
|
|
|
170
170
|
|
|
171
171
|
def init_workspace(workspace: Path, force: bool = False) -> dict[str, Path]:
|
|
172
172
|
ensure_workspace_dirs(workspace)
|
|
173
|
+
team_dir = workspace / ".team" / "current"
|
|
174
|
+
team_dir.mkdir(parents=True, exist_ok=True)
|
|
173
175
|
spec_path = team_dir / "team.spec.yaml"
|
|
174
176
|
state_path = workspace / "team_state.md"
|
|
175
177
|
from team_agent.paths import example_path, template_path
|
|
@@ -939,10 +941,12 @@ def _attach_leader_to_state(
|
|
|
939
941
|
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
940
942
|
get_adapter(provider)
|
|
941
943
|
pane_info, discovery = _resolve_leader_pane(pane, provider, workspace=workspace, require_current=require_current)
|
|
944
|
+
inferred_provider = _leader_command_provider(pane_info.get("pane_current_command", ""))
|
|
945
|
+
receiver_provider = inferred_provider or provider
|
|
942
946
|
receiver = {
|
|
943
947
|
"mode": "direct_tmux",
|
|
944
948
|
"status": "attached",
|
|
945
|
-
"provider":
|
|
949
|
+
"provider": receiver_provider,
|
|
946
950
|
"pane_id": pane_info["pane_id"],
|
|
947
951
|
"session_name": pane_info["session_name"],
|
|
948
952
|
"window_index": pane_info["window_index"],
|
|
@@ -954,6 +958,8 @@ def _attach_leader_to_state(
|
|
|
954
958
|
"attached_at": datetime.now(timezone.utc).isoformat(),
|
|
955
959
|
"discovery": discovery,
|
|
956
960
|
}
|
|
961
|
+
if receiver_provider != provider:
|
|
962
|
+
receiver["requested_provider"] = provider
|
|
957
963
|
validation = _validate_leader_receiver(receiver)
|
|
958
964
|
if not validation["ok"]:
|
|
959
965
|
event_log.write(
|
|
@@ -978,7 +984,8 @@ def _attach_leader_to_state(
|
|
|
978
984
|
pane_index=receiver["pane_index"],
|
|
979
985
|
pane_tty=receiver["pane_tty"],
|
|
980
986
|
pane_current_command=receiver["pane_current_command"],
|
|
981
|
-
provider=
|
|
987
|
+
provider=receiver_provider,
|
|
988
|
+
requested_provider=provider if receiver_provider != provider else None,
|
|
982
989
|
discovery=discovery,
|
|
983
990
|
source=source,
|
|
984
991
|
)
|
|
@@ -3744,12 +3751,12 @@ def _resolve_leader_pane(
|
|
|
3744
3751
|
details = (
|
|
3745
3752
|
f" Current tmux client points at pane {pane_info.get('pane_id')} "
|
|
3746
3753
|
f"command={pane_info.get('pane_current_command')!r} "
|
|
3747
|
-
f"cwd={pane_info.get('pane_current_path')!r}, not
|
|
3754
|
+
f"cwd={pane_info.get('pane_current_path')!r}, not a usable pane for this workspace."
|
|
3748
3755
|
)
|
|
3749
3756
|
raise RuntimeError(
|
|
3750
3757
|
"Team Agent could not locate a tmux-managed leader pane for this workspace. "
|
|
3751
|
-
"
|
|
3752
|
-
|
|
3758
|
+
"Run quick-start from the visible tmux-managed leader pane, pass --pane explicitly, "
|
|
3759
|
+
"or use `team-agent codex`/`team-agent claude` as a convenience fallback."
|
|
3753
3760
|
+ details
|
|
3754
3761
|
)
|
|
3755
3762
|
if pane_info and workspace is None:
|
|
@@ -3822,6 +3829,7 @@ def _infer_workspace_tmux_pane(provider: str, workspace: Path) -> dict[str, Any]
|
|
|
3822
3829
|
pane
|
|
3823
3830
|
for pane in workspace_panes
|
|
3824
3831
|
if _leader_command_looks_usable(pane.get("pane_current_command", ""), provider)
|
|
3832
|
+
or _leader_command_provider(pane.get("pane_current_command", "")) is not None
|
|
3825
3833
|
]
|
|
3826
3834
|
if not candidates:
|
|
3827
3835
|
return {"status": "missing", "workspace_panes": workspace_panes}
|
|
@@ -3834,7 +3842,8 @@ def _infer_workspace_tmux_pane(provider: str, workspace: Path) -> dict[str, Any]
|
|
|
3834
3842
|
|
|
3835
3843
|
|
|
3836
3844
|
def _pane_is_usable_leader(pane: dict[str, str], provider: str, workspace: Path | None) -> bool:
|
|
3837
|
-
|
|
3845
|
+
command = pane.get("pane_current_command", "")
|
|
3846
|
+
if not _leader_command_looks_usable(command, provider) and _leader_command_provider(command) is None:
|
|
3838
3847
|
return False
|
|
3839
3848
|
if workspace is not None and not _pane_path_matches_workspace(pane, workspace):
|
|
3840
3849
|
return False
|
|
@@ -3872,6 +3881,15 @@ def _leader_command_is_exact(command: str, provider: str) -> bool:
|
|
|
3872
3881
|
return provider == "fake"
|
|
3873
3882
|
|
|
3874
3883
|
|
|
3884
|
+
def _leader_command_provider(command: str) -> str | None:
|
|
3885
|
+
command_name = Path(command).name
|
|
3886
|
+
if command_name in {"codex", "node", "nodejs"}:
|
|
3887
|
+
return "codex"
|
|
3888
|
+
if command_name in {"claude", "claude.exe"}:
|
|
3889
|
+
return "claude_code"
|
|
3890
|
+
return None
|
|
3891
|
+
|
|
3892
|
+
|
|
3875
3893
|
def _format_leader_pane_candidates(candidates: list[dict[str, str]]) -> str:
|
|
3876
3894
|
compact = []
|
|
3877
3895
|
for pane in candidates[:5]:
|