Compare commits

...

2 Commits

Author SHA1 Message Date
J
35271ee4ba fix: correct source-map note on agent-template usage + guard --from-template
Review of #3805 (MUL-3070) flagged a factual error in the source-map note:
it claimed onboarding uses the agent-template backend. It does not.
`packages/views/onboarding/steps/step-agent.tsx` builds four hardcoded
local presets (i18n-resolved) and creates via plain `POST /api/agents`
(`createAgent`), never `POST /api/agents/from-template`. The whole
agent-template stack (registry, handler, routes, `packages/core` client +
query wrappers) is orphaned — the removed CLI flag was its only non-test
caller. Rewrite the note to say so.

Also add a regression test asserting `agent create` exposes no
`--from-template` flag, so it can't be silently re-added.

MUL-3070

Co-authored-by: multica-agent <github@multica.ai>
2026-06-05 14:02:08 +08:00
J
6454f17b76 chore(cli): remove the --from-template flag from agent create
The `--from-template` CLI flag was an untaught, immature surface (the
built-in skill's source-map explicitly marked the template path "out of
scope"). It also silently ignored sibling create flags (--custom-env,
--mcp-config, etc.) by short-circuiting before body assembly. Remove the
flag and its runAgentCreateFromTemplate handler from the CLI.

Scope is CLI-only. The agent-template product feature stays intact:
- registry server/internal/agenttmpl/ (embedded curated templates)
- handler server/internal/handler/agent_template.go
- routes GET /api/agent-templates, GET /api/agent-templates/{slug},
  POST /api/agents/from-template
- the onboarding "create from template" flow (packages/views/onboarding)

The onboarding flow calls the API directly and does not depend on the
CLI flag, so removing the flag does not affect it.

Updates the multica-creating-agents source map accordingly.

MUL-3070

Co-authored-by: multica-agent <github@multica.ai>
2026-06-05 13:45:18 +08:00
3 changed files with 35 additions and 79 deletions

View File

@@ -160,12 +160,6 @@ func init() {
agentCreateCmd.Flags().String("description", "", "Agent description")
agentCreateCmd.Flags().String("instructions", "", "Agent instructions")
agentCreateCmd.Flags().String("runtime-id", "", "Runtime ID (required)")
// --from-template seeds the new agent from a curated template: imports the
// template's skills into the workspace (find-or-create by name) and applies
// the template's instructions. When set, --description/--instructions/
// --custom-args/--custom-env/--runtime-config are ignored (the template
// provides all the agent shape); --name and --runtime-id are still required.
agentCreateCmd.Flags().String("from-template", "", "Template slug to seed the agent from (e.g. code-reviewer). Lists are available via GET /api/agent-templates.")
agentCreateCmd.Flags().String("runtime-config", "", "Runtime config as JSON string")
agentCreateCmd.Flags().String("model", "", "Model identifier (e.g. claude-sonnet-4-6, openai/gpt-4o). Prefer this over passing --model in --custom-args.")
agentCreateCmd.Flags().String("custom-args", "", "Custom CLI arguments as JSON array. For model selection prefer --model; some providers (codex app-server, openclaw) reject --model in custom_args.")
@@ -432,14 +426,6 @@ func runAgentCreate(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("--runtime-id is required")
}
// --from-template short-circuits to the dedicated endpoint, which
// fetches the template's skill URLs in parallel and creates the agent
// + skill rows atomically. Skip the manual-create body building and
// post the small template payload instead.
if templateSlug, _ := cmd.Flags().GetString("from-template"); templateSlug != "" {
return runAgentCreateFromTemplate(cmd, client, name, runtimeID, templateSlug)
}
body := map[string]any{
"name": name,
"runtime_id": runtimeID,
@@ -506,55 +492,6 @@ func runAgentCreate(cmd *cobra.Command, _ []string) error {
return nil
}
// runAgentCreateFromTemplate posts to POST /api/agents/from-template. The
// server fetches every referenced skill in parallel and writes everything in
// a single transaction; a 422 here means at least one upstream URL was
// unreachable, in which case the body carries the failing URLs so we can
// surface them verbatim to the operator instead of a generic error.
func runAgentCreateFromTemplate(cmd *cobra.Command, client *cli.APIClient, name, runtimeID, slug string) error {
body := map[string]any{
"template_slug": slug,
"name": name,
"runtime_id": runtimeID,
}
if cmd.Flags().Changed("model") {
v, _ := cmd.Flags().GetString("model")
body["model"] = v
}
if cmd.Flags().Changed("visibility") {
v, _ := cmd.Flags().GetString("visibility")
body["visibility"] = v
}
if cmd.Flags().Changed("max-concurrent-tasks") {
v, _ := cmd.Flags().GetInt32("max-concurrent-tasks")
body["max_concurrent_tasks"] = v
}
// 60s ceiling: templates fan out N HTTP fetches to GitHub, each ~200-500ms.
// Matches the timeout used by `multica skill import` (cmd_skill.go).
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
var result map[string]any
if err := client.PostJSON(ctx, "/api/agents/from-template", body, &result); err != nil {
return fmt.Errorf("create agent from template: %w", err)
}
output, _ := cmd.Flags().GetString("output")
if output == "json" {
return cli.PrintJSON(os.Stdout, result)
}
agent, _ := result["agent"].(map[string]any)
imported, _ := result["imported_skill_ids"].([]any)
reused, _ := result["reused_skill_ids"].([]any)
fmt.Printf("Agent created from template %q: %s (%s)\n", slug, strVal(agent, "name"), strVal(agent, "id"))
if len(imported) > 0 || len(reused) > 0 {
fmt.Printf(" Skills: %d imported, %d reused\n", len(imported), len(reused))
}
return nil
}
func runAgentUpdate(cmd *cobra.Command, args []string) error {
client, err := newAPIClient(cmd)
if err != nil {

View File

@@ -238,6 +238,18 @@ func TestAgentUpdateDoesNotExposeCustomEnvFlags(t *testing.T) {
}
}
// TestAgentCreateDoesNotExposeFromTemplate guards against re-adding the
// `--from-template` flag. It was an untaught, immature CLI surface that
// short-circuited before body assembly — silently dropping sibling create
// flags like --mcp-config / --custom-env — and was removed. The agent-template
// backend API still exists but has no CLI surface; manual `agent create` is the
// only supported CLI creation path.
func TestAgentCreateDoesNotExposeFromTemplate(t *testing.T) {
if agentCreateCmd.Flag("from-template") != nil {
t.Error("agent create must NOT expose --from-template; it was removed as an untaught CLI surface that silently dropped sibling flags")
}
}
// TestParseCustomEnvErrorSanitization guards against future changes
// re-introducing %w wrapping of json.Unmarshal errors. Those errors
// can surface short fragments of the input, which — for a flag that

View File

@@ -19,23 +19,30 @@ go test ./internal/service -run TestBuiltinSkillsConformToTemplate
| Contract | Line | Behavior | Safe check |
|---|---|---|---|
| Create flags: `name`, `description`, `instructions`, `runtime-id` | 159162 | Registered create flags; `name`/`runtime-id` enforced in `runAgentCreate` | `multica agent create --help` |
| `runtime-config`, `model`, `custom-args` flags | 169171 | `model` help: "Prefer this over passing --model in --custom-args"; `custom-args` help names codex/openclaw rejecting `--model` (CLI help only, not server-enforced) | `multica agent create --help` |
| Secret-safe env input: `custom-env`, `custom-env-stdin`, `custom-env-file` | 172174 | `--custom-env` warns about shell history / `ps`; stdin and file modes keep secrets off the command line; mutually exclusive | `multica agent create --help` |
| Secret-safe MCP input: `mcp-config`, `mcp-config-stdin`, `mcp-config-file` (create) | 175177 | Same three-channel pattern as `custom-env`; `--mcp-config` warns about shell history / `ps`; value must be a JSON object or `null` | `multica agent create --help` |
| MCP flags on `agent update` | 198200 | Same three channels on update; `--mcp-config null` clears. Unlike `custom_env`, `mcp_config` IS settable via update | `multica agent update --help` |
| `runAgentCreate` builds body + `POST /api/agents` | 420 | Only sets a body key when the flag `Changed`; posts to `/api/agents` (line 496) | read 420501 |
| Body assembly: description/instructions/runtime-config/custom-args/custom-env/mcp-config/model | 443487 | `resolveCustomEnv` (469) and `resolveMcpConfig` (474) gate their secret channels; omitted flags are not sent | read 443487 |
| `runAgentUpdate` sends `mcp_config` | 613 | `resolveMcpConfig` adds `mcp_config` to the `PUT /api/agents/{id}` body (627); `custom_env` is intentionally not a flag here | read 558633 |
| `parseMcpConfig` / `resolveMcpConfig` helpers | 1129, 1157 | Validator (object-or-`null`, content-free errors) + three-channel resolver, mirroring `parseCustomEnv`/`resolveCustomEnv` | read 11291215 |
| `agent skills set` = replace-all | 835 | `PUT /api/agents/{id}/skills` (853); `--skill-ids ''` clears all (842) | `multica agent skills set --help` |
| `agent skills add` = additive | 860 | `POST /api/agents/{id}/skills/add` (881); requires ≥1 id (867, 871) | `multica agent skills add --help` |
| `agent skills list` | 803 | reads bindings, no side effect | `multica agent skills list --help` |
| `agent env get` | 937 | `GET /api/agents/{id}/env` | `multica agent env get --help` |
| `agent env set` | 972 | `PUT /api/agents/{id}/env` with full `custom_env` map (986, 992) | `multica agent env set --help` |
| `runtime-config`, `model`, `custom-args` flags | 163165 | `model` help: "Prefer this over passing --model in --custom-args"; `custom-args` help names codex/openclaw rejecting `--model` (CLI help only, not server-enforced) | `multica agent create --help` |
| Secret-safe env input: `custom-env`, `custom-env-stdin`, `custom-env-file` | 166168 | `--custom-env` warns about shell history / `ps`; stdin and file modes keep secrets off the command line; mutually exclusive | `multica agent create --help` |
| Secret-safe MCP input: `mcp-config`, `mcp-config-stdin`, `mcp-config-file` (create) | 169171 | Same three-channel pattern as `custom-env`; `--mcp-config` warns about shell history / `ps`; value must be a JSON object or `null` | `multica agent create --help` |
| MCP flags on `agent update` | 192194 | Same three channels on update; `--mcp-config null` clears. Unlike `custom_env`, `mcp_config` IS settable via update | `multica agent update --help` |
| `runAgentCreate` builds body + `POST /api/agents` | 414 | Only sets a body key when the flag `Changed`; posts to `/api/agents` (line 482) | read 414489 |
| Body assembly: description/instructions/runtime-config/custom-args/custom-env/mcp-config/model | 437478 | `resolveCustomEnv` (455) and `resolveMcpConfig` (460) gate their secret channels; omitted flags are not sent | read 437478 |
| `runAgentUpdate` sends `mcp_config` | 550 | `resolveMcpConfig` adds `mcp_config` to the `PUT /api/agents/{id}` body (564); `custom_env` is intentionally not a flag here | read 495565 |
| `parseMcpConfig` / `resolveMcpConfig` helpers | 1066, 1094 | Validator (object-or-`null`, content-free errors) + three-channel resolver, mirroring `parseCustomEnv`/`resolveCustomEnv` | read 10661150 |
| `agent skills set` = replace-all | 772 | `PUT /api/agents/{id}/skills` (790); `--skill-ids ''` clears all (779) | `multica agent skills set --help` |
| `agent skills add` = additive | 797 | `POST /api/agents/{id}/skills/add` (818); requires ≥1 id (804, 808) | `multica agent skills add --help` |
| `agent skills list` | 740 | reads bindings, no side effect | `multica agent skills list --help` |
| `agent env get` | 874 | `GET /api/agents/{id}/env` | `multica agent env get --help` |
| `agent env set` | 909 | `PUT /api/agents/{id}/env` with full `custom_env` map (923, 929) | `multica agent env set --help` |
Note: `--from-template` exists at line 168 and short-circuits to
`runAgentCreateFromTemplate` (line 514). It is intentionally NOT taught — the
template path is immature and out of scope for this skill.
Note: the CLI no longer exposes `--from-template`. The agent-template backend
still exists (registry `server/internal/agenttmpl/`, handler `agent_template.go`,
routes `GET /api/agent-templates` and `POST /api/agents/from-template`, plus the
`packages/core` client/query wrappers) but is currently orphaned plumbing with no
live caller: the removed CLI flag was its only non-test consumer, and onboarding
does NOT use it — `packages/views/onboarding/steps/step-agent.tsx` builds four
hardcoded local presets (i18n-resolved) and creates via plain `POST /api/agents`
(`createAgent`), never `POST /api/agents/from-template`. Do not treat the template
API as a supported agent-creation path. This skill teaches manual `agent create`
only.
## Create handler — `server/internal/handler/agent.go`