@team-agent/installer 0.5.82 → 0.5.84
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/emit.rs +193 -14
- package/crates/team-agent/src/cli/leaders.rs +304 -167
- package/crates/team-agent/src/cli/models.rs +231 -24
- package/crates/team-agent/src/cli/spec.rs +2 -2
- package/crates/team-agent/src/cli/types.rs +12 -1
- package/crates/team-agent/src/leader/registry.rs +295 -64
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +16 -18
- package/crates/team-agent/src/mcp_server/tests/tools.rs +31 -20
- package/crates/team-agent/src/mcp_server/tests/wire.rs +30 -0
- package/crates/team-agent/src/mcp_server/tools.rs +113 -11
- package/crates/team-agent/src/mcp_server/wire.rs +20 -1
- package/crates/team-agent/src/state/persist.rs +50 -1
- package/package.json +4 -4
|
@@ -1,53 +1,98 @@
|
|
|
1
|
-
//! Read-only
|
|
1
|
+
//! Read-only provider model catalog discovery for `team-agent models`.
|
|
2
2
|
use super::{CliError, CmdOutput, CmdResult, ExitCode, ModelsArgs};
|
|
3
3
|
use serde_json::{json, Value};
|
|
4
4
|
#[cfg(test)]
|
|
5
5
|
use std::collections::BTreeMap;
|
|
6
6
|
use std::path::Path;
|
|
7
|
-
use std::process::Command;
|
|
8
7
|
use std::time::Duration;
|
|
9
8
|
const CATALOG_TIMEOUT: Duration = Duration::from_secs(10);
|
|
10
9
|
const MAX_CATALOG_BYTES: u64 = 1024 * 1024;
|
|
11
10
|
|
|
11
|
+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
12
|
+
enum CatalogFormat {
|
|
13
|
+
Pi,
|
|
14
|
+
CursorAgent,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Clone, Debug)]
|
|
18
|
+
struct ModelRecord {
|
|
19
|
+
provider: String,
|
|
20
|
+
vendor: String,
|
|
21
|
+
id: String,
|
|
22
|
+
display_name: String,
|
|
23
|
+
default: Option<bool>,
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
pub fn cmd_models(args: &ModelsArgs) -> Result<CmdResult, CliError> {
|
|
13
|
-
|
|
27
|
+
let (program, format) = match args.provider.as_str() {
|
|
28
|
+
"pi" => (Path::new("pi"), CatalogFormat::Pi),
|
|
29
|
+
"cursor_agent" => (Path::new("agent"), CatalogFormat::CursorAgent),
|
|
30
|
+
provider => {
|
|
31
|
+
return Ok(failure(
|
|
32
|
+
args,
|
|
33
|
+
&format!("unsupported model provider {provider:?}"),
|
|
34
|
+
))
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
cmd_models_with_format(args, program, format, CATALOG_TIMEOUT, MAX_CATALOG_BYTES)
|
|
14
38
|
}
|
|
15
39
|
|
|
40
|
+
/// Existing injectable Pi boundary retained for deterministic command/timeout tests.
|
|
16
41
|
fn cmd_models_with(
|
|
17
42
|
args: &ModelsArgs,
|
|
18
43
|
program: &Path,
|
|
19
44
|
timeout: Duration,
|
|
20
45
|
max_bytes: u64,
|
|
46
|
+
) -> Result<CmdResult, CliError> {
|
|
47
|
+
cmd_models_with_format(args, program, CatalogFormat::Pi, timeout, max_bytes)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fn cmd_models_with_format(
|
|
51
|
+
args: &ModelsArgs,
|
|
52
|
+
program: &Path,
|
|
53
|
+
format: CatalogFormat,
|
|
54
|
+
timeout: Duration,
|
|
55
|
+
max_bytes: u64,
|
|
21
56
|
) -> Result<CmdResult, CliError> {
|
|
22
57
|
let bytes = match run_catalog(program, timeout, max_bytes) {
|
|
23
58
|
Ok(bytes) => bytes,
|
|
24
|
-
Err(message) => return Ok(failure(args, &message)),
|
|
59
|
+
Err(message) => return Ok(failure(args, &catalog_error_prefix(format, message))),
|
|
25
60
|
};
|
|
26
|
-
let all = match
|
|
61
|
+
let all = match parse_catalog(&bytes, format) {
|
|
27
62
|
Ok(models) => models,
|
|
28
|
-
Err(
|
|
63
|
+
Err(message) => return Ok(failure(args, &message)),
|
|
29
64
|
};
|
|
30
65
|
let visible = args.search.as_deref().map_or_else(
|
|
31
66
|
|| all.clone(),
|
|
32
67
|
|search| {
|
|
33
68
|
all.iter()
|
|
34
|
-
.filter(|model| model
|
|
69
|
+
.filter(|model| model_matches(model, search))
|
|
35
70
|
.cloned()
|
|
36
71
|
.collect::<Vec<_>>()
|
|
37
72
|
},
|
|
38
73
|
);
|
|
39
|
-
let current =
|
|
74
|
+
let current = current_model_for(format, &all);
|
|
40
75
|
let entries: Vec<Value> = visible
|
|
41
76
|
.iter()
|
|
42
77
|
.map(|model| {
|
|
43
78
|
json!({
|
|
44
|
-
"role_model": model,
|
|
45
|
-
"current":
|
|
79
|
+
"role_model": model.id,
|
|
80
|
+
"current": if format == CatalogFormat::Pi {
|
|
81
|
+
json!(current.as_deref() == Some(model.id.as_str()))
|
|
82
|
+
} else {
|
|
83
|
+
Value::Null
|
|
84
|
+
},
|
|
85
|
+
"provider": model.provider,
|
|
86
|
+
"vendor": model.vendor,
|
|
87
|
+
"model_id": model.id,
|
|
88
|
+
"display_name": model.display_name,
|
|
89
|
+
"default": model.default,
|
|
46
90
|
})
|
|
47
91
|
})
|
|
48
92
|
.collect();
|
|
93
|
+
let provider = args.provider.as_str();
|
|
49
94
|
let value = json!({
|
|
50
|
-
"schema_version": "models.v1", "ok": true, "provider":
|
|
95
|
+
"schema_version": "models.v1", "ok": true, "provider": provider, "models": entries,
|
|
51
96
|
"auth": "ok", "auth_basis": "catalog_visibility", "current_role_model": current, "search": args.search,
|
|
52
97
|
});
|
|
53
98
|
if args.json {
|
|
@@ -59,11 +104,25 @@ fn cmd_models_with(
|
|
|
59
104
|
preserve_json_order: true,
|
|
60
105
|
})
|
|
61
106
|
} else {
|
|
62
|
-
let mut lines = vec![
|
|
107
|
+
let mut lines = vec![format!(
|
|
108
|
+
"models.v1 | {provider} models (copyable model_id):"
|
|
109
|
+
)];
|
|
110
|
+
lines.push("PROVIDER VENDOR MODEL_ID DISPLAY_NAME CURRENT DEFAULT".to_string());
|
|
63
111
|
lines.extend(visible.iter().map(|model| {
|
|
64
112
|
format!(
|
|
65
|
-
"
|
|
66
|
-
|
|
113
|
+
"{} {} {} {} {} {}",
|
|
114
|
+
model.provider,
|
|
115
|
+
model.vendor,
|
|
116
|
+
model.id,
|
|
117
|
+
model.display_name,
|
|
118
|
+
if format == CatalogFormat::Pi {
|
|
119
|
+
(current.as_deref() == Some(model.id.as_str())).to_string()
|
|
120
|
+
} else {
|
|
121
|
+
"—".to_string()
|
|
122
|
+
},
|
|
123
|
+
model
|
|
124
|
+
.default
|
|
125
|
+
.map_or_else(|| "—".to_string(), |value| value.to_string()),
|
|
67
126
|
)
|
|
68
127
|
}));
|
|
69
128
|
lines.push(format!(
|
|
@@ -71,14 +130,135 @@ fn cmd_models_with(
|
|
|
71
130
|
visible.len()
|
|
72
131
|
));
|
|
73
132
|
if visible.is_empty() {
|
|
74
|
-
lines.push("No models matched --search.".to_string());
|
|
133
|
+
lines.push("No models matched --search/query.".to_string());
|
|
75
134
|
}
|
|
76
135
|
Ok(CmdResult::human(lines.join("\n")))
|
|
77
136
|
}
|
|
78
137
|
}
|
|
79
138
|
|
|
139
|
+
fn parse_catalog(bytes: &[u8], format: CatalogFormat) -> Result<Vec<ModelRecord>, String> {
|
|
140
|
+
match format {
|
|
141
|
+
CatalogFormat::Pi => crate::lifecycle::launch::pi_mcp::parse_pi_list_models_table(bytes)
|
|
142
|
+
.map(|models| {
|
|
143
|
+
models
|
|
144
|
+
.into_iter()
|
|
145
|
+
.map(|id| {
|
|
146
|
+
let vendor = id
|
|
147
|
+
.split_once('/')
|
|
148
|
+
.map_or_else(|| "pi".to_string(), |(vendor, _)| vendor.to_string());
|
|
149
|
+
ModelRecord {
|
|
150
|
+
provider: "pi".to_string(),
|
|
151
|
+
vendor,
|
|
152
|
+
display_name: id.clone(),
|
|
153
|
+
id,
|
|
154
|
+
default: None,
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
.collect()
|
|
158
|
+
})
|
|
159
|
+
.map_err(safe_catalog_error),
|
|
160
|
+
CatalogFormat::CursorAgent => parse_cursor_catalog(bytes),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
fn parse_cursor_catalog(bytes: &[u8]) -> Result<Vec<ModelRecord>, String> {
|
|
165
|
+
let text = std::str::from_utf8(bytes)
|
|
166
|
+
.map_err(|_| "Cursor model catalog is not valid UTF-8".to_string())?;
|
|
167
|
+
let mut models = Vec::new();
|
|
168
|
+
for line in text.lines() {
|
|
169
|
+
let line = line.trim();
|
|
170
|
+
if line.is_empty()
|
|
171
|
+
|| line
|
|
172
|
+
.trim_end_matches(':')
|
|
173
|
+
.eq_ignore_ascii_case("available models")
|
|
174
|
+
{
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
let Some((id, display)) = line.split_once(" - ") else {
|
|
178
|
+
return Err("Cursor model catalog contains a malformed row".to_string());
|
|
179
|
+
};
|
|
180
|
+
let id = id.trim();
|
|
181
|
+
if id.is_empty() || id.chars().any(char::is_whitespace) || display.trim().is_empty() {
|
|
182
|
+
return Err("Cursor model catalog contains a malformed row".to_string());
|
|
183
|
+
}
|
|
184
|
+
if models.iter().any(|model: &ModelRecord| model.id == id) {
|
|
185
|
+
return Err("Cursor model catalog contains duplicate model ids".to_string());
|
|
186
|
+
}
|
|
187
|
+
let default = display.trim_end().ends_with("(default)");
|
|
188
|
+
let display_name = display.trim().strip_suffix("(default)").map_or_else(
|
|
189
|
+
|| display.trim().to_string(),
|
|
190
|
+
|name| name.trim().to_string(),
|
|
191
|
+
);
|
|
192
|
+
let vendor = id
|
|
193
|
+
.split_once('-')
|
|
194
|
+
.map_or_else(|| "cursor".to_string(), |(vendor, _)| vendor.to_string());
|
|
195
|
+
models.push(ModelRecord {
|
|
196
|
+
provider: "cursor_agent".to_string(),
|
|
197
|
+
vendor,
|
|
198
|
+
id: id.to_string(),
|
|
199
|
+
display_name,
|
|
200
|
+
default: Some(default),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if models.is_empty() {
|
|
204
|
+
return Err("Cursor model catalog is empty".to_string());
|
|
205
|
+
}
|
|
206
|
+
Ok(models)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fn model_matches(model: &ModelRecord, search: &str) -> bool {
|
|
210
|
+
let fields = [
|
|
211
|
+
model.provider.as_str(),
|
|
212
|
+
model.vendor.as_str(),
|
|
213
|
+
model.id.as_str(),
|
|
214
|
+
model.display_name.as_str(),
|
|
215
|
+
];
|
|
216
|
+
search
|
|
217
|
+
.split_whitespace()
|
|
218
|
+
.map(str::to_lowercase)
|
|
219
|
+
.all(|needle| {
|
|
220
|
+
fields
|
|
221
|
+
.iter()
|
|
222
|
+
.any(|field| field.to_lowercase().contains(needle.as_str()))
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
fn current_model_for(format: CatalogFormat, models: &[ModelRecord]) -> Option<String> {
|
|
227
|
+
match format {
|
|
228
|
+
CatalogFormat::Pi => current_role_model(
|
|
229
|
+
&models
|
|
230
|
+
.iter()
|
|
231
|
+
.map(|model| model.id.clone())
|
|
232
|
+
.collect::<Vec<_>>(),
|
|
233
|
+
),
|
|
234
|
+
// Cursor's catalog does not expose the active role model; never mark a
|
|
235
|
+
// catalog default as current without an explicit runtime identity.
|
|
236
|
+
CatalogFormat::CursorAgent => None,
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fn catalog_error_prefix(format: CatalogFormat, message: String) -> String {
|
|
241
|
+
match format {
|
|
242
|
+
CatalogFormat::Pi => message,
|
|
243
|
+
CatalogFormat::CursorAgent => message.replace("Pi", "Cursor"),
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
80
247
|
fn failure(args: &ModelsArgs, message: &str) -> CmdResult {
|
|
81
|
-
let
|
|
248
|
+
let executable = if args.provider == "cursor_agent" {
|
|
249
|
+
"agent"
|
|
250
|
+
} else {
|
|
251
|
+
"pi"
|
|
252
|
+
};
|
|
253
|
+
let action = if message.starts_with("unsupported model provider") {
|
|
254
|
+
"choose a supported provider: pi or cursor_agent".to_string()
|
|
255
|
+
} else {
|
|
256
|
+
format!(
|
|
257
|
+
"install or repair the PATH-first `{executable}` executable, then retry `team-agent models --provider {}`",
|
|
258
|
+
args.provider
|
|
259
|
+
)
|
|
260
|
+
};
|
|
261
|
+
let value = json!({ "schema_version": "models.v1", "ok": false, "provider": args.provider, "auth": "not_ready", "auth_basis": "catalog_visibility", "models": Value::Array(Vec::new()), "current_role_model": Value::Null, "error": message, "action": action });
|
|
82
262
|
if args.json {
|
|
83
263
|
let text = serde_json::to_string_pretty(&value)
|
|
84
264
|
.unwrap_or_else(|_| "{\"schema_version\":\"models.v1\",\"ok\":false}".to_string());
|
|
@@ -89,7 +269,7 @@ fn failure(args: &ModelsArgs, message: &str) -> CmdResult {
|
|
|
89
269
|
preserve_json_order: true,
|
|
90
270
|
}
|
|
91
271
|
} else {
|
|
92
|
-
CmdResult { output: CmdOutput::Human(format!("models.v1 | error: {message}\naction:
|
|
272
|
+
CmdResult { output: CmdOutput::Human(format!("models.v1 | error: {message}\naction: {action}\nauth: not_ready (catalog_visibility)")), exit: ExitCode::Error, as_json: false, preserve_json_order: false }
|
|
93
273
|
}
|
|
94
274
|
}
|
|
95
275
|
|
|
@@ -279,6 +459,38 @@ mod tests {
|
|
|
279
459
|
assert_eq!(models[1]["role_model"], "team-agent/gpt-5.6-luna");
|
|
280
460
|
}
|
|
281
461
|
|
|
462
|
+
#[cfg(unix)]
|
|
463
|
+
#[test]
|
|
464
|
+
fn cursor_catalog_parses_copyable_ids_and_multiword_search() {
|
|
465
|
+
let path = fixture(
|
|
466
|
+
"printf 'Available models\\ngpt-5.6-luna-high - GPT-5.6 Luna 1M High\\ncursor-grok-4.6-high - Cursor Grok 4.6\\nauto - Auto (default)\\n'",
|
|
467
|
+
);
|
|
468
|
+
let result = cmd_models_with_format(
|
|
469
|
+
&ModelsArgs {
|
|
470
|
+
provider: "cursor_agent".into(),
|
|
471
|
+
search: Some("GPT luna".into()),
|
|
472
|
+
json: true,
|
|
473
|
+
},
|
|
474
|
+
&path,
|
|
475
|
+
CatalogFormat::CursorAgent,
|
|
476
|
+
Duration::from_secs(1),
|
|
477
|
+
MAX_CATALOG_BYTES,
|
|
478
|
+
)
|
|
479
|
+
.unwrap();
|
|
480
|
+
let CmdOutput::Human(text) = result.output else {
|
|
481
|
+
panic!("expected JSON projection")
|
|
482
|
+
};
|
|
483
|
+
let value: Value = serde_json::from_str(&text).unwrap();
|
|
484
|
+
assert_eq!(value["ok"], true);
|
|
485
|
+
assert_eq!(value["provider"], "cursor_agent");
|
|
486
|
+
assert_eq!(value["models"].as_array().unwrap().len(), 1);
|
|
487
|
+
assert_eq!(value["models"][0]["role_model"], "gpt-5.6-luna-high");
|
|
488
|
+
assert_eq!(value["models"][0]["model_id"], "gpt-5.6-luna-high");
|
|
489
|
+
assert_eq!(value["models"][0]["display_name"], "GPT-5.6 Luna 1M High");
|
|
490
|
+
assert_eq!(value["models"][0]["current"], Value::Null);
|
|
491
|
+
cleanup_fixture(&path);
|
|
492
|
+
}
|
|
493
|
+
|
|
282
494
|
#[cfg(unix)]
|
|
283
495
|
fn fixture(body: &str) -> std::path::PathBuf {
|
|
284
496
|
use std::os::unix::fs::PermissionsExt;
|
|
@@ -785,12 +997,7 @@ mod tests {
|
|
|
785
997
|
&["--provider".into(), "codex".into()],
|
|
786
998
|
Path::new("/tmp"),
|
|
787
999
|
);
|
|
788
|
-
|
|
789
|
-
Err(CliError::Usage(message)) => {
|
|
790
|
-
assert_eq!(message, "models supports only --provider pi, got \"codex\"")
|
|
791
|
-
}
|
|
792
|
-
other => panic!("expected provider refusal, got {other:?}"),
|
|
793
|
-
}
|
|
1000
|
+
assert!(matches!(refusal, Ok(ExitCode::Error)));
|
|
794
1001
|
cleanup_fixture(&path);
|
|
795
1002
|
}
|
|
796
1003
|
|
|
@@ -894,7 +1101,7 @@ mod tests {
|
|
|
894
1101
|
);
|
|
895
1102
|
assert_eq!(
|
|
896
1103
|
crate::cli::spec::command_spec("models").unwrap().usage,
|
|
897
|
-
"usage: team-agent models --provider pi [
|
|
1104
|
+
"usage: team-agent models [--provider pi|cursor_agent] [QUERY|--search TEXT] [--json]"
|
|
898
1105
|
);
|
|
899
1106
|
}
|
|
900
1107
|
|
|
@@ -169,7 +169,7 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
169
169
|
CommandSpec { name: "quick-start", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::QuickStart), summary: "start or attach a team from TEAM.md", usage: "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--no-display] [--backend tmux|conpty] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
|
|
170
170
|
CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "persist a message for an in-team short name or fully-qualified logical recipient", usage: "usage: team-agent send TO MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--mailbox] [--json]\nTO forms are co-equal: an in-team short name (for example `team-agent send reviewer \"Review\"`) or `<workspace>::<team>/<agent>`.", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: Some("next compatibility release"), action: Some("use positional logical TO and the returned message id"), governance: None },
|
|
171
171
|
CommandSpec { name: "status", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Status), summary: "show current team status", usage: "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
172
|
-
CommandSpec { name: "models", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Models), summary: "list exact provider model ids", usage: "usage: team-agent models --provider pi [
|
|
172
|
+
CommandSpec { name: "models", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Models), summary: "list exact provider model ids", usage: "usage: team-agent models [--provider pi|cursor_agent] [QUERY|--search TEXT] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
173
173
|
CommandSpec { name: "collect", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Collect), summary: "collect reported results", usage: "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
174
174
|
CommandSpec { name: "results", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Results), summary: "read reported results for a case", usage: "usage: team-agent results --case CASE_ID [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
175
175
|
CommandSpec { name: "wait", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Wait), summary: "block until a task result is stored", usage: "usage: team-agent wait --task TASK [--workspace WORKSPACE] [--json]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
@@ -198,7 +198,7 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
198
198
|
CommandSpec { name: "stuck-list", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::Dispatch(DispatchKind::StuckList), summary: "legacy stuck alert listing", usage: "usage: team-agent stuck-list [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: Some("C2"), action: Some("use `team-agent diagnose`"), governance: None },
|
|
199
199
|
CommandSpec { name: "stuck-cancel", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::Dispatch(DispatchKind::StuckCancel), summary: "legacy stuck alert suppression", usage: "usage: team-agent stuck-cancel AGENT [--workspace WORKSPACE] [--alert-type stuck|idle_fallback|cross_worker_deadlock|all] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: Some("C2"), action: Some("use the concrete notification action or `team-agent diagnose`"), governance: None },
|
|
200
200
|
CommandSpec { name: "acknowledge-idle", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::Dispatch(DispatchKind::AcknowledgeIdle), summary: "legacy idle acknowledgement", usage: "usage: team-agent acknowledge-idle [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: Some("C2"), action: Some("use the concrete idle notification action or `team-agent diagnose`"), governance: None },
|
|
201
|
-
CommandSpec { name: "leaders", tier: CommandTier::Secondary, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Leaders), summary: "
|
|
201
|
+
CommandSpec { name: "leaders", tier: CommandTier::Secondary, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Leaders), summary: "list live, stale, or ambiguous host leaders", usage: "usage: team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
202
202
|
CommandSpec { name: "doctor", tier: CommandTier::Secondary, category: CommandCategory::Setup, kind: CommandKind::Dispatch(DispatchKind::Doctor), summary: "run host maintenance checks", usage: "usage: team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--gate orphans|comms] [--comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
203
203
|
CommandSpec { name: "attach-app-server-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::AttachAppServerLeader), summary: "bind an app-server leader receiver", usage: "usage: team-agent attach-app-server-leader [--workspace WORKSPACE] [--team TEAM] --socket unix:///path.sock --thread-id THREAD_ID [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
204
204
|
CommandSpec { name: "remove-agent", tier: CommandTier::Secondary, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::RemoveAgent), summary: "remove a worker", usage: "usage: team-agent remove-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--from-spec] [--confirm] [--force] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
@@ -639,12 +639,23 @@ pub struct SessionsArgs {
|
|
|
639
639
|
/// enumerates the host discovery index and classifies each entry as
|
|
640
640
|
/// LIVE, STALE, or AMBIGUOUS after re-validating against canonical
|
|
641
641
|
/// workspace state.
|
|
642
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
643
|
+
pub enum LeadersView {
|
|
644
|
+
Live,
|
|
645
|
+
All,
|
|
646
|
+
Stale,
|
|
647
|
+
}
|
|
648
|
+
|
|
642
649
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
643
650
|
pub struct LeadersArgs {
|
|
651
|
+
pub view: LeadersView,
|
|
652
|
+
pub query: Option<String>,
|
|
653
|
+
pub prune: bool,
|
|
654
|
+
pub dry_run: bool,
|
|
644
655
|
pub json: bool,
|
|
645
656
|
}
|
|
646
657
|
|
|
647
|
-
/// Read-only
|
|
658
|
+
/// Read-only provider model catalog discovery.
|
|
648
659
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
649
660
|
pub struct ModelsArgs {
|
|
650
661
|
pub provider: String,
|