@team-agent/installer 0.5.112 → 0.5.114
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 +252 -84
- package/Cargo.toml +3 -1
- package/crates/team-agent/Cargo.toml +3 -0
- package/crates/team-agent/src/cli/adapters.rs +119 -50
- package/crates/team-agent/src/cli/diagnose.rs +36 -13
- package/crates/team-agent/src/cli/emit.rs +173 -109
- package/crates/team-agent/src/cli/leader.rs +16 -0
- package/crates/team-agent/src/cli/leader_prompt.rs +261 -0
- package/crates/team-agent/src/cli/leaders.rs +32 -32
- package/crates/team-agent/src/cli/mod.rs +158 -14
- package/crates/team-agent/src/cli/models.rs +68 -18
- package/crates/team-agent/src/cli/named_address.rs +17 -16
- package/crates/team-agent/src/cli/route.rs +4 -4
- package/crates/team-agent/src/cli/send/presentation.rs +19 -18
- package/crates/team-agent/src/cli/send/resolve.rs +11 -11
- package/crates/team-agent/src/cli/send.rs +4 -4
- package/crates/team-agent/src/cli/spec.rs +35 -29
- package/crates/team-agent/src/cli/status.rs +7 -7
- package/crates/team-agent/src/cli/status_port/brief.rs +268 -101
- package/crates/team-agent/src/cli/tests/base.rs +94 -6
- package/crates/team-agent/src/cli/tests/divergence.rs +3 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +1 -1
- package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +9 -7
- package/crates/team-agent/src/cli/triage.rs +53 -2
- package/crates/team-agent/src/cli/types.rs +8 -8
- package/crates/team-agent/src/compiler/tests.rs +12 -0
- package/crates/team-agent/src/compiler.rs +65 -26
- package/crates/team-agent/src/contract_runtime/backend.rs +646 -0
- package/crates/team-agent/src/contract_runtime/config.rs +309 -0
- package/crates/team-agent/src/contract_runtime/discovery.rs +237 -0
- package/crates/team-agent/src/contract_runtime/forward.rs +229 -0
- package/crates/team-agent/src/contract_runtime/framework.rs +261 -0
- package/crates/team-agent/src/contract_runtime/mcp.rs +396 -0
- package/crates/team-agent/src/contract_runtime/mod.rs +14 -0
- package/crates/team-agent/src/contract_runtime/prompt.rs +216 -0
- package/crates/team-agent/src/contract_runtime/pump.rs +207 -0
- package/crates/team-agent/src/contract_runtime/registry.rs +30 -0
- package/crates/team-agent/src/coordinator/tick.rs +26 -8
- package/crates/team-agent/src/leader/mod.rs +1 -0
- package/crates/team-agent/src/leader/prompt.rs +906 -0
- package/crates/team-agent/src/leader/start.rs +265 -16
- package/crates/team-agent/src/leader/tests/identity.rs +21 -0
- package/crates/team-agent/src/leader/tests/mod.rs +2 -0
- package/crates/team-agent/src/leader/tests/prompt_contract.rs +268 -0
- package/crates/team-agent/src/leader/types.rs +6 -0
- package/crates/team-agent/src/lib.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +760 -365
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +38 -9
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +17 -0
- package/crates/team-agent/src/lifecycle/launch.rs +39 -28
- package/crates/team-agent/src/lifecycle/profile_launch.rs +58 -46
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +97 -52
- package/crates/team-agent/src/lifecycle/restart.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
- package/crates/team-agent/src/lifecycle/types.rs +4 -1
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +37 -27
- package/crates/team-agent/src/main.rs +6 -0
- package/crates/team-agent/src/messaging/delivery.rs +50 -1
- package/crates/team-agent/src/messaging/send.rs +10 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +131 -0
- package/crates/team-agent/src/model/spec.rs +24 -12
- package/crates/team-agent/src/provider/adapter.rs +8 -59
- package/crates/team-agent/src/provider/adapters/claude.rs +6 -14
- package/crates/team-agent/src/provider/adapters/codex.rs +8 -18
- package/crates/team-agent/src/provider/adapters/copilot.rs +5 -22
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +8 -45
- package/crates/team-agent/src/provider/adapters/grok.rs +7 -31
- package/crates/team-agent/src/provider/adapters/pi.rs +7 -18
- package/crates/team-agent/src/provider/command_helpers.rs +45 -0
- package/crates/team-agent/src/provider/mod.rs +1 -0
- package/crates/team-agent/src/provider/model_catalog.rs +21 -28
- package/crates/team-agent/src/provider/tests/adapter.rs +70 -0
- package/crates/team-agent/src/provider/tests/command_helpers.rs +55 -0
- package/crates/team-agent/src/state/persist.rs +73 -0
- package/package.json +4 -4
|
@@ -49,12 +49,12 @@ use crate::messaging::{self, MessageTarget, SendOptions, TrustedSender};
|
|
|
49
49
|
use crate::model::ids::{TaskId, TeamKey};
|
|
50
50
|
|
|
51
51
|
pub(crate) const COMMS_BOUNDARY_TEXT: &str =
|
|
52
|
-
"
|
|
53
|
-
pub(crate) const QUICK_START_REMINDER: &str = "
|
|
52
|
+
"This checks terminal connection consistency only; it does not send a message or prove that a worker replied.";
|
|
53
|
+
pub(crate) const QUICK_START_REMINDER: &str = "Next: send a task. Use team-agent status for status and team-agent inbox leader -n 3 for replies; do not inspect worker terminal content.";
|
|
54
54
|
pub(crate) const SEND_REMINDER: &str =
|
|
55
|
-
"
|
|
55
|
+
"Wait for an actual reply; use team-agent inbox leader -n 3. Do not inspect worker terminal content.";
|
|
56
56
|
pub(crate) const STATUS_REMINDER: &str =
|
|
57
|
-
"
|
|
57
|
+
"Next: use team-agent inbox leader -n 3 for replies. Status is not proof of task completion; do not inspect worker terminal content.";
|
|
58
58
|
|
|
59
59
|
pub mod adapters;
|
|
60
60
|
pub mod attach_app_server_leader;
|
|
@@ -66,6 +66,7 @@ pub mod leaders;
|
|
|
66
66
|
pub mod models;
|
|
67
67
|
pub mod named_address;
|
|
68
68
|
pub mod profile;
|
|
69
|
+
pub(crate) mod leader_prompt;
|
|
69
70
|
pub(crate) mod route;
|
|
70
71
|
pub mod send;
|
|
71
72
|
pub(crate) mod spec;
|
|
@@ -172,12 +173,18 @@ pub mod lifecycle_port {
|
|
|
172
173
|
Err(crate::leader::start::PrepareLeaderStartError::PaneAuthorityRefused(refusal)) => {
|
|
173
174
|
return Ok(pane_authority_refusal_value(provider, attach, &refusal));
|
|
174
175
|
}
|
|
176
|
+
Err(crate::leader::start::PrepareLeaderStartError::Leader(
|
|
177
|
+
crate::leader::LeaderError::Prompt(error),
|
|
178
|
+
)) => return Ok(error.report()),
|
|
175
179
|
Err(crate::leader::start::PrepareLeaderStartError::Leader(error)) => {
|
|
176
180
|
return Err(CliError::Runtime(error.to_string()));
|
|
177
181
|
}
|
|
178
182
|
};
|
|
179
|
-
let outcome = crate::leader::start::execute_prepared_leader_start(&prepared, cwd)
|
|
180
|
-
|
|
183
|
+
let outcome = match crate::leader::start::execute_prepared_leader_start(&prepared, cwd) {
|
|
184
|
+
Ok(outcome) => outcome,
|
|
185
|
+
Err(crate::leader::LeaderError::Prompt(error)) => return Ok(error.report()),
|
|
186
|
+
Err(error) => return Err(CliError::Runtime(error.to_string())),
|
|
187
|
+
};
|
|
181
188
|
let plan = prepared.plan();
|
|
182
189
|
let ok = match outcome.status {
|
|
183
190
|
crate::leader::LeaderLaunchStatus::Exited => outcome.exit_code == Some(0),
|
|
@@ -1297,6 +1304,16 @@ pub mod lifecycle_port {
|
|
|
1297
1304
|
Some(state) => state,
|
|
1298
1305
|
None => shutdown_state_for_team(&run_workspace, team)?,
|
|
1299
1306
|
};
|
|
1307
|
+
#[cfg(unix)]
|
|
1308
|
+
if crate::contract_runtime::framework::has_contract(&state) {
|
|
1309
|
+
crate::contract_runtime::framework::stop_selected(&run_workspace, &state).map_err(
|
|
1310
|
+
|error| {
|
|
1311
|
+
CliError::Runtime(format!(
|
|
1312
|
+
"native shutdown failed; owned resources preserved: {error}"
|
|
1313
|
+
))
|
|
1314
|
+
},
|
|
1315
|
+
)?;
|
|
1316
|
+
}
|
|
1300
1317
|
deadline.check("refresh_provider_sessions")?;
|
|
1301
1318
|
let captured_missing_sessions =
|
|
1302
1319
|
crate::lifecycle::restart::refresh_missing_provider_sessions(&mut state)
|
|
@@ -3108,6 +3125,10 @@ pub mod lifecycle_port {
|
|
|
3108
3125
|
team: Option<&str>,
|
|
3109
3126
|
session_converge_deadline_ms: Option<u64>,
|
|
3110
3127
|
) -> Result<Value, CliError> {
|
|
3128
|
+
#[cfg(unix)]
|
|
3129
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3130
|
+
workspace, team, None, "restart",
|
|
3131
|
+
)?;
|
|
3111
3132
|
match crate::lifecycle::restart_with_session_convergence_deadline(
|
|
3112
3133
|
workspace,
|
|
3113
3134
|
allow_fresh,
|
|
@@ -3128,6 +3149,19 @@ pub mod lifecycle_port {
|
|
|
3128
3149
|
team: Option<&str>,
|
|
3129
3150
|
role_config: &crate::lifecycle::role_config::RoleConfigPatch,
|
|
3130
3151
|
) -> Result<Value, CliError> {
|
|
3152
|
+
#[cfg(unix)]
|
|
3153
|
+
{
|
|
3154
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3155
|
+
workspace,
|
|
3156
|
+
team,
|
|
3157
|
+
Some(agent),
|
|
3158
|
+
"start-agent",
|
|
3159
|
+
)?;
|
|
3160
|
+
crate::contract_runtime::framework::require_legacy_role(
|
|
3161
|
+
None,
|
|
3162
|
+
role_config.provider.as_deref(),
|
|
3163
|
+
)?;
|
|
3164
|
+
}
|
|
3131
3165
|
let _ = force; // Never grants authority to stop an already-running seat.
|
|
3132
3166
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
3133
3167
|
match crate::lifecycle::role_config::start_agent_from_role(
|
|
@@ -3195,6 +3229,13 @@ pub mod lifecycle_port {
|
|
|
3195
3229
|
team: Option<&str>,
|
|
3196
3230
|
) -> Result<Value, CliError> {
|
|
3197
3231
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
3232
|
+
#[cfg(unix)]
|
|
3233
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3234
|
+
workspace,
|
|
3235
|
+
team,
|
|
3236
|
+
Some(agent),
|
|
3237
|
+
"stop-agent",
|
|
3238
|
+
)?;
|
|
3198
3239
|
match crate::lifecycle::stop_agent(workspace, &agent_id, team) {
|
|
3199
3240
|
Ok(report) => Ok(json!({"ok": true, "agent_id": agent, "stopped": report.stopped})),
|
|
3200
3241
|
Err(e) => Ok(error_value(e)),
|
|
@@ -3209,6 +3250,13 @@ pub mod lifecycle_port {
|
|
|
3209
3250
|
team: Option<&str>,
|
|
3210
3251
|
) -> Result<Value, CliError> {
|
|
3211
3252
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
3253
|
+
#[cfg(unix)]
|
|
3254
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3255
|
+
workspace,
|
|
3256
|
+
team,
|
|
3257
|
+
Some(agent),
|
|
3258
|
+
"reset-agent",
|
|
3259
|
+
)?;
|
|
3212
3260
|
match crate::lifecycle::reset_agent(workspace, &agent_id, discard_session, true, team) {
|
|
3213
3261
|
Ok(crate::lifecycle::ResetAgentOutcome::Reset {
|
|
3214
3262
|
env,
|
|
@@ -3256,6 +3304,19 @@ pub mod lifecycle_port {
|
|
|
3256
3304
|
) -> Result<Value, CliError> {
|
|
3257
3305
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
3258
3306
|
let source = (!role_file.is_empty()).then(|| Path::new(role_file));
|
|
3307
|
+
#[cfg(unix)]
|
|
3308
|
+
{
|
|
3309
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3310
|
+
workspace,
|
|
3311
|
+
team,
|
|
3312
|
+
Some(agent),
|
|
3313
|
+
"add-agent",
|
|
3314
|
+
)?;
|
|
3315
|
+
crate::contract_runtime::framework::require_legacy_role(
|
|
3316
|
+
source,
|
|
3317
|
+
role_config.provider.as_deref(),
|
|
3318
|
+
)?;
|
|
3319
|
+
}
|
|
3259
3320
|
match crate::lifecycle::role_config::add_agent_from_role(
|
|
3260
3321
|
workspace,
|
|
3261
3322
|
&agent_id,
|
|
@@ -3308,6 +3369,13 @@ pub mod lifecycle_port {
|
|
|
3308
3369
|
) -> Result<Value, CliError> {
|
|
3309
3370
|
let source = crate::model::ids::AgentId::new(source_agent);
|
|
3310
3371
|
let dest = crate::model::ids::AgentId::new(as_agent_id);
|
|
3372
|
+
#[cfg(unix)]
|
|
3373
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3374
|
+
workspace,
|
|
3375
|
+
team,
|
|
3376
|
+
Some(source_agent),
|
|
3377
|
+
"fork-agent",
|
|
3378
|
+
)?;
|
|
3311
3379
|
match crate::lifecycle::fork_agent(workspace, &source, &dest, label, true, team) {
|
|
3312
3380
|
Ok(report) => {
|
|
3313
3381
|
let mut value = json!({
|
|
@@ -3345,6 +3413,13 @@ pub mod lifecycle_port {
|
|
|
3345
3413
|
) -> Result<Value, CliError> {
|
|
3346
3414
|
let source = crate::model::ids::AgentId::new(source_agent);
|
|
3347
3415
|
let dest = crate::model::ids::AgentId::new(as_agent_id);
|
|
3416
|
+
#[cfg(unix)]
|
|
3417
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3418
|
+
workspace,
|
|
3419
|
+
team,
|
|
3420
|
+
Some(source_agent),
|
|
3421
|
+
"clone-agent",
|
|
3422
|
+
)?;
|
|
3348
3423
|
match crate::lifecycle::clone_agent(workspace, &source, &dest, label, true, team) {
|
|
3349
3424
|
Ok(report) => Ok(json!({
|
|
3350
3425
|
"ok": true,
|
|
@@ -3370,6 +3445,13 @@ pub mod lifecycle_port {
|
|
|
3370
3445
|
team: Option<&str>,
|
|
3371
3446
|
) -> Result<Value, CliError> {
|
|
3372
3447
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
3448
|
+
#[cfg(unix)]
|
|
3449
|
+
crate::contract_runtime::framework::require_legacy_lifecycle(
|
|
3450
|
+
workspace,
|
|
3451
|
+
team,
|
|
3452
|
+
Some(agent),
|
|
3453
|
+
"remove-agent",
|
|
3454
|
+
)?;
|
|
3373
3455
|
match crate::lifecycle::remove_agent_flag_requirements(workspace, &agent_id, team) {
|
|
3374
3456
|
Ok(requirements) => {
|
|
3375
3457
|
if !remove_agent_missing_flags(from_spec, confirm, force, &requirements).is_empty()
|
|
@@ -3712,7 +3794,13 @@ pub mod lifecycle_port {
|
|
|
3712
3794
|
// tool-set load has not been confirmed yet (PendingToolLoad).
|
|
3713
3795
|
let incomplete_session_capture_agents =
|
|
3714
3796
|
launch.session_capture_incomplete_agents.clone();
|
|
3715
|
-
let
|
|
3797
|
+
let agent_ids: Vec<_> = launch
|
|
3798
|
+
.started
|
|
3799
|
+
.iter()
|
|
3800
|
+
.map(|agent| agent.agent_id.as_str())
|
|
3801
|
+
.chain(launch.contract_started.iter().map(|id| id.as_str()))
|
|
3802
|
+
.collect();
|
|
3803
|
+
let all_spawned = !agent_ids.is_empty();
|
|
3716
3804
|
let leader_receiver_attached = launch.leader_receiver_attached;
|
|
3717
3805
|
let all_resumable_have_session = incomplete_session_capture_agents.is_empty();
|
|
3718
3806
|
let all_workers_spawned = all_spawned;
|
|
@@ -3840,7 +3928,7 @@ pub mod lifecycle_port {
|
|
|
3840
3928
|
"ready": readiness_json.get("ready").cloned().unwrap_or(Value::Bool(false)),
|
|
3841
3929
|
"session_name": session_name.as_str(),
|
|
3842
3930
|
"team": team,
|
|
3843
|
-
"agent_ids":
|
|
3931
|
+
"agent_ids": agent_ids,
|
|
3844
3932
|
"dry_run": launch.dry_run,
|
|
3845
3933
|
"next_actions": next_actions,
|
|
3846
3934
|
"attach_commands": attach_commands,
|
|
@@ -3961,9 +4049,8 @@ pub mod lifecycle_port {
|
|
|
3961
4049
|
.is_some());
|
|
3962
4050
|
}
|
|
3963
4051
|
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
let value = quick_start_value(crate::lifecycle::QuickStartReport::Ready {
|
|
4052
|
+
fn pending_tool_load_report() -> crate::lifecycle::QuickStartReport {
|
|
4053
|
+
crate::lifecycle::QuickStartReport::Ready {
|
|
3967
4054
|
session_name: crate::transport::SessionName::new("team-demo"),
|
|
3968
4055
|
launch: Box::new(crate::lifecycle::LaunchReport {
|
|
3969
4056
|
session_name: crate::transport::SessionName::new("team-demo"),
|
|
@@ -3979,6 +4066,7 @@ pub mod lifecycle_port {
|
|
|
3979
4066
|
provider_projects_root: None,
|
|
3980
4067
|
managed_mcp_config: false,
|
|
3981
4068
|
}],
|
|
4069
|
+
contract_started: Vec::new(),
|
|
3982
4070
|
dry_run: false,
|
|
3983
4071
|
tmux_endpoint: None,
|
|
3984
4072
|
routes: Vec::new(),
|
|
@@ -3991,7 +4079,63 @@ pub mod lifecycle_port {
|
|
|
3991
4079
|
attach_commands: Vec::new(),
|
|
3992
4080
|
worker_readiness: crate::lifecycle::QuickStartReadiness::PendingToolLoad,
|
|
3993
4081
|
team: "team-demo".to_string(),
|
|
3994
|
-
}
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
#[test]
|
|
4086
|
+
fn contract_start_receipts_join_summary_without_bypassing_leader_binding() {
|
|
4087
|
+
for (legacy, contract) in [(false, true), (true, true), (true, false), (false, false)] {
|
|
4088
|
+
for bound in [false, true] {
|
|
4089
|
+
let mut report = pending_tool_load_report();
|
|
4090
|
+
let crate::lifecycle::QuickStartReport::Ready { launch, .. } = &mut report else {
|
|
4091
|
+
panic!("ready fixture required");
|
|
4092
|
+
};
|
|
4093
|
+
if !legacy {
|
|
4094
|
+
launch.started.clear();
|
|
4095
|
+
}
|
|
4096
|
+
if contract {
|
|
4097
|
+
launch
|
|
4098
|
+
.contract_started
|
|
4099
|
+
.push(crate::model::ids::AgentId::new("kiro-worker"));
|
|
4100
|
+
}
|
|
4101
|
+
launch.leader_receiver_attached = bound;
|
|
4102
|
+
launch.leader_bind_stage = (!bound).then(|| "caller_pane".into());
|
|
4103
|
+
launch.leader_bind_reason = (!bound).then(|| "caller_pane_missing".into());
|
|
4104
|
+
assert_eq!(launch.started.len(), usize::from(legacy));
|
|
4105
|
+
let expected: Vec<_> = [(legacy, "worker"), (contract, "kiro-worker")]
|
|
4106
|
+
.into_iter()
|
|
4107
|
+
.filter_map(|(started, id)| started.then_some(id))
|
|
4108
|
+
.collect();
|
|
4109
|
+
let value = quick_start_value(report);
|
|
4110
|
+
assert_eq!(value["agent_ids"], json!(expected));
|
|
4111
|
+
assert_eq!(
|
|
4112
|
+
value["readiness"]["all_workers_spawned"],
|
|
4113
|
+
json!(legacy || contract)
|
|
4114
|
+
);
|
|
4115
|
+
assert_eq!(value["readiness"]["all_spawned"], json!(legacy || contract));
|
|
4116
|
+
assert_eq!(value["readiness"]["leader_receiver_attached"], json!(bound));
|
|
4117
|
+
assert_eq!(value["ok"], json!((legacy || contract) && bound));
|
|
4118
|
+
assert_eq!(value["ready"], json!((legacy || contract) && bound));
|
|
4119
|
+
if !bound {
|
|
4120
|
+
assert_eq!(value["status"], "leader_binding_incomplete");
|
|
4121
|
+
assert_eq!(value["reason"], "caller_pane_missing");
|
|
4122
|
+
} else {
|
|
4123
|
+
assert_eq!(value["status"], "pending_tool_load");
|
|
4124
|
+
}
|
|
4125
|
+
let mut compact = value.clone();
|
|
4126
|
+
compact_quick_start_value(&mut compact);
|
|
4127
|
+
assert_eq!(
|
|
4128
|
+
compact["worker_readiness"]["all_workers_spawned"],
|
|
4129
|
+
json!(legacy || contract)
|
|
4130
|
+
);
|
|
4131
|
+
assert_eq!(compact["ready"], value["ready"]);
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
|
|
4136
|
+
#[test]
|
|
4137
|
+
fn pending_tool_load_success_output_explains_bound_send_without_doctor() {
|
|
4138
|
+
let value = quick_start_value(pending_tool_load_report());
|
|
3995
4139
|
assert_eq!(
|
|
3996
4140
|
value.get("summary").and_then(Value::as_str),
|
|
3997
4141
|
Some("team started; leader bound; send a task next (worker tool load unverified): team-demo")
|
|
@@ -4176,7 +4320,7 @@ pub mod lifecycle_port {
|
|
|
4176
4320
|
"team-agent".to_string(),
|
|
4177
4321
|
"send".to_string(),
|
|
4178
4322
|
"implementer".to_string(),
|
|
4179
|
-
"
|
|
4323
|
+
"Complete the task and reply to the leader.".to_string(),
|
|
4180
4324
|
"--workspace".to_string(),
|
|
4181
4325
|
team_dir.to_string_lossy().into_owned(),
|
|
4182
4326
|
"--team".to_string(),
|
|
@@ -4213,7 +4357,7 @@ pub mod lifecycle_port {
|
|
|
4213
4357
|
"team-agent".to_string(),
|
|
4214
4358
|
"send".to_string(),
|
|
4215
4359
|
"implementer".to_string(),
|
|
4216
|
-
"
|
|
4360
|
+
"Complete the task and reply to the leader.".to_string(),
|
|
4217
4361
|
"--workspace".to_string(),
|
|
4218
4362
|
team_dir.to_string_lossy().into_owned(),
|
|
4219
4363
|
"--team".to_string(),
|
|
@@ -22,6 +22,10 @@ enum CatalogFormat {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
pub fn cmd_models(args: &ModelsArgs) -> Result<CmdResult, CliError> {
|
|
25
|
+
#[cfg(unix)]
|
|
26
|
+
if crate::contract_runtime::registry::descriptor(&args.provider).is_some() {
|
|
27
|
+
return crate::contract_runtime::discovery::models(args);
|
|
28
|
+
}
|
|
25
29
|
let records = match crate::provider::model_catalog::discover_model_catalog(&args.provider) {
|
|
26
30
|
Ok(records) => records,
|
|
27
31
|
Err(error) => {
|
|
@@ -36,7 +40,12 @@ pub fn cmd_models(args: &ModelsArgs) -> Result<CmdResult, CliError> {
|
|
|
36
40
|
fn render_catalog(args: &ModelsArgs, all: Vec<ModelRecord>) -> Result<CmdResult, CliError> {
|
|
37
41
|
let visible = args.search.as_deref().map_or_else(
|
|
38
42
|
|| all.clone(),
|
|
39
|
-
|search|
|
|
43
|
+
|search| {
|
|
44
|
+
all.iter()
|
|
45
|
+
.filter(|model| crate::provider::model_catalog::model_matches(model, search))
|
|
46
|
+
.cloned()
|
|
47
|
+
.collect()
|
|
48
|
+
},
|
|
40
49
|
);
|
|
41
50
|
let current = if args.provider == "pi" {
|
|
42
51
|
current_role_model(&all.iter().map(|model| model.id.clone()).collect::<Vec<_>>())
|
|
@@ -66,14 +75,31 @@ fn render_catalog(args: &ModelsArgs, all: Vec<ModelRecord>) -> Result<CmdResult,
|
|
|
66
75
|
preserve_json_order: true,
|
|
67
76
|
})
|
|
68
77
|
} else {
|
|
69
|
-
let mut lines = vec![format!(
|
|
78
|
+
let mut lines = vec![format!(
|
|
79
|
+
"models.v1 | {provider} models (copyable model_id):"
|
|
80
|
+
)];
|
|
70
81
|
lines.push("PROVIDER VENDOR MODEL_ID DISPLAY_NAME CURRENT DEFAULT".to_string());
|
|
71
|
-
lines.extend(visible.iter().map(|model|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
lines.extend(visible.iter().map(|model| {
|
|
83
|
+
format!(
|
|
84
|
+
"{} {} {} {} {} {}",
|
|
85
|
+
model.provider,
|
|
86
|
+
model.vendor,
|
|
87
|
+
model.id,
|
|
88
|
+
model.display_name,
|
|
89
|
+
if args.provider == "pi" {
|
|
90
|
+
(current.as_deref() == Some(model.id.as_str())).to_string()
|
|
91
|
+
} else {
|
|
92
|
+
"—".to_string()
|
|
93
|
+
},
|
|
94
|
+
model
|
|
95
|
+
.default
|
|
96
|
+
.map_or_else(|| "—".to_string(), |value| value.to_string()),
|
|
97
|
+
)
|
|
98
|
+
}));
|
|
99
|
+
lines.push(format!(
|
|
100
|
+
"auth: ok (catalog_visibility); {} model(s)",
|
|
101
|
+
visible.len()
|
|
102
|
+
));
|
|
77
103
|
if visible.is_empty() {
|
|
78
104
|
lines.push("No models matched --search/query; rerun without a search to list the full catalog.".to_string());
|
|
79
105
|
}
|
|
@@ -83,12 +109,23 @@ fn render_catalog(args: &ModelsArgs, all: Vec<ModelRecord>) -> Result<CmdResult,
|
|
|
83
109
|
|
|
84
110
|
/// Existing injectable Pi boundary retained for deterministic command/timeout tests.
|
|
85
111
|
#[cfg(test)]
|
|
86
|
-
fn cmd_models_with(
|
|
112
|
+
fn cmd_models_with(
|
|
113
|
+
args: &ModelsArgs,
|
|
114
|
+
program: &Path,
|
|
115
|
+
timeout: Duration,
|
|
116
|
+
max_bytes: u64,
|
|
117
|
+
) -> Result<CmdResult, CliError> {
|
|
87
118
|
cmd_models_with_format(args, program, CatalogFormat::Pi, timeout, max_bytes)
|
|
88
119
|
}
|
|
89
120
|
|
|
90
121
|
#[cfg(test)]
|
|
91
|
-
fn cmd_models_with_format(
|
|
122
|
+
fn cmd_models_with_format(
|
|
123
|
+
args: &ModelsArgs,
|
|
124
|
+
program: &Path,
|
|
125
|
+
format: CatalogFormat,
|
|
126
|
+
timeout: Duration,
|
|
127
|
+
max_bytes: u64,
|
|
128
|
+
) -> Result<CmdResult, CliError> {
|
|
92
129
|
let bytes = match run_catalog(program, timeout, max_bytes) {
|
|
93
130
|
Ok(bytes) => bytes,
|
|
94
131
|
Err(message) => return Ok(failure(args, &message)),
|
|
@@ -102,7 +139,10 @@ fn cmd_models_with_format(args: &ModelsArgs, program: &Path, format: CatalogForm
|
|
|
102
139
|
|
|
103
140
|
#[cfg(test)]
|
|
104
141
|
fn parse_catalog(bytes: &[u8], format: CatalogFormat) -> Result<Vec<ModelRecord>, String> {
|
|
105
|
-
let provider = match format {
|
|
142
|
+
let provider = match format {
|
|
143
|
+
CatalogFormat::Pi => "pi",
|
|
144
|
+
CatalogFormat::CursorAgent => "cursor_agent",
|
|
145
|
+
};
|
|
106
146
|
let result = match format {
|
|
107
147
|
CatalogFormat::Pi => crate::provider::model_catalog::parse_pi_catalog(bytes),
|
|
108
148
|
CatalogFormat::CursorAgent => crate::provider::model_catalog::parse_cursor_catalog(bytes),
|
|
@@ -124,7 +164,12 @@ fn model_matches(model: &ModelRecord, search: &str) -> bool {
|
|
|
124
164
|
#[cfg(test)]
|
|
125
165
|
fn current_model_for(format: CatalogFormat, models: &[ModelRecord]) -> Option<String> {
|
|
126
166
|
match format {
|
|
127
|
-
CatalogFormat::Pi => current_role_model(
|
|
167
|
+
CatalogFormat::Pi => current_role_model(
|
|
168
|
+
&models
|
|
169
|
+
.iter()
|
|
170
|
+
.map(|model| model.id.clone())
|
|
171
|
+
.collect::<Vec<_>>(),
|
|
172
|
+
),
|
|
128
173
|
CatalogFormat::CursorAgent => None,
|
|
129
174
|
}
|
|
130
175
|
}
|
|
@@ -148,8 +193,14 @@ fn failure(args: &ModelsArgs, message: &str) -> CmdResult {
|
|
|
148
193
|
fn failure_with_action(args: &ModelsArgs, message: &str, action: &str) -> CmdResult {
|
|
149
194
|
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 });
|
|
150
195
|
if args.json {
|
|
151
|
-
let text = serde_json::to_string_pretty(&value)
|
|
152
|
-
|
|
196
|
+
let text = serde_json::to_string_pretty(&value)
|
|
197
|
+
.unwrap_or_else(|_| "{\"schema_version\":\"models.v1\",\"ok\":false}".to_string());
|
|
198
|
+
CmdResult {
|
|
199
|
+
output: CmdOutput::Human(text),
|
|
200
|
+
exit: ExitCode::Error,
|
|
201
|
+
as_json: false,
|
|
202
|
+
preserve_json_order: true,
|
|
203
|
+
}
|
|
153
204
|
} else {
|
|
154
205
|
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 }
|
|
155
206
|
}
|
|
@@ -1271,10 +1322,9 @@ mod tests {
|
|
|
1271
1322
|
),
|
|
1272
1323
|
ExitCode::Error
|
|
1273
1324
|
);
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
);
|
|
1325
|
+
let usage = "team-agent models [--provider pi|cursor_agent|codex|claude|claude_code|grok|kiro] [QUERY|--search TEXT] [--json]";
|
|
1326
|
+
assert_eq!(crate::cli::spec::command_spec("models").unwrap().usage, usage);
|
|
1327
|
+
assert!(crate::cli::emit::command_help(Some("models")).contains(usage));
|
|
1278
1328
|
}
|
|
1279
1329
|
|
|
1280
1330
|
#[test]
|
|
@@ -78,16 +78,16 @@ pub(crate) struct NamedAddressError {
|
|
|
78
78
|
|
|
79
79
|
pub(crate) fn human_address_reason(reason: &str) -> Option<&'static str> {
|
|
80
80
|
Some(match reason {
|
|
81
|
-
"name_invalid" => "
|
|
82
|
-
"workspace_not_found" => "
|
|
81
|
+
"name_invalid" => "Invalid recipient name; use a name from the agent list.",
|
|
82
|
+
"workspace_not_found" => "The selected workspace directory does not exist; verify the path.",
|
|
83
83
|
"state_not_found" | "workspace_no_state" => {
|
|
84
|
-
"
|
|
84
|
+
"No usable team exists in the selected workspace; use quick-start for initial creation or restart for an initialized team."
|
|
85
85
|
}
|
|
86
|
-
"team_key_not_found" => "
|
|
87
|
-
"leader_not_attached" => "
|
|
88
|
-
"name_not_resolvable" => "
|
|
89
|
-
"name_not_live" => "
|
|
90
|
-
"name_ambiguous" => "
|
|
86
|
+
"team_key_not_found" => "The selected workspace has no such team; verify --workspace and --team.",
|
|
87
|
+
"leader_not_attached" => "No leader is attached to this team; diagnose it and follow the recovery action.",
|
|
88
|
+
"name_not_resolvable" => "This team has no such agent; check the agent list.",
|
|
89
|
+
"name_not_live" => "The agent is not running or its channel is stale; check status before recovery.",
|
|
90
|
+
"name_ambiguous" => "Multiple targets share this name; select an explicit workspace and team.",
|
|
91
91
|
_ => return None,
|
|
92
92
|
})
|
|
93
93
|
}
|
|
@@ -121,23 +121,24 @@ impl NamedAddressError {
|
|
|
121
121
|
pub(crate) fn human_guidance(&self, requested: &str) -> (String, String) {
|
|
122
122
|
let workspace = self.human_scope.as_deref();
|
|
123
123
|
let mut message = format!(
|
|
124
|
-
"{}\
|
|
125
|
-
human_address_reason(self.kind.as_str()).unwrap_or("
|
|
124
|
+
"{}\nRequested recipient: {requested:?}\nReason: {}",
|
|
125
|
+
human_address_reason(self.kind.as_str()).unwrap_or("No usable recipient could be resolved."),
|
|
126
|
+
self.message
|
|
126
127
|
);
|
|
127
128
|
if let Some(workspace) = workspace {
|
|
128
|
-
message.push_str(&format!("\
|
|
129
|
+
message.push_str(&format!("\nSelected workspace: {:?}", workspace.to_string_lossy()));
|
|
129
130
|
}
|
|
130
131
|
if let Some(team) = self.human_team.as_deref() {
|
|
131
|
-
message.push_str(&format!("\
|
|
132
|
+
message.push_str(&format!("\nSelected team: {team:?}"));
|
|
132
133
|
}
|
|
133
|
-
let mut action = "
|
|
134
|
+
let mut action = "No reliable spelling suggestion is available; verify the workspace and team before choosing a target.".to_string();
|
|
134
135
|
for candidate in &self.candidates {
|
|
135
136
|
if let (Some(agent), Some(team)) = (
|
|
136
137
|
candidate.get("agent_id").and_then(Value::as_str),
|
|
137
138
|
candidate.get("team_key").and_then(Value::as_str),
|
|
138
139
|
) {
|
|
139
140
|
message.push_str(&format!(
|
|
140
|
-
"\
|
|
141
|
+
"\nCandidate team {team:?}, agent {agent:?}; confirm the intended target."
|
|
141
142
|
));
|
|
142
143
|
}
|
|
143
144
|
}
|
|
@@ -162,7 +163,7 @@ impl NamedAddressError {
|
|
|
162
163
|
if let Some(command) =
|
|
163
164
|
super::adapters::send_command(agent, workspace, Some(team))
|
|
164
165
|
{
|
|
165
|
-
action = format!("
|
|
166
|
+
action = format!("Did you mean agent {agent:?} in team {team:?}? After confirmation, replace the example message below. Nothing was sent:\n{command}");
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
}
|
|
@@ -171,7 +172,7 @@ impl NamedAddressError {
|
|
|
171
172
|
workspace.filter(|path| !path.to_string_lossy().chars().any(char::is_control))
|
|
172
173
|
{
|
|
173
174
|
action.push_str(&format!(
|
|
174
|
-
"\
|
|
175
|
+
"\nList available agents with:\nteam-agent status --workspace {}",
|
|
175
176
|
super::adapters::shell_quote(&workspace.to_string_lossy())
|
|
176
177
|
));
|
|
177
178
|
}
|
|
@@ -5,7 +5,7 @@ use crate::provider::argv_route::{self, Config, Mutation, Override, RouteError};
|
|
|
5
5
|
use crate::provider::Provider;
|
|
6
6
|
use serde_json::{json, Value};
|
|
7
7
|
|
|
8
|
-
pub(crate) const HELP: &str = "
|
|
8
|
+
pub(crate) const HELP: &str = "Purpose:\nConfigure additional provider launch arguments. Disabled by default.\nUsage: team-agent route [status|enable|disable|show [TOOL]|set TOOL -- ARG...|add TOOL -- ARG...|clear TOOL] [--json]\n\nOptions:\nstatus reports the switch; enable/disable change it; show lists all or one provider's arguments.\nset replaces arguments; add appends; clear removes a provider's arguments. Use --json for structured output.\nTOOL is a supported provider name, such as pi/codex/claude/copilot/grok/cursor_agent.\nEverything after the first -- is a literal provider argument, including --help and --json.\nConfiguration: ~/.team-agent/argv-routing.json. This is host-global, not isolated by workspace or team.\nTEAM_AGENT_CLI_ARGV_ROUTING overrides the persisted switch.\nExamples:\nteam-agent route status\nteam-agent route set pi -- --mode text\nteam-agent route enable\n\nNext Action:\nVerify arguments with route show pi. Changes apply to the next launch, not an existing session.\nWithout an enabled configuration, providers retain their original arguments. Leave routing disabled unless the arguments are understood.";
|
|
9
9
|
|
|
10
10
|
struct Request {
|
|
11
11
|
operation: String,
|
|
@@ -16,9 +16,9 @@ struct Request {
|
|
|
16
16
|
|
|
17
17
|
fn usage() -> RouteError {
|
|
18
18
|
RouteError {
|
|
19
|
-
error: "
|
|
19
|
+
error: "Invalid or incomplete arguments; set/add require a provider name, -- and at least one provider argument".to_string(),
|
|
20
20
|
reason: "argv_route_usage",
|
|
21
|
-
action: "
|
|
21
|
+
action: "Follow the Examples in team-agent route --help, such as team-agent route set pi -- --mode text".to_string(),
|
|
22
22
|
config_path: None,
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -118,7 +118,7 @@ fn execute(request: &Request) -> Result<Value, RouteError> {
|
|
|
118
118
|
}
|
|
119
119
|
if override_state != Override::Unset {
|
|
120
120
|
value["notice"] = json!(format!(
|
|
121
|
-
"
|
|
121
|
+
"Switch overridden by environment variable {} ({})",
|
|
122
122
|
argv_route::ENV_NAME,
|
|
123
123
|
override_state.status()
|
|
124
124
|
));
|