@team-agent/installer 0.5.102 → 0.5.103
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 +2 -1
- package/crates/team-agent/src/cli/models.rs +92 -226
- package/crates/team-agent/src/cli/spec.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +37 -161
- package/crates/team-agent/src/messaging/results.rs +11 -42
- package/crates/team-agent/src/provider/mod.rs +1 -0
- package/crates/team-agent/src/provider/model_catalog.rs +650 -0
- package/package.json +4 -4
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
|
@@ -290,6 +290,7 @@ pub(crate) fn default_help() -> String {
|
|
|
290
290
|
&["claim-leader", "takeover", "attach-leader"],
|
|
291
291
|
);
|
|
292
292
|
out.push_str("\nWorkflow: `team-agent models --provider pi`, then quick-start/add-agent/start-agent, then `team-agent send AGENT MESSAGE`.\n");
|
|
293
|
+
out.push_str("Model catalogs: pi, cursor_agent, codex, claude, claude_code.\n");
|
|
293
294
|
out.push_str("Provider launchers:\n team-agent codex|claude|copilot|grok|cursor|pi ...\n");
|
|
294
295
|
out.push_str("\nRun `team-agent <command> --help` for command flags.");
|
|
295
296
|
out
|
|
@@ -340,7 +341,7 @@ fn command_help(command: Option<&str>) -> String {
|
|
|
340
341
|
.to_string(),
|
|
341
342
|
Some("allow-peer-talk") => "usage: team-agent allow-peer-talk A B [--workspace WORKSPACE] [--json]".to_string(),
|
|
342
343
|
Some("status") => "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]\n\n输出七字段:name/provider/runtime_status/activity/health/session_name/tmux_command;人读与 --json 使用同一投影。缺少可靠定位或原生 tmux/process 采样时显示 unknown;tmux_command 可复制到对应目标。--summary/--detail 仅保留兼容性,不增加诊断字段。".to_string(),
|
|
343
|
-
Some("models") => "usage: team-agent models [--provider pi|cursor_agent] [QUERY|--search TEXT] [--json]\n\nLists exact provider model ids with case-insensitive multi-word search across provider, vendor, id, and
|
|
344
|
+
Some("models") => "usage: team-agent models [--provider pi|cursor_agent|codex|claude|claude_code] [QUERY|--search TEXT] [--json]\n\nLists exact provider model ids with case-insensitive multi-word search across provider, vendor, id, display name, and source aliases. Uses each provider's native catalog; Codex uses `codex debug models` and Claude uses SDK stream-json initialize.".to_string(),
|
|
344
345
|
Some("leaders") => "usage: team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]\n\nLists LIVE leaders by default. Use --all to include retained STALE entries, --stale to inspect only STALE entries, QUERY or --search TEXT to match workspace/team/name fields, and --prune to remove only entries proven terminal by canonical state. --dry-run is valid only with --prune.".to_string(),
|
|
345
346
|
Some("shutdown") => "usage: team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]".to_string(),
|
|
346
347
|
Some("restart") => "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json] [--detail]".to_string(),
|
|
@@ -3,292 +3,157 @@ 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
|
+
#[cfg(test)]
|
|
6
7
|
use std::path::Path;
|
|
8
|
+
#[cfg(test)]
|
|
7
9
|
use std::time::Duration;
|
|
10
|
+
|
|
11
|
+
#[cfg(test)]
|
|
8
12
|
const CATALOG_TIMEOUT: Duration = Duration::from_secs(10);
|
|
13
|
+
#[cfg(test)]
|
|
9
14
|
const MAX_CATALOG_BYTES: u64 = 1024 * 1024;
|
|
15
|
+
type ModelRecord = crate::provider::model_catalog::ModelRecord;
|
|
10
16
|
|
|
17
|
+
#[cfg(test)]
|
|
11
18
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
12
19
|
enum CatalogFormat {
|
|
13
20
|
Pi,
|
|
14
21
|
CursorAgent,
|
|
15
22
|
}
|
|
16
23
|
|
|
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
|
-
|
|
26
24
|
pub fn cmd_models(args: &ModelsArgs) -> Result<CmdResult, CliError> {
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
&format!("unsupported model provider {provider:?}"),
|
|
34
|
-
))
|
|
25
|
+
let records = match crate::provider::model_catalog::discover_model_catalog(&args.provider) {
|
|
26
|
+
Ok(records) => records,
|
|
27
|
+
Err(error) => {
|
|
28
|
+
let message = error.message(&args.provider);
|
|
29
|
+
let action = error.action(&args.provider);
|
|
30
|
+
return Ok(failure_with_action(args, &message, &action));
|
|
35
31
|
}
|
|
36
32
|
};
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/// Existing injectable Pi boundary retained for deterministic command/timeout tests.
|
|
41
|
-
fn cmd_models_with(
|
|
42
|
-
args: &ModelsArgs,
|
|
43
|
-
program: &Path,
|
|
44
|
-
timeout: Duration,
|
|
45
|
-
max_bytes: u64,
|
|
46
|
-
) -> Result<CmdResult, CliError> {
|
|
47
|
-
cmd_models_with_format(args, program, CatalogFormat::Pi, timeout, max_bytes)
|
|
33
|
+
render_catalog(args, records)
|
|
48
34
|
}
|
|
49
35
|
|
|
50
|
-
fn
|
|
51
|
-
args: &ModelsArgs,
|
|
52
|
-
program: &Path,
|
|
53
|
-
format: CatalogFormat,
|
|
54
|
-
timeout: Duration,
|
|
55
|
-
max_bytes: u64,
|
|
56
|
-
) -> Result<CmdResult, CliError> {
|
|
57
|
-
let bytes = match run_catalog(program, timeout, max_bytes) {
|
|
58
|
-
Ok(bytes) => bytes,
|
|
59
|
-
Err(message) => return Ok(failure(args, &catalog_error_prefix(format, message))),
|
|
60
|
-
};
|
|
61
|
-
let all = match parse_catalog(&bytes, format) {
|
|
62
|
-
Ok(models) => models,
|
|
63
|
-
Err(message) => return Ok(failure(args, &message)),
|
|
64
|
-
};
|
|
36
|
+
fn render_catalog(args: &ModelsArgs, all: Vec<ModelRecord>) -> Result<CmdResult, CliError> {
|
|
65
37
|
let visible = args.search.as_deref().map_or_else(
|
|
66
38
|
|| all.clone(),
|
|
67
|
-
|search|
|
|
68
|
-
all.iter()
|
|
69
|
-
.filter(|model| model_matches(model, search))
|
|
70
|
-
.cloned()
|
|
71
|
-
.collect::<Vec<_>>()
|
|
72
|
-
},
|
|
39
|
+
|search| all.iter().filter(|model| crate::provider::model_catalog::model_matches(model, search)).cloned().collect(),
|
|
73
40
|
);
|
|
74
|
-
let current =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
"default": model.default,
|
|
90
|
-
})
|
|
91
|
-
})
|
|
92
|
-
.collect();
|
|
41
|
+
let current = if args.provider == "pi" {
|
|
42
|
+
current_role_model(&all.iter().map(|model| model.id.clone()).collect::<Vec<_>>())
|
|
43
|
+
} else {
|
|
44
|
+
None
|
|
45
|
+
};
|
|
46
|
+
let entries: Vec<Value> = visible.iter().map(|model| json!({
|
|
47
|
+
"role_model": model.id,
|
|
48
|
+
"current": if args.provider == "pi" { json!(current.as_deref() == Some(model.id.as_str())) } else { Value::Null },
|
|
49
|
+
"provider": model.provider,
|
|
50
|
+
"vendor": model.vendor,
|
|
51
|
+
"model_id": model.id,
|
|
52
|
+
"display_name": model.display_name,
|
|
53
|
+
"default": model.default,
|
|
54
|
+
"aliases": model.aliases,
|
|
55
|
+
})).collect();
|
|
93
56
|
let provider = args.provider.as_str();
|
|
94
57
|
let value = json!({
|
|
95
58
|
"schema_version": "models.v1", "ok": true, "provider": provider, "models": entries,
|
|
96
59
|
"auth": "ok", "auth_basis": "catalog_visibility", "current_role_model": current, "search": args.search,
|
|
97
60
|
});
|
|
98
61
|
if args.json {
|
|
99
|
-
let text = serde_json::to_string_pretty(&value)?;
|
|
100
62
|
Ok(CmdResult {
|
|
101
|
-
output: CmdOutput::Human(
|
|
63
|
+
output: CmdOutput::Human(serde_json::to_string_pretty(&value)?),
|
|
102
64
|
exit: ExitCode::Ok,
|
|
103
65
|
as_json: false,
|
|
104
66
|
preserve_json_order: true,
|
|
105
67
|
})
|
|
106
68
|
} else {
|
|
107
|
-
let mut lines = vec![format!(
|
|
108
|
-
"models.v1 | {provider} models (copyable model_id):"
|
|
109
|
-
)];
|
|
69
|
+
let mut lines = vec![format!("models.v1 | {provider} models (copyable model_id):")];
|
|
110
70
|
lines.push("PROVIDER VENDOR MODEL_ID DISPLAY_NAME CURRENT DEFAULT".to_string());
|
|
111
|
-
lines.extend(visible.iter().map(|model|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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()),
|
|
126
|
-
)
|
|
127
|
-
}));
|
|
128
|
-
lines.push(format!(
|
|
129
|
-
"auth: ok (catalog_visibility); {} model(s)",
|
|
130
|
-
visible.len()
|
|
131
|
-
));
|
|
71
|
+
lines.extend(visible.iter().map(|model| format!(
|
|
72
|
+
"{} {} {} {} {} {}", model.provider, model.vendor, model.id, model.display_name,
|
|
73
|
+
if args.provider == "pi" { (current.as_deref() == Some(model.id.as_str())).to_string() } else { "—".to_string() },
|
|
74
|
+
model.default.map_or_else(|| "—".to_string(), |value| value.to_string()),
|
|
75
|
+
)));
|
|
76
|
+
lines.push(format!("auth: ok (catalog_visibility); {} model(s)", visible.len()));
|
|
132
77
|
if visible.is_empty() {
|
|
133
|
-
lines.push("No models matched --search/query.".to_string());
|
|
78
|
+
lines.push("No models matched --search/query; rerun without a search to list the full catalog.".to_string());
|
|
134
79
|
}
|
|
135
80
|
Ok(CmdResult::human(lines.join("\n")))
|
|
136
81
|
}
|
|
137
82
|
}
|
|
138
83
|
|
|
84
|
+
/// Existing injectable Pi boundary retained for deterministic command/timeout tests.
|
|
85
|
+
#[cfg(test)]
|
|
86
|
+
fn cmd_models_with(args: &ModelsArgs, program: &Path, timeout: Duration, max_bytes: u64) -> Result<CmdResult, CliError> {
|
|
87
|
+
cmd_models_with_format(args, program, CatalogFormat::Pi, timeout, max_bytes)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[cfg(test)]
|
|
91
|
+
fn cmd_models_with_format(args: &ModelsArgs, program: &Path, format: CatalogFormat, timeout: Duration, max_bytes: u64) -> Result<CmdResult, CliError> {
|
|
92
|
+
let bytes = match run_catalog(program, timeout, max_bytes) {
|
|
93
|
+
Ok(bytes) => bytes,
|
|
94
|
+
Err(message) => return Ok(failure(args, &message)),
|
|
95
|
+
};
|
|
96
|
+
let all = match parse_catalog(&bytes, format) {
|
|
97
|
+
Ok(models) => models,
|
|
98
|
+
Err(message) => return Ok(failure(args, &message)),
|
|
99
|
+
};
|
|
100
|
+
render_catalog(args, all)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[cfg(test)]
|
|
139
104
|
fn parse_catalog(bytes: &[u8], format: CatalogFormat) -> Result<Vec<ModelRecord>, String> {
|
|
140
|
-
match format {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
}
|
|
105
|
+
let provider = match format { CatalogFormat::Pi => "pi", CatalogFormat::CursorAgent => "cursor_agent" };
|
|
106
|
+
let result = match format {
|
|
107
|
+
CatalogFormat::Pi => crate::provider::model_catalog::parse_pi_catalog(bytes),
|
|
108
|
+
CatalogFormat::CursorAgent => crate::provider::model_catalog::parse_cursor_catalog(bytes),
|
|
109
|
+
};
|
|
110
|
+
result.map_err(|error| error.message(provider))
|
|
162
111
|
}
|
|
163
112
|
|
|
113
|
+
#[cfg(test)]
|
|
164
114
|
fn parse_cursor_catalog(bytes: &[u8]) -> Result<Vec<ModelRecord>, String> {
|
|
165
|
-
|
|
166
|
-
.map_err(|
|
|
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)
|
|
115
|
+
crate::provider::model_catalog::parse_cursor_catalog(bytes)
|
|
116
|
+
.map_err(|error| error.message("cursor_agent"))
|
|
207
117
|
}
|
|
208
118
|
|
|
119
|
+
#[cfg(test)]
|
|
209
120
|
fn model_matches(model: &ModelRecord, search: &str) -> bool {
|
|
210
|
-
|
|
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
|
-
})
|
|
121
|
+
crate::provider::model_catalog::model_matches(model, search)
|
|
224
122
|
}
|
|
225
123
|
|
|
124
|
+
#[cfg(test)]
|
|
226
125
|
fn current_model_for(format: CatalogFormat, models: &[ModelRecord]) -> Option<String> {
|
|
227
126
|
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.
|
|
127
|
+
CatalogFormat::Pi => current_role_model(&models.iter().map(|model| model.id.clone()).collect::<Vec<_>>()),
|
|
236
128
|
CatalogFormat::CursorAgent => None,
|
|
237
129
|
}
|
|
238
130
|
}
|
|
239
131
|
|
|
240
|
-
|
|
241
|
-
match format {
|
|
242
|
-
CatalogFormat::Pi => message,
|
|
243
|
-
CatalogFormat::CursorAgent => message.replace("Pi", "Cursor"),
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
132
|
+
#[cfg(test)]
|
|
247
133
|
fn failure(args: &ModelsArgs, message: &str) -> CmdResult {
|
|
248
|
-
let executable = if args.provider == "cursor_agent" {
|
|
249
|
-
"agent"
|
|
250
|
-
} else {
|
|
251
|
-
"pi"
|
|
252
|
-
};
|
|
253
134
|
let action = if message.starts_with("unsupported model provider") {
|
|
254
|
-
"choose a supported provider: pi or
|
|
135
|
+
"choose a supported provider: pi, cursor_agent, codex, claude, or claude_code".to_string()
|
|
255
136
|
} else {
|
|
256
|
-
|
|
257
|
-
"
|
|
258
|
-
|
|
259
|
-
|
|
137
|
+
let executable = match args.provider.as_str() {
|
|
138
|
+
"cursor_agent" => "agent",
|
|
139
|
+
"codex" => "codex",
|
|
140
|
+
"claude" | "claude_code" => "claude",
|
|
141
|
+
_ => "pi",
|
|
142
|
+
};
|
|
143
|
+
format!("install or repair the PATH-first `{executable}` executable, then retry `team-agent models --provider {}`", args.provider)
|
|
260
144
|
};
|
|
145
|
+
failure_with_action(args, message, &action)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn failure_with_action(args: &ModelsArgs, message: &str, action: &str) -> CmdResult {
|
|
261
149
|
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 });
|
|
262
150
|
if args.json {
|
|
263
|
-
let text = serde_json::to_string_pretty(&value)
|
|
264
|
-
|
|
265
|
-
CmdResult {
|
|
266
|
-
output: CmdOutput::Human(text),
|
|
267
|
-
exit: ExitCode::Error,
|
|
268
|
-
as_json: false,
|
|
269
|
-
preserve_json_order: true,
|
|
270
|
-
}
|
|
151
|
+
let text = serde_json::to_string_pretty(&value).unwrap_or_else(|_| "{\"schema_version\":\"models.v1\",\"ok\":false}".to_string());
|
|
152
|
+
CmdResult { output: CmdOutput::Human(text), exit: ExitCode::Error, as_json: false, preserve_json_order: true }
|
|
271
153
|
} else {
|
|
272
154
|
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 }
|
|
273
155
|
}
|
|
274
156
|
}
|
|
275
|
-
|
|
276
|
-
fn safe_catalog_error(error: crate::provider::ProviderError) -> String {
|
|
277
|
-
let text = error.to_string();
|
|
278
|
-
if text.contains("not UTF-8") {
|
|
279
|
-
"Pi model catalog is not valid UTF-8".into()
|
|
280
|
-
} else if text.contains("duplicate") {
|
|
281
|
-
"Pi model catalog contains duplicate model ids".into()
|
|
282
|
-
} else if text.contains("header") {
|
|
283
|
-
"Pi model catalog has an unexpected header".into()
|
|
284
|
-
} else if text.contains("row") {
|
|
285
|
-
"Pi model catalog contains a malformed row".into()
|
|
286
|
-
} else if text.contains("empty") || text.contains("no models") {
|
|
287
|
-
"Pi model catalog is empty".into()
|
|
288
|
-
} else {
|
|
289
|
-
"Pi model catalog is invalid".into()
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
157
|
fn current_role_model(models: &[String]) -> Option<String> {
|
|
293
158
|
current_role_model_from(
|
|
294
159
|
models,
|
|
@@ -309,6 +174,7 @@ fn current_role_model_from(
|
|
|
309
174
|
}
|
|
310
175
|
|
|
311
176
|
/// Injectable executable, deadline, and byte-limit boundary for deterministic tests.
|
|
177
|
+
#[cfg(test)]
|
|
312
178
|
fn run_catalog(program: &Path, timeout: Duration, max_bytes: u64) -> Result<Vec<u8>, String> {
|
|
313
179
|
crate::lifecycle::launch::pi_mcp::run_pi_catalog(program, timeout, max_bytes)
|
|
314
180
|
}
|
|
@@ -995,7 +861,7 @@ mod tests {
|
|
|
995
861
|
assert!(text.contains("No models matched --search") && text.contains("auth: ok"));
|
|
996
862
|
let refusal = crate::cli::emit::__test_dispatch(
|
|
997
863
|
"models",
|
|
998
|
-
&["--provider".into(), "
|
|
864
|
+
&["--provider".into(), "cloud".into()],
|
|
999
865
|
Path::new("/tmp"),
|
|
1000
866
|
);
|
|
1001
867
|
assert!(matches!(refusal, Ok(ExitCode::Error)));
|
|
@@ -1095,14 +961,14 @@ mod tests {
|
|
|
1095
961
|
);
|
|
1096
962
|
assert_eq!(
|
|
1097
963
|
crate::cli::run(
|
|
1098
|
-
&["models".into(), "--provider".into(), "
|
|
964
|
+
&["models".into(), "--provider".into(), "cloud".into()],
|
|
1099
965
|
Path::new("/tmp")
|
|
1100
966
|
),
|
|
1101
967
|
ExitCode::Error
|
|
1102
968
|
);
|
|
1103
969
|
assert_eq!(
|
|
1104
970
|
crate::cli::spec::command_spec("models").unwrap().usage,
|
|
1105
|
-
"usage: team-agent models [--provider pi|cursor_agent] [QUERY|--search TEXT] [--json]"
|
|
971
|
+
"usage: team-agent models [--provider pi|cursor_agent|codex|claude|claude_code] [QUERY|--search TEXT] [--json]"
|
|
1106
972
|
);
|
|
1107
973
|
}
|
|
1108
974
|
|
|
@@ -147,7 +147,7 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
147
147
|
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] [--backend tmux|conpty] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
|
|
148
148
|
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, sunset: Some("next compatibility release"), action: Some("use positional logical TO and the returned message id"), governance: None },
|
|
149
149
|
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, sunset: None, action: None, governance: None },
|
|
150
|
-
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, sunset: None, action: None, governance: None },
|
|
150
|
+
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|codex|claude|claude_code] [QUERY|--search TEXT] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, sunset: None, action: None, governance: None },
|
|
151
151
|
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, sunset: None, action: None, governance: None },
|
|
152
152
|
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, sunset: None, action: None, governance: None },
|
|
153
153
|
CommandSpec { name: "restart", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Restart), summary: "restart the selected team", usage: "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, sunset: None, action: None, governance: None },
|