@team-agent/installer 0.5.111 → 0.5.113
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/adapters.rs +93 -779
- package/crates/team-agent/src/cli/diagnose.rs +2 -424
- package/crates/team-agent/src/cli/emit.rs +158 -373
- package/crates/team-agent/src/cli/leaders.rs +32 -32
- package/crates/team-agent/src/cli/mod.rs +8 -15
- 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 +29 -32
- package/crates/team-agent/src/cli/status.rs +7 -7
- package/crates/team-agent/src/cli/tests/base.rs +6 -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/leader_watch.rs +2 -86
- package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
- package/crates/team-agent/src/cli/tests/mod.rs +0 -2
- package/crates/team-agent/src/cli/tests/named_address.rs +3 -3
- package/crates/team-agent/src/cli/tests/status_send.rs +7 -7
- package/crates/team-agent/src/cli/triage.rs +53 -2
- package/crates/team-agent/src/cli/types.rs +12 -116
- package/crates/team-agent/src/compiler/tests.rs +12 -0
- package/crates/team-agent/src/compiler.rs +0 -9
- package/crates/team-agent/src/coordinator/health.rs +3 -283
- package/crates/team-agent/src/coordinator/mod.rs +0 -5
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
- package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
- package/crates/team-agent/src/coordinator/types.rs +1 -21
- package/crates/team-agent/src/db/message_store.rs +0 -42
- package/crates/team-agent/src/db/migration.rs +49 -9
- package/crates/team-agent/src/db/schema.rs +27 -20
- package/crates/team-agent/src/leader/owner_bind.rs +0 -24
- package/crates/team-agent/src/leader/start.rs +5 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
- package/crates/team-agent/src/leader/tests/identity.rs +21 -32
- package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
- package/crates/team-agent/src/lifecycle/launch/identity.rs +172 -13
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +325 -14
- package/crates/team-agent/src/lifecycle/launch.rs +2 -2
- 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/restart_session_capture_red.rs +2 -51
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
- package/crates/team-agent/src/messaging/mod.rs +0 -2
- package/crates/team-agent/src/messaging/results.rs +0 -58
- package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
- 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/repository.rs +1 -5
- package/package.json +4 -4
- package/templates/team_state.md +1 -1
- package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
- package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
- package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
- package/crates/team-agent/src/messaging/peers.rs +0 -34
|
@@ -110,25 +110,25 @@ fn emit_cli_error_for_command(
|
|
|
110
110
|
cwd: &Path,
|
|
111
111
|
error: &CliError,
|
|
112
112
|
) -> ExitCode {
|
|
113
|
-
let missing_input = matches!(error, CliError::Usage(message) if message.starts_with("missing ") || message.starts_with("
|
|
113
|
+
let missing_input = matches!(error, CliError::Usage(message) if message.starts_with("missing ") || message.starts_with("Provide ") || matches!(message.as_str(), "add-agent requires --provider <name>" | "add-agent requires --bypass <true|false>" | "--source is required unless --uninstall"));
|
|
114
114
|
if command_spec(command).is_some() && missing_input {
|
|
115
115
|
let help = command_help(Some(command));
|
|
116
116
|
let explanation = match error {
|
|
117
117
|
CliError::Usage(message) => match message.as_str() {
|
|
118
|
-
"missing agent" | "missing source_agent" => "
|
|
118
|
+
"missing agent" | "missing source_agent" => "Provide an agent name.".to_string(),
|
|
119
119
|
"missing profile command" => {
|
|
120
|
-
"
|
|
120
|
+
"Provide a profile operation: init, doctor or show.".to_string()
|
|
121
121
|
}
|
|
122
|
-
"missing profile name" => "
|
|
122
|
+
"missing profile name" => "Provide an authentication/proxy profile name.".to_string(),
|
|
123
123
|
"add-agent requires --provider <name>" => {
|
|
124
|
-
"
|
|
124
|
+
"Select a provider with --provider, or supply a role file with --role-file.".to_string()
|
|
125
125
|
}
|
|
126
126
|
"add-agent requires --bypass <true|false>" => {
|
|
127
|
-
"
|
|
127
|
+
"Explicitly retain permission prompts with --bypass false, or declare bypass in the role file.".to_string()
|
|
128
128
|
}
|
|
129
129
|
other => other
|
|
130
130
|
.strip_prefix("missing ")
|
|
131
|
-
.map(|field| format!("
|
|
131
|
+
.map(|field| format!("Provide the required argument: {field}."))
|
|
132
132
|
.unwrap_or_else(|| other.to_string()),
|
|
133
133
|
},
|
|
134
134
|
_ => error.to_string(),
|
|
@@ -140,7 +140,7 @@ fn emit_cli_error_for_command(
|
|
|
140
140
|
value["error"] = serde_json::json!(explanation);
|
|
141
141
|
value["action"] = serde_json::json!(format!("team-agent {command} --help"));
|
|
142
142
|
value["next_actions"] = serde_json::json!([format!(
|
|
143
|
-
"
|
|
143
|
+
"Complete the required arguments using the Examples in team-agent {command} --help"
|
|
144
144
|
)]);
|
|
145
145
|
println!("{}", python_compact_json(&value));
|
|
146
146
|
} else {
|
|
@@ -179,7 +179,7 @@ pub(super) fn emit_result(r: CmdResult) -> ExitCode {
|
|
|
179
179
|
let mut stderr = stderr.lock();
|
|
180
180
|
let _ = writeln!(
|
|
181
181
|
stderr,
|
|
182
|
-
"
|
|
182
|
+
"Output unavailable; the message was persisted with message_id={message_id}. Use team-agent inbox leader -n 3 for replies; do not resend."
|
|
183
183
|
);
|
|
184
184
|
}
|
|
185
185
|
return if error.kind() == ErrorKind::BrokenPipe {
|
|
@@ -251,20 +251,16 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
251
251
|
| "clone-agent"
|
|
252
252
|
| "fork-agent"
|
|
253
253
|
| "remove-agent"
|
|
254
|
-
| "allow-peer-talk"
|
|
255
254
|
| "profile"
|
|
256
255
|
)
|
|
257
256
|
{
|
|
258
257
|
return Err(CliError::Usage(
|
|
259
|
-
"
|
|
258
|
+
"Provide the agent name, task message or required command settings; see Usage and Examples below".to_string(),
|
|
260
259
|
));
|
|
261
260
|
}
|
|
262
261
|
match command {
|
|
263
262
|
"quick-start" => cmd_quick_start(&quick_start_args(args, cwd)?).map(emit_result),
|
|
264
263
|
"send" => cmd_send(&send_args(args, cwd)?).map(emit_result),
|
|
265
|
-
"allow-peer-talk" => {
|
|
266
|
-
cmd_allow_peer_talk(&allow_peer_talk_args(args, cwd)?).map(emit_result)
|
|
267
|
-
}
|
|
268
264
|
"status" => cmd_status_for_team(&status_args(args, cwd), parse_args(args).team.as_deref())
|
|
269
265
|
.map(emit_result),
|
|
270
266
|
"shutdown" => cmd_shutdown(&shutdown_args(args, cwd)?).map(emit_result),
|
|
@@ -306,33 +302,13 @@ fn dispatch(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliE
|
|
|
306
302
|
fn is_machine_command(command: &str) -> bool {
|
|
307
303
|
matches!(
|
|
308
304
|
command,
|
|
309
|
-
"
|
|
310
|
-
| "wait"
|
|
311
|
-
| "wait-ready"
|
|
312
|
-
| "preflight"
|
|
313
|
-
| "validate"
|
|
314
|
-
| "identity"
|
|
315
|
-
| "sessions"
|
|
316
|
-
| "watch"
|
|
317
|
-
| "e2e"
|
|
318
|
-
| "peek"
|
|
319
|
-
| "coordinator"
|
|
320
|
-
| "attach-app-server-leader"
|
|
305
|
+
"wait" | "coordinator" | "attach-app-server-leader"
|
|
321
306
|
)
|
|
322
307
|
}
|
|
323
308
|
|
|
324
309
|
fn dispatch_machine(command: &str, args: &[String], cwd: &Path) -> Result<ExitCode, CliError> {
|
|
325
310
|
match command {
|
|
326
|
-
"results" => cmd_results(&results_args(args, cwd)?).map(emit_result),
|
|
327
311
|
"wait" => cmd_wait(&wait_args(args, cwd)?).map(emit_result),
|
|
328
|
-
"wait-ready" => cmd_wait_ready(&wait_ready_args(args, cwd)).map(emit_result),
|
|
329
|
-
"preflight" => cmd_preflight(&preflight_args(args, cwd)).map(emit_result),
|
|
330
|
-
"validate" => cmd_validate(&validate_args(args, cwd)).map(emit_result),
|
|
331
|
-
"identity" => cmd_identity(&identity_args(args, cwd)).map(emit_result),
|
|
332
|
-
"sessions" => cmd_sessions(&sessions_args(args, cwd)).map(emit_result),
|
|
333
|
-
"watch" => cmd_watch(&watch_args(args, cwd)).map(emit_result),
|
|
334
|
-
"e2e" => cmd_e2e(&e2e_args(args, cwd)).map(emit_result),
|
|
335
|
-
"peek" => cmd_peek(&peek_args(args, cwd)?).map(emit_result),
|
|
336
312
|
"coordinator" => run_coordinator(args, cwd),
|
|
337
313
|
"attach-app-server-leader" => {
|
|
338
314
|
cmd_attach_app_server_leader(&attach_app_server_leader_args(args, cwd)?)
|
|
@@ -364,16 +340,16 @@ fn is_known_subcommand(command: &str) -> bool {
|
|
|
364
340
|
}
|
|
365
341
|
|
|
366
342
|
pub(crate) fn default_help() -> String {
|
|
367
|
-
let mut out = String::from("Team Agent
|
|
343
|
+
let mut out = String::from("Team Agent: create a team, send tasks, read replies and shut down safely.\n\n1. Prepare agents\n Run team-agent quick-start in the project directory. An empty directory receives TEAM.md and agents/worker.md templates to create manually.\n2. Open a leader and create the team\n With Pi installed and signed in, run team-agent pi.\n Run team-agent quick-start in that leader's tool context.\n To recover an existing team, see team-agent restart --help; do not take over or delete it.\n3. Send tasks and read replies\n team-agent send worker 'Calculate 245 * 37 and reply to the leader.'\n team-agent inbox leader -n 3\n team-agent status\n Sending is not completion; wait for an actual reply.\n4. Diagnose and shut down\n team-agent doctor --workspace .\n team-agent shutdown --workspace . --json\n Verify this team's residual resources are empty, then diagnose the selected workspace.\n\nSelect workspace/team scope with --workspace/--team. Discover model names with models.\nWindows ConPTY requires a Windows host and an installed shim; tmux commands are not a generic Windows path.\n");
|
|
368
344
|
append_help_section(
|
|
369
345
|
&mut out,
|
|
370
|
-
"
|
|
346
|
+
"Getting started",
|
|
371
347
|
&["quick-start", "send", "status", "models", "inbox"],
|
|
372
348
|
);
|
|
373
|
-
append_help_section(&mut out, "
|
|
349
|
+
append_help_section(&mut out, "Team lifecycle", &["restart", "shutdown"]);
|
|
374
350
|
append_help_section(
|
|
375
351
|
&mut out,
|
|
376
|
-
"
|
|
352
|
+
"Agent lifecycle",
|
|
377
353
|
&[
|
|
378
354
|
"add-agent",
|
|
379
355
|
"start-agent",
|
|
@@ -386,21 +362,21 @@ pub(crate) fn default_help() -> String {
|
|
|
386
362
|
);
|
|
387
363
|
append_help_section(
|
|
388
364
|
&mut out,
|
|
389
|
-
"
|
|
390
|
-
&["leaders", "doctor", "approvals"
|
|
365
|
+
"Observation and collaboration",
|
|
366
|
+
&["leaders", "doctor", "approvals"],
|
|
391
367
|
);
|
|
392
|
-
append_help_section(&mut out, "
|
|
368
|
+
append_help_section(&mut out, "Configuration", &["route", "profile", "install-skill"]);
|
|
393
369
|
append_help_section(
|
|
394
370
|
&mut out,
|
|
395
|
-
"
|
|
371
|
+
"Guided recovery",
|
|
396
372
|
&["claim-leader", "takeover", "attach-leader"],
|
|
397
373
|
);
|
|
398
374
|
append_help_section(
|
|
399
375
|
&mut out,
|
|
400
|
-
"
|
|
376
|
+
"Leader launch",
|
|
401
377
|
&["pi", "codex", "claude", "copilot", "grok", "cursor"],
|
|
402
378
|
);
|
|
403
|
-
out.push_str("\
|
|
379
|
+
out.push_str("\nCommand arguments and examples: team-agent <command> --help");
|
|
404
380
|
out
|
|
405
381
|
}
|
|
406
382
|
|
|
@@ -435,9 +411,9 @@ pub fn __test_quick_start_args(
|
|
|
435
411
|
}
|
|
436
412
|
|
|
437
413
|
pub(super) const TEAM_TEMPLATE: &str =
|
|
438
|
-
"---\nname:
|
|
439
|
-
pub(super) const WORKER_TEMPLATE: &str = "---\nname: worker\nrole: assistant\nprovider: pi\nmodel: openai-codex/gpt-6-luna\nauth_mode: subscription\ndangerously_skip_permissions: false\n---\
|
|
440
|
-
const HUMAN_NAVIGATION: &str = "
|
|
414
|
+
"---\nname: current\n---\nA small team for a command-line example.\n";
|
|
415
|
+
pub(super) const WORKER_TEMPLATE: &str = "---\nname: worker\nrole: assistant\nprovider: pi\nmodel: openai-codex/gpt-6-luna\nauth_mode: subscription\ndangerously_skip_permissions: false\n---\nComplete the task and send a concise reply to the leader.\n";
|
|
416
|
+
const HUMAN_NAVIGATION: &str = "Use team-agent --help to list available commands.";
|
|
441
417
|
|
|
442
418
|
pub(super) fn command_help(command: Option<&str>) -> String {
|
|
443
419
|
let Some(name) = command else {
|
|
@@ -451,56 +427,54 @@ pub(super) fn command_help(command: Option<&str>) -> String {
|
|
|
451
427
|
}
|
|
452
428
|
let (details, examples, next) = match name {
|
|
453
429
|
"quick-start" => (
|
|
454
|
-
"TEAMDIR
|
|
430
|
+
"TEAMDIR contains TEAM.md and agents/; --workspace selects the runtime project.\n--name sets the team name; --team/--team-id selects the team; --yes retains compatibility; --detail includes full diagnostics.\nProviders must be installed and signed in. Declare provider/model/effort in role files; permission bypass defaults to false.\n--backend tmux is for POSIX; conpty requires a Windows host and an installed ConPTY shim.",
|
|
455
431
|
"team-agent quick-start\nteam-agent quick-start ./roles --workspace ./project\nteam-agent quick-start . --json",
|
|
456
|
-
"
|
|
457
|
-
"send" => ("<agent>
|
|
458
|
-
"team-agent send worker '
|
|
459
|
-
"
|
|
460
|
-
"inbox" => ("<agent>
|
|
461
|
-
"team-agent inbox leader -n 3\nteam-agent inbox worker --limit 5\nteam-agent inbox leader --workspace . --team help-demo --json", "
|
|
462
|
-
"status" => ("
|
|
463
|
-
"team-agent status\nteam-agent status worker\nteam-agent status --workspace . --team help-demo --json", "
|
|
464
|
-
"models" => ("--provider
|
|
465
|
-
"team-agent models --provider pi\nteam-agent models --provider pi --search luna\nteam-agent models --provider cursor_agent --json", "
|
|
466
|
-
"restart" => ("WORKSPACE
|
|
467
|
-
"team-agent restart .\nteam-agent restart . --team help-demo --json", "
|
|
468
|
-
"shutdown" => ("
|
|
469
|
-
"team-agent shutdown --workspace .\nteam-agent shutdown --workspace . --team help-demo --json", "
|
|
470
|
-
"add-agent" => ("<agent>
|
|
471
|
-
"team-agent add-agent reviewer --provider pi --model openai-codex/gpt-6-luna --bypass false --prompt '
|
|
472
|
-
"start-agent" => ("
|
|
473
|
-
"team-agent start-agent worker\nteam-agent start-agent worker --model openai-codex/gpt-6-luna", "
|
|
474
|
-
"stop-agent" => ("<agent>
|
|
475
|
-
"team-agent stop-agent worker\nteam-agent stop-agent worker --workspace . --team help-demo --json", "
|
|
476
|
-
"reset-agent" => ("
|
|
477
|
-
"team-agent reset-agent worker --discard-session\nteam-agent reset-agent worker --discard-session --workspace . --team help-demo --json", "
|
|
478
|
-
"clone-agent" => ("<agent>
|
|
479
|
-
"team-agent clone-agent worker --as reviewer\nteam-agent clone-agent worker --as reviewer --label '
|
|
480
|
-
"fork-agent" => ("<agent>
|
|
481
|
-
"team-agent fork-agent worker --as experiment\nteam-agent fork-agent worker --as experiment --workspace . --team help-demo --json", "
|
|
482
|
-
"remove-agent" => ("
|
|
483
|
-
"team-agent remove-agent reviewer --confirm\nteam-agent remove-agent reviewer --from-spec --confirm --workspace . --team help-demo --json", "
|
|
484
|
-
"leaders" => ("
|
|
485
|
-
"team-agent leaders\nteam-agent leaders --all --json\nteam-agent leaders --prune --dry-run", "
|
|
486
|
-
"doctor" => ("
|
|
487
|
-
"team-agent doctor --workspace .\nteam-agent doctor --workspace . --team help-demo --json", "
|
|
488
|
-
"approvals" => ("
|
|
489
|
-
"team-agent approvals\nteam-agent approvals worker --json", "
|
|
490
|
-
"
|
|
491
|
-
"team-agent
|
|
492
|
-
"
|
|
493
|
-
"team-agent
|
|
494
|
-
"
|
|
495
|
-
"team-agent
|
|
496
|
-
"
|
|
497
|
-
"team-agent
|
|
498
|
-
"
|
|
499
|
-
"team-agent
|
|
500
|
-
"attach-leader
|
|
501
|
-
"
|
|
502
|
-
_ => ("先确保工具已安装并完成原生登录;当前目录作为项目。\n--attach-existing 与 --confirm 只在已核验主控归属并明确授权后使用;--attach-session 选择已核验的会话。\n--external-leader 选择外部主控;--allow-nested-attach 仅在明确授权使用嵌套终端时添加。\n-- 后面的参数原样交给工具;纯 --help/-h 只显示这里的帮助,不启动工具。",
|
|
503
|
-
"", "在打开的主控命令行/工具上下文运行 team-agent quick-start;不是在普通队友对话中起队。"),
|
|
432
|
+
"Check Pi installation and sign-in; verify models with team-agent models --provider pi.\nRun team-agent pi, then quick-start in that leader's tool context. For an existing team, see restart --help."),
|
|
433
|
+
"send" => ("<agent> is an agent name from status; MESSAGE is the task text.\n--mailbox stores a message without injecting it into the current conversation; default delivery targets the agent conversation.",
|
|
434
|
+
"team-agent send worker 'Calculate 245 * 37 and reply to the leader.'\nteam-agent send worker 'Review the changes' --workspace . --team help-demo\nteam-agent send worker 'Read this when next available' --mailbox",
|
|
435
|
+
"Acceptance is not delivery or completion. Wait for an actual reply; use team-agent inbox leader -n 3. Diagnose with status/doctor rather than repeated sends."),
|
|
436
|
+
"inbox" => ("<agent> is the recipient name; -n/--limit bounds the message count. Reading replies does not resend messages.",
|
|
437
|
+
"team-agent inbox leader -n 3\nteam-agent inbox worker --limit 5\nteam-agent inbox leader --workspace . --team help-demo --json", "Wait for a natural reply, then check team-agent status if needed. Do not inspect worker terminal content."),
|
|
438
|
+
"status" => ("Optional <agent> selects one agent. --summary/--detail retain compatibility and do not add fields.\nNine fields: name/provider/model/effort/runtime_status/activity/health/session_name/tmux_command.\nRuntime, activity and health are distinct. unknown means insufficient evidence; null means unset. No default model is guessed.",
|
|
439
|
+
"team-agent status\nteam-agent status worker\nteam-agent status --workspace . --team help-demo --json", "Read replies with team-agent inbox leader -n 3; diagnose problems with team-agent doctor --workspace ."),
|
|
440
|
+
"models" => ("--provider selects the provider, default pi. QUERY or --search performs keyword search; they are mutually exclusive.\nUse cursor_agent for Cursor. Copilot has no model discovery entrypoint here. Results come from the native catalog, not guesses.",
|
|
441
|
+
"team-agent models --provider pi\nteam-agent models --provider pi --search luna\nteam-agent models --provider cursor_agent --json", "Use the exact returned model name in a role file or add-agent --model before launch."),
|
|
442
|
+
"restart" => ("WORKSPACE defaults to the current project; saved sessions are preferred.\nUse --allow-fresh only after explicit authorization to lose saved context. --session-converge-deadline sets the wait in seconds.",
|
|
443
|
+
"team-agent restart .\nteam-agent restart . --team help-demo --json", "Check status after restart. Obtain authorization before creating a fresh session; takeover is not a substitute for restart."),
|
|
444
|
+
"shutdown" => ("Shuts down only the selected workspace/team. Logs are retained by default; --keep-logs remains compatible.\n--json reports scope, degradation and owned residuals. Success does not mean other teams were shut down.",
|
|
445
|
+
"team-agent shutdown --workspace .\nteam-agent shutdown --workspace . --team help-demo --json", "Verify this team's owned residuals are empty, then run team-agent doctor --workspace . Do not clean up other teams."),
|
|
446
|
+
"add-agent" => ("<agent> names the new agent. --provider selects the provider; --model sets the model; --effort sets reasoning effort.\n--bypass controls permission bypass (example: false); --prompt sets responsibilities; --profile selects authentication/proxy settings.\nProvider and bypass must be explicit in arguments or --role-file. Conflicting settings are refused, not guessed. Use --force to replace an agent only after explicit authorization.",
|
|
447
|
+
"team-agent add-agent reviewer --provider pi --model openai-codex/gpt-6-luna --bypass false --prompt 'Review tasks'\nteam-agent add-agent reviewer --role-file ./agents/reviewer.md", "Verify the agent with status, then run team-agent send reviewer 'Review the changes'."),
|
|
448
|
+
"start-agent" => ("Starts an existing agent only; stop-agent first if it is running.\n--provider selects the provider; --model sets the model; --effort sets reasoning effort; --bypass controls permission bypass.\n--prompt/--profile changes responsibilities/authentication. Use --allow-fresh only after explicit authorization to lose saved context.",
|
|
449
|
+
"team-agent start-agent worker\nteam-agent start-agent worker --model openai-codex/gpt-6-luna", "Use add-agent for a new agent. Check status after starting, then send a task."),
|
|
450
|
+
"stop-agent" => ("<agent> names the agent to stop. This is not removal; configuration and session history are retained.",
|
|
451
|
+
"team-agent stop-agent worker\nteam-agent stop-agent worker --workspace . --team help-demo --json", "Verify stopped status; resume with team-agent start-agent worker."),
|
|
452
|
+
"reset-agent" => ("Requires explicit --discard-session. Stops the old process, clears the saved session association and attempts a fresh restart. Paused agents stay paused. This does not delete all native provider history.",
|
|
453
|
+
"team-agent reset-agent worker --discard-session\nteam-agent reset-agent worker --discard-session --workspace . --team help-demo --json", "Verify with team-agent status. After a normal reset, send a new task; another start-agent is unnecessary."),
|
|
454
|
+
"clone-agent" => ("<agent> is the source; --as names the new agent; --label sets a readable label.\nCreates an agent from copied configuration, not a complete conversation copy.",
|
|
455
|
+
"team-agent clone-agent worker --as reviewer\nteam-agent clone-agent worker --as reviewer --label 'Review agent' --workspace .", "Check the new agent with status, then send reviewer an independent task."),
|
|
456
|
+
"fork-agent" => ("<agent> is the source; --as names the new agent; --label sets a readable label.\nRequires a captured source session and supported fork behavior. Unsupported cases are refused; not all providers support the same kind of fork.",
|
|
457
|
+
"team-agent fork-agent worker --as experiment\nteam-agent fork-agent worker --as experiment --workspace . --team help-demo --json", "Inspect status after success, then send a task to the resulting agent. Stop or remove a new seat when finished."),
|
|
458
|
+
"remove-agent" => ("Requires --confirm. Spec-defined agents also require --from-spec; dynamic agents do not.\nDeletes only the managed role copy, not an external user role file. Stop a running agent first.\n--force stops and removes a running agent; use only after understanding the risk and receiving explicit authorization.",
|
|
459
|
+
"team-agent remove-agent reviewer --confirm\nteam-agent remove-agent reviewer --from-spec --confirm --workspace . --team help-demo --json", "Save required replies first; verify status and configuration scope after removal. Do not force-clean other agents."),
|
|
460
|
+
"leaders" => ("Lists live leaders by default; --all includes stale registrations and --stale selects stale entries.\nQUERY/--search filters workspace/team/name. --prune removes verified retired registrations; preview with --dry-run.",
|
|
461
|
+
"team-agent leaders\nteam-agent leaders --all --json\nteam-agent leaders --prune --dry-run", "Select --workspace/--team from the reported scope. Registry cleanup is not process shutdown."),
|
|
462
|
+
"doctor" => ("Read-only diagnosis by default; SPEC is an optional configuration path.\n--comms/--gate comms checks channels, not actual replies; --gate orphans checks residual resources.\nUse --fix/--fix-schema/--cleanup-orphans only within authorized scope. Dangerous repairs require --confirm.",
|
|
463
|
+
"team-agent doctor --workspace .\nteam-agent doctor --workspace . --team help-demo --json", "Follow the actual issue and selected-team scope. A suggested repair is not an executed repair."),
|
|
464
|
+
"approvals" => ("Optional <agent> selects one agent. Observes permission prompts without granting approval.",
|
|
465
|
+
"team-agent approvals\nteam-agent approvals worker --json", "Verify the requested permission, handle it in the actual provider prompt, then check status."),
|
|
466
|
+
"profile" => ("init creates a profile; doctor checks it; show displays settings. NAME is the profile name.\n--auth-mode selects authentication; --proxy-mode direct bypasses proxies and inherit uses the environment proxy.\nProfiles belong to the selected workspace, not automatically to an isolated --team. Do not expose credentials.",
|
|
467
|
+
"team-agent profile init local --auth-mode subscription\nteam-agent profile doctor local\nteam-agent profile show local", "After validation, select --profile local with add-agent/start-agent. Use native provider sign-in when needed."),
|
|
468
|
+
"install-skill" => ("--source must be a verified skill directory; --target selects codex/claude/copilot/all.\n--dest overrides the destination; --dry-run previews; --uninstall removes the target skill. Installation is not required to create a team.",
|
|
469
|
+
"team-agent install-skill --source \"$SKILL_DIR\" --target codex --dry-run\nteam-agent install-skill --source \"$SKILL_DIR\" --target codex", "Set SKILL_DIR to a verified directory and inspect the preview before installation. Do not overwrite unknown user files."),
|
|
470
|
+
"claim-leader" => ("Use only when doctor recommends leader registration and this terminal is the actual leader.\n--confirm authorizes registration; it does not open a new provider.",
|
|
471
|
+
"team-agent claim-leader --workspace . --json\nteam-agent claim-leader --workspace . --team help-demo --confirm --json", "Verify doctor findings and terminal ownership first. A refusal without confirmation is not successful registration."),
|
|
472
|
+
"takeover" => ("Use only when doctor recommends takeover and the user has authorized it. This changes leader ownership.\nWith a usable team terminal, takeover may execute even without --confirm; it is not a read-only preview. Use restart for normal recovery.",
|
|
473
|
+
"team-agent takeover --workspace . --team help-demo --confirm\nteam-agent takeover --workspace . --team help-demo --confirm --json", "Verify leader/team ownership before confirmation. Check status/doctor afterwards; do not take over someone else's team."),
|
|
474
|
+
"attach-leader" => ("PANE must be a verified leader terminal, not a guessed pane id.\n--provider selects the actual provider; --confirm authorizes attachment to an existing leader. Use only when doctor recommends it.",
|
|
475
|
+
"team-agent attach-leader --pane \"$PANE\" --provider pi --workspace .\nteam-agent attach-leader --pane \"$PANE\" --provider pi --workspace . --team help-demo --confirm --json", "Set PANE to the verified terminal. Confirm attachment with doctor before sending a task."),
|
|
476
|
+
_ => ("Install the provider and complete native sign-in first. The current directory is the workspace.\nUse --attach-existing/--confirm only after verifying leader ownership and receiving authorization; --attach-session selects a verified session.\n--external-leader selects an external leader; --allow-nested-attach requires explicit authorization for nested terminals.\nArguments after -- pass through unchanged. Bare --help/-h displays this help without starting the provider.",
|
|
477
|
+
"", "Run team-agent quick-start in the opened leader's tool context, not in an ordinary worker conversation."),
|
|
504
478
|
};
|
|
505
479
|
let examples = if matches!(spec.kind, CommandKind::LeaderPassthrough { .. }) {
|
|
506
480
|
let native = if name == "cursor" { "agent" } else { name };
|
|
@@ -513,30 +487,30 @@ pub(super) fn command_help(command: Option<&str>) -> String {
|
|
|
513
487
|
examples.to_string()
|
|
514
488
|
};
|
|
515
489
|
let scope = if matches!(spec.kind, CommandKind::LeaderPassthrough { .. }) {
|
|
516
|
-
"--json
|
|
490
|
+
"Use --json for structured output. --workspace/--team are not launcher options here; they pass to the native provider."
|
|
517
491
|
} else {
|
|
518
|
-
"--json
|
|
492
|
+
"Use --json for structured output. --workspace selects the project; supported commands use --team to select the team."
|
|
519
493
|
};
|
|
520
|
-
let mut out = format!("
|
|
494
|
+
let mut out = format!("Purpose:\n{}.\nUsage: {}\n\nOptions:\n{}\n{}\nExamples:\n{}\n\nNext Action:\n{}", spec.summary, spec.usage, details, scope, examples, next);
|
|
521
495
|
if name == "quick-start" {
|
|
522
|
-
out.push_str(&format!("\n\
|
|
496
|
+
out.push_str(&format!("\n\nMinimal two-file configuration (create manually; not written automatically):\nTEAM.md:\n{TEAM_TEMPLATE}\nagents/worker.md:\n{WORKER_TEMPLATE}"));
|
|
523
497
|
}
|
|
524
498
|
if name == "cursor" {
|
|
525
|
-
out.push_str("\
|
|
499
|
+
out.push_str("\nDiscover models with team-agent models --provider cursor_agent.");
|
|
526
500
|
}
|
|
527
501
|
if name == "copilot" {
|
|
528
|
-
out.push_str("\nCopilot
|
|
502
|
+
out.push_str("\nCopilot has no team-agent models entrypoint; consult native provider help.");
|
|
529
503
|
}
|
|
530
504
|
out
|
|
531
505
|
}
|
|
532
506
|
|
|
533
507
|
fn emit_unknown_subcommand_usage(command: &str) -> ExitCode {
|
|
534
508
|
emit_usage_error(&format!(
|
|
535
|
-
"
|
|
509
|
+
"Unknown command: '{command}'. Use team-agent --help to list available commands."
|
|
536
510
|
));
|
|
537
511
|
// E8 (N38): 错路引导 —— 拼写近似时建议最接近的真子命令(additive,不改既有 golden 行)。
|
|
538
512
|
if let Some(suggestion) = nearest_subcommand(command) {
|
|
539
|
-
eprintln!("
|
|
513
|
+
eprintln!("Did you mean `{suggestion}`? See team-agent {suggestion} --help for examples.");
|
|
540
514
|
}
|
|
541
515
|
// 0.5.45 naming-addressing (RED-6): unknown subcommand exits 1
|
|
542
516
|
// (Error), aligned with the family of typo refusals throughout
|
|
@@ -568,11 +542,10 @@ fn nearest_subcommand(input: &str) -> Option<&'static str> {
|
|
|
568
542
|
}
|
|
569
543
|
|
|
570
544
|
fn emit_usage_error(message: &str) {
|
|
571
|
-
eprintln!("
|
|
572
|
-
eprintln!("
|
|
545
|
+
eprintln!("Usage: team-agent <command>; list commands with team-agent --help");
|
|
546
|
+
eprintln!("Error: {message}");
|
|
573
547
|
}
|
|
574
548
|
|
|
575
|
-
/// `cmd_validate` delegates to runtime validate_file.
|
|
576
549
|
/// `install-skill` 参数(RED-1 根治:把 skill 安装单源收敛到二进制,install.mjs 调它)。
|
|
577
550
|
struct InstallSkillArgs {
|
|
578
551
|
target: crate::packaging::SkillTarget,
|
|
@@ -688,93 +661,10 @@ fn cmd_install_skill(args: &InstallSkillArgs) -> Result<CmdResult, CliError> {
|
|
|
688
661
|
))
|
|
689
662
|
}
|
|
690
663
|
|
|
691
|
-
pub fn cmd_validate(args: &ValidateArgs) -> Result<CmdResult, CliError> {
|
|
692
|
-
let spec = resolve_path(&args.spec);
|
|
693
|
-
let value = if spec.is_dir() {
|
|
694
|
-
validate_team_dir(&spec)?
|
|
695
|
-
} else {
|
|
696
|
-
validate_spec_file(&spec)?
|
|
697
|
-
};
|
|
698
|
-
Ok(CmdResult::from_json(value, args.json))
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
fn validate_spec_file(spec_path: &Path) -> Result<Value, CliError> {
|
|
702
|
-
let text = std::fs::read_to_string(spec_path)?;
|
|
703
|
-
let base_dir = spec_path.parent().unwrap_or_else(|| Path::new("."));
|
|
704
|
-
let spec =
|
|
705
|
-
crate::model::spec::load_and_validate_spec(&text, base_dir).map_err(model_error_to_cli)?;
|
|
706
|
-
let team = spec
|
|
707
|
-
.get("team")
|
|
708
|
-
.and_then(|team| team.get("name"))
|
|
709
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
710
|
-
.unwrap_or("");
|
|
711
|
-
let workspace = spec
|
|
712
|
-
.get("team")
|
|
713
|
-
.and_then(|team| team.get("workspace"))
|
|
714
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
715
|
-
.map(PathBuf::from)
|
|
716
|
-
.unwrap_or_else(|| base_dir.to_path_buf());
|
|
717
|
-
let mut obj = Map::new();
|
|
718
|
-
obj.insert("ok".to_string(), Value::Bool(true));
|
|
719
|
-
obj.insert("team".to_string(), Value::String(team.to_string()));
|
|
720
|
-
obj.insert(
|
|
721
|
-
"workspace".to_string(),
|
|
722
|
-
Value::String(workspace.to_string_lossy().to_string()),
|
|
723
|
-
);
|
|
724
|
-
Ok(Value::Object(obj))
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
fn validate_team_dir(team_dir: &Path) -> Result<Value, CliError> {
|
|
728
|
-
let spec = crate::compiler::compile_team(team_dir).map_err(model_error_to_cli)?;
|
|
729
|
-
let team = spec
|
|
730
|
-
.get("team")
|
|
731
|
-
.and_then(|team| team.get("name"))
|
|
732
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
733
|
-
.unwrap_or("");
|
|
734
|
-
let workspace = spec
|
|
735
|
-
.get("team")
|
|
736
|
-
.and_then(|team| team.get("workspace"))
|
|
737
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
738
|
-
.map(PathBuf::from)
|
|
739
|
-
.unwrap_or_else(|| team_dir.to_path_buf());
|
|
740
|
-
let agents = spec
|
|
741
|
-
.get("agents")
|
|
742
|
-
.and_then(crate::model::yaml::Value::as_list)
|
|
743
|
-
.map(|agents| {
|
|
744
|
-
agents
|
|
745
|
-
.iter()
|
|
746
|
-
.filter_map(|agent| {
|
|
747
|
-
agent
|
|
748
|
-
.get("id")
|
|
749
|
-
.and_then(crate::model::yaml::Value::as_str)
|
|
750
|
-
.map(|id| Value::String(id.to_string()))
|
|
751
|
-
})
|
|
752
|
-
.collect::<Vec<_>>()
|
|
753
|
-
})
|
|
754
|
-
.unwrap_or_default();
|
|
755
|
-
let mut obj = Map::new();
|
|
756
|
-
obj.insert("ok".to_string(), Value::Bool(true));
|
|
757
|
-
obj.insert("type".to_string(), Value::String("team_dir".to_string()));
|
|
758
|
-
obj.insert(
|
|
759
|
-
"workspace".to_string(),
|
|
760
|
-
Value::String(workspace.to_string_lossy().to_string()),
|
|
761
|
-
);
|
|
762
|
-
obj.insert("team".to_string(), Value::String(team.to_string()));
|
|
763
|
-
obj.insert("agents".to_string(), Value::Array(agents));
|
|
764
|
-
Ok(Value::Object(obj))
|
|
765
|
-
}
|
|
766
|
-
|
|
767
664
|
fn resolve_path(path: &Path) -> PathBuf {
|
|
768
665
|
std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
|
|
769
666
|
}
|
|
770
667
|
|
|
771
|
-
fn model_error_to_cli(error: crate::model::errors::ModelError) -> CliError {
|
|
772
|
-
match error {
|
|
773
|
-
crate::model::errors::ModelError::Validation(message) => CliError::Runtime(message),
|
|
774
|
-
other => CliError::Runtime(other.to_string()),
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
668
|
fn emit_cli_error(command: &str, args: &[String], cwd: &Path, error: &CliError) -> ExitCode {
|
|
779
669
|
let parsed = parse_args(args);
|
|
780
670
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -802,12 +692,12 @@ fn emit_cli_error(command: &str, args: &[String], cwd: &Path, error: &CliError)
|
|
|
802
692
|
{
|
|
803
693
|
if let Some(message) = super::named_address::human_address_reason(reason) {
|
|
804
694
|
payload.error = message.to_string();
|
|
805
|
-
payload.action = "
|
|
695
|
+
payload.action = "Run team-agent status and use an agent name from the list. Select scope with --workspace/--team; diagnose channel issues with doctor.".to_string();
|
|
806
696
|
}
|
|
807
697
|
}
|
|
808
698
|
if command_spec(command).is_some()
|
|
809
699
|
&& payload.action
|
|
810
|
-
== "
|
|
700
|
+
== "Run team-agent doctor --workspace . for the selected team, or inspect the error log shown here."
|
|
811
701
|
{
|
|
812
702
|
let mut doctor = format!(
|
|
813
703
|
"team-agent doctor --workspace {}",
|
|
@@ -816,7 +706,7 @@ fn emit_cli_error(command: &str, args: &[String], cwd: &Path, error: &CliError)
|
|
|
816
706
|
if let Some(team) = parsed.team.as_deref() {
|
|
817
707
|
doctor.push_str(&format!(" --team {}", super::adapters::shell_quote(team)));
|
|
818
708
|
}
|
|
819
|
-
payload.action = format!("
|
|
709
|
+
payload.action = format!("Run {doctor} for the selected team, or inspect the error log shown here.");
|
|
820
710
|
}
|
|
821
711
|
payload.action = crate::redaction::redact_external_text(&payload.action);
|
|
822
712
|
payload.log = crate::redaction::redact_external_text(&payload.log);
|
|
@@ -951,14 +841,9 @@ struct ParsedArgs {
|
|
|
951
841
|
once: bool,
|
|
952
842
|
tick_interval: Option<f64>,
|
|
953
843
|
status_value: Option<String>,
|
|
954
|
-
providers: Option<String>,
|
|
955
|
-
allow_raw_screen: bool,
|
|
956
|
-
tail: Option<usize>,
|
|
957
|
-
head: Option<usize>,
|
|
958
844
|
search: Option<String>,
|
|
959
845
|
file: Option<PathBuf>,
|
|
960
846
|
result: Option<String>,
|
|
961
|
-
real: bool,
|
|
962
847
|
assignee: Option<String>,
|
|
963
848
|
auth_mode: Option<String>,
|
|
964
849
|
proxy_mode: Option<String>,
|
|
@@ -1045,14 +930,9 @@ fn parse_args(args: &[String]) -> ParsedArgs {
|
|
|
1045
930
|
parsed.tick_interval = next_arg(args, &mut i).and_then(|v| v.parse::<f64>().ok())
|
|
1046
931
|
}
|
|
1047
932
|
"--status" => parsed.status_value = next_arg(args, &mut i),
|
|
1048
|
-
"--providers" => parsed.providers = next_arg(args, &mut i),
|
|
1049
|
-
"--allow-raw-screen" => parsed.allow_raw_screen = true,
|
|
1050
|
-
"--tail" => parsed.tail = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
|
|
1051
|
-
"--head" => parsed.head = next_arg(args, &mut i).and_then(|v| v.parse::<usize>().ok()),
|
|
1052
933
|
"--search" => parsed.search = next_arg(args, &mut i),
|
|
1053
934
|
"--file" => parsed.file = next_arg(args, &mut i).map(PathBuf::from),
|
|
1054
935
|
"--result" => parsed.result = next_arg(args, &mut i),
|
|
1055
|
-
"--real" => parsed.real = true,
|
|
1056
936
|
"--assignee" => parsed.assignee = next_arg(args, &mut i),
|
|
1057
937
|
"--auth-mode" => parsed.auth_mode = next_arg(args, &mut i),
|
|
1058
938
|
"--proxy-mode" => parsed.proxy_mode = next_arg(args, &mut i),
|
|
@@ -1203,7 +1083,7 @@ fn quick_start_args(args: &[String], cwd: &Path) -> Result<QuickStartArgs, CliEr
|
|
|
1203
1083
|
let normalized = literal.trim().to_ascii_lowercase();
|
|
1204
1084
|
if normalized != "tmux" && normalized != "conpty" {
|
|
1205
1085
|
return Err(CliError::Usage(format!(
|
|
1206
|
-
"--backend
|
|
1086
|
+
"--backend must be tmux or conpty; received {literal:?}. Use tmux on POSIX. ConPTY requires a Windows host and an installed shim."
|
|
1207
1087
|
)));
|
|
1208
1088
|
}
|
|
1209
1089
|
}
|
|
@@ -1257,7 +1137,7 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
|
1257
1137
|
&& parsed.positionals.len() < 2
|
|
1258
1138
|
{
|
|
1259
1139
|
return Err(CliError::Usage(
|
|
1260
|
-
"
|
|
1140
|
+
"Provide a recipient agent name and task message, such as team-agent send worker 'Review the changes'".to_string(),
|
|
1261
1141
|
));
|
|
1262
1142
|
}
|
|
1263
1143
|
let target = if parsed.targets.is_some()
|
|
@@ -1291,7 +1171,7 @@ fn send_args(args: &[String], cwd: &Path) -> Result<SendArgs, CliError> {
|
|
|
1291
1171
|
)
|
|
1292
1172
|
.map_err(|error| CliError::Usage(format!("invalid send routing: {error}")))?;
|
|
1293
1173
|
if normalized.deprecation.is_some() {
|
|
1294
|
-
eprintln!("
|
|
1174
|
+
eprintln!("Warning: legacy send settings are deprecated. Use team-agent send <agent> 'task message'; add --mailbox for storage without injection.");
|
|
1295
1175
|
}
|
|
1296
1176
|
Ok(SendArgs {
|
|
1297
1177
|
target,
|
|
@@ -1370,16 +1250,16 @@ fn warn_send_legacy_delivery_flags(args: &[String]) {
|
|
|
1370
1250
|
"--message-id",
|
|
1371
1251
|
];
|
|
1372
1252
|
let spec = command_spec("send");
|
|
1373
|
-
let sunset = spec.and_then(|spec| spec.sunset).unwrap_or("
|
|
1253
|
+
let sunset = spec.and_then(|spec| spec.sunset).unwrap_or("a future compatibility release");
|
|
1374
1254
|
let action = spec
|
|
1375
1255
|
.and_then(|spec| spec.action)
|
|
1376
|
-
.unwrap_or("
|
|
1256
|
+
.unwrap_or("Use team-agent send <agent> 'task message'; select scope with --workspace/--team");
|
|
1377
1257
|
for flag in FLAGS {
|
|
1378
1258
|
if args
|
|
1379
1259
|
.iter()
|
|
1380
1260
|
.any(|arg| arg == flag || arg.starts_with(&format!("{flag}=")))
|
|
1381
1261
|
{
|
|
1382
|
-
eprintln!("
|
|
1262
|
+
eprintln!("Warning: {flag} is deprecated and will be removed in {sunset}. Next: {action}");
|
|
1383
1263
|
}
|
|
1384
1264
|
}
|
|
1385
1265
|
}
|
|
@@ -1458,17 +1338,6 @@ fn refuse_if_multi_alive_team_missing_scope(
|
|
|
1458
1338
|
Ok(())
|
|
1459
1339
|
}
|
|
1460
1340
|
|
|
1461
|
-
fn allow_peer_talk_args(args: &[String], cwd: &Path) -> Result<AllowPeerTalkArgs, CliError> {
|
|
1462
|
-
let parsed = parse_args(args);
|
|
1463
|
-
Ok(AllowPeerTalkArgs {
|
|
1464
|
-
a: required_pos(&parsed, 0, "a")?,
|
|
1465
|
-
b: required_pos(&parsed, 1, "b")?,
|
|
1466
|
-
workspace: workspace(&parsed, cwd),
|
|
1467
|
-
json: parsed.json,
|
|
1468
|
-
team: parsed.team,
|
|
1469
|
-
})
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
1341
|
fn status_args(args: &[String], cwd: &Path) -> StatusArgs {
|
|
1473
1342
|
let parsed = parse_args(args);
|
|
1474
1343
|
StatusArgs {
|
|
@@ -1481,14 +1350,6 @@ fn status_args(args: &[String], cwd: &Path) -> StatusArgs {
|
|
|
1481
1350
|
}
|
|
1482
1351
|
}
|
|
1483
1352
|
|
|
1484
|
-
fn watch_args(args: &[String], cwd: &Path) -> WatchArgs {
|
|
1485
|
-
let parsed = parse_args(args);
|
|
1486
|
-
WatchArgs {
|
|
1487
|
-
workspace: workspace(&parsed, cwd),
|
|
1488
|
-
team: parsed.team,
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
1353
|
fn wait_args(args: &[String], cwd: &Path) -> Result<WaitArgs, CliError> {
|
|
1493
1354
|
let parsed = parse_args(args);
|
|
1494
1355
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -1586,15 +1447,6 @@ fn attach_app_server_leader_args(
|
|
|
1586
1447
|
})
|
|
1587
1448
|
}
|
|
1588
1449
|
|
|
1589
|
-
fn identity_args(args: &[String], cwd: &Path) -> IdentityArgs {
|
|
1590
|
-
let parsed = parse_args(args);
|
|
1591
|
-
IdentityArgs {
|
|
1592
|
-
workspace: workspace(&parsed, cwd),
|
|
1593
|
-
team: parsed.team,
|
|
1594
|
-
json: parsed.json,
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
1450
|
fn shutdown_args(args: &[String], cwd: &Path) -> Result<ShutdownArgs, CliError> {
|
|
1599
1451
|
let parsed = parse_args(args);
|
|
1600
1452
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -1821,7 +1673,7 @@ fn reset_agent_args(args: &[String], cwd: &Path) -> Result<ResetAgentArgs, CliEr
|
|
|
1821
1673
|
let agent = required_pos(&parsed, 0, "agent")?;
|
|
1822
1674
|
if !parsed.discard_session {
|
|
1823
1675
|
return Err(CliError::Usage(
|
|
1824
|
-
"missing --discard-session
|
|
1676
|
+
"missing --discard-session; add this option only after explicit authorization to discard the saved session association".to_string(),
|
|
1825
1677
|
));
|
|
1826
1678
|
}
|
|
1827
1679
|
let workspace = workspace(&parsed, cwd);
|
|
@@ -1917,15 +1769,6 @@ fn doctor_gate(raw: Option<&str>) -> Option<DoctorGate> {
|
|
|
1917
1769
|
}
|
|
1918
1770
|
}
|
|
1919
1771
|
|
|
1920
|
-
fn sessions_args(args: &[String], cwd: &Path) -> SessionsArgs {
|
|
1921
|
-
let parsed = parse_args(args);
|
|
1922
|
-
SessionsArgs {
|
|
1923
|
-
workspace: workspace(&parsed, cwd),
|
|
1924
|
-
json: parsed.json,
|
|
1925
|
-
team: parsed.team,
|
|
1926
|
-
}
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
1772
|
fn leaders_args(args: &[String], _cwd: &Path) -> Result<LeadersArgs, CliError> {
|
|
1930
1773
|
let mut view = None;
|
|
1931
1774
|
let mut query = None;
|
|
@@ -2031,18 +1874,6 @@ fn leaders_args(args: &[String], _cwd: &Path) -> Result<LeadersArgs, CliError> {
|
|
|
2031
1874
|
})
|
|
2032
1875
|
}
|
|
2033
1876
|
|
|
2034
|
-
fn validate_args(args: &[String], cwd: &Path) -> ValidateArgs {
|
|
2035
|
-
let parsed = parse_args(args);
|
|
2036
|
-
ValidateArgs {
|
|
2037
|
-
spec: parsed
|
|
2038
|
-
.positionals
|
|
2039
|
-
.first()
|
|
2040
|
-
.map(PathBuf::from)
|
|
2041
|
-
.unwrap_or_else(|| cwd.join("team.spec.yaml")),
|
|
2042
|
-
json: parsed.json,
|
|
2043
|
-
}
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
1877
|
fn profile_args(args: &[String], cwd: &Path) -> Result<ProfileArgs, CliError> {
|
|
2047
1878
|
let parsed = parse_args(args);
|
|
2048
1879
|
let workspace = resolve_cli_path(cwd, &workspace(&parsed, cwd));
|
|
@@ -2057,106 +1888,6 @@ fn profile_args(args: &[String], cwd: &Path) -> Result<ProfileArgs, CliError> {
|
|
|
2057
1888
|
})
|
|
2058
1889
|
}
|
|
2059
1890
|
|
|
2060
|
-
fn results_args(args: &[String], cwd: &Path) -> Result<ResultsArgs, CliError> {
|
|
2061
|
-
if args
|
|
2062
|
-
.iter()
|
|
2063
|
-
.any(|arg| arg == "--to" || arg.starts_with("--to="))
|
|
2064
|
-
{
|
|
2065
|
-
return Err(CliError::Usage(
|
|
2066
|
-
"results does not accept --to; it is a read-only command".to_string(),
|
|
2067
|
-
));
|
|
2068
|
-
}
|
|
2069
|
-
let parsed = parse_args(args);
|
|
2070
|
-
let case_id = option_value(args, "--case")
|
|
2071
|
-
.filter(|value| !value.is_empty())
|
|
2072
|
-
.ok_or_else(|| CliError::Usage("missing --case".to_string()))?;
|
|
2073
|
-
Ok(ResultsArgs {
|
|
2074
|
-
case_id,
|
|
2075
|
-
workspace: workspace(&parsed, cwd),
|
|
2076
|
-
team: parsed.team,
|
|
2077
|
-
json: parsed.json,
|
|
2078
|
-
})
|
|
2079
|
-
}
|
|
2080
|
-
|
|
2081
|
-
fn option_value(args: &[String], flag: &str) -> Option<String> {
|
|
2082
|
-
let prefix = format!("{flag}=");
|
|
2083
|
-
let mut i = 0usize;
|
|
2084
|
-
while i < args.len() {
|
|
2085
|
-
let arg = args.get(i)?;
|
|
2086
|
-
if let Some(value) = arg.strip_prefix(&prefix) {
|
|
2087
|
-
return Some(value.to_string());
|
|
2088
|
-
}
|
|
2089
|
-
if arg == flag {
|
|
2090
|
-
return args
|
|
2091
|
-
.get(i.saturating_add(1))
|
|
2092
|
-
.filter(|value| !value.starts_with('-'))
|
|
2093
|
-
.cloned();
|
|
2094
|
-
}
|
|
2095
|
-
i = i.saturating_add(1);
|
|
2096
|
-
}
|
|
2097
|
-
None
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
fn preflight_args(args: &[String], cwd: &Path) -> PreflightArgs {
|
|
2101
|
-
let parsed = parse_args(args);
|
|
2102
|
-
let team = parsed
|
|
2103
|
-
.team
|
|
2104
|
-
.as_deref()
|
|
2105
|
-
.map(PathBuf::from)
|
|
2106
|
-
.or_else(|| parsed.positionals.first().map(PathBuf::from))
|
|
2107
|
-
.unwrap_or_else(|| cwd.to_path_buf());
|
|
2108
|
-
PreflightArgs {
|
|
2109
|
-
team,
|
|
2110
|
-
json: parsed.json,
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
fn wait_ready_args(args: &[String], cwd: &Path) -> WaitReadyArgs {
|
|
2115
|
-
let parsed = parse_args(args);
|
|
2116
|
-
WaitReadyArgs {
|
|
2117
|
-
workspace: workspace(&parsed, cwd),
|
|
2118
|
-
timeout: parsed.timeout.unwrap_or(60.0),
|
|
2119
|
-
json: parsed.json,
|
|
2120
|
-
team: parsed.team,
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
|
-
fn e2e_args(args: &[String], cwd: &Path) -> E2eArgs {
|
|
2125
|
-
let parsed = parse_args(args);
|
|
2126
|
-
let providers = parsed
|
|
2127
|
-
.providers
|
|
2128
|
-
.as_deref()
|
|
2129
|
-
.unwrap_or("fake")
|
|
2130
|
-
.split(',')
|
|
2131
|
-
.filter_map(|p| {
|
|
2132
|
-
let trimmed = p.trim();
|
|
2133
|
-
if trimmed.is_empty() {
|
|
2134
|
-
None
|
|
2135
|
-
} else {
|
|
2136
|
-
Some(trimmed.to_string())
|
|
2137
|
-
}
|
|
2138
|
-
})
|
|
2139
|
-
.collect();
|
|
2140
|
-
E2eArgs {
|
|
2141
|
-
workspace: workspace(&parsed, cwd),
|
|
2142
|
-
providers,
|
|
2143
|
-
real: parsed.real,
|
|
2144
|
-
json: parsed.json,
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
fn peek_args(args: &[String], cwd: &Path) -> Result<PeekArgs, CliError> {
|
|
2149
|
-
let parsed = parse_args(args);
|
|
2150
|
-
Ok(PeekArgs {
|
|
2151
|
-
agent: required_pos(&parsed, 0, "agent")?,
|
|
2152
|
-
workspace: workspace(&parsed, cwd),
|
|
2153
|
-
tail: parsed.tail.unwrap_or(80),
|
|
2154
|
-
head: parsed.head,
|
|
2155
|
-
search: parsed.search,
|
|
2156
|
-
allow_raw_screen: parsed.allow_raw_screen,
|
|
2157
|
-
json: parsed.json,
|
|
2158
|
-
})
|
|
2159
|
-
}
|
|
2160
1891
|
fn run_coordinator(args: &[String], cwd: &Path) -> Result<ExitCode, CliError> {
|
|
2161
1892
|
let parsed = parse_args(args);
|
|
2162
1893
|
let workspace = crate::coordinator::WorkspacePath::new(workspace(&parsed, cwd));
|
|
@@ -2343,7 +2074,7 @@ mod tests {
|
|
|
2343
2074
|
.filter(|spec| spec.default_help)
|
|
2344
2075
|
.map(|spec| spec.name)
|
|
2345
2076
|
.collect();
|
|
2346
|
-
assert_eq!(expected.len(),
|
|
2077
|
+
assert_eq!(expected.len(), 29, "the public catalog is Human29");
|
|
2347
2078
|
for spec_name in &expected {
|
|
2348
2079
|
assert!(
|
|
2349
2080
|
visible.iter().any(|command| command == spec_name),
|
|
@@ -2357,7 +2088,7 @@ mod tests {
|
|
|
2357
2088
|
expected_sorted.sort();
|
|
2358
2089
|
assert_eq!(
|
|
2359
2090
|
actual, expected_sorted,
|
|
2360
|
-
"default help must match the exact
|
|
2091
|
+
"default help must match the exact Human29 public spec set, not a slack threshold; got {visible:?}"
|
|
2361
2092
|
);
|
|
2362
2093
|
assert!(
|
|
2363
2094
|
top_help.contains("copilot"),
|
|
@@ -2392,6 +2123,32 @@ mod tests {
|
|
|
2392
2123
|
}
|
|
2393
2124
|
}
|
|
2394
2125
|
|
|
2126
|
+
#[test]
|
|
2127
|
+
fn retired_commands_have_no_catalog_machine_or_help_gate() {
|
|
2128
|
+
for command in [
|
|
2129
|
+
"e2e",
|
|
2130
|
+
"allow-peer-talk",
|
|
2131
|
+
"results",
|
|
2132
|
+
"validate",
|
|
2133
|
+
"identity",
|
|
2134
|
+
"sessions",
|
|
2135
|
+
"watch",
|
|
2136
|
+
"peek",
|
|
2137
|
+
"wait-ready",
|
|
2138
|
+
"preflight",
|
|
2139
|
+
] {
|
|
2140
|
+
assert!(
|
|
2141
|
+
command_spec(command).is_none(),
|
|
2142
|
+
"retired catalog entry: {command}"
|
|
2143
|
+
);
|
|
2144
|
+
assert!(
|
|
2145
|
+
!is_machine_command(command),
|
|
2146
|
+
"retired Machine route: {command}"
|
|
2147
|
+
);
|
|
2148
|
+
assert!(!is_known_subcommand(command), "retired help gate: {command}");
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2395
2152
|
#[test]
|
|
2396
2153
|
fn leaders_help_publishes_selectors_and_prune() {
|
|
2397
2154
|
let help = command_help(Some("leaders"));
|
|
@@ -2405,7 +2162,6 @@ mod tests {
|
|
|
2405
2162
|
#[test]
|
|
2406
2163
|
fn observation_a_commands_have_terminal_tiers() {
|
|
2407
2164
|
for (command, tier) in [
|
|
2408
|
-
("allow-peer-talk", CommandTier::Core),
|
|
2409
2165
|
("approvals", CommandTier::Core),
|
|
2410
2166
|
("profile", CommandTier::Core),
|
|
2411
2167
|
("install-skill", CommandTier::Core),
|
|
@@ -2547,7 +2303,7 @@ mod tests {
|
|
|
2547
2303
|
}
|
|
2548
2304
|
}
|
|
2549
2305
|
let cwd = tmp_workspace();
|
|
2550
|
-
for command in ["
|
|
2306
|
+
for command in ["wait", "attach-app-server-leader", "coordinator"] {
|
|
2551
2307
|
assert_eq!(
|
|
2552
2308
|
run(&cli_argv(&[command, "--help"]), &cwd),
|
|
2553
2309
|
ExitCode::Usage,
|
|
@@ -2561,13 +2317,42 @@ mod tests {
|
|
|
2561
2317
|
);
|
|
2562
2318
|
}
|
|
2563
2319
|
|
|
2320
|
+
#[test]
|
|
2321
|
+
fn public_help_templates_and_cli_guidance_are_english() {
|
|
2322
|
+
fn assert_english(text: &str) {
|
|
2323
|
+
assert!(
|
|
2324
|
+
!text.chars().any(|ch| matches!(ch, '\u{3400}'..='\u{9fff}')),
|
|
2325
|
+
"unauthorized CJK CLI guidance: {text}"
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
assert_english(&default_help());
|
|
2329
|
+
assert_eq!(TEAM_TEMPLATE.lines().nth(1), Some("name: current"));
|
|
2330
|
+
for spec in COMMAND_SPECS {
|
|
2331
|
+
assert_english(spec.summary);
|
|
2332
|
+
assert_english(&command_help(Some(spec.name)));
|
|
2333
|
+
}
|
|
2334
|
+
for text in [
|
|
2335
|
+
TEAM_TEMPLATE, WORKER_TEMPLATE, HUMAN_NAVIGATION,
|
|
2336
|
+
crate::cli::route::HELP, crate::cli::COMMS_BOUNDARY_TEXT,
|
|
2337
|
+
crate::cli::QUICK_START_REMINDER, crate::cli::SEND_REMINDER,
|
|
2338
|
+
crate::cli::STATUS_REMINDER,
|
|
2339
|
+
] {
|
|
2340
|
+
assert_english(text);
|
|
2341
|
+
}
|
|
2342
|
+
for command in ["quick-start", "restart", "send"] {
|
|
2343
|
+
let payload = CliError::Runtime("fixture failure".to_string())
|
|
2344
|
+
.to_payload(Path::new("/tmp/fixture.log"), command);
|
|
2345
|
+
assert_english(&payload.action);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2564
2349
|
#[test]
|
|
2565
2350
|
fn worker_lifecycle_help_uses_authoritative_spec_usage_and_summary() {
|
|
2566
2351
|
for name in ["start-agent", "add-agent"] {
|
|
2567
2352
|
let spec = command_spec(name).expect("registered worker lifecycle command");
|
|
2568
2353
|
let help = command_help(Some(name));
|
|
2569
2354
|
assert!(help.contains(spec.usage));
|
|
2570
|
-
assert!(help.contains("
|
|
2355
|
+
assert!(help.contains("Purpose:"));
|
|
2571
2356
|
for flag in [
|
|
2572
2357
|
"--model MODEL",
|
|
2573
2358
|
"--effort LEVEL",
|
|
@@ -2578,13 +2363,13 @@ mod tests {
|
|
|
2578
2363
|
] {
|
|
2579
2364
|
assert!(help.contains(flag), "{name} help missing {flag}: {help}");
|
|
2580
2365
|
}
|
|
2581
|
-
assert!(help.contains("
|
|
2366
|
+
assert!(help.contains("Next Action") && help.contains("send"));
|
|
2582
2367
|
}
|
|
2583
2368
|
assert!(!command_help(Some("start-agent")).contains("--force"));
|
|
2584
2369
|
let add = command_help(Some("add-agent"));
|
|
2585
2370
|
assert!(add.contains("[--role-file FILE]"));
|
|
2586
2371
|
assert!(add.contains("provider") && add.contains("bypass") && add.contains("--role-file"));
|
|
2587
|
-
assert!(add.contains("
|
|
2372
|
+
assert!(add.contains("Conflicting") && add.contains("refused"));
|
|
2588
2373
|
}
|
|
2589
2374
|
|
|
2590
2375
|
#[test]
|
|
@@ -2592,17 +2377,17 @@ mod tests {
|
|
|
2592
2377
|
let help = command_help(Some("status"));
|
|
2593
2378
|
for marker in [
|
|
2594
2379
|
"name/provider/model/effort/runtime_status/activity/health/session_name/tmux_command",
|
|
2595
|
-
"--json
|
|
2596
|
-
"unknown
|
|
2380
|
+
"Use --json for structured output",
|
|
2381
|
+
"unknown means insufficient evidence",
|
|
2597
2382
|
"tmux_command",
|
|
2598
|
-
"
|
|
2383
|
+
"do not add fields",
|
|
2599
2384
|
] {
|
|
2600
2385
|
assert!(
|
|
2601
2386
|
help.contains(marker),
|
|
2602
2387
|
"status help missing {marker}: {help}"
|
|
2603
2388
|
);
|
|
2604
2389
|
}
|
|
2605
|
-
assert!(!help.contains("
|
|
2390
|
+
assert!(!help.contains("error breakdown via status --summary"));
|
|
2606
2391
|
}
|
|
2607
2392
|
|
|
2608
2393
|
#[test]
|
|
@@ -2741,14 +2526,14 @@ mod tests {
|
|
|
2741
2526
|
.unwrap();
|
|
2742
2527
|
let err = cmd_send(&args).unwrap_err();
|
|
2743
2528
|
assert!(
|
|
2744
|
-
matches!(err, CliError::Usage(ref message) if message.contains("--pane") && message.contains("--to") && message.contains("
|
|
2529
|
+
matches!(err, CliError::Usage(ref message) if message.contains("--pane") && message.contains("--to") && message.contains("cannot") && message.contains("combined")),
|
|
2745
2530
|
"expected --pane/--to mutual-exclusion usage error, got {err:?}"
|
|
2746
2531
|
);
|
|
2747
2532
|
|
|
2748
2533
|
let args = send_args(&cli_argv(&["--pane", "%1596"]), &cwd).unwrap();
|
|
2749
2534
|
let err = cmd_send(&args).unwrap_err();
|
|
2750
2535
|
assert!(
|
|
2751
|
-
matches!(err, CliError::Usage(ref message) if message.contains("--pane") && message.contains("
|
|
2536
|
+
matches!(err, CliError::Usage(ref message) if message.contains("--pane") && message.contains("cannot send an empty message")),
|
|
2752
2537
|
"expected empty-message usage error, got {err:?}"
|
|
2753
2538
|
);
|
|
2754
2539
|
|