@team-agent/installer 0.5.110 → 0.5.111

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 CHANGED
@@ -581,7 +581,7 @@ dependencies = [
581
581
 
582
582
  [[package]]
583
583
  name = "team-agent"
584
- version = "0.5.110"
584
+ version = "0.5.111"
585
585
  dependencies = [
586
586
  "anyhow",
587
587
  "chrono",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent", "crates/win-conpty-phase0", "crates/conpty-trans
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.5.110"
12
+ version = "0.5.111"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -16,6 +16,9 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
16
16
  args.yes,
17
17
  args.backend.as_deref(),
18
18
  )?;
19
+ if let Some(result) = quick_start_config_guidance(&mut value, args) {
20
+ return Ok(result);
21
+ }
19
22
  append_send_guidance(&mut value, &args.workspace, args.team_id.as_deref());
20
23
  let readiness = value.get("readiness").and_then(Value::as_object);
21
24
  let all_resumable_have_session = readiness
@@ -46,6 +49,29 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
46
49
  if args.json && status.as_deref() == Some("pending_tool_load") {
47
50
  result.exit = ExitCode::Ok;
48
51
  }
52
+ if !args.json {
53
+ if let CmdOutput::Json(value) = &result.output {
54
+ let explanation = if value.get("ok").and_then(Value::as_bool) == Some(false) {
55
+ format!(
56
+ "启动未完成:{}",
57
+ value
58
+ .get("error")
59
+ .and_then(Value::as_str)
60
+ .unwrap_or("队伍尚未准备好接收任务")
61
+ )
62
+ } else {
63
+ "队伍尚未准备好接收任务;请等待工具完成启动。".to_string()
64
+ };
65
+ let mut doctor = format!(
66
+ "team-agent doctor --workspace {}",
67
+ shell_quote(&args.workspace.to_string_lossy())
68
+ );
69
+ if let Some(team) = args.team_id.as_deref() {
70
+ doctor.push_str(&format!(" --team {}", shell_quote(team)));
71
+ }
72
+ result.output = CmdOutput::Human(format!("{explanation}\n下一步:运行 {doctor},按体检提示处理;用 status 查看状态,不反复起队或重发任务。"));
73
+ }
74
+ }
49
75
  Ok(result)
50
76
  } else {
51
77
  // E13:happy 人类路径必须带 attach_commands(json 路径 cli/mod.rs:1775 已有)。
@@ -53,13 +79,99 @@ pub fn cmd_quick_start(args: &QuickStartArgs) -> Result<CmdResult, CliError> {
53
79
  }
54
80
  }
55
81
 
82
+ // Project only an actual compiler rejection; never reject a valid lifecycle fallback.
83
+ fn quick_start_config_guidance(value: &mut Value, args: &QuickStartArgs) -> Option<CmdResult> {
84
+ if value.get("ok").and_then(Value::as_bool) != Some(false) {
85
+ return None;
86
+ }
87
+ let error = value.get("error")?.as_str()?;
88
+ let compile = error.strip_prefix("spec compile failed: ")?;
89
+ let detail = compile
90
+ .strip_prefix("validation error: ")
91
+ .unwrap_or(compile);
92
+ let team_path = args.agents_dir.join("TEAM.md");
93
+ let agents_path = args.agents_dir.join("agents");
94
+ let missing_team = detail == format!("{}: missing TEAM.md", team_path.display())
95
+ && team_path.try_exists().ok() == Some(false);
96
+ let missing_roles = detail == format!("{}: missing agents directory", agents_path.display())
97
+ || detail == format!("{}: no role docs found", agents_path.display());
98
+ let missing_field = detail.split_once(": ").is_some_and(|(path, reason)| {
99
+ Path::new(path).parent() == Some(agents_path.as_path())
100
+ && (reason.contains("missing required field") || reason.contains("is required"))
101
+ });
102
+ if !missing_team && !(team_path.is_file() && (missing_roles || missing_field)) {
103
+ return None;
104
+ }
105
+ let role_path = agents_path.join("worker.md");
106
+ let mut retry = format!(
107
+ "team-agent quick-start {} --workspace {}",
108
+ shell_quote(&args.agents_dir.to_string_lossy()),
109
+ shell_quote(&args.workspace.to_string_lossy())
110
+ );
111
+ if let Some(team) = args.team_id.as_deref() {
112
+ retry.push_str(&format!(" --team {}", shell_quote(team)));
113
+ }
114
+ if let Some(name) = args.name.as_deref() {
115
+ retry.push_str(&format!(" --name {}", shell_quote(name)));
116
+ }
117
+ if let Some(backend) = args.backend.as_deref() {
118
+ retry.push_str(&format!(" --backend {}", shell_quote(backend)));
119
+ }
120
+ if args.yes {
121
+ retry.push_str(" --yes");
122
+ }
123
+ if args.detail {
124
+ retry.push_str(" --detail");
125
+ }
126
+ if args.json {
127
+ retry.push_str(" --json");
128
+ }
129
+ let explanation = if missing_team {
130
+ format!("这里还没有队伍配置:{}。没有启动任何队友。请自行创建下面两个文件;本命令没有写入这些文件。", team_path.display())
131
+ } else {
132
+ format!("队友配置尚不完整:{detail}。请补齐角色目录或必需字段;下面仅给安全示例,不覆盖已有文件。")
133
+ };
134
+ let next = format!("确认 Pi 已安装并登录;运行 team-agent models --provider pi 核对模型。\n运行 team-agent pi,在该主控的命令行/工具上下文再次执行:\n{retry}");
135
+ let mut templates =
136
+ vec![serde_json::json!({"path": role_path, "content": super::emit::WORKER_TEMPLATE})];
137
+ let mut human = explanation.clone();
138
+ if missing_team {
139
+ templates.insert(
140
+ 0,
141
+ serde_json::json!({"path": team_path, "content": super::emit::TEAM_TEMPLATE}),
142
+ );
143
+ human.push_str(&format!(
144
+ "\n\n{}:\n{}",
145
+ team_path.display(),
146
+ super::emit::TEAM_TEMPLATE
147
+ ));
148
+ }
149
+ human.push_str(&format!(
150
+ "\n\n{}:\n{}\n下一步:\n{next}",
151
+ role_path.display(),
152
+ super::emit::WORKER_TEMPLATE
153
+ ));
154
+ value["action"] = serde_json::json!(explanation);
155
+ value["next_actions"] =
156
+ serde_json::json!(["team-agent models --provider pi", "team-agent pi", retry]);
157
+ value["templates"] = serde_json::json!(templates);
158
+ if args.json {
159
+ Some(CmdResult::from_json(value.clone(), true))
160
+ } else {
161
+ let mut result = CmdResult::human(&human);
162
+ result.exit = ExitCode::Error;
163
+ Some(result)
164
+ }
165
+ }
166
+
56
167
  /// E13:quick-start "team 起了" 人类输出 = summary + attach 块。所有成功出口共用(别每分支手拷)。
57
168
  /// attach_commands 缺/空 → 只 summary(向后兼容)。
58
169
  fn quickstart_human(value: &Value) -> String {
59
- let summary = value
60
- .get("summary")
61
- .and_then(Value::as_str)
62
- .unwrap_or("quick-start complete");
170
+ let summary = if value.get("summary").and_then(Value::as_str) == Some("existing runtime") {
171
+ "发现已有队伍;请查看返回的连接方式,不会另建同名队伍。"
172
+ } else {
173
+ "队伍已启动;下面是连接方式和派发任务的命令。"
174
+ };
63
175
  let attach: Vec<&str> = value
64
176
  .get("attach_commands")
65
177
  .and_then(Value::as_array)
@@ -72,14 +184,14 @@ fn quickstart_human(value: &Value) -> String {
72
184
  .unwrap_or_default();
73
185
  let mut out = String::from(summary);
74
186
  if !attach.is_empty() {
75
- out.push_str("\n\nattach:");
187
+ out.push_str("\n\n连接队伍:");
76
188
  for cmd in attach {
77
189
  out.push_str("\n ");
78
190
  out.push_str(cmd);
79
191
  }
80
192
  }
81
193
  if !sends.is_empty() {
82
- out.push_str("\n\nsend:");
194
+ out.push_str("\n\n派发任务(请替换任务内容):");
83
195
  for cmd in sends {
84
196
  out.push_str("\n ");
85
197
  out.push_str(cmd);
@@ -146,7 +258,7 @@ pub(crate) fn send_command(agent: &str, workspace: &Path, team: Option<&str>) ->
146
258
  let mut command = format!(
147
259
  "team-agent send {} {} --workspace {}",
148
260
  shell_quote(agent),
149
- shell_quote("MESSAGE"),
261
+ shell_quote("请完成任务并把答案回复给 leader。"),
150
262
  shell_quote(workspace)
151
263
  );
152
264
  if let Some(team) = team {
@@ -156,7 +268,7 @@ pub(crate) fn send_command(agent: &str, workspace: &Path, team: Option<&str>) ->
156
268
  Some(command)
157
269
  }
158
270
 
159
- fn shell_quote(value: &str) -> String {
271
+ pub(crate) fn shell_quote(value: &str) -> String {
160
272
  if value.bytes().all(|byte| {
161
273
  byte.is_ascii_alphanumeric() || matches!(byte, b'/' | b'.' | b'_' | b'-' | b':')
162
274
  }) {
@@ -551,10 +663,8 @@ fn run_fake_e2e(workspace: &Path) -> Result<Value, CliError> {
551
663
  "next_actions": [],
552
664
  }),
553
665
  )?;
554
- let auto_finalized = report
555
- .get("finalization_status")
556
- .and_then(Value::as_str)
557
- == Some("auto_finalized");
666
+ let auto_finalized =
667
+ report.get("finalization_status").and_then(Value::as_str) == Some("auto_finalized");
558
668
  let shutdown = fake_shutdown(workspace)?;
559
669
  let ok = launch.get("ok").and_then(Value::as_bool) == Some(true)
560
670
  && send.ok
@@ -1015,27 +1125,24 @@ fn format_inbox_human(agent: &str, value: &Value) -> String {
1015
1125
  .and_then(Value::as_str)
1016
1126
  .unwrap_or("-"),
1017
1127
  );
1018
- let sender = clean_inbox_field(
1019
- message.get("sender").and_then(Value::as_str).unwrap_or("-"),
1020
- );
1128
+ let sender =
1129
+ clean_inbox_field(message.get("sender").and_then(Value::as_str).unwrap_or("-"));
1021
1130
  let recipient = clean_inbox_field(
1022
1131
  message
1023
1132
  .get("recipient")
1024
1133
  .and_then(Value::as_str)
1025
1134
  .unwrap_or("-"),
1026
1135
  );
1027
- let status = clean_inbox_field(
1028
- message.get("status").and_then(Value::as_str).unwrap_or("-"),
1029
- );
1136
+ let status =
1137
+ clean_inbox_field(message.get("status").and_then(Value::as_str).unwrap_or("-"));
1030
1138
  let time = clean_inbox_field(
1031
1139
  message
1032
1140
  .get("created_at")
1033
1141
  .and_then(Value::as_str)
1034
1142
  .unwrap_or("-"),
1035
1143
  );
1036
- let summary = clean_inbox_field(
1037
- message.get("summary").and_then(Value::as_str).unwrap_or(""),
1038
- );
1144
+ let summary =
1145
+ clean_inbox_field(message.get("summary").and_then(Value::as_str).unwrap_or(""));
1039
1146
  truncate_inbox_line(&format!(
1040
1147
  "[{id}] [{sender} -> {recipient}] [{status}] [{time}] [{summary}]"
1041
1148
  ))
@@ -1054,8 +1161,7 @@ pub fn cmd_takeover(args: &TakeoverArgs) -> Result<CmdResult, CliError> {
1054
1161
 
1055
1162
  /// `cmd_claim_leader`(`commands.py:156`)。
1056
1163
  pub fn cmd_claim_leader(args: &ClaimLeaderArgs) -> Result<CmdResult, CliError> {
1057
- let mut value =
1058
- leader_port::claim_leader(&args.workspace, args.team.as_deref(), args.confirm)?;
1164
+ let mut value = leader_port::claim_leader(&args.workspace, args.team.as_deref(), args.confirm)?;
1059
1165
  if !args.detail {
1060
1166
  leader_port::compact_lease_value(&mut value);
1061
1167
  }
@@ -1104,10 +1210,7 @@ pub fn cmd_start_agent(args: &StartAgentArgs) -> Result<CmdResult, CliError> {
1104
1210
  )?;
1105
1211
  if value.get("agent_ids").is_none() {
1106
1212
  if let Some(object) = value.as_object_mut() {
1107
- object.insert(
1108
- "agent_ids".to_string(),
1109
- json!([args.agent.as_str()]),
1110
- );
1213
+ object.insert("agent_ids".to_string(), json!([args.agent.as_str()]));
1111
1214
  }
1112
1215
  }
1113
1216
  append_send_guidance(&mut value, &args.workspace, args.team.as_deref());
@@ -1147,10 +1250,7 @@ pub fn cmd_add_agent(args: &AddAgentArgs) -> Result<CmdResult, CliError> {
1147
1250
  )?;
1148
1251
  if value.get("agent_ids").is_none() {
1149
1252
  if let Some(object) = value.as_object_mut() {
1150
- object.insert(
1151
- "agent_ids".to_string(),
1152
- json!([args.agent.as_str()]),
1153
- );
1253
+ object.insert("agent_ids".to_string(), json!([args.agent.as_str()]));
1154
1254
  }
1155
1255
  }
1156
1256
  append_send_guidance(&mut value, &args.workspace, args.team.as_deref());
@@ -1222,10 +1322,8 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
1222
1322
  }
1223
1323
 
1224
1324
  let explicit_comms = args.comms || matches!(args.gate, Some(DoctorGate::Comms));
1225
- let default_report = !explicit_comms
1226
- && args.gate.is_none()
1227
- && !args.cleanup_orphans
1228
- && !args.fix_schema;
1325
+ let default_report =
1326
+ !explicit_comms && args.gate.is_none() && !args.cleanup_orphans && !args.fix_schema;
1229
1327
  let mut value = if explicit_comms {
1230
1328
  crate::diagnose::comms::doctor_comms_json(
1231
1329
  &args.workspace,
@@ -1282,22 +1380,28 @@ fn unified_default_doctor_report(args: &DoctorArgs, mut value: Value) -> Value {
1282
1380
  args.team.as_deref(),
1283
1381
  crate::state::selector::SelectorMode::RuntimeOnly,
1284
1382
  );
1285
- let run_workspace = selected.as_ref().map(|selected| selected.run_workspace.clone())
1286
- .unwrap_or_else(|_| crate::model::paths::canonical_run_workspace(&args.workspace)
1287
- .unwrap_or_else(|_| args.workspace.clone()));
1383
+ let run_workspace = selected
1384
+ .as_ref()
1385
+ .map(|selected| selected.run_workspace.clone())
1386
+ .unwrap_or_else(|_| {
1387
+ crate::model::paths::canonical_run_workspace(&args.workspace)
1388
+ .unwrap_or_else(|_| args.workspace.clone())
1389
+ });
1288
1390
  let runtime_present = selected.as_ref().is_ok_and(|selected| {
1289
1391
  crate::cli::diagnose::workspace_has_existing_team_runtime(
1290
1392
  &selected.run_workspace,
1291
1393
  &selected.team_key,
1292
1394
  )
1293
1395
  });
1294
- let db_present = crate::model::paths::runtime_dir(&run_workspace).join("team.db").exists();
1396
+ let db_present = crate::model::paths::runtime_dir(&run_workspace)
1397
+ .join("team.db")
1398
+ .exists();
1295
1399
  // One read-only observation supplies both issue classification and details.
1296
1400
  // A physical database must be checked even before state.json is created.
1297
1401
  let health = (runtime_present || db_present).then(|| {
1298
- crate::coordinator::coordinator_health_read_only(
1299
- &crate::coordinator::WorkspacePath::new(run_workspace.clone()),
1300
- )
1402
+ crate::coordinator::coordinator_health_read_only(&crate::coordinator::WorkspacePath::new(
1403
+ run_workspace.clone(),
1404
+ ))
1301
1405
  });
1302
1406
  match selected {
1303
1407
  Ok(selected) => {
@@ -1358,10 +1462,17 @@ fn unified_default_doctor_report(args: &DoctorArgs, mut value: Value) -> Value {
1358
1462
  }
1359
1463
  if let Some(object) = value.as_object_mut() {
1360
1464
  object.insert("runtime".to_string(), runtime);
1361
- object.insert("event_log".to_string(), Value::String(
1362
- run_workspace.join(".team").join("logs").join("events.jsonl")
1363
- .to_string_lossy().to_string(),
1364
- ));
1465
+ object.insert(
1466
+ "event_log".to_string(),
1467
+ Value::String(
1468
+ run_workspace
1469
+ .join(".team")
1470
+ .join("logs")
1471
+ .join("events.jsonl")
1472
+ .to_string_lossy()
1473
+ .to_string(),
1474
+ ),
1475
+ );
1365
1476
  }
1366
1477
  finalize_doctor_report(&mut value, true);
1367
1478
  value
@@ -1385,7 +1496,10 @@ fn merge_report_array(report: &mut Value, key: &str, incoming: &Value) {
1385
1496
  .expect("report array normalized before merge");
1386
1497
  for item in items {
1387
1498
  let identity = report_item_identity(item);
1388
- if !target.iter().any(|existing| report_item_identity(existing) == identity) {
1499
+ if !target
1500
+ .iter()
1501
+ .any(|existing| report_item_identity(existing) == identity)
1502
+ {
1389
1503
  target.push(item.clone());
1390
1504
  }
1391
1505
  }
@@ -1450,11 +1564,17 @@ fn finalize_doctor_report(report: &mut Value, default_report: bool) {
1450
1564
  .and_then(|value| value.as_array().cloned())
1451
1565
  .unwrap_or_default();
1452
1566
  let mut add = |issue: Value, repair: Option<Value>| {
1453
- if !issues.iter().any(|existing| report_item_identity(existing) == report_item_identity(&issue)) {
1567
+ if !issues
1568
+ .iter()
1569
+ .any(|existing| report_item_identity(existing) == report_item_identity(&issue))
1570
+ {
1454
1571
  issues.push(issue);
1455
1572
  }
1456
1573
  if let Some(repair) = repair {
1457
- if !repairs.iter().any(|existing| report_item_identity(existing) == report_item_identity(&repair)) {
1574
+ if !repairs
1575
+ .iter()
1576
+ .any(|existing| report_item_identity(existing) == report_item_identity(&repair))
1577
+ {
1458
1578
  repairs.push(repair);
1459
1579
  }
1460
1580
  }
@@ -1467,10 +1587,10 @@ fn finalize_doctor_report(report: &mut Value, default_report: bool) {
1467
1587
  .or_else(|| object.get("reason").and_then(Value::as_str))
1468
1588
  .unwrap_or("doctor_failed");
1469
1589
  let issue = if status == "failed" {
1470
- object
1471
- .get("gate")
1472
- .and_then(Value::as_str)
1473
- .map_or_else(|| "doctor_failed".to_string(), |gate| format!("{gate}_failed"))
1590
+ object.get("gate").and_then(Value::as_str).map_or_else(
1591
+ || "doctor_failed".to_string(),
1592
+ |gate| format!("{gate}_failed"),
1593
+ )
1474
1594
  } else if status.ends_with("_failed") || status == "refused" {
1475
1595
  status.to_string()
1476
1596
  } else {
@@ -1481,7 +1601,10 @@ fn finalize_doctor_report(report: &mut Value, default_report: bool) {
1481
1601
  .or_else(|| object.get("next_action"))
1482
1602
  .or_else(|| object.get("reason"))
1483
1603
  .cloned();
1484
- add(Value::String(issue), repair.map(|value| json!({"action": value})));
1604
+ add(
1605
+ Value::String(issue),
1606
+ repair.map(|value| json!({"action": value})),
1607
+ );
1485
1608
  }
1486
1609
  if object
1487
1610
  .get("workspace")
@@ -1512,9 +1635,9 @@ fn finalize_doctor_report(report: &mut Value, default_report: bool) {
1512
1635
  {
1513
1636
  add(
1514
1637
  json!("grok_slot_mismatch"),
1515
- grok.get("reason").cloned().map(|reason| {
1516
- json!({"issue": "grok_slot_mismatch", "action": reason})
1517
- }),
1638
+ grok.get("reason")
1639
+ .cloned()
1640
+ .map(|reason| json!({"issue": "grok_slot_mismatch", "action": reason})),
1518
1641
  );
1519
1642
  }
1520
1643
  }
@@ -1559,7 +1682,9 @@ fn finalize_doctor_report(report: &mut Value, default_report: bool) {
1559
1682
  if runtime_status == "unresolved" {
1560
1683
  add(
1561
1684
  json!("runtime_selection_failed"),
1562
- Some(json!({"issue": "runtime_selection_failed", "action": "select an existing Team runtime"})),
1685
+ Some(
1686
+ json!({"issue": "runtime_selection_failed", "action": "select an existing Team runtime"}),
1687
+ ),
1563
1688
  );
1564
1689
  }
1565
1690
  if runtime_status == "present"
@@ -1610,7 +1735,10 @@ mod tests {
1610
1735
  let output = format_inbox_human("worker", &value);
1611
1736
  for line in output.lines() {
1612
1737
  assert!(line.len() <= 160, "line is {} bytes: {line:?}", line.len());
1613
- assert!(!line.chars().any(char::is_control), "controls leaked: {line:?}");
1738
+ assert!(
1739
+ !line.chars().any(char::is_control),
1740
+ "controls leaked: {line:?}"
1741
+ );
1614
1742
  }
1615
1743
  }
1616
1744
 
@@ -1625,9 +1753,12 @@ mod tests {
1625
1753
  ],
1626
1754
  });
1627
1755
  let out = quickstart_human(&value);
1628
- assert!(out.contains("team started"), "must keep summary; got {out}");
1629
1756
  assert!(
1630
- out.contains("attach:"),
1757
+ out.contains("队伍已启动"),
1758
+ "must show startup summary; got {out}"
1759
+ );
1760
+ assert!(
1761
+ out.contains("连接队伍:"),
1631
1762
  "must render attach block; got {out}"
1632
1763
  );
1633
1764
  assert!(
@@ -1643,14 +1774,20 @@ mod tests {
1643
1774
  #[test]
1644
1775
  fn reminders_only_recommend_supported_result_commands() {
1645
1776
  let reminder = crate::cli::QUICK_START_REMINDER;
1646
- assert!(reminder.contains("Use team-agent status / inbox instead."));
1777
+ assert!(reminder.contains("team-agent status"));
1778
+ assert!(reminder.contains("team-agent inbox"));
1647
1779
  for text in [
1648
1780
  reminder,
1649
1781
  crate::cli::STATUS_REMINDER,
1650
1782
  crate::cli::SEND_REMINDER,
1651
1783
  &quickstart_human(&json!({"summary": "team started"})),
1652
1784
  ] {
1653
- assert!(!text.contains("collect"), "removed CLI command leaked: {text}");
1785
+ for hidden in ["collect", "team-agent results", "team-agent wait"] {
1786
+ assert!(
1787
+ !text.contains(hidden),
1788
+ "private/removed CLI command leaked: {text}"
1789
+ );
1790
+ }
1654
1791
  }
1655
1792
  }
1656
1793
 
@@ -1659,13 +1796,19 @@ mod tests {
1659
1796
  let value = json!({"summary": "quick-start complete"});
1660
1797
  assert_eq!(
1661
1798
  quickstart_human(&value),
1662
- format!("quick-start complete\n{}", crate::cli::QUICK_START_REMINDER)
1799
+ format!(
1800
+ "队伍已启动;下面是连接方式和派发任务的命令。\n{}",
1801
+ crate::cli::QUICK_START_REMINDER
1802
+ )
1663
1803
  );
1664
- // 空数组也只 summary。
1804
+ // 空数组也只显示人类摘要与下一步,不凭空编造连接方式。
1665
1805
  let value2 = json!({"summary": "s", "attach_commands": []});
1666
1806
  assert_eq!(
1667
1807
  quickstart_human(&value2),
1668
- format!("s\n{}", crate::cli::QUICK_START_REMINDER)
1808
+ format!(
1809
+ "队伍已启动;下面是连接方式和派发任务的命令。\n{}",
1810
+ crate::cli::QUICK_START_REMINDER
1811
+ )
1669
1812
  );
1670
1813
  }
1671
1814
 
@@ -1684,7 +1827,7 @@ mod tests {
1684
1827
  "team-agent",
1685
1828
  "send",
1686
1829
  "worker name; echo unsafe",
1687
- "MESSAGE",
1830
+ "请完成任务并把答案回复给 leader。",
1688
1831
  "--workspace",
1689
1832
  "/tmp/my workspace",
1690
1833
  "--team",
@@ -1695,11 +1838,15 @@ mod tests {
1695
1838
 
1696
1839
  #[test]
1697
1840
  fn send_guidance_is_copyable_and_preserves_explicit_scope() {
1698
- let command = send_command("worker name; echo unsafe", Path::new("/tmp/my workspace"), Some("team-a"))
1699
- .unwrap();
1841
+ let command = send_command(
1842
+ "worker name; echo unsafe",
1843
+ Path::new("/tmp/my workspace"),
1844
+ Some("team-a"),
1845
+ )
1846
+ .unwrap();
1700
1847
  assert_eq!(
1701
1848
  command,
1702
- "team-agent send 'worker name; echo unsafe' MESSAGE --workspace '/tmp/my workspace' --team team-a"
1849
+ "team-agent send 'worker name; echo unsafe' '请完成任务并把答案回复给 leader。' --workspace '/tmp/my workspace' --team team-a"
1703
1850
  );
1704
1851
  }
1705
1852
 
@@ -1720,7 +1867,10 @@ mod tests {
1720
1867
  fn quick_start_guidance_lists_every_known_agent_without_guessing() {
1721
1868
  let mut value = json!({"ok": true, "agent_ids": ["sol", "luna"]});
1722
1869
  append_send_guidance(&mut value, Path::new("/tmp/ws"), Some("team"));
1723
- let commands = value.get("send_commands").and_then(|v| v.as_array()).unwrap();
1870
+ let commands = value
1871
+ .get("send_commands")
1872
+ .and_then(|v| v.as_array())
1873
+ .unwrap();
1724
1874
  assert_eq!(commands.len(), 2);
1725
1875
  assert!(commands[0].as_str().unwrap().contains("send sol"));
1726
1876
  assert!(commands[1].as_str().unwrap().contains("send luna"));
@@ -1747,8 +1897,19 @@ mod tests {
1747
1897
  .unwrap();
1748
1898
  assert!(command.contains("send worker"));
1749
1899
  assert!(command.contains("--team team-a"));
1750
- let parts: Vec<&str> = command.split_whitespace().collect();
1751
- assert_eq!(parts[3], "MESSAGE");
1900
+ assert_eq!(
1901
+ split_shell_argv(command),
1902
+ [
1903
+ "team-agent",
1904
+ "send",
1905
+ "worker",
1906
+ "请完成任务并把答案回复给 leader。",
1907
+ "--workspace",
1908
+ "/tmp/ws",
1909
+ "--team",
1910
+ "team-a",
1911
+ ]
1912
+ );
1752
1913
  }
1753
1914
 
1754
1915
  #[test]