@team-agent/installer 0.5.109 → 0.5.110
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 +33 -0
- package/README.zh.md +30 -0
- package/crates/team-agent/src/cli/emit.rs +8 -1
- package/crates/team-agent/src/cli/mod.rs +2 -1
- package/crates/team-agent/src/cli/route.rs +207 -0
- package/crates/team-agent/src/cli/spec.rs +3 -0
- package/crates/team-agent/src/leader/start.rs +5 -1
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/cli_argv_routing_red.rs +1457 -0
- package/crates/team-agent/src/lifecycle/tests.rs +1 -0
- package/crates/team-agent/src/provider/argv_route.rs +409 -0
- package/crates/team-agent/src/provider/mod.rs +3 -2
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +32 -0
|
@@ -52,6 +52,7 @@ mod b0_reader_hideone_audit_contract;
|
|
|
52
52
|
mod behavioral_diff_264_red;
|
|
53
53
|
mod claude_compatible_config_red;
|
|
54
54
|
mod claude_profile_launch_red;
|
|
55
|
+
mod cli_argv_routing_red;
|
|
55
56
|
mod clone_agent_preserves_source_tools;
|
|
56
57
|
mod clone_fork_copilot_perms_red;
|
|
57
58
|
mod codex_weak_window_attribution_red;
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
//! Global, opt-in native CLI argv routing. No workspace state or child processes.
|
|
2
|
+
|
|
3
|
+
use super::{wire, Provider};
|
|
4
|
+
use serde::de::{self, MapAccess, Visitor};
|
|
5
|
+
use serde::{Deserialize, Deserializer, Serialize};
|
|
6
|
+
use std::collections::BTreeMap;
|
|
7
|
+
use std::ffi::OsStr;
|
|
8
|
+
use std::fmt;
|
|
9
|
+
use std::fs::{self, File, OpenOptions};
|
|
10
|
+
use std::io::{self, Write};
|
|
11
|
+
use std::path::{Path, PathBuf};
|
|
12
|
+
use std::time::{Duration, Instant};
|
|
13
|
+
|
|
14
|
+
pub const ENV_NAME: &str = "TEAM_AGENT_CLI_ARGV_ROUTING";
|
|
15
|
+
|
|
16
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
17
|
+
#[serde(deny_unknown_fields)]
|
|
18
|
+
pub struct Config {
|
|
19
|
+
pub schema_version: u32,
|
|
20
|
+
pub enabled: bool,
|
|
21
|
+
#[serde(deserialize_with = "deserialize_providers")]
|
|
22
|
+
pub providers: BTreeMap<String, Vec<String>>,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
impl Default for Config {
|
|
26
|
+
fn default() -> Self {
|
|
27
|
+
Self {
|
|
28
|
+
schema_version: 1,
|
|
29
|
+
enabled: false,
|
|
30
|
+
providers: BTreeMap::new(),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// serde's struct decoder rejects duplicate top-level fields. A map needs its
|
|
36
|
+
// own visitor to reject duplicate provider keys rather than silently last-win.
|
|
37
|
+
fn deserialize_providers<'de, D>(decoder: D) -> Result<BTreeMap<String, Vec<String>>, D::Error>
|
|
38
|
+
where
|
|
39
|
+
D: Deserializer<'de>,
|
|
40
|
+
{
|
|
41
|
+
struct ProvidersVisitor;
|
|
42
|
+
impl<'de> Visitor<'de> for ProvidersVisitor {
|
|
43
|
+
type Value = BTreeMap<String, Vec<String>>;
|
|
44
|
+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
45
|
+
formatter.write_str("unique canonical provider keys with string arrays")
|
|
46
|
+
}
|
|
47
|
+
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
|
|
48
|
+
let mut result = BTreeMap::new();
|
|
49
|
+
while let Some((key, value)) = map.next_entry::<String, Vec<String>>()? {
|
|
50
|
+
if result.insert(key, value).is_some() {
|
|
51
|
+
return Err(de::Error::custom("duplicate provider key"));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
Ok(result)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
decoder.deserialize_map(ProvidersVisitor)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub fn route_key(provider: Provider) -> Option<&'static str> {
|
|
61
|
+
match provider {
|
|
62
|
+
Provider::Claude | Provider::ClaudeCode => Some("claude"),
|
|
63
|
+
Provider::Fake => None,
|
|
64
|
+
other => Some(wire::provider_wire(other)),
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub fn parse_route_provider(raw: &str) -> Option<Provider> {
|
|
69
|
+
let provider = if raw == "cursor" {
|
|
70
|
+
Provider::CursorAgent
|
|
71
|
+
} else {
|
|
72
|
+
wire::parse_provider(raw)?
|
|
73
|
+
};
|
|
74
|
+
route_key(provider).map(|_| provider)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fn valid_tokens(tokens: &[String]) -> bool {
|
|
78
|
+
tokens.iter().all(|token| !token.contains('\0'))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl Config {
|
|
82
|
+
pub fn parse(bytes: &[u8], path: &Path) -> Result<Self, RouteError> {
|
|
83
|
+
let config: Self = serde_json::from_slice(bytes).map_err(|_| RouteError::invalid(path))?;
|
|
84
|
+
if config.schema_version != 1
|
|
85
|
+
|| config.providers.iter().any(|(key, tokens)| {
|
|
86
|
+
parse_route_provider(key).and_then(route_key) != Some(key.as_str())
|
|
87
|
+
|| !valid_tokens(tokens)
|
|
88
|
+
})
|
|
89
|
+
{
|
|
90
|
+
return Err(RouteError::invalid(path));
|
|
91
|
+
}
|
|
92
|
+
Ok(config)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
97
|
+
pub enum Override {
|
|
98
|
+
Unset,
|
|
99
|
+
On,
|
|
100
|
+
Off,
|
|
101
|
+
Invalid,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl Override {
|
|
105
|
+
pub fn parse(raw: Option<&OsStr>) -> Self {
|
|
106
|
+
match raw {
|
|
107
|
+
None => Self::Unset,
|
|
108
|
+
Some(raw) => match raw
|
|
109
|
+
.to_str()
|
|
110
|
+
.map(|s| s.trim().to_ascii_lowercase())
|
|
111
|
+
.as_deref()
|
|
112
|
+
{
|
|
113
|
+
Some("1" | "true" | "on") => Self::On,
|
|
114
|
+
Some("0" | "false" | "off") => Self::Off,
|
|
115
|
+
_ => Self::Invalid,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
pub fn current() -> Self {
|
|
120
|
+
Self::parse(std::env::var_os(ENV_NAME).as_deref())
|
|
121
|
+
}
|
|
122
|
+
pub fn status(self) -> &'static str {
|
|
123
|
+
match self {
|
|
124
|
+
Self::Unset => "unset",
|
|
125
|
+
Self::On => "on",
|
|
126
|
+
Self::Off => "off",
|
|
127
|
+
Self::Invalid => "invalid",
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
pub fn effective(self, persisted: bool) -> bool {
|
|
131
|
+
match self {
|
|
132
|
+
Self::On => true,
|
|
133
|
+
Self::Unset => persisted,
|
|
134
|
+
Self::Off | Self::Invalid => false,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
pub fn source(self, file_present: bool) -> &'static str {
|
|
138
|
+
match self {
|
|
139
|
+
Self::Unset if file_present => "config",
|
|
140
|
+
Self::Unset => "default",
|
|
141
|
+
_ => "env",
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#[derive(Debug, Clone, Serialize)]
|
|
147
|
+
pub struct RouteError {
|
|
148
|
+
pub error: String,
|
|
149
|
+
pub reason: &'static str,
|
|
150
|
+
pub action: String,
|
|
151
|
+
pub config_path: Option<PathBuf>,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
impl RouteError {
|
|
155
|
+
fn new(reason: &'static str, message: &str, path: Option<&Path>) -> Self {
|
|
156
|
+
Self {
|
|
157
|
+
error: message.to_string(), reason, config_path: path.map(Path::to_path_buf),
|
|
158
|
+
action: format!("inspect/fix the argv-routing configuration; to force native argv temporarily set {ENV_NAME}=off"),
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
fn invalid(path: &Path) -> Self {
|
|
162
|
+
Self::new(
|
|
163
|
+
"argv_route_config_invalid",
|
|
164
|
+
"invalid argv-routing schema, provider mapping or token",
|
|
165
|
+
Some(path),
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
fn io(path: &Path, error: io::Error) -> Self {
|
|
169
|
+
Self::new(
|
|
170
|
+
"argv_route_config_unreadable",
|
|
171
|
+
&format!("argv-routing I/O failed ({:?})", error.kind()),
|
|
172
|
+
Some(path),
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
impl fmt::Display for RouteError {
|
|
178
|
+
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
179
|
+
write!(formatter, "{}: {}", self.reason, self.error)?;
|
|
180
|
+
if let Some(path) = &self.config_path {
|
|
181
|
+
write!(formatter, " (config: {})", path.display())?;
|
|
182
|
+
}
|
|
183
|
+
write!(formatter, "; action: {}", self.action)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
impl std::error::Error for RouteError {}
|
|
187
|
+
|
|
188
|
+
pub fn config_path() -> Result<PathBuf, RouteError> {
|
|
189
|
+
fn absolute_home(raw: Option<std::ffi::OsString>) -> Option<PathBuf> {
|
|
190
|
+
raw.map(PathBuf::from)
|
|
191
|
+
.filter(|path| path.is_absolute() && !path.as_os_str().is_empty())
|
|
192
|
+
}
|
|
193
|
+
let home = absolute_home(std::env::var_os("HOME"));
|
|
194
|
+
#[cfg(windows)]
|
|
195
|
+
let home = home.or_else(|| absolute_home(std::env::var_os("USERPROFILE")));
|
|
196
|
+
home.map(|path| path.join(".team-agent").join("argv-routing.json"))
|
|
197
|
+
.ok_or_else(|| {
|
|
198
|
+
RouteError::new(
|
|
199
|
+
"argv_route_home_unavailable",
|
|
200
|
+
"an absolute nonempty host home is required",
|
|
201
|
+
None,
|
|
202
|
+
)
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn read_bytes(path: &Path) -> Result<Option<Vec<u8>>, RouteError> {
|
|
207
|
+
match fs::read(path) {
|
|
208
|
+
Ok(bytes) => Ok(Some(bytes)),
|
|
209
|
+
Err(error) if error.kind() == io::ErrorKind::NotFound => {
|
|
210
|
+
// A dangling symlink is an existing broken configuration, not a
|
|
211
|
+
// missing file that management may silently replace.
|
|
212
|
+
if fs::symlink_metadata(path).is_ok() {
|
|
213
|
+
Err(RouteError::io(path, error))
|
|
214
|
+
} else {
|
|
215
|
+
Ok(None)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
Err(error) => Err(RouteError::io(path, error)),
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
pub fn read_config(path: &Path) -> Result<(Config, bool), RouteError> {
|
|
223
|
+
match read_bytes(path)? {
|
|
224
|
+
Some(bytes) => Config::parse(&bytes, path).map(|config| (config, true)),
|
|
225
|
+
None => Ok((Config::default(), false)),
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/// Insert literal tokens after the executable, preserving the entire old tail.
|
|
230
|
+
pub fn insert(config: &Config, provider: Provider, argv: &mut Vec<String>) {
|
|
231
|
+
if let Some(tokens) = route_key(provider).and_then(|key| config.providers.get(key)) {
|
|
232
|
+
if !argv.is_empty() && !tokens.is_empty() {
|
|
233
|
+
argv.splice(1..1, tokens.iter().cloned());
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// One read per actual native launch. OFF never reads or writes configuration.
|
|
239
|
+
pub fn apply(provider: Provider, argv: &mut Vec<String>) -> Result<(), RouteError> {
|
|
240
|
+
let override_state = Override::current();
|
|
241
|
+
if route_key(provider).is_none() || matches!(override_state, Override::Off | Override::Invalid)
|
|
242
|
+
{
|
|
243
|
+
return Ok(());
|
|
244
|
+
}
|
|
245
|
+
let path = match config_path() {
|
|
246
|
+
Ok(path) => path,
|
|
247
|
+
Err(_) if override_state == Override::Unset => return Ok(()),
|
|
248
|
+
Err(error) => return Err(error),
|
|
249
|
+
};
|
|
250
|
+
let bytes = match read_bytes(&path) {
|
|
251
|
+
Ok(bytes) => bytes,
|
|
252
|
+
Err(_) if override_state == Override::Unset => return Ok(()),
|
|
253
|
+
Err(error) => return Err(error),
|
|
254
|
+
};
|
|
255
|
+
let Some(bytes) = bytes else {
|
|
256
|
+
return Ok(());
|
|
257
|
+
};
|
|
258
|
+
if override_state == Override::Unset {
|
|
259
|
+
// Tolerant OFF detection precedes strict mapping validation. Never
|
|
260
|
+
// make a disabled or unrecognizable file block historical launches.
|
|
261
|
+
let enabled = serde_json::from_slice::<serde_json::Value>(&bytes)
|
|
262
|
+
.ok()
|
|
263
|
+
.and_then(|value| value.get("enabled").and_then(serde_json::Value::as_bool));
|
|
264
|
+
if enabled != Some(true) {
|
|
265
|
+
return Ok(());
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
insert(&Config::parse(&bytes, &path)?, provider, argv);
|
|
269
|
+
Ok(())
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
pub enum Mutation {
|
|
273
|
+
Enable(bool),
|
|
274
|
+
Set(Provider, Vec<String>),
|
|
275
|
+
Add(Provider, Vec<String>),
|
|
276
|
+
Clear(Provider),
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
struct Lock(File);
|
|
280
|
+
impl Drop for Lock {
|
|
281
|
+
fn drop(&mut self) {
|
|
282
|
+
let _ = crate::platform::file_lock::unlock(&self.0);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
fn private_options() -> OpenOptions {
|
|
287
|
+
let mut options = OpenOptions::new();
|
|
288
|
+
options.read(true).write(true);
|
|
289
|
+
#[cfg(unix)]
|
|
290
|
+
{
|
|
291
|
+
use std::os::unix::fs::OpenOptionsExt;
|
|
292
|
+
options.mode(0o600);
|
|
293
|
+
}
|
|
294
|
+
options
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
fn acquire_lock(path: &Path) -> Result<Lock, RouteError> {
|
|
298
|
+
let parent = path.parent().ok_or_else(|| RouteError::invalid(path))?;
|
|
299
|
+
let mut builder = fs::DirBuilder::new();
|
|
300
|
+
#[cfg(unix)]
|
|
301
|
+
{
|
|
302
|
+
use std::os::unix::fs::DirBuilderExt;
|
|
303
|
+
builder.mode(0o700);
|
|
304
|
+
}
|
|
305
|
+
match builder.create(parent) {
|
|
306
|
+
Ok(()) => {}
|
|
307
|
+
Err(error) if error.kind() == io::ErrorKind::AlreadyExists && parent.is_dir() => {}
|
|
308
|
+
Err(error) => return Err(RouteError::io(path, error)),
|
|
309
|
+
}
|
|
310
|
+
let file = private_options()
|
|
311
|
+
.create(true)
|
|
312
|
+
.truncate(false)
|
|
313
|
+
.open(parent.join("argv-routing.lock"))
|
|
314
|
+
.map_err(|error| RouteError::io(path, error))?;
|
|
315
|
+
let started = Instant::now();
|
|
316
|
+
loop {
|
|
317
|
+
match crate::platform::file_lock::try_lock_once_nonblocking(&file) {
|
|
318
|
+
Ok(true) => return Ok(Lock(file)),
|
|
319
|
+
Ok(false) if started.elapsed() < Duration::from_secs(5) => {
|
|
320
|
+
std::thread::sleep(Duration::from_millis(25))
|
|
321
|
+
}
|
|
322
|
+
Ok(false) => {
|
|
323
|
+
return Err(RouteError::new(
|
|
324
|
+
"argv_route_lock_timeout",
|
|
325
|
+
"argv-routing write lock timed out",
|
|
326
|
+
Some(path),
|
|
327
|
+
))
|
|
328
|
+
}
|
|
329
|
+
Err(error) => return Err(RouteError::io(path, error)),
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
fn write_config(path: &Path, config: &Config) -> Result<(), RouteError> {
|
|
335
|
+
static COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
|
336
|
+
let nanos = std::time::SystemTime::now()
|
|
337
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
338
|
+
.map_or(0, |duration| duration.as_nanos());
|
|
339
|
+
let serial = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
340
|
+
let temp = path.with_file_name(format!(
|
|
341
|
+
".argv-routing-{}-{nanos}-{serial}.tmp",
|
|
342
|
+
std::process::id()
|
|
343
|
+
));
|
|
344
|
+
// Only remove a temp path after successfully creating it ourselves.
|
|
345
|
+
let mut file = private_options()
|
|
346
|
+
.create_new(true)
|
|
347
|
+
.open(&temp)
|
|
348
|
+
.map_err(|error| RouteError::io(path, error))?;
|
|
349
|
+
let result = (|| {
|
|
350
|
+
serde_json::to_writer_pretty(&mut file, config).map_err(|_| RouteError::invalid(path))?;
|
|
351
|
+
file.write_all(b"\n")
|
|
352
|
+
.and_then(|_| file.flush())
|
|
353
|
+
.and_then(|_| file.sync_all())
|
|
354
|
+
.map_err(|error| RouteError::io(path, error))?;
|
|
355
|
+
drop(file);
|
|
356
|
+
fs::rename(&temp, path).map_err(|error| RouteError::io(path, error))
|
|
357
|
+
})();
|
|
358
|
+
if result.is_err() {
|
|
359
|
+
let _ = fs::remove_file(&temp);
|
|
360
|
+
}
|
|
361
|
+
result
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
pub fn mutate(path: &Path, mutation: Mutation) -> Result<(Config, bool), RouteError> {
|
|
365
|
+
match &mutation {
|
|
366
|
+
Mutation::Set(provider, tokens) | Mutation::Add(provider, tokens) => {
|
|
367
|
+
if route_key(*provider).is_none() || tokens.is_empty() || !valid_tokens(tokens) {
|
|
368
|
+
return Err(RouteError::invalid(path));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
Mutation::Clear(provider) if route_key(*provider).is_none() => {
|
|
372
|
+
return Err(RouteError::invalid(path))
|
|
373
|
+
}
|
|
374
|
+
_ => {}
|
|
375
|
+
}
|
|
376
|
+
// Strict preflight prevents creating lock/dir apparatus for a bad file.
|
|
377
|
+
let (config, present) = read_config(path)?;
|
|
378
|
+
if !present && matches!(mutation, Mutation::Clear(_)) {
|
|
379
|
+
return Ok((config, false));
|
|
380
|
+
}
|
|
381
|
+
let _lock = acquire_lock(path)?;
|
|
382
|
+
let (mut config, present) = read_config(path)?;
|
|
383
|
+
match mutation {
|
|
384
|
+
Mutation::Enable(enabled) => config.enabled = enabled,
|
|
385
|
+
Mutation::Set(provider, tokens) => {
|
|
386
|
+
if let Some(key) = route_key(provider) {
|
|
387
|
+
config.providers.insert(key.to_string(), tokens);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
Mutation::Add(provider, tokens) => {
|
|
391
|
+
if let Some(key) = route_key(provider) {
|
|
392
|
+
config
|
|
393
|
+
.providers
|
|
394
|
+
.entry(key.to_string())
|
|
395
|
+
.or_default()
|
|
396
|
+
.extend(tokens);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
Mutation::Clear(provider) => {
|
|
400
|
+
if let Some(key) = route_key(provider) {
|
|
401
|
+
if config.providers.remove(key).is_none() {
|
|
402
|
+
return Ok((config, present));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
write_config(path, &config)?;
|
|
408
|
+
Ok((config, true))
|
|
409
|
+
}
|
|
@@ -42,13 +42,14 @@
|
|
|
42
42
|
// 复用 model::enums 的 Provider/AuthMode,并 re-export(下游惯于 `provider::Provider` 取用)。
|
|
43
43
|
pub use crate::model::enums::{AuthMode, Provider};
|
|
44
44
|
|
|
45
|
-
/// 0.5.66 bypass 单源:provider → bypass argv flag 配置表(唯一权威)。
|
|
46
|
-
pub(crate) mod bypass_flags;
|
|
47
45
|
pub mod adapter;
|
|
48
46
|
/// 0.4.x decoupling step 2: per-provider command builders + permission/auth
|
|
49
47
|
/// helpers split out of `adapter.rs`. Pure extraction — behavior unchanged.
|
|
50
48
|
pub(crate) mod adapters;
|
|
51
49
|
pub mod approvals;
|
|
50
|
+
pub mod argv_route;
|
|
51
|
+
/// 0.5.66 bypass 单源:provider → bypass argv flag 配置表(唯一权威)。
|
|
52
|
+
pub(crate) mod bypass_flags;
|
|
52
53
|
pub mod classify;
|
|
53
54
|
pub mod faults;
|
|
54
55
|
pub mod model_catalog;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.110",
|
|
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.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.110",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.110",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.110"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|
|
@@ -79,6 +79,38 @@ A different `--provider`, including a manually edited role provider, is rejected
|
|
|
79
79
|
|
|
80
80
|
Profiles keep provider authentication, endpoints and custom-model settings local. Imported roles use this team's/workspace's `profiles`, never the original external directory. Prepare a named profile locally through the public profile commands; do not search for or copy external credentials. Never put secrets in `TEAM.md` or a role file. See [the operator reference](references/team-agent-operator.md) for existing profile and field details. Supported metadata also includes `agent_id` (legacy `name`), `role`, `auth_mode`, and `communication_mode`; old `tools`, `permission_mode`, and `label` keys do not configure a worker.
|
|
81
81
|
|
|
82
|
+
## Global native CLI argv (optional)
|
|
83
|
+
|
|
84
|
+
Default OFF; no workspace or provider login is needed to manage routes:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
team-agent route set pi -- --mode rpc
|
|
88
|
+
team-agent route enable
|
|
89
|
+
team-agent route status --json
|
|
90
|
+
team-agent route show pi --json
|
|
91
|
+
team-agent route add pi -- --verbose
|
|
92
|
+
team-agent route clear pi
|
|
93
|
+
team-agent route disable
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`set` replaces one mapping, `add` appends without deduplication, `clear` removes it;
|
|
97
|
+
none changes the switch. `enable`/`disable` preserve mappings in `~/.team-agent/argv-routing.json`.
|
|
98
|
+
`TEAM_AGENT_CLI_ARGV_ROUTING` overrides the switch: `1/true/on`, `0/false/off` (case-insensitive,
|
|
99
|
+
trimmed); invalid values disable routing. Check `persisted_enabled`, `effective_enabled` and
|
|
100
|
+
`override_status` rather than assuming `enable` defeats an environment OFF.
|
|
101
|
+
|
|
102
|
+
Keys: `claude`, `codex`, `copilot`, `gemini_cli`, `grok`, `cursor_agent`, `pi`.
|
|
103
|
+
Claude/Claude Code share `claude`; aliases `claude_code/claude-code`, `agent/cursor` are accepted.
|
|
104
|
+
The first `--` ends control parsing: subsequent tokens, including `--help`, `--json`, empty strings
|
|
105
|
+
and `{workspace}`, are literal data. Tokens go after the executable and before its original tail;
|
|
106
|
+
there is no shell expansion. Do not route secrets or override framework-managed session/MCP/permission flags.
|
|
107
|
+
|
|
108
|
+
The current mapping affects the next actual Leader/worker spawn (also restart, single-seat start/reset,
|
|
109
|
+
add/clone, Pi new-seat fork and remove rollback), not running/attached processes, dry-run or helper probes.
|
|
110
|
+
Enabled bad configuration prevents native spawn; an emergency environment OFF avoids reading it,
|
|
111
|
+
while `route disable` will not overwrite a broken file. Pi `--mode rpc` does not provide an RPC host
|
|
112
|
+
or change the existing Team Agent transport.
|
|
113
|
+
|
|
82
114
|
## Inspect and stop
|
|
83
115
|
|
|
84
116
|
Use `team-agent status --json` for readiness and the installed command's `--help` for syntax. `ok: true` with `ready: false` means the team is not ready; follow the reported action. Accepted/queued send is not the worker's reply: wait for a natural response or result. Use `<workspace>::<team>/<agent>` when a fully qualified recipient is needed.
|