@team-agent/installer 0.4.6 → 0.4.7
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/crates/team-agent/src/cli/adapters.rs +6 -1
- package/crates/team-agent/src/cli/status_port.rs +119 -66
- package/crates/team-agent/src/cli/tests/status_send.rs +74 -48
- package/crates/team-agent/src/coordinator/tick.rs +2 -21
- package/crates/team-agent/src/leader/helpers.rs +1 -22
- package/crates/team-agent/src/lifecycle/launch.rs +1 -11
- package/crates/team-agent/src/lifecycle/profile_launch.rs +1 -11
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -22
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +11 -7
- package/crates/team-agent/src/provider/adapter.rs +18 -372
- package/crates/team-agent/src/provider/adapters/claude.rs +106 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +117 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +124 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +17 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +27 -0
- package/crates/team-agent/src/provider/mod.rs +6 -0
- package/crates/team-agent/src/provider/wire.rs +139 -0
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +6 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
//! Copilot provider-local command builders + permission helpers.
|
|
2
|
+
//!
|
|
3
|
+
//! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2). Pure
|
|
4
|
+
//! extraction — byte-identical to the original inline forms. Scope kept
|
|
5
|
+
//! small: base command + resume + permission flags + MCP type→transport
|
|
6
|
+
//! translation. Auth hint (`copilot_auth_hint`) and session store scan
|
|
7
|
+
//! (`scan_copilot_session_store`, `copilot_candidate`) stay in
|
|
8
|
+
//! `adapter.rs` because they depend on `command_on_path` /
|
|
9
|
+
//! `CaptureSessionContext` / sqlite session-store helpers. Step 3 of the
|
|
10
|
+
//! decoupling plan can move those into provider session store hooks.
|
|
11
|
+
|
|
12
|
+
use crate::model::enums::AuthMode;
|
|
13
|
+
use crate::provider::McpConfig;
|
|
14
|
+
|
|
15
|
+
pub(crate) fn copilot_base_command(
|
|
16
|
+
auth_mode: AuthMode,
|
|
17
|
+
mcp_config: Option<&McpConfig>,
|
|
18
|
+
system_prompt: Option<&str>,
|
|
19
|
+
model: Option<&str>,
|
|
20
|
+
tools: &[&str],
|
|
21
|
+
) -> Vec<String> {
|
|
22
|
+
let _ = (auth_mode, system_prompt);
|
|
23
|
+
let mut argv = vec![
|
|
24
|
+
"copilot".to_string(),
|
|
25
|
+
// Noise control trio + disable remote control (防 GitHub web 远控 worker)
|
|
26
|
+
"--no-color".to_string(),
|
|
27
|
+
"--no-auto-update".to_string(),
|
|
28
|
+
"--no-remote".to_string(),
|
|
29
|
+
// P0: disable built-in github-mcp-server. Residual risk covered by
|
|
30
|
+
// spawn-time `copilot mcp list` scan + per-name --disable-mcp-server.
|
|
31
|
+
"--disable-builtin-mcps".to_string(),
|
|
32
|
+
];
|
|
33
|
+
if copilot_dangerous_auto_approve(tools) {
|
|
34
|
+
argv.push("--allow-all".to_string());
|
|
35
|
+
} else {
|
|
36
|
+
for flag in copilot_permission_flags(tools) {
|
|
37
|
+
argv.push(flag);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// mcp_team ∈ canonical → approval-free (whole-server pattern).
|
|
41
|
+
argv.push("--allow-tool".to_string());
|
|
42
|
+
argv.push("team_orchestrator".to_string());
|
|
43
|
+
if let Some(model) = model {
|
|
44
|
+
argv.push("--model".to_string());
|
|
45
|
+
argv.push(model.to_string());
|
|
46
|
+
}
|
|
47
|
+
if let Some(config) = mcp_config {
|
|
48
|
+
// Copilot mcp config schema field is `transport` (stdio|http|sse),
|
|
49
|
+
// not the canonical `type`. McpConfig.raw is canonical; only copilot
|
|
50
|
+
// translates type→transport for --additional-mcp-config.
|
|
51
|
+
argv.push("--additional-mcp-config".to_string());
|
|
52
|
+
argv.push(copilot_translate_mcp_config(&config.raw).to_string());
|
|
53
|
+
}
|
|
54
|
+
argv
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Translate `McpConfig.raw` canonical schema (`type`) into the copilot
|
|
58
|
+
/// `mcp add`/`--additional-mcp-config` expected `transport` field
|
|
59
|
+
/// (stdio|http|sse). Only the copilot adapter walks this translation —
|
|
60
|
+
/// claude/codex paths leave canonical schema untouched.
|
|
61
|
+
pub(crate) fn copilot_translate_mcp_config(raw: &serde_json::Value) -> serde_json::Value {
|
|
62
|
+
let Some(servers) = raw.as_object() else {
|
|
63
|
+
return raw.clone();
|
|
64
|
+
};
|
|
65
|
+
let mut translated = serde_json::Map::new();
|
|
66
|
+
for (name, server) in servers {
|
|
67
|
+
let Some(obj) = server.as_object() else {
|
|
68
|
+
translated.insert(name.clone(), server.clone());
|
|
69
|
+
continue;
|
|
70
|
+
};
|
|
71
|
+
let mut out = serde_json::Map::new();
|
|
72
|
+
for (key, value) in obj {
|
|
73
|
+
if key == "type" {
|
|
74
|
+
out.insert("transport".to_string(), value.clone());
|
|
75
|
+
} else {
|
|
76
|
+
out.insert(key.clone(), value.clone());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
translated.insert(name.clone(), serde_json::Value::Object(out));
|
|
80
|
+
}
|
|
81
|
+
serde_json::Value::Object(translated)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Resume path = base + `--resume <sid>` (drop --session-id); kept separate
|
|
85
|
+
/// to avoid the plan accidentally emitting --session-id and --resume in the
|
|
86
|
+
/// same frame.
|
|
87
|
+
pub(crate) fn copilot_base_command_resume(
|
|
88
|
+
auth_mode: AuthMode,
|
|
89
|
+
mcp_config: Option<&McpConfig>,
|
|
90
|
+
system_prompt: Option<&str>,
|
|
91
|
+
model: Option<&str>,
|
|
92
|
+
tools: &[&str],
|
|
93
|
+
) -> Vec<String> {
|
|
94
|
+
copilot_base_command(auth_mode, mcp_config, system_prompt, model, tools)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
pub(crate) fn copilot_dangerous_auto_approve(tools: &[&str]) -> bool {
|
|
98
|
+
tools.contains(&"dangerous_auto_approve")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Granular deny mapping (canonical tool → copilot flag, all via
|
|
102
|
+
/// `--deny-tool <kind>`; help-permissions Tool Permissions has four kinds:
|
|
103
|
+
/// shell/write/mcp/url):
|
|
104
|
+
/// execute_bash ∉ allowed → `--deny-tool 'shell'`
|
|
105
|
+
/// fs_write ∉ allowed → `--deny-tool 'write'`
|
|
106
|
+
/// network ∉ allowed → `--deny-tool 'url'`
|
|
107
|
+
/// fs_read/fs_list have no copilot deny kind (honestly prompt_only).
|
|
108
|
+
pub(crate) fn copilot_permission_flags(tools: &[&str]) -> Vec<String> {
|
|
109
|
+
let mut flags = Vec::new();
|
|
110
|
+
if !tools.contains(&"execute_bash") {
|
|
111
|
+
flags.push("--deny-tool".to_string());
|
|
112
|
+
flags.push("shell".to_string());
|
|
113
|
+
}
|
|
114
|
+
if !tools.contains(&"fs_write") {
|
|
115
|
+
flags.push("--deny-tool".to_string());
|
|
116
|
+
flags.push("write".to_string());
|
|
117
|
+
}
|
|
118
|
+
if !tools.contains(&"network") {
|
|
119
|
+
// `--deny-tool 'url'` (omit domain → match all URLs).
|
|
120
|
+
flags.push("--deny-tool".to_string());
|
|
121
|
+
flags.push("url".to_string());
|
|
122
|
+
}
|
|
123
|
+
flags
|
|
124
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//! Fake provider — scripted worker built into the team-agent CLI itself.
|
|
2
|
+
//! Extracted from `provider/adapter.rs` (0.4.x decoupling step 2).
|
|
3
|
+
|
|
4
|
+
pub(crate) fn fake_worker_command() -> Vec<String> {
|
|
5
|
+
let exe = std::env::current_exe()
|
|
6
|
+
.ok()
|
|
7
|
+
.and_then(|p| p.into_os_string().into_string().ok())
|
|
8
|
+
.unwrap_or_else(|| "team-agent".to_string());
|
|
9
|
+
vec![
|
|
10
|
+
exe,
|
|
11
|
+
"fake-worker".to_string(),
|
|
12
|
+
"--workspace".to_string(),
|
|
13
|
+
"{workspace}".to_string(),
|
|
14
|
+
"--agent-id".to_string(),
|
|
15
|
+
"{agent_id}".to_string(),
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//! Provider-local adapter implementations. Split from the monolithic
|
|
2
|
+
//! `provider/adapter.rs` as 0.4.x decoupling step 2. Each file owns its
|
|
3
|
+
//! provider's command builders, permission/sandbox/auth helpers, and
|
|
4
|
+
//! anything else that varies by provider but doesn't need shared
|
|
5
|
+
//! capture/scan utilities. The `ProviderAdapter` trait and the
|
|
6
|
+
//! `BasicProviderAdapter` registry stay in `adapter.rs`; the trait impl
|
|
7
|
+
//! dispatches into these helpers exactly as the inline forms did.
|
|
8
|
+
//!
|
|
9
|
+
//! Per-file scope:
|
|
10
|
+
//! * `claude` — Claude/ClaudeCode argv, dangerous-skip flag, disallowed
|
|
11
|
+
//! tools mapping, auth hint, model passthrough.
|
|
12
|
+
//! * `codex` — Codex argv, profile/sandbox flags, MCP `-c` injection
|
|
13
|
+
//! with 600s tool_timeout, developer-instructions escaping.
|
|
14
|
+
//! * `copilot` — Copilot argv (no-color/no-remote/disable-builtin-mcps),
|
|
15
|
+
//! allow/deny flag matrix, MCP `type→transport` translation, resume
|
|
16
|
+
//! base, weak auth hint.
|
|
17
|
+
//! * `fake` — Built-in scripted worker exec path.
|
|
18
|
+
//!
|
|
19
|
+
//! Behavior is byte-identical to pre-split. Future steps may move
|
|
20
|
+
//! `pre_spawn_adjust_plan`, `profile_env`, `session_backing_probe`, and
|
|
21
|
+
//! `session_candidates` into provider-owned hooks following the same
|
|
22
|
+
//! per-file layout.
|
|
23
|
+
|
|
24
|
+
pub(crate) mod claude;
|
|
25
|
+
pub(crate) mod codex;
|
|
26
|
+
pub(crate) mod copilot;
|
|
27
|
+
pub(crate) mod fake;
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
pub use crate::model::enums::{AuthMode, Provider};
|
|
33
33
|
|
|
34
34
|
pub mod adapter;
|
|
35
|
+
/// 0.4.x decoupling step 2: per-provider command builders + permission/auth
|
|
36
|
+
/// helpers split out of `adapter.rs`. Pure extraction — behavior unchanged.
|
|
37
|
+
pub(crate) mod adapters;
|
|
35
38
|
pub mod approvals;
|
|
36
39
|
pub mod classify;
|
|
37
40
|
pub mod command;
|
|
@@ -40,6 +43,9 @@ pub mod session;
|
|
|
40
43
|
pub mod session_scan;
|
|
41
44
|
pub mod startup_prompt;
|
|
42
45
|
pub mod types;
|
|
46
|
+
/// 0.4.x: provider wire-format helpers — single source of truth for
|
|
47
|
+
/// `Provider` ↔ string conversions (replaces 7 hand-rolled match copies).
|
|
48
|
+
pub(crate) mod wire;
|
|
43
49
|
// helpers 全部原为模块私有(JSONL 解析 / 正则编译),非根可见 → 私有 mod,不参与 re-export。
|
|
44
50
|
mod helpers;
|
|
45
51
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
//! Provider wire-format helpers — the single source of truth for converting
|
|
2
|
+
//! `Provider` ↔ string forms used across state JSON, CLI args, log fields,
|
|
3
|
+
//! and the workers we shell out to.
|
|
4
|
+
//!
|
|
5
|
+
//! This module replaces 7 hand-rolled copies of these match arms that lived
|
|
6
|
+
//! in `leader/helpers.rs`, `coordinator/tick.rs` (×2), `lifecycle/restart/common.rs`,
|
|
7
|
+
//! `lifecycle/profile_launch.rs`, `lifecycle/launch.rs`, `provider/adapter.rs`,
|
|
8
|
+
//! and `lifecycle/restart/rebuild.rs`. Centralising them removes the kind of
|
|
9
|
+
//! drift that left `restart/rebuild.rs` with a `claude-code` alias the other
|
|
10
|
+
//! sites silently dropped, and shrinks the surface area when a new provider
|
|
11
|
+
//! is added.
|
|
12
|
+
//!
|
|
13
|
+
//! Wire-format invariants (do not change without a coordinated migration):
|
|
14
|
+
//! * `provider_wire(Provider)` returns the canonical snake_case string used
|
|
15
|
+
//! in state JSON. These values are persisted on disk and read by older
|
|
16
|
+
//! runtimes — changing them is a breaking change.
|
|
17
|
+
//! * `parse_provider(&str)` accepts the canonical form AND every historical
|
|
18
|
+
//! alias we have ever written. New aliases go in `aliases()` and become
|
|
19
|
+
//! a parse target automatically.
|
|
20
|
+
//! * `command_name(Provider)` returns the binary name we exec for that
|
|
21
|
+
//! provider. `Claude` and `ClaudeCode` both shell out to `claude` —
|
|
22
|
+
//! they are distinct state values but share an executable.
|
|
23
|
+
|
|
24
|
+
use crate::model::enums::Provider;
|
|
25
|
+
|
|
26
|
+
/// Canonical snake_case wire string for `Provider`. Used for state JSON,
|
|
27
|
+
/// log fields, and any cross-process serialization. Stable: do not change
|
|
28
|
+
/// existing values.
|
|
29
|
+
pub(crate) fn provider_wire(provider: Provider) -> &'static str {
|
|
30
|
+
match provider {
|
|
31
|
+
Provider::Claude => "claude",
|
|
32
|
+
Provider::ClaudeCode => "claude_code",
|
|
33
|
+
Provider::Codex => "codex",
|
|
34
|
+
Provider::Copilot => "copilot",
|
|
35
|
+
Provider::GeminiCli => "gemini_cli",
|
|
36
|
+
Provider::Fake => "fake",
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Parse a wire string back to `Provider`. Accepts the canonical
|
|
41
|
+
/// `provider_wire` output AND every historical alias listed by `aliases()`.
|
|
42
|
+
/// Returns `None` for unknown strings.
|
|
43
|
+
pub(crate) fn parse_provider(raw: &str) -> Option<Provider> {
|
|
44
|
+
for &provider in ALL_PROVIDERS {
|
|
45
|
+
for alias in aliases(provider) {
|
|
46
|
+
if *alias == raw {
|
|
47
|
+
return Some(provider);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
None
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// All aliases (including the canonical wire form) that parse to `provider`.
|
|
55
|
+
/// The canonical wire form is always the first entry, so callers that need
|
|
56
|
+
/// the "primary" string can take `aliases(p)[0]` — though most code should
|
|
57
|
+
/// use `provider_wire(p)` directly for clarity.
|
|
58
|
+
pub(crate) fn aliases(provider: Provider) -> &'static [&'static str] {
|
|
59
|
+
match provider {
|
|
60
|
+
Provider::Claude => &["claude"],
|
|
61
|
+
// `claude-code` (kebab) is a historical alias that appeared in
|
|
62
|
+
// restart/rebuild.rs:1186-1194 only. Keeping it parseable means
|
|
63
|
+
// legacy state files still load.
|
|
64
|
+
Provider::ClaudeCode => &["claude_code", "claude-code"],
|
|
65
|
+
Provider::Codex => &["codex"],
|
|
66
|
+
Provider::Copilot => &["copilot"],
|
|
67
|
+
Provider::GeminiCli => &["gemini_cli"],
|
|
68
|
+
Provider::Fake => &["fake"],
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Executable name we exec for the worker. `Claude` and `ClaudeCode` both
|
|
73
|
+
/// resolve to `claude` — the distinction is in state semantics
|
|
74
|
+
/// (`ClaudeCode` is the canonical CLI; `Claude` is a legacy alias kept for
|
|
75
|
+
/// older specs).
|
|
76
|
+
pub(crate) fn command_name(provider: Provider) -> &'static str {
|
|
77
|
+
match provider {
|
|
78
|
+
Provider::Claude | Provider::ClaudeCode => "claude",
|
|
79
|
+
Provider::Codex => "codex",
|
|
80
|
+
Provider::Copilot => "copilot",
|
|
81
|
+
Provider::GeminiCli => "gemini",
|
|
82
|
+
Provider::Fake => "team-agent",
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const ALL_PROVIDERS: &[Provider] = &[
|
|
87
|
+
Provider::Claude,
|
|
88
|
+
Provider::ClaudeCode,
|
|
89
|
+
Provider::Codex,
|
|
90
|
+
Provider::Copilot,
|
|
91
|
+
Provider::GeminiCli,
|
|
92
|
+
Provider::Fake,
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
#[cfg(test)]
|
|
96
|
+
mod tests {
|
|
97
|
+
use super::*;
|
|
98
|
+
|
|
99
|
+
#[test]
|
|
100
|
+
fn wire_round_trip_for_every_provider() {
|
|
101
|
+
for &p in ALL_PROVIDERS {
|
|
102
|
+
let wire = provider_wire(p);
|
|
103
|
+
assert_eq!(parse_provider(wire), Some(p), "round-trip {p:?}");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#[test]
|
|
108
|
+
fn claude_code_kebab_alias_parses() {
|
|
109
|
+
// Historical alias preserved from restart/rebuild.rs:1186-1194.
|
|
110
|
+
assert_eq!(parse_provider("claude-code"), Some(Provider::ClaudeCode));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#[test]
|
|
114
|
+
fn unknown_string_returns_none() {
|
|
115
|
+
assert_eq!(parse_provider(""), None);
|
|
116
|
+
assert_eq!(parse_provider("openai"), None);
|
|
117
|
+
assert_eq!(parse_provider("CLAUDE"), None); // case-sensitive
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[test]
|
|
121
|
+
fn aliases_first_entry_is_canonical_wire() {
|
|
122
|
+
for &p in ALL_PROVIDERS {
|
|
123
|
+
assert_eq!(aliases(p)[0], provider_wire(p), "canonical first for {p:?}");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[test]
|
|
128
|
+
fn command_name_claude_and_claude_code_both_resolve_to_claude_binary() {
|
|
129
|
+
assert_eq!(command_name(Provider::Claude), "claude");
|
|
130
|
+
assert_eq!(command_name(Provider::ClaudeCode), "claude");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn command_name_covers_every_provider() {
|
|
135
|
+
for &p in ALL_PROVIDERS {
|
|
136
|
+
assert!(!command_name(p).is_empty(), "{p:?} has no command name");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
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.4.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.4.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.4.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.4.7",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.4.7",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.4.7"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|
|
@@ -18,6 +18,12 @@ team-agent claude
|
|
|
18
18
|
|
|
19
19
|
Pass provider flags after the provider name, for example `team-agent codex --dangerously-bypass-approvals-and-sandbox`. Existing tmux layouts are valid too, including Finder/Ghostty launchers, as long as `team-agent quick-start` is invoked from the leader's current tmux pane. Do not start a real team from a naked terminal that Team Agent cannot address through tmux.
|
|
20
20
|
|
|
21
|
+
## Leader Role
|
|
22
|
+
|
|
23
|
+
Invoking this skill turns the current agent into the team leader. The leader **orchestrates**: read reports, set direction, decompose work, dispatch tasks to teammates, review results, and decide. The leader does **not** execute hands-on work — no `cargo test`, no product-code edits, no `git push`, no build/verify cycles. Those belong to teammates. If the leader catches themselves running tests, editing source files, or pushing commits, they have stepped out of role; stop and re-dispatch.
|
|
24
|
+
|
|
25
|
+
When the user has been communicating in Chinese throughout the conversation, all leader↔teammate messaging (`send`, `report_result`, MCP messages, task descriptions) must also be in Chinese. The leader dispatches in Chinese, the worker reports back in Chinese. Switch back to the user's language only at the user-facing boundary.
|
|
26
|
+
|
|
21
27
|
## Minimal Copy-Paste Team
|
|
22
28
|
|
|
23
29
|
```bash
|