mirror of
https://github.com/multica-ai/multica.git
synced 2026-06-29 18:39:17 +02:00
Compare commits
1 Commits
agent/lamb
...
agent/matt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e8c5b8f24 |
@@ -88,7 +88,7 @@ CI나 headless 환경에서는 브라우저 플로우를 건너뛰세요. 웹
|
||||
| `multica squad create --name "..." --leader <agent>` | 스쿼드 생성(owner / admin) |
|
||||
| `multica squad update <id> ...` | 이름, 설명, 지침, 리더, 또는 아바타 업데이트 |
|
||||
| `multica squad delete <id>` | 보관(소프트 삭제) — 할당된 이슈를 리더에게 이관 |
|
||||
| `multica squad member list/add/remove <squad-id>` | 스쿼드 멤버 관리 |
|
||||
| `multica squad member list/add/remove/set-role <squad-id>` | 스쿼드 멤버 관리 및 역할 직접 업데이트 |
|
||||
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | 스쿼드 리더 에이전트가 매 턴마다 평가를 기록할 때 사용 |
|
||||
|
||||
전체 모델은 [스쿼드](/squads)를 참고하세요.
|
||||
|
||||
@@ -88,7 +88,7 @@ For the difference between token types, see [Authentication and tokens](/auth-to
|
||||
| `multica squad create --name "..." --leader <agent>` | Create a squad (owner / admin) |
|
||||
| `multica squad update <id> ...` | Update name, description, instructions, leader, or avatar |
|
||||
| `multica squad delete <id>` | Archive (soft-delete) — transfers assigned issues to the leader |
|
||||
| `multica squad member list/add/remove <squad-id>` | Manage squad members |
|
||||
| `multica squad member list/add/remove/set-role <squad-id>` | Manage squad members and update roles in place |
|
||||
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | Used by squad leader agents to record an evaluation per turn |
|
||||
|
||||
See [Squads](/squads) for the full model.
|
||||
|
||||
@@ -88,7 +88,7 @@ Token 类型的详细区分见 [认证与令牌](/auth-tokens)。
|
||||
| `multica squad create --name "..." --leader <agent>` | 创建小队(owner / admin)|
|
||||
| `multica squad update <id> ...` | 修改名字、描述、instructions、队长、头像 |
|
||||
| `multica squad delete <id>` | 归档(软删除)—— 同时把分配给小队的 issue 转给队长 |
|
||||
| `multica squad member list/add/remove <squad-id>` | 管理小队成员 |
|
||||
| `multica squad member list/add/remove/set-role <squad-id>` | 管理小队成员并原地更新 role |
|
||||
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | 队长智能体每轮结束时调用,记录 evaluation |
|
||||
|
||||
完整模型见 [小队](/squads)。
|
||||
|
||||
@@ -123,6 +123,7 @@ multica squad member add <squad-id> --member-id <agent-or-user-uuid> --type agen
|
||||
| `multica squad delete <id>` | 보관(소프트 삭제) — 할당된 이슈를 리더에게 이전 |
|
||||
| `multica squad member list <id>` | 스쿼드의 멤버 목록 표시 |
|
||||
| `multica squad member add <id> --member-id <uuid> --type agent\|member [--role "..."]` | 멤버 추가(owner / admin) |
|
||||
| `multica squad member set-role <id> --member-id <uuid> --member-type agent\|member --role "..."` | 멤버를 제거하지 않고 역할 변경 |
|
||||
| `multica squad member remove <id> --member-id <uuid> --type agent\|member` | 멤버 제거(리더는 제거할 수 없습니다 — 먼저 리더를 변경하세요) |
|
||||
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | 리더 에이전트가 매 턴 종료 시 기록 |
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ There is currently no unarchive command; create a new squad if you need the rout
|
||||
| `multica squad delete <id>` | Archive (soft-delete) — transfers assigned issues to the leader |
|
||||
| `multica squad member list <id>` | List a squad's members |
|
||||
| `multica squad member add <id> --member-id <uuid> --type agent\|member [--role "..."]` | Add a member (owner / admin) |
|
||||
| `multica squad member set-role <id> --member-id <uuid> --member-type agent\|member --role "..."` | Change a member's role without removing it |
|
||||
| `multica squad member remove <id> --member-id <uuid> --type agent\|member` | Remove a member (the leader cannot be removed — change leader first) |
|
||||
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | Recorded by the leader agent at the end of every turn |
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ multica squad member add <squad-id> --member-id <agent-or-user-uuid> --type agen
|
||||
| `multica squad delete <id>` | 归档(软删除)——同时把当前分配给小队的 issue 转给队长 |
|
||||
| `multica squad member list <id>` | 列出小队成员 |
|
||||
| `multica squad member add <id> --member-id <uuid> --type agent\|member [--role "..."]` | 加成员(owner / admin)|
|
||||
| `multica squad member set-role <id> --member-id <uuid> --member-type agent\|member --role "..."` | 不移除成员,直接修改 role |
|
||||
| `multica squad member remove <id> --member-id <uuid> --type agent\|member` | 移除成员(**不能移除队长**——先换队长)|
|
||||
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | 队长每次结束前由它自己调用 |
|
||||
|
||||
|
||||
@@ -344,6 +344,56 @@ func runSquadMemberAdd(cmd *cobra.Command, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── Member Set Role ─────────────────────────────────────────────────────────
|
||||
|
||||
var squadMemberSetRoleCmd = &cobra.Command{
|
||||
Use: "set-role <squad-id>",
|
||||
Short: "Change a squad member's role",
|
||||
Args: exactArgs(1),
|
||||
RunE: runSquadMemberSetRole,
|
||||
}
|
||||
|
||||
func runSquadMemberSetRole(cmd *cobra.Command, args []string) error {
|
||||
memberID, _ := cmd.Flags().GetString("member-id")
|
||||
memberType, _ := cmd.Flags().GetString("member-type")
|
||||
role, _ := cmd.Flags().GetString("role")
|
||||
|
||||
if memberID == "" {
|
||||
return fmt.Errorf("--member-id is required")
|
||||
}
|
||||
if memberType != "agent" && memberType != "member" {
|
||||
return fmt.Errorf("--member-type must be 'agent' or 'member'")
|
||||
}
|
||||
if role == "" {
|
||||
return fmt.Errorf("--role is required")
|
||||
}
|
||||
|
||||
client, err := newAPIClient(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
body := map[string]any{
|
||||
"member_type": memberType,
|
||||
"member_id": memberID,
|
||||
"role": role,
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
if err := client.PatchJSON(ctx, "/api/squads/"+args[0]+"/members/role", body, &result); err != nil {
|
||||
return fmt.Errorf("set member role: %w", err)
|
||||
}
|
||||
|
||||
output, _ := cmd.Flags().GetString("output")
|
||||
if output == "json" {
|
||||
return cli.PrintJSON(os.Stdout, result)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Member %s role updated to %s.\n", memberID, role)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── Member Remove ───────────────────────────────────────────────────────────
|
||||
|
||||
var squadMemberRemoveCmd = &cobra.Command{
|
||||
@@ -487,6 +537,12 @@ func init() {
|
||||
squadMemberRemoveCmd.Flags().String("type", "agent", "Member type: agent or member")
|
||||
squadMemberRemoveCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
|
||||
// member set-role
|
||||
squadMemberSetRoleCmd.Flags().String("member-id", "", "Member or agent ID (required)")
|
||||
squadMemberSetRoleCmd.Flags().String("member-type", "agent", "Member type: agent or member")
|
||||
squadMemberSetRoleCmd.Flags().String("role", "", "New role in the squad (required)")
|
||||
squadMemberSetRoleCmd.Flags().String("output", "json", "Output format: table or json")
|
||||
|
||||
// activity
|
||||
squadActivityCmd.Flags().String("reason", "", "Short explanation of the decision")
|
||||
squadActivityCmd.Flags().String("output", "table", "Output format: table or json")
|
||||
@@ -494,6 +550,7 @@ func init() {
|
||||
squadMemberCmd.AddCommand(squadMemberListCmd)
|
||||
squadMemberCmd.AddCommand(squadMemberAddCmd)
|
||||
squadMemberCmd.AddCommand(squadMemberRemoveCmd)
|
||||
squadMemberCmd.AddCommand(squadMemberSetRoleCmd)
|
||||
|
||||
squadCmd.AddCommand(squadListCmd)
|
||||
squadCmd.AddCommand(squadGetCmd)
|
||||
|
||||
107
server/cmd/multica/cmd_squad_test.go
Normal file
107
server/cmd/multica/cmd_squad_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newSquadMemberSetRoleTestCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{Use: "set-role"}
|
||||
cmd.Flags().String("server-url", "", "")
|
||||
cmd.Flags().String("workspace-id", "", "")
|
||||
cmd.Flags().String("profile", "", "")
|
||||
cmd.Flags().String("member-id", "", "")
|
||||
cmd.Flags().String("member-type", "agent", "")
|
||||
cmd.Flags().String("role", "", "")
|
||||
cmd.Flags().String("output", "json", "")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func TestSquadMemberSetRoleCommandIsRegistered(t *testing.T) {
|
||||
cmd, _, err := squadMemberCmd.Find([]string{"set-role", "squad-123"})
|
||||
if err != nil {
|
||||
t.Fatalf("find set-role command: %v", err)
|
||||
}
|
||||
if cmd == nil || cmd.Name() != "set-role" {
|
||||
t.Fatalf("set-role command not registered; got %#v", cmd)
|
||||
}
|
||||
for _, flag := range []string{"member-id", "member-type", "role", "output"} {
|
||||
if cmd.Flags().Lookup(flag) == nil {
|
||||
t.Fatalf("set-role command missing --%s flag", flag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSquadMemberSetRolePatchesRole(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
t.Setenv("MULTICA_TOKEN", "test-token")
|
||||
t.Setenv("MULTICA_WORKSPACE_ID", "workspace-123")
|
||||
|
||||
var gotMethod, gotPath string
|
||||
var gotBody map[string]any
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotMethod = r.Method
|
||||
gotPath = r.URL.Path
|
||||
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
|
||||
t.Fatalf("decode request body: %v", err)
|
||||
}
|
||||
if r.Header.Get("X-Workspace-ID") != "workspace-123" {
|
||||
t.Fatalf("X-Workspace-ID = %q, want workspace-123", r.Header.Get("X-Workspace-ID"))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"squad_id": "squad-123",
|
||||
"member_id": "member-456",
|
||||
"member_type": "agent",
|
||||
"role": "reviewer",
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
t.Setenv("MULTICA_SERVER_URL", srv.URL)
|
||||
|
||||
cmd := newSquadMemberSetRoleTestCmd()
|
||||
_ = cmd.Flags().Set("member-id", "member-456")
|
||||
_ = cmd.Flags().Set("member-type", "agent")
|
||||
_ = cmd.Flags().Set("role", "reviewer")
|
||||
_ = cmd.Flags().Set("output", "json")
|
||||
|
||||
if err := runSquadMemberSetRole(cmd, []string{"squad-123"}); err != nil {
|
||||
t.Fatalf("runSquadMemberSetRole: %v", err)
|
||||
}
|
||||
if gotMethod != http.MethodPatch {
|
||||
t.Fatalf("method = %s, want PATCH", gotMethod)
|
||||
}
|
||||
if gotPath != "/api/squads/squad-123/members/role" {
|
||||
t.Fatalf("path = %q, want /api/squads/squad-123/members/role", gotPath)
|
||||
}
|
||||
wantBody := map[string]any{"member_id": "member-456", "member_type": "agent", "role": "reviewer"}
|
||||
for k, want := range wantBody {
|
||||
if gotBody[k] != want {
|
||||
t.Fatalf("body[%s] = %v, want %v (full body: %#v)", k, gotBody[k], want, gotBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSquadMemberSetRoleValidatesRequiredFlags(t *testing.T) {
|
||||
cmd := newSquadMemberSetRoleTestCmd()
|
||||
if err := runSquadMemberSetRole(cmd, []string{"squad-123"}); err == nil {
|
||||
t.Fatal("expected missing --member-id error")
|
||||
}
|
||||
|
||||
cmd = newSquadMemberSetRoleTestCmd()
|
||||
_ = cmd.Flags().Set("member-id", "member-456")
|
||||
_ = cmd.Flags().Set("member-type", "invalid")
|
||||
if err := runSquadMemberSetRole(cmd, []string{"squad-123"}); err == nil {
|
||||
t.Fatal("expected invalid --member-type error")
|
||||
}
|
||||
|
||||
cmd = newSquadMemberSetRoleTestCmd()
|
||||
_ = cmd.Flags().Set("member-id", "member-456")
|
||||
if err := runSquadMemberSetRole(cmd, []string{"squad-123"}); err == nil {
|
||||
t.Fatal("expected missing --role error")
|
||||
}
|
||||
}
|
||||
@@ -613,6 +613,7 @@ func TestInjectRuntimeConfigAvailableCommandsCoreOnly(t *testing.T) {
|
||||
"multica issue status <id> <status>",
|
||||
"multica issue comment add <issue-id>",
|
||||
"multica issue comment add --help",
|
||||
"multica squad member set-role <squad-id>",
|
||||
} {
|
||||
if !strings.Contains(s, want) {
|
||||
t.Errorf("AGENTS.md missing core command/help text %q\n---\n%s", want, s)
|
||||
|
||||
@@ -452,6 +452,8 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
|
||||
b.WriteString("- `multica issue metadata list <issue-id> [--output json]` — List every metadata key pinned to an issue. Empty `{}` is normal.\n")
|
||||
b.WriteString("- `multica issue metadata set <issue-id> --key <k> --value <v> [--type string|number|bool]` — Pin (or overwrite) a single metadata key. The CLI auto-infers JSON primitives, so URLs and plain text are stored as strings — pass `--type number` or `--type bool` only when the semantic type matters.\n")
|
||||
b.WriteString("- `multica issue metadata delete <issue-id> --key <k>` — Remove a metadata key.\n\n")
|
||||
b.WriteString("### Squad maintenance\n")
|
||||
b.WriteString("- `multica squad member set-role <squad-id> --member-id <id> --member-type <agent|member> --role <role> [--output json]` — Change a squad member role in place; use this instead of remove+add when only the role changes.\n\n")
|
||||
|
||||
if provider == "codex" {
|
||||
b.WriteString("## Codex-Specific Comment Formatting\n\n")
|
||||
|
||||
Reference in New Issue
Block a user