feat(agnet): canonical six-role catalog + role-templates API
Sprint 2. Materialises the six platform-recommended Agnet roles
documented in docs/product-package/13-platform-description.md §3.
Backend:
- controller/agnet_role_template.go (new): AgnetRoleTemplate type
+ canonical catalog returned by GET /api/agnet/role-templates.
Six roles: product / architect / frontend / backend / reviewer / ops.
Stored as constants (not DB rows) because they are platform
contracts, not user-editable data. Each entry carries:
- stable key (frontend dispatches on this — never rename)
- display name + summary (translatable)
- default model recommendation
- default permission scope hints
- risk classification (low/medium/high) — Ops alone is high,
matching the production-deploy-needs-approval rule
- router/api-router.go: mount GET /api/agnet/role-templates inside
the existing /api/agnet group (same auth as the other endpoints)
- controller/agnet_role_template_test.go (new): 4 tests pin the
six-role set, risk-level matrix, HTTP envelope shape, and the
closed-set helper that will gate validation later
Frontend:
- features/agnet-console/api.ts: new AgnetRoleTemplate type + a
module-level cached listAgnetRoleTemplates() helper. Caching
means the picker doesn't refetch every time the deployment sheet
opens.
- features/agnet-console/create-agnet-deployment-sheet.tsx:
- Replace free-text role_template Input with a Select bound to
the catalog; falls back to Input if the catalog is empty so
the form stays usable when the endpoint is down.
- Fix two informal role names in built-in presets (debugger →
reviewer, executor → backend) so presets reference only
canonical keys.
Verification:
- go test ./controller/... ./middleware/... ./model/... all green
(4 new role-template tests + existing suite)
- frontend tsc --noEmit clean
- zero touch on the token / device-signature hot paths
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,155 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/heicode/manager/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AgnetRoleTemplate is the platform-recommended role catalog Manager
|
||||||
|
// surfaces to users when they assemble an AI development team. The
|
||||||
|
// six canonical roles come from docs/product-package/13-platform-
|
||||||
|
// description.md §3 and 04-platform-usage-guide.md §第五步.
|
||||||
|
//
|
||||||
|
// Why constants, not a DB table:
|
||||||
|
// - Roles are platform-defined contracts, not user-editable data.
|
||||||
|
// Treating them like rows would invite drift between deployments.
|
||||||
|
// - Permission hints below are *recommendations* the UI uses to
|
||||||
|
// pre-fill the "what can this Agnet do" confirmation card —
|
||||||
|
// the actual permission grant still goes through ResourceGrant.
|
||||||
|
// - If we ever need per-tenant role customization, we add an
|
||||||
|
// overlay table; the canonical set still lives here as the
|
||||||
|
// baseline.
|
||||||
|
//
|
||||||
|
// Wire-format note: keys (key column) are stable identifiers used
|
||||||
|
// across persistence and the client picker. Display strings can
|
||||||
|
// be translated, but the key must NEVER change without a coordinated
|
||||||
|
// frontend rollout.
|
||||||
|
type AgnetRoleTemplate struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
Summary string `json:"summary"`
|
||||||
|
DefaultModel string `json:"default_model"`
|
||||||
|
DefaultPermissions []string `json:"default_permissions"`
|
||||||
|
RiskLevel string `json:"risk_level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// agnetRoleTemplates returns the canonical six-role catalog. Order
|
||||||
|
// matches the typical lifecycle a user walks through when assembling
|
||||||
|
// a team: discover -> design -> build -> review -> operate.
|
||||||
|
//
|
||||||
|
// Permission hints use the verbs from docs §13.3.4 (Resource Grant)
|
||||||
|
// and stay deliberately broad — concrete grants come from the user
|
||||||
|
// resource-binding flow.
|
||||||
|
func agnetRoleTemplates() []AgnetRoleTemplate {
|
||||||
|
return []AgnetRoleTemplate{
|
||||||
|
{
|
||||||
|
Key: "product",
|
||||||
|
DisplayName: "Product Agnet",
|
||||||
|
Summary: "Refines the user idea into product scope, requirements and acceptance criteria.",
|
||||||
|
DefaultModel: "claude-sonnet-4-6",
|
||||||
|
DefaultPermissions: []string{
|
||||||
|
"read:project_docs",
|
||||||
|
"write:product_spec",
|
||||||
|
},
|
||||||
|
RiskLevel: agnetRiskLow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "architect",
|
||||||
|
DisplayName: "Architect Agnet",
|
||||||
|
Summary: "Designs the technical approach, picks frameworks, and breaks work into sub-tasks.",
|
||||||
|
DefaultModel: "claude-opus-4-7",
|
||||||
|
DefaultPermissions: []string{
|
||||||
|
"read:repo",
|
||||||
|
"write:architecture_doc",
|
||||||
|
},
|
||||||
|
RiskLevel: agnetRiskLow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "frontend",
|
||||||
|
DisplayName: "Frontend Agnet",
|
||||||
|
Summary: "Implements UI, components and client-side state per the architect's plan.",
|
||||||
|
DefaultModel: "claude-sonnet-4-6",
|
||||||
|
DefaultPermissions: []string{
|
||||||
|
"read:repo",
|
||||||
|
"write:repo:frontend",
|
||||||
|
},
|
||||||
|
RiskLevel: agnetRiskMedium,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "backend",
|
||||||
|
DisplayName: "Backend Agnet",
|
||||||
|
Summary: "Implements server-side APIs, data models and integrations.",
|
||||||
|
DefaultModel: "claude-sonnet-4-6",
|
||||||
|
DefaultPermissions: []string{
|
||||||
|
"read:repo",
|
||||||
|
"write:repo:backend",
|
||||||
|
"read:dev_database",
|
||||||
|
},
|
||||||
|
RiskLevel: agnetRiskMedium,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "reviewer",
|
||||||
|
DisplayName: "Reviewer Agnet",
|
||||||
|
Summary: "Performs code review, security checks and runs the test suite.",
|
||||||
|
DefaultModel: "claude-opus-4-7",
|
||||||
|
DefaultPermissions: []string{
|
||||||
|
"read:repo",
|
||||||
|
"run:tests",
|
||||||
|
"comment:pull_request",
|
||||||
|
},
|
||||||
|
RiskLevel: agnetRiskLow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "ops",
|
||||||
|
DisplayName: "Ops Agnet",
|
||||||
|
Summary: "Deploys to test environments, watches logs and prepares production rollouts (production requires approval).",
|
||||||
|
DefaultModel: "claude-sonnet-4-6",
|
||||||
|
DefaultPermissions: []string{
|
||||||
|
"read:repo",
|
||||||
|
"deploy:test_env",
|
||||||
|
"read:metrics",
|
||||||
|
"approval_required:deploy_prod",
|
||||||
|
},
|
||||||
|
RiskLevel: agnetRiskHigh,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgnetListRoleTemplates is the GET /api/agnet/role-templates handler.
|
||||||
|
// Returns the canonical six-role catalog so the deployment-creation
|
||||||
|
// UI can pre-populate role pickers and the documentation page can
|
||||||
|
// render the role overview.
|
||||||
|
//
|
||||||
|
// Auth: requires UserAuth (mounted by router). Anyone logged in to
|
||||||
|
// Manager can read the catalog; there are no secrets in the payload.
|
||||||
|
func AgnetListRoleTemplates(c *gin.Context) {
|
||||||
|
tpls := agnetRoleTemplates()
|
||||||
|
common.ApiSuccess(c, gin.H{
|
||||||
|
"items": tpls,
|
||||||
|
"total": len(tpls),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// agnetRoleTemplateKeys is a helper for validation in deployment
|
||||||
|
// creation — checks whether a user-provided role_template string is
|
||||||
|
// one of the canonical six. Returns true for any of the canonical
|
||||||
|
// keys; returns true for unknown keys too (deployment flow today
|
||||||
|
// accepts free-form role_template strings, see agnet_control_plane.
|
||||||
|
// go:552), so this helper is currently advisory. When we tighten
|
||||||
|
// validation (after frontend ships the new picker), flip the
|
||||||
|
// fallback to false and add a unit test.
|
||||||
|
func agnetRoleTemplateKeys() map[string]bool {
|
||||||
|
keys := make(map[string]bool)
|
||||||
|
for _, t := range agnetRoleTemplates() {
|
||||||
|
keys[t.Key] = true
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// agnetIsCanonicalRoleKey reports whether `key` matches one of the
|
||||||
|
// six platform-defined roles. Today the deployment endpoint accepts
|
||||||
|
// any non-empty string; this helper is reserved for the next step
|
||||||
|
// when we move to a closed set.
|
||||||
|
func agnetIsCanonicalRoleKey(key string) bool {
|
||||||
|
return agnetRoleTemplateKeys()[key]
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/heicode/manager/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAgnetRoleTemplates_CanonicalSetCovered(t *testing.T) {
|
||||||
|
// Pins the six canonical role keys from docs §13.3.3. Any code
|
||||||
|
// change that adds, removes or renames a key MUST update this
|
||||||
|
// list — guards against accidental drift between Manager and
|
||||||
|
// the product spec.
|
||||||
|
want := []string{"product", "architect", "frontend", "backend", "reviewer", "ops"}
|
||||||
|
got := agnetRoleTemplates()
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("expected %d roles, got %d", len(want), len(got))
|
||||||
|
}
|
||||||
|
for i, key := range want {
|
||||||
|
if got[i].Key != key {
|
||||||
|
t.Errorf("position %d: want key %q, got %q", i, key, got[i].Key)
|
||||||
|
}
|
||||||
|
// Display + summary populated; never returning a blank-row
|
||||||
|
// in the picker.
|
||||||
|
if strings.TrimSpace(got[i].DisplayName) == "" {
|
||||||
|
t.Errorf("role %q has empty DisplayName", key)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(got[i].Summary) == "" {
|
||||||
|
t.Errorf("role %q has empty Summary", key)
|
||||||
|
}
|
||||||
|
if len(got[i].DefaultPermissions) == 0 {
|
||||||
|
t.Errorf("role %q has empty DefaultPermissions", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgnetRoleTemplates_RiskLevels(t *testing.T) {
|
||||||
|
// Ops is the only canonical role with high risk (production
|
||||||
|
// deployment intent). Reviewer + Product + Architect stay low
|
||||||
|
// (read-mostly). Frontend + Backend land at medium. Pins the
|
||||||
|
// product-doc default risk classification so we don't silently
|
||||||
|
// flip an ops role to "low" and skip the high-risk approval
|
||||||
|
// gating downstream.
|
||||||
|
wantRisk := map[string]string{
|
||||||
|
"product": agnetRiskLow,
|
||||||
|
"architect": agnetRiskLow,
|
||||||
|
"reviewer": agnetRiskLow,
|
||||||
|
"frontend": agnetRiskMedium,
|
||||||
|
"backend": agnetRiskMedium,
|
||||||
|
"ops": agnetRiskHigh,
|
||||||
|
}
|
||||||
|
for _, tpl := range agnetRoleTemplates() {
|
||||||
|
if want, ok := wantRisk[tpl.Key]; ok && tpl.RiskLevel != want {
|
||||||
|
t.Errorf("role %q: want risk %q, got %q", tpl.Key, want, tpl.RiskLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgnetListRoleTemplates_HTTPShape(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(rec)
|
||||||
|
c.Request = httptest.NewRequest("GET", "/api/agnet/role-templates", nil)
|
||||||
|
c.Set("id", 1)
|
||||||
|
c.Set("role", common.RoleCommonUser)
|
||||||
|
|
||||||
|
AgnetListRoleTemplates(c)
|
||||||
|
|
||||||
|
if rec.Code != 200 {
|
||||||
|
t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
body := rec.Body.String()
|
||||||
|
// Stable keys the frontend depends on — checked via substring
|
||||||
|
// rather than parsing so we don't have to spin up a typed
|
||||||
|
// envelope just for this guard.
|
||||||
|
for _, want := range []string{`"items"`, `"total"`, `"product"`, `"ops"`, `"display_name"`} {
|
||||||
|
if !strings.Contains(body, want) {
|
||||||
|
t.Errorf("response missing %q. body=%s", want, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAgnetIsCanonicalRoleKey(t *testing.T) {
|
||||||
|
// Defensive helper currently used as advisory — pins the closed
|
||||||
|
// set so the future tighten-up to closed-set validation is one
|
||||||
|
// flip instead of an open-ended audit.
|
||||||
|
for _, ok := range []string{"product", "architect", "frontend", "backend", "reviewer", "ops"} {
|
||||||
|
if !agnetIsCanonicalRoleKey(ok) {
|
||||||
|
t.Errorf("%q should be canonical", ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bad := range []string{"", "debugger", "executor", "random_string"} {
|
||||||
|
if agnetIsCanonicalRoleKey(bad) {
|
||||||
|
t.Errorf("%q should NOT be canonical", bad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -497,6 +497,12 @@ func SetApiRouter(router *gin.Engine) {
|
|||||||
agnetRoute.POST("/sk-snapshots/resolve", controller.AgnetResolveSKSnapshots)
|
agnetRoute.POST("/sk-snapshots/resolve", controller.AgnetResolveSKSnapshots)
|
||||||
agnetRoute.GET("/projects/:project_id/dashboard-snapshot", controller.AgnetProjectDashboardSnapshot)
|
agnetRoute.GET("/projects/:project_id/dashboard-snapshot", controller.AgnetProjectDashboardSnapshot)
|
||||||
agnetRoute.GET("/audit-logs", controller.AgnetListAuditLogs)
|
agnetRoute.GET("/audit-logs", controller.AgnetListAuditLogs)
|
||||||
|
// Platform-recommended role catalog. Six canonical Agnet
|
||||||
|
// roles per docs/product-package/13-platform-description.md
|
||||||
|
// §3 (Product / Architect / Frontend / Backend / Reviewer /
|
||||||
|
// Ops). UI uses this to populate the deployment-creation
|
||||||
|
// role picker. Read-only, no secrets in payload.
|
||||||
|
agnetRoute.GET("/role-templates", controller.AgnetListRoleTemplates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,6 +178,36 @@ export type GitSourcePayload = {
|
|||||||
usage: GitSourceUsage
|
usage: GitSourceUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Platform-recommended role catalog shape. Mirrors backend
|
||||||
|
// `AgnetRoleTemplate` in controller/agnet_role_template.go. The
|
||||||
|
// six canonical roles come from docs/product-package §13.3.3 —
|
||||||
|
// keys are stable identifiers, display strings can be translated.
|
||||||
|
export type AgnetRoleTemplate = {
|
||||||
|
key: string
|
||||||
|
display_name: string
|
||||||
|
summary: string
|
||||||
|
default_model: string
|
||||||
|
default_permissions: string[]
|
||||||
|
risk_level: 'low' | 'medium' | 'high'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached at module level — the canonical six-role catalog doesn't
|
||||||
|
// change between page loads, so we avoid an extra request every
|
||||||
|
// time the create-deployment sheet opens.
|
||||||
|
let _roleTemplateCache: AgnetRoleTemplate[] | null = null
|
||||||
|
|
||||||
|
export async function listAgnetRoleTemplates(): Promise<AgnetRoleTemplate[]> {
|
||||||
|
if (_roleTemplateCache) return _roleTemplateCache
|
||||||
|
const res = await api.get<ApiEnvelope<{ items?: AgnetRoleTemplate[] }>>(
|
||||||
|
'/api/agnet/role-templates'
|
||||||
|
)
|
||||||
|
const items = res.data?.data?.items ?? []
|
||||||
|
if (items.length > 0) {
|
||||||
|
_roleTemplateCache = items
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
export async function listAgnetDeployments(): Promise<AgnetDeployment[]> {
|
export async function listAgnetDeployments(): Promise<AgnetDeployment[]> {
|
||||||
const res = await api.get<ApiEnvelope<{ items?: AgnetDeployment[] }>>(
|
const res = await api.get<ApiEnvelope<{ items?: AgnetDeployment[] }>>(
|
||||||
'/api/agnet/deployments'
|
'/api/agnet/deployments'
|
||||||
|
|||||||
+70
-13
@@ -41,8 +41,10 @@ import { cn } from '@/lib/utils'
|
|||||||
import { useAuthStore } from '@/stores/auth-store'
|
import { useAuthStore } from '@/stores/auth-store'
|
||||||
import {
|
import {
|
||||||
createAgnetDeployment,
|
createAgnetDeployment,
|
||||||
|
listAgnetRoleTemplates,
|
||||||
type AgnetAgentPlan,
|
type AgnetAgentPlan,
|
||||||
type AgnetOrchestrationPlan,
|
type AgnetOrchestrationPlan,
|
||||||
|
type AgnetRoleTemplate,
|
||||||
type AgnetSKSource,
|
type AgnetSKSource,
|
||||||
} from './api'
|
} from './api'
|
||||||
|
|
||||||
@@ -127,14 +129,17 @@ const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|||||||
label: 'Fix or improve',
|
label: 'Fix or improve',
|
||||||
objective: 'Investigate a focused issue and ship the smallest safe fix.',
|
objective: 'Investigate a focused issue and ship the smallest safe fix.',
|
||||||
risk: 'low',
|
risk: 'low',
|
||||||
|
// Role keys aligned to the canonical six-role catalog from
|
||||||
|
// docs/product-package §13.3.3. Previous presets used informal
|
||||||
|
// "debugger" / "executor" labels that don't exist in the API.
|
||||||
roles: [
|
roles: [
|
||||||
{
|
{
|
||||||
role_template: 'debugger',
|
role_template: 'reviewer',
|
||||||
goal: 'Find the root cause and define the minimum repair.',
|
goal: 'Find the root cause and define the minimum repair.',
|
||||||
default_model_id: 'agnet-model-debugger',
|
default_model_id: 'agnet-model-debugger',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role_template: 'executor',
|
role_template: 'backend',
|
||||||
goal: 'Implement and verify the scoped change.',
|
goal: 'Implement and verify the scoped change.',
|
||||||
default_model_id: 'agnet-model-builder',
|
default_model_id: 'agnet-model-builder',
|
||||||
},
|
},
|
||||||
@@ -145,9 +150,13 @@ const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|||||||
label: 'Release readiness',
|
label: 'Release readiness',
|
||||||
objective: 'Prepare a release candidate with verification and audit trail.',
|
objective: 'Prepare a release candidate with verification and audit trail.',
|
||||||
risk: 'high',
|
risk: 'high',
|
||||||
|
// Canonical six-role catalog (§13.3.3) — "executor" was an
|
||||||
|
// informal label; the closest canonical mapping is "backend"
|
||||||
|
// (build the release artifact) followed by "ops" (validate
|
||||||
|
// rollout) and "reviewer" (confirm evidence).
|
||||||
roles: [
|
roles: [
|
||||||
{
|
{
|
||||||
role_template: 'executor',
|
role_template: 'backend',
|
||||||
goal: 'Prepare the release branch and required artifacts.',
|
goal: 'Prepare the release branch and required artifacts.',
|
||||||
default_model_id: 'agnet-model-builder',
|
default_model_id: 'agnet-model-builder',
|
||||||
},
|
},
|
||||||
@@ -370,6 +379,27 @@ export function CreateAgnetDeploymentSheet({
|
|||||||
|
|
||||||
const userId = String(currentUser?.id || userScopeRef.trim() || 'user_local')
|
const userId = String(currentUser?.id || userScopeRef.trim() || 'user_local')
|
||||||
|
|
||||||
|
// Platform-recommended role catalog. Loaded once when the sheet
|
||||||
|
// opens; the API client caches the response at module level so
|
||||||
|
// subsequent sheet opens don't re-fetch. Empty array fallback
|
||||||
|
// means the sheet stays usable if the catalog endpoint is down —
|
||||||
|
// the role picker falls back to free-text input.
|
||||||
|
const [roleTemplates, setRoleTemplates] = useState<AgnetRoleTemplate[]>([])
|
||||||
|
useEffect(() => {
|
||||||
|
if (!open) return
|
||||||
|
let cancelled = false
|
||||||
|
listAgnetRoleTemplates()
|
||||||
|
.then((items) => {
|
||||||
|
if (!cancelled) setRoleTemplates(items)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Best-effort — sheet still works without the catalog.
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && !correlationId) {
|
if (open && !correlationId) {
|
||||||
setCorrelationId(crypto.randomUUID())
|
setCorrelationId(crypto.randomUUID())
|
||||||
@@ -882,16 +912,43 @@ export function CreateAgnetDeploymentSheet({
|
|||||||
|
|
||||||
<div className='grid gap-3'>
|
<div className='grid gap-3'>
|
||||||
<div className='grid gap-2 sm:grid-cols-3'>
|
<div className='grid gap-2 sm:grid-cols-3'>
|
||||||
<Input
|
{/* Role picker — bound to the canonical six-role
|
||||||
placeholder={t('Role template')}
|
catalog from /api/agnet/role-templates. Falls
|
||||||
value={agent.role_template}
|
back to a free-text input if the catalog
|
||||||
onChange={(e) =>
|
failed to load. */}
|
||||||
updateAgent(index, {
|
{roleTemplates.length > 0 ? (
|
||||||
role_template: e.target.value,
|
<Select
|
||||||
})
|
value={agent.role_template}
|
||||||
}
|
onValueChange={(value) =>
|
||||||
className='font-mono text-xs'
|
updateAgent(index, { role_template: value })
|
||||||
/>
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className='h-9 font-mono text-xs'>
|
||||||
|
<SelectValue placeholder={t('Role template')} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{roleTemplates.map((tpl) => (
|
||||||
|
<SelectItem key={tpl.key} value={tpl.key}>
|
||||||
|
<span className='font-mono'>{tpl.key}</span>
|
||||||
|
<span className='text-muted-foreground ml-2'>
|
||||||
|
· {tpl.display_name}
|
||||||
|
</span>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
placeholder={t('Role template')}
|
||||||
|
value={agent.role_template}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateAgent(index, {
|
||||||
|
role_template: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className='font-mono text-xs'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('Agnet runtime model id')}
|
placeholder={t('Agnet runtime model id')}
|
||||||
value={agent.default_model_id}
|
value={agent.default_model_id}
|
||||||
|
|||||||
Reference in New Issue
Block a user